Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit54da7bd

Browse files
authored
fix(ts-client): alias encoding (#809)
1 parent4ac0cd1 commit54da7bd

File tree

14 files changed

+159
-87
lines changed

14 files changed

+159
-87
lines changed

‎src/layers/2_generator/__snapshots__/files.test.ts.snap

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,7 @@ export namespace Root {
310310
resultNonNull: $.Field<
311311
Union.Result,
312312
$.Args<{
313-
case:Enum.Case
313+
case:$.Input.Nullable<Enum.Case>
314314
}>
315315
>
316316
string: $.Field<$.Output.Nullable<$Scalar.String>, null>
@@ -705,7 +705,7 @@ export const Query = $.Object$(\`Query\`, {
705705
}),
706706
),
707707
result:$.field($.Output.Nullable(()=>Result),$.Args({ case:Case })),
708-
resultNonNull:$.field(()=>Result,$.Args({ case:Case })),
708+
resultNonNull:$.field(()=>Result,$.Args({ case:$.Input.Nullable(Case) })),
709709
string:$.field($.Output.Nullable($Scalar.String)),
710710
stringWithArgEnum:$.field($.Output.Nullable($Scalar.String),$.Args({ ABCEnum:$.Input.Nullable(ABCEnum) })),
711711
stringWithArgInputObject:$.field(

‎src/layers/3_SelectionSet/__snapshots__/encode.test.ts.snap

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -552,7 +552,7 @@ exports[`alias > Query 1`] = `
552552
}
553553
--------------
554554
{
555-
id:x
555+
x:id
556556
}
557557
"
558558
`;
@@ -565,8 +565,8 @@ exports[`alias > Query 2`] = `
565565
}
566566
--------------
567567
{
568-
id:x
569-
id:id2
568+
x:id
569+
id2:id
570570
}
571571
"
572572
`;
@@ -580,7 +580,7 @@ exports[`alias > Query 3`] = `
580580
}
581581
--------------
582582
{
583-
id:x @skip(if:true)
583+
x:id @skip(if:true)
584584
}
585585
"
586586
`;
@@ -595,7 +595,7 @@ exports[`alias > Query 4`] = `
595595
}
596596
--------------
597597
{
598-
object:x @skip(if:true) {
598+
x:object @skip(if:true) {
599599
id
600600
}
601601
}

‎src/layers/3_SelectionSet/encode.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,9 @@ export const resolveObjectLikeFieldValue = (
203203
constfieldName=parseClientFieldName(clientFieldName)
204204
constschemaField=schemaItem.fields[fieldName.actual]
205205
if(!schemaField)thrownewError(`Field${clientFieldName} not found in schema object`)
206+
/**
207+
* Inject __typename field for result fields that are missing it.
208+
*/
206209
// dprint-ignore
207210
if(rootTypeName&&context.config.returnMode===`successData`&&context.schemaIndex.error.rootResultFields[rootTypeName][fieldName.actual]){
208211
(ssasRecord<string,boolean>)[`__typename`]=true

‎src/layers/3_SelectionSet/runtime/FieldName.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ export const parseClientFieldName = (field: string): FieldName => {
2323

2424
exportconsttoGraphQLFieldName=(fieldName:FieldName)=>{
2525
if(fieldName.alias){
26-
return`${fieldName.actual}:${fieldName.alias}`
26+
// todo test coverage for this, discovered broken, not tested
27+
return`${fieldName.alias}:${fieldName.actual}`
2728
}else{
2829
returnfieldName.actual
2930
}

‎src/layers/3_SelectionSet/types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,9 @@ export type ParseAliasExpression<E> =
171171

172172
exporttypeAliasNameOrigin<N>=ParseAliasExpression<N>extendsAlias<inferO,any> ?O :N
173173

174+
/**
175+
* Resolve the target of an alias or if is not an alias just pass through the name.
176+
*/
174177
exporttypeAliasNameTarget<N>=ParseAliasExpression<N>extendsAlias<any, inferT> ?T :N
175178

176179
exporttypeResolveAliasTargets<SelectionSet>={

‎src/layers/5_client/Config.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
importtype{ExecutionResult}from'graphql'
22
importtype{GraphQLExecutionResultError}from'../../lib/graphql.js'
3-
importtype{SetProperty}from'../../lib/prelude.js'
3+
importtype{SetProperty,StringKeyof}from'../../lib/prelude.js'
44
importtype{Schema}from'../1_Schema/__.js'
55
importtype{GlobalRegistry}from'../2_generator/globalRegistry.js'
6+
importtype{SelectionSet}from'../3_SelectionSet/__.js'
67

78
exporttypeReturnModeType=
89
|ReturnModeTypeGraphQL
@@ -76,14 +77,22 @@ export type IsNeedSelectionTypename<$Config extends Config, $Index extends Schem
7677
$Config['returnMode']extends'successData' ?GlobalRegistry.HasSchemaErrors<$Index['name']>extendstrue ?true :
7778
false :
7879
false
80+
7981
exporttypeAugmentRootTypeSelectionWithTypename<
8082
$ConfigextendsConfig,
8183
$IndexextendsSchema.Index,
8284
$RootTypeNameextendsSchema.RootTypeName,
8385
$Selectionextendsobject,
8486
>=IsNeedSelectionTypename<$Config,$Index>extendstrue ?{
85-
[$Keyinkeyof$Selection]:
87+
[$KeyinStringKeyof<$Selection>]:
8688
&$Selection[$Key]
87-
&($Keyextendskeyof$Index['error']['rootResultFields'][$RootTypeName] ?TypenameSelection :{})// eslint-disable-line
89+
&(IsRootFieldNameAResultField<$Index,$RootTypeName,$Key>extendstrue ?TypenameSelection :{})// eslint-disable-line
8890
}
8991
:$Selection
92+
93+
typeIsRootFieldNameAResultField<
94+
$IndexextendsSchema.Index,
95+
$RootTypeNameextendsSchema.RootTypeName,
96+
$FieldNameextendsstring,
97+
>=SelectionSet.AliasNameOrigin<$FieldName>extendskeyof$Index['error']['rootResultFields'][$RootTypeName] ?true
98+
:false

‎src/layers/5_client/client.document.test-d.ts

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -51,26 +51,45 @@ describe(`input`, () => {
5151
})
5252
})
5353

54-
describe(`output`,()=>{
55-
test(`document with one query`,async()=>{
54+
describe(`document(...).run()`,()=>{
55+
test(`document with one query`,()=>{
5656
{
57-
constresult=awaitclient.document({foo:{query:{id:true}}}).run()
58-
expectTypeOf(result).toEqualTypeOf<{id:string|null}>()
57+
constresult=client.document({x:{query:{id:true}}}).run()
58+
expectTypeOf(result).resolves.toEqualTypeOf<{id:string|null}>()
5959
}
6060
{
61-
constresult=awaitclient.document({foo:{query:{id:true}}}).run(`foo`)
62-
expectTypeOf(result).toEqualTypeOf<{id:string|null}>()
61+
constresult=client.document({x:{query:{id:true}}}).run(`x`)
62+
expectTypeOf(result).resolves.toEqualTypeOf<{id:string|null}>()
6363
}
6464
{
65-
constresult=awaitclient.document({foo:{query:{id:true}}}).run(undefined)
66-
expectTypeOf(result).toEqualTypeOf<{id:string|null}>()
65+
constresult=client.document({x:{query:{id:true}}}).run(undefined)
66+
expectTypeOf(result).resolves.toEqualTypeOf<{id:string|null}>()
6767
}
6868
})
69-
test(`document with two queries`,async()=>{
70-
constresult=awaitclient.document({
69+
test(`document with two queries`,()=>{
70+
constresult=client.document({
7171
foo:{query:{id:true}},
7272
bar:{query:{id:true}},
7373
}).run(`foo`)
74-
expectTypeOf(result).toEqualTypeOf<{id:string|null}>()
74+
expectTypeOf(result).resolves.toEqualTypeOf<{id:string|null}>()
75+
})
76+
})
77+
78+
describe(`document(...).runOrThrow()`,()=>{
79+
describe(`query result field`,()=>{
80+
test(`with __typename`,()=>{
81+
constresult=client.document({x:{query:{resultNonNull:{__typename:true}}}}).runOrThrow()
82+
expectTypeOf(result).resolves.toEqualTypeOf<{resultNonNull:{__typename:'Object1'}}>()
83+
})
84+
test(`without __typename`,()=>{
85+
constresult=client.document({x:{query:{resultNonNull:{}}}}).runOrThrow()
86+
expectTypeOf(result).resolves.toEqualTypeOf<{resultNonNull:{__typename:'Object1'}}>()
87+
})
88+
test(`multiple via alias`,()=>{
89+
constresult=client.document({x:{query:{resultNonNull:{},resultNonNull_as_x:{}}}}).runOrThrow()
90+
expectTypeOf(result).resolves.toEqualTypeOf<
91+
{resultNonNull:{__typename:'Object1'};x:{__typename:'Object1'}}
92+
>()
93+
})
7594
})
7695
})

‎src/layers/5_client/client.document.test.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,3 +71,27 @@ test(`document with one mutation and one query`, async () => {
7171
awaitexpect(run(`foo`)).resolves.toEqual({id:db.id1})
7272
awaitexpect(run(`bar`)).resolves.toEqual({idNonNull:db.id1})
7373
})
74+
75+
describe(`document(...).runOrThrow()`,()=>{
76+
describe(`query result field`,()=>{
77+
test(`with __typename`,async()=>{
78+
constresult=client.document({x:{query:{resultNonNull:{$:{case:`ErrorOne`},__typename:true}}}})
79+
.runOrThrow()
80+
awaitexpect(result).rejects.toMatchInlineSnapshot(`[Error: Failure on field resultNonNull: ErrorOne]`)
81+
})
82+
test(`without __typename`,async()=>{
83+
constresult=client.document({x:{query:{resultNonNull:{$:{case:`ErrorOne`}}}}}).runOrThrow()
84+
awaitexpect(result).rejects.toMatchInlineSnapshot(
85+
`[Error: Failure on field resultNonNull: ErrorOne]`,
86+
)
87+
})
88+
test(`multiple via alias`,async()=>{
89+
constresult=client.document({
90+
x:{query:{resultNonNull:{$:{case:`ErrorOne`}},resultNonNull_as_x:{$:{case:`ErrorOne`}}}},
91+
}).runOrThrow()
92+
awaitexpect(result).rejects.toMatchInlineSnapshot(
93+
`[ContextualAggregateError: Two or more schema errors in the execution result.]`,
94+
)
95+
})
96+
})
97+
})

‎src/layers/5_client/client.ts

Lines changed: 70 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ export const create: Create = (
186186
},
187187
}asGraphQLObjectSelection
188188
constresult=awaitrootObjectExecutors[rootTypeName](documentObject)
189-
constresultHandled=handleReturn(result)
189+
constresultHandled=handleReturn(input.schemaIndex,result,returnMode)
190190
if(resultHandledinstanceofError)returnresultHandled
191191
returnreturnMode===`data`||returnMode===`dataAndErrors`||returnMode===`successData`
192192
//@ts-expect-error make this type safe?
@@ -195,54 +195,6 @@ export const create: Create = (
195195
}
196196
}
197197

198-
consthandleReturn=(result:ExecutionResult)=>{
199-
switch(returnMode){
200-
case`dataAndErrors`:
201-
case`successData`:
202-
case`data`:{
203-
if(result.errors&&result.errors.length>0){
204-
consterror=newErrors.ContextualAggregateError(
205-
`One or more errors in the execution result.`,
206-
{},
207-
result.errors,
208-
)
209-
if(returnMode===`data`||returnMode===`successData`)throwerror
210-
returnerror
211-
}
212-
if(returnMode===`successData`){
213-
if(!isPlainObject(result.data))thrownewError(`Expected data to be an object.`)
214-
constschemaErrors=Object.entries(result.data).map(([rootFieldName,rootFieldValue])=>{
215-
// todo do not hardcode root type
216-
constisResultField=Boolean(input.schemaIndex.error.rootResultFields.Query[rootFieldName])
217-
if(!isResultField)returnnull
218-
if(!isPlainObject(rootFieldValue))returnnewError(`Expected result field to be an object.`)
219-
const__typename=rootFieldValue[`__typename`]
220-
if(typeof__typename!==`string`)thrownewError(`Expected __typename to be selected and a string.`)
221-
constisErrorObject=Boolean(
222-
input.schemaIndex.error.objectsTypename[__typename],
223-
)
224-
if(!isErrorObject)returnnull
225-
// todo extract message
226-
returnnewError(`Failure on field${rootFieldName}:${__typename}`)
227-
}).filter((_):_ isError=>_!==null)
228-
if(schemaErrors.length===1)throwschemaErrors[0]!
229-
if(schemaErrors.length>0){
230-
consterror=newErrors.ContextualAggregateError(
231-
`One or more schema errors in the execution result.`,
232-
{},
233-
schemaErrors,
234-
)
235-
throwerror
236-
}
237-
}
238-
returnresult.data
239-
}
240-
default:{
241-
returnresult
242-
}
243-
}
244-
}
245-
246198
constrootObjectExecutors={
247199
Mutation:executeRootType(`Mutation`),
248200
Query:executeRootType(`Query`),
@@ -262,7 +214,7 @@ export const create: Create = (
262214
constresultRaw=awaitrootObjectExecutors[rootTypeName]({
263215
[rootTypeNameToOperationName[rootTypeName]]:selectionSetOrIndicator,
264216
})
265-
constresult=handleReturn(resultRaw)
217+
constresult=handleReturn(input.schemaIndex,resultRaw,returnMode)
266218
if(isOrThrow&&resultinstanceofError)throwresult
267219
// todo consolidate
268220
//@ts-expect-error fixme
@@ -338,19 +290,26 @@ export const create: Create = (
338290
operationName,
339291
// todo variables
340292
})
341-
returnhandleReturn(result)
293+
returnhandleReturn(input.schemaIndex,result,returnMode)
342294
}
343295
return{
344296
run,
345297
runOrThrow:async(operationName:string)=>{
346-
constresult=awaitrun(operationName)
347-
if(resultinstanceofError)throwresult
348-
//@ts-expect-error fixme
349-
if(returnMode===`graphql`&&result.errors&&result.errors.length>0){
350-
//@ts-expect-error fixme
351-
thrownewErrors.ContextualAggregateError(`One or more errors in the execution result.`,{},result.errors)
352-
}
353-
returnresult
298+
constdocumentString=toDocumentString({
299+
...encodeContext,
300+
config:{
301+
...encodeContext.config,
302+
returnMode:`successData`,
303+
},
304+
},documentObject)
305+
constresult=awaitexecuteGraphQLDocument({
306+
document:documentString,
307+
operationName,
308+
// todo variables
309+
})
310+
// todo refactor...
311+
constresultReturn=handleReturn(input.schemaIndex,result,`successData`)
312+
returnreturnMode===`graphql` ?result :resultReturn
354313
},
355314
}
356315
},
@@ -362,3 +321,55 @@ export const create: Create = (
362321

363322
returnclient
364323
}
324+
325+
consthandleReturn=(schemaIndex:Schema.Index,result:ExecutionResult,returnMode:ReturnModeType)=>{
326+
switch(returnMode){
327+
case`dataAndErrors`:
328+
case`successData`:
329+
case`data`:{
330+
if(result.errors&&result.errors.length>0){
331+
consterror=newErrors.ContextualAggregateError(
332+
`One or more errors in the execution result.`,
333+
{},
334+
result.errors,
335+
)
336+
if(returnMode===`data`||returnMode===`successData`)throwerror
337+
returnerror
338+
}
339+
if(returnMode===`successData`){
340+
if(!isPlainObject(result.data))thrownewError(`Expected data to be an object.`)
341+
constschemaErrors=Object.entries(result.data).map(([rootFieldName,rootFieldValue])=>{
342+
// todo this check would be nice but it doesn't account for aliases right now. To achieve this we would
343+
// need to have the selection set available to use and then do a costly analysis for all fields that were aliases.
344+
// So costly that we would probably instead want to create an index of them on the initial encoding step and
345+
// then make available down stream. Also, note, here, the hardcoding of Query, needs to be any root type.
346+
// const isResultField = Boolean(schemaIndex.error.rootResultFields.Query[rootFieldName])
347+
// if (!isResultField) return null
348+
// if (!isPlainObject(rootFieldValue)) return new Error(`Expected result field to be an object.`)
349+
if(!isPlainObject(rootFieldValue))returnnull
350+
const__typename=rootFieldValue[`__typename`]
351+
if(typeof__typename!==`string`)thrownewError(`Expected __typename to be selected and a string.`)
352+
constisErrorObject=Boolean(
353+
schemaIndex.error.objectsTypename[__typename],
354+
)
355+
if(!isErrorObject)returnnull
356+
// todo extract message
357+
returnnewError(`Failure on field${rootFieldName}:${__typename}`)
358+
}).filter((_):_ isError=>_!==null)
359+
if(schemaErrors.length===1)throwschemaErrors[0]!
360+
if(schemaErrors.length>0){
361+
consterror=newErrors.ContextualAggregateError(
362+
`Two or more schema errors in the execution result.`,
363+
{},
364+
schemaErrors,
365+
)
366+
throwerror
367+
}
368+
}
369+
returnresult.data
370+
}
371+
default:{
372+
returnresult
373+
}
374+
}
375+
}

‎src/lib/prelude.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,3 +245,5 @@ export function assertArray(v: unknown): asserts v is unknown[] {
245245
exportfunctionassertObject(v:unknown): assertsv isobject{
246246
if(v===null||typeofv!==`object`)thrownewError(`Expected object. Got:${String(v)}`)
247247
}
248+
249+
exporttypeStringKeyof<T>=keyofT&string

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp