@znight/graphql 0.2.2 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +262 -17
- package/dist/compiler.d.mts +21 -1
- package/dist/compiler.mjs +116 -15
- package/dist/compiler.mjs.map +1 -1
- package/dist/{types-DQ713UgI.d.mts → driver-DgbpFj4-.d.mts} +193 -9
- package/dist/http-BWcayUtH.mjs +2165 -0
- package/dist/http-BWcayUtH.mjs.map +1 -0
- package/dist/index.d.mts +52 -51
- package/dist/index.mjs +2 -1
- package/dist/index.mjs.map +1 -1
- package/dist/names-BlzDM5cF.mjs.map +1 -1
- package/dist/{plugin-CR_wVfRU.mjs → plugin-BssB7wtn.mjs} +242 -1778
- package/dist/plugin-BssB7wtn.mjs.map +1 -0
- package/dist/testing.d.mts +40 -1
- package/dist/testing.mjs +649 -4
- package/dist/testing.mjs.map +1 -1
- package/dist/yoga.d.mts +64 -0
- package/dist/yoga.mjs +379 -0
- package/dist/yoga.mjs.map +1 -0
- package/package.json +19 -10
- package/src/compiler/analyze.ts +27 -0
- package/src/compiler/data.ts +20 -1
- package/src/compiler/generate.ts +137 -19
- package/src/conformance.ts +700 -0
- package/src/context.ts +13 -0
- package/src/driver.ts +63 -0
- package/src/engine.ts +146 -0
- package/src/execution.ts +602 -67
- package/src/index.ts +7 -0
- package/src/limits.ts +22 -1
- package/src/metadata.ts +20 -4
- package/src/options.ts +53 -4
- package/src/pipeline.ts +72 -9
- package/src/plugin.ts +172 -29
- package/src/testing.ts +3 -0
- package/src/transport/builtin.ts +57 -0
- package/src/transport/fetch.ts +57 -0
- package/src/transport/http.ts +12 -9
- package/src/types.ts +22 -4
- package/src/yoga.ts +471 -0
- package/dist/plugin-CR_wVfRU.mjs.map +0 -1
package/src/compiler/generate.ts
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* ```
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
import type { GenerateContext, GenInput, GenNode, GenParam } from '@znight/compiler'
|
|
16
|
+
import type { ClassRef, GenerateContext, GenInput, GenNode, GenParam, StaticValue, ValueRef } from '@znight/compiler'
|
|
17
17
|
import type { Token } from '@znight/core'
|
|
18
18
|
import type { MetaEntry } from '@znight/http/compiler'
|
|
19
19
|
import type { FieldData, GraphQLData, ParamData, ResolverData } from './data.ts'
|
|
@@ -34,6 +34,8 @@ interface Imports {
|
|
|
34
34
|
argsType(): GenNode
|
|
35
35
|
contextType(): GenNode
|
|
36
36
|
infoType(): GenNode
|
|
37
|
+
resolverContextType(): GenNode
|
|
38
|
+
paramFunctionOf(): GenNode
|
|
37
39
|
validateArgs(): GenNode
|
|
38
40
|
isThenable(): GenNode
|
|
39
41
|
hostType(identity: string): GenNode
|
|
@@ -63,6 +65,8 @@ function createImports(ctx: Context): Imports {
|
|
|
63
65
|
let args: GenNode | undefined
|
|
64
66
|
let context: GenNode | undefined
|
|
65
67
|
let info: GenNode | undefined
|
|
68
|
+
let resolverContext: GenNode | undefined
|
|
69
|
+
let paramFunctionOf: GenNode | undefined
|
|
66
70
|
let validateArgs: GenNode | undefined
|
|
67
71
|
let isThenable: GenNode | undefined
|
|
68
72
|
const hosts = new Map<string, GenNode>()
|
|
@@ -79,6 +83,14 @@ function createImports(ctx: Context): Imports {
|
|
|
79
83
|
info ??= ctx.importFrom('graphql', 'GraphQLResolveInfo', { type: true })
|
|
80
84
|
return info
|
|
81
85
|
},
|
|
86
|
+
resolverContextType: () => {
|
|
87
|
+
resolverContext ??= ctx.importFrom(RUNTIME_MODULE, 'ResolverContext', { type: true })
|
|
88
|
+
return resolverContext
|
|
89
|
+
},
|
|
90
|
+
paramFunctionOf: () => {
|
|
91
|
+
paramFunctionOf ??= ctx.importFrom('@znight/core', 'ParamFunctionOf', { type: true })
|
|
92
|
+
return paramFunctionOf
|
|
93
|
+
},
|
|
82
94
|
validateArgs: () => {
|
|
83
95
|
validateArgs ??= ctx.importFrom(RUNTIME_MODULE, 'validateArgs')
|
|
84
96
|
return validateArgs
|
|
@@ -208,7 +220,13 @@ function resolverNode(ctx: Context, imports: Imports, resolver: ResolverData, fi
|
|
|
208
220
|
]
|
|
209
221
|
const loaders = field.params.flatMap(param => (param?.kind === 'loader' ? [loaderToken(ctx, param)] : []))
|
|
210
222
|
if (loaders.length > 0) entries.push(['loaders', ctx.lit(loaders)])
|
|
211
|
-
|
|
223
|
+
// custom parameters (RFC 0010): the pipeline builds the ResolverContext and resolves the class resolvers
|
|
224
|
+
const resolvers = paramResolvers(field)
|
|
225
|
+
if (field.params.some(param => param?.kind === 'custom')) entries.push(['context', ctx.lit(true)])
|
|
226
|
+
if (resolvers.length > 0) {
|
|
227
|
+
entries.push(['paramResolvers', ctx.lit(resolvers.map(ref => ctx.tokenOf(ref) ?? ref.token))])
|
|
228
|
+
}
|
|
229
|
+
entries.push(['invoke', invokeNode(ctx, imports, resolver, field, resolvers)])
|
|
212
230
|
return ctx.obj(entries)
|
|
213
231
|
}
|
|
214
232
|
|
|
@@ -222,14 +240,31 @@ function loaderToken(ctx: Context, param: Extract<ParamData, { kind: 'loader' }>
|
|
|
222
240
|
|
|
223
241
|
// ------------------------------------------------------------------------------------------- invoke
|
|
224
242
|
|
|
225
|
-
/**
|
|
226
|
-
function
|
|
243
|
+
/** The provider classes resolving custom parameters, in order of first use (`x[i]`). */
|
|
244
|
+
function paramResolvers(field: FieldData): ClassRef[] {
|
|
245
|
+
const found = new Map<string, ClassRef>()
|
|
246
|
+
for (const param of field.params) {
|
|
247
|
+
const resolver = param?.kind === 'custom' ? param.resolver : undefined
|
|
248
|
+
if (resolver !== undefined && !found.has(resolver.token)) found.set(resolver.token, resolver)
|
|
249
|
+
}
|
|
250
|
+
return [...found.values()]
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/** Which `invoke` parameters a field uses: `(r, p, a, c, i, f, x)`. */
|
|
254
|
+
function usedParameters(field: FieldData): readonly boolean[] {
|
|
227
255
|
let p = false
|
|
228
256
|
let a = false
|
|
229
257
|
let c = false
|
|
230
258
|
let i = false
|
|
259
|
+
let f = false
|
|
260
|
+
let x = false
|
|
231
261
|
for (const param of field.params) {
|
|
232
262
|
switch (param?.kind) {
|
|
263
|
+
case 'custom':
|
|
264
|
+
// a class resolver whose `resolve()` takes no parameter does not read `f`
|
|
265
|
+
f ||= param.resolver === undefined || param.arity !== 0
|
|
266
|
+
x ||= param.resolver !== undefined
|
|
267
|
+
break
|
|
233
268
|
case 'parent':
|
|
234
269
|
p = true
|
|
235
270
|
break
|
|
@@ -245,39 +280,73 @@ function usedParameters(field: FieldData): readonly [p: boolean, a: boolean, c:
|
|
|
245
280
|
break
|
|
246
281
|
}
|
|
247
282
|
}
|
|
248
|
-
return [p, a, c, i]
|
|
283
|
+
return [p, a, c, i, f, x]
|
|
249
284
|
}
|
|
250
285
|
|
|
251
286
|
/**
|
|
252
287
|
* `(r: Resolver, p: unknown, a: GraphQLArgs, c: GraphQLContext, i: GraphQLResolveInfo) => r.m(...)`:
|
|
253
288
|
* unused middle parameters are `_`-prefixed, trailing ones omitted (`noUnusedParameters`).
|
|
254
289
|
*/
|
|
255
|
-
function invokeNode(
|
|
290
|
+
function invokeNode(
|
|
291
|
+
ctx: Context,
|
|
292
|
+
imports: Imports,
|
|
293
|
+
resolver: ResolverData,
|
|
294
|
+
field: FieldData,
|
|
295
|
+
resolvers: readonly ClassRef[],
|
|
296
|
+
): GenNode {
|
|
256
297
|
const used = usedParameters(field)
|
|
257
298
|
const last = used.lastIndexOf(true)
|
|
258
|
-
const types = [
|
|
259
|
-
|
|
299
|
+
const types = [
|
|
300
|
+
() => ctx.code`unknown`,
|
|
301
|
+
imports.argsType,
|
|
302
|
+
imports.contextType,
|
|
303
|
+
imports.infoType,
|
|
304
|
+
imports.resolverContextType,
|
|
305
|
+
() =>
|
|
306
|
+
tupleType(
|
|
307
|
+
ctx,
|
|
308
|
+
resolvers.map(ref => ctx.importType(ref)),
|
|
309
|
+
),
|
|
310
|
+
]
|
|
311
|
+
const names = ['p', 'a', 'c', 'i', 'f', 'x']
|
|
260
312
|
const params: [string, GenNode][] = [['r', imports.hostType(resolver.host.identity)]]
|
|
261
313
|
for (let index = 0; index <= last; index++) {
|
|
262
314
|
params.push([`${used[index] ? '' : '_'}${names[index] as string}`, (types[index] as () => GenNode)()])
|
|
263
315
|
}
|
|
264
|
-
const
|
|
265
|
-
|
|
266
|
-
)
|
|
316
|
+
const deferred = field.params.flatMap((param, index) => (param !== undefined && isDeferred(param) ? [index] : []))
|
|
317
|
+
const positions = new Map(resolvers.map((ref, position) => [ref.token, position]))
|
|
267
318
|
return ctx.arrow(params, (r, ...rest) => {
|
|
268
|
-
const [p, a, c, i] = rest as (GenParam | undefined)[]
|
|
269
|
-
const scope: Scope = { p, a, c, i }
|
|
270
|
-
if (
|
|
271
|
-
return validatingBlock(ctx, imports, r, scope, field,
|
|
319
|
+
const [p, a, c, i, f, x] = rest as (GenParam | undefined)[]
|
|
320
|
+
const scope: Scope = { p, a, c, i, f, x, resolvers: positions, imports }
|
|
321
|
+
if (deferred.length === 0) return call(ctx, r, field, index => argument(ctx, scope, field, index))
|
|
322
|
+
return validatingBlock(ctx, imports, r, scope, field, deferred)
|
|
272
323
|
})
|
|
273
324
|
}
|
|
274
325
|
|
|
326
|
+
/** `readonly [A, B]`. */
|
|
327
|
+
function tupleType(ctx: Context, nodes: readonly GenNode[]): GenNode {
|
|
328
|
+
const joined = nodes.slice(1).reduce((all, node) => ctx.code`${all}, ${node}`, nodes[0] as GenNode)
|
|
329
|
+
return ctx.code`readonly [${joined}]`
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/** An argument whose expression may return a thenable: a validated `@Args`, or a custom parameter. */
|
|
333
|
+
function isDeferred(param: ParamData): boolean {
|
|
334
|
+
return (param.kind === 'args' && param.schema !== undefined) || param.kind === 'custom'
|
|
335
|
+
}
|
|
336
|
+
|
|
275
337
|
/** The parameters of the `invoke` arrow (absent when not declared). */
|
|
276
338
|
interface Scope {
|
|
277
339
|
readonly p: GenNode | undefined
|
|
278
340
|
readonly a: GenNode | undefined
|
|
279
341
|
readonly c: GenNode | undefined
|
|
280
342
|
readonly i: GenNode | undefined
|
|
343
|
+
/** The `ResolverContext` of custom parameters. */
|
|
344
|
+
readonly f: GenNode | undefined
|
|
345
|
+
/** The class resolver instances (`x[i]`). */
|
|
346
|
+
readonly x: GenNode | undefined
|
|
347
|
+
/** Position of each class resolver token in `x`. */
|
|
348
|
+
readonly resolvers: ReadonlyMap<string, number>
|
|
349
|
+
readonly imports: Imports
|
|
281
350
|
}
|
|
282
351
|
|
|
283
352
|
/** `r.method(args)`. */
|
|
@@ -302,11 +371,62 @@ function argument(ctx: Context, scope: Scope, field: FieldData, index: number):
|
|
|
302
371
|
return ctx.code`${ctx.call(ctx.member(scope.c as GenNode, 'loader'), [loaderToken(ctx, param)])} as never`
|
|
303
372
|
case 'args':
|
|
304
373
|
return ctx.code`${argsValue(ctx, scope, param.name)} as never`
|
|
374
|
+
case 'custom':
|
|
375
|
+
return customArgument(ctx, scope, param)
|
|
305
376
|
default:
|
|
306
377
|
return ctx.lit(undefined)
|
|
307
378
|
}
|
|
308
379
|
}
|
|
309
380
|
|
|
381
|
+
/**
|
|
382
|
+
* A custom parameter (RFC 0010): `(D.resolvers.graphql as ParamFunctionOf<typeof D, 'graphql'>)(f, data)`
|
|
383
|
+
* or `x[i].resolve(f, data)`, cast with `as never` when the value type could not be checked.
|
|
384
|
+
*/
|
|
385
|
+
function customArgument(ctx: Context, scope: Scope, param: Extract<ParamData, { kind: 'custom' }>): GenNode {
|
|
386
|
+
const f = scope.f as GenNode
|
|
387
|
+
const data = param.data === undefined ? ctx.lit(undefined) : dataNode(ctx, param.data.value)
|
|
388
|
+
let node: GenNode
|
|
389
|
+
if (param.resolver === undefined) {
|
|
390
|
+
const decorator = ctx.importValue(param.identity)
|
|
391
|
+
const type = ctx.code`${scope.imports.paramFunctionOf()}<typeof ${decorator}, 'graphql'>`
|
|
392
|
+
node = ctx.call(ctx.code`${ctx.member(decorator, 'resolvers', 'graphql')} as ${type}`, [f, data])
|
|
393
|
+
} else {
|
|
394
|
+
const position = scope.resolvers.get(param.resolver.token) as number
|
|
395
|
+
if (param.arity === undefined) {
|
|
396
|
+
// a `resolve` the compiler cannot read (inherited from a mixin...): the instance is cast to a
|
|
397
|
+
// method taking both arguments (method parameters are bivariant), the value with `as never`
|
|
398
|
+
return ctx.code`(${scope.x as GenNode}[${position}] as { resolve(ctx: unknown, data: unknown): unknown }).resolve(${f}, ${data}) as never`
|
|
399
|
+
}
|
|
400
|
+
// a `resolve(ctx)` that ignores the data gets no data argument (TypeScript checks the arity)
|
|
401
|
+
const arity = Math.min(param.arity, 2)
|
|
402
|
+
let args = ctx.code``
|
|
403
|
+
if (arity === 1) args = f
|
|
404
|
+
else if (arity === 2) args = ctx.code`${f}, ${data}`
|
|
405
|
+
node = ctx.code`${scope.x as GenNode}[${position}].resolve(${args})`
|
|
406
|
+
}
|
|
407
|
+
return param.cast ? ctx.code`${node} as never` : node
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/** The data argument of a custom parameter: a literal (references imported) or a reference. */
|
|
411
|
+
function dataNode(ctx: Context, value: StaticValue): GenNode {
|
|
412
|
+
return isValueRef(value) ? ctx.ref(value as ValueRef) : ctx.lit(value as never)
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/** `true` for a {@link ValueRef} (the only non-plain object a static value can hold). */
|
|
416
|
+
function isValueRef(value: unknown): boolean {
|
|
417
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) return false
|
|
418
|
+
const prototype = Object.getPrototypeOf(value)
|
|
419
|
+
return prototype !== Object.prototype && prototype !== null && (value as { kind?: unknown }).kind === 'ref'
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/** The expression of a deferred parameter (validation or custom resolver), before any `await`. */
|
|
423
|
+
function deferredValue(ctx: Context, imports: Imports, scope: Scope, field: FieldData, index: number): GenNode {
|
|
424
|
+
const param = field.params[index] as ParamData
|
|
425
|
+
return param.kind === 'custom'
|
|
426
|
+
? customArgument(ctx, scope, param)
|
|
427
|
+
: validation(ctx, imports, scope, param as Extract<ParamData, { kind: 'args' }>)
|
|
428
|
+
}
|
|
429
|
+
|
|
310
430
|
/** `a['name']`, or `a` for `@Args()`. */
|
|
311
431
|
function argsValue(ctx: Context, scope: Scope, name: string | undefined): GenNode {
|
|
312
432
|
const a = scope.a as GenNode
|
|
@@ -356,8 +476,7 @@ function validatingBlock(
|
|
|
356
476
|
const statements: GenNode[] = []
|
|
357
477
|
for (const index of validated) {
|
|
358
478
|
const name = local.get(index) as GenNode
|
|
359
|
-
const
|
|
360
|
-
statements.push(ctx.code`const ${name} = ${validation(ctx, imports, scope, param)}`)
|
|
479
|
+
statements.push(ctx.code`const ${name} = ${deferredValue(ctx, imports, scope, field, index)}`)
|
|
361
480
|
const resume = ctx.arrow(
|
|
362
481
|
[],
|
|
363
482
|
() =>
|
|
@@ -366,8 +485,7 @@ function validatingBlock(
|
|
|
366
485
|
if (value === undefined) return argument(ctx, scope, field, position)
|
|
367
486
|
if (position < index) return ctx.code`${value} as never`
|
|
368
487
|
if (position === index) return ctx.code`await ${value} as never`
|
|
369
|
-
|
|
370
|
-
return ctx.code`await ${validation(ctx, imports, scope, later)} as never`
|
|
488
|
+
return ctx.code`await ${deferredValue(ctx, imports, scope, field, position)} as never`
|
|
371
489
|
}),
|
|
372
490
|
{ async: true },
|
|
373
491
|
)
|