@znight/graphql 0.2.0 → 0.2.1
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 +288 -5
- package/dist/compiler.d.mts +24 -3
- package/dist/compiler.mjs +1876 -109
- package/dist/compiler.mjs.map +1 -1
- package/dist/index.d.mts +49 -8
- package/dist/index.mjs +10 -4
- package/dist/index.mjs.map +1 -1
- package/dist/names-BlzDM5cF.mjs.map +1 -1
- package/dist/{plugin-BdBxr5NQ.mjs → plugin-CR_wVfRU.mjs} +247 -24
- package/dist/plugin-CR_wVfRU.mjs.map +1 -0
- package/dist/testing.d.mts +1 -1
- package/dist/testing.mjs +1 -1
- package/dist/{types-CgbV9LjD.d.mts → types-DQ713UgI.d.mts} +34 -2
- package/package.json +8 -8
- package/src/compiler/analyze.ts +129 -10
- package/src/compiler/builder.ts +6 -85
- package/src/compiler/checks.ts +105 -0
- package/src/compiler/conformance.ts +1068 -0
- package/src/compiler/constants.ts +6 -0
- package/src/compiler/data.ts +10 -0
- package/src/compiler/generate.ts +41 -27
- package/src/compiler/graphql-js.ts +42 -0
- package/src/compiler/index.ts +8 -3
- package/src/compiler/locate.ts +39 -1
- package/src/compiler/model.ts +16 -1
- package/src/compiler/options.ts +39 -8
- package/src/compiler/schema-first.ts +595 -0
- package/src/compiler/sdl-source.ts +529 -0
- package/src/compiler/sdl-view.ts +3 -1
- package/src/compiler/suggest.ts +39 -0
- package/src/decorators.ts +9 -3
- package/src/index.ts +5 -0
- package/src/metadata.ts +28 -1
- package/src/options.ts +23 -1
- package/src/pipeline.ts +66 -5
- package/src/plugin.ts +27 -5
- package/src/results.ts +206 -0
- package/src/scalars.ts +10 -3
- package/src/schema.ts +90 -12
- package/src/sdl.ts +17 -2
- package/src/types.ts +35 -0
- package/dist/plugin-BdBxr5NQ.mjs.map +0 -1
package/src/schema.ts
CHANGED
|
@@ -17,13 +17,13 @@ import type {
|
|
|
17
17
|
GraphQLInputType,
|
|
18
18
|
GraphQLNamedType,
|
|
19
19
|
GraphQLOutputType,
|
|
20
|
-
GraphQLTypeResolver,
|
|
21
20
|
} from 'graphql'
|
|
22
21
|
import type {
|
|
23
22
|
GraphQLArgumentDefinition,
|
|
24
23
|
GraphQLDiscriminator,
|
|
25
24
|
GraphQLFieldDefinition,
|
|
26
25
|
GraphQLInputTypeDefinition,
|
|
26
|
+
GraphQLInterfaceTypeDefinition,
|
|
27
27
|
GraphQLMetadata,
|
|
28
28
|
GraphQLObjectTypeDefinition,
|
|
29
29
|
GraphQLResolverBinding,
|
|
@@ -40,6 +40,7 @@ import {
|
|
|
40
40
|
GraphQLID,
|
|
41
41
|
GraphQLInputObjectType,
|
|
42
42
|
GraphQLInt,
|
|
43
|
+
GraphQLInterfaceType,
|
|
43
44
|
GraphQLList,
|
|
44
45
|
GraphQLNonNull,
|
|
45
46
|
GraphQLObjectType,
|
|
@@ -90,7 +91,7 @@ export function graphqlSection(metadata: ZnightMetadata): GraphQLMetadata {
|
|
|
90
91
|
|
|
91
92
|
const OPERATIONS: ReadonlySet<string> = new Set(['query', 'mutation', 'subscription', 'field'])
|
|
92
93
|
const INPUT_KINDS: ReadonlySet<string> = new Set(['input', 'enum', 'scalar'])
|
|
93
|
-
const OUTPUT_KINDS: ReadonlySet<string> = new Set(['object', 'enum', 'scalar', 'union'])
|
|
94
|
+
const OUTPUT_KINDS: ReadonlySet<string> = new Set(['object', 'interface', 'enum', 'scalar', 'union'])
|
|
94
95
|
|
|
95
96
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
96
97
|
return value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
@@ -172,7 +173,10 @@ export function checkGraphQLSection(section: unknown): string[] {
|
|
|
172
173
|
if (!isRecord(type) || typeof type['name'] !== 'string') continue
|
|
173
174
|
const name = type['name']
|
|
174
175
|
switch (type['kind']) {
|
|
175
|
-
case 'object':
|
|
176
|
+
case 'object':
|
|
177
|
+
case 'interface': {
|
|
178
|
+
const isInterface = type['kind'] === 'interface'
|
|
179
|
+
checkInterfaces(name, type['interfaces'], kinds, problems)
|
|
176
180
|
const fields = type['fields']
|
|
177
181
|
if (!Array.isArray(fields) || fields.length === 0) {
|
|
178
182
|
problems.push(`${name}: fields must be a non-empty array`)
|
|
@@ -190,7 +194,25 @@ export function checkGraphQLSection(section: unknown): string[] {
|
|
|
190
194
|
seen.add(field['name'])
|
|
191
195
|
checkRef(coordinate, field['type'], OUTPUT_KINDS, 'output')
|
|
192
196
|
if (field['args'] !== undefined) checkInputValues(`${coordinate} arguments`, field['args'], false)
|
|
193
|
-
if (field['resolver']
|
|
197
|
+
if (field['resolver'] === undefined) continue
|
|
198
|
+
if (isInterface)
|
|
199
|
+
problems.push(`${coordinate}: interface fields have no resolver (bind it on the object types)`)
|
|
200
|
+
else checkBinding(coordinate, field['resolver'], problems)
|
|
201
|
+
}
|
|
202
|
+
if (isInterface) {
|
|
203
|
+
const implementations = types.filter(
|
|
204
|
+
other =>
|
|
205
|
+
isRecord(other) &&
|
|
206
|
+
other['kind'] === 'object' &&
|
|
207
|
+
Array.isArray(other['interfaces']) &&
|
|
208
|
+
other['interfaces'].includes(name),
|
|
209
|
+
)
|
|
210
|
+
checkDiscriminator(
|
|
211
|
+
name,
|
|
212
|
+
type['discriminator'],
|
|
213
|
+
implementations.map(other => (other as Record<string, unknown>)['name']),
|
|
214
|
+
problems,
|
|
215
|
+
)
|
|
194
216
|
}
|
|
195
217
|
break
|
|
196
218
|
}
|
|
@@ -258,6 +280,11 @@ export function checkGraphQLSection(section: unknown): string[] {
|
|
|
258
280
|
)
|
|
259
281
|
}
|
|
260
282
|
}
|
|
283
|
+
for (const key of ['description', 'specifiedByURL'] as const) {
|
|
284
|
+
if (type[key] !== undefined && typeof type[key] !== 'string')
|
|
285
|
+
problems.push(`${name}: ${key} must be a string`)
|
|
286
|
+
}
|
|
287
|
+
if (type['sdl'] !== undefined && type['sdl'] !== true) problems.push(`${name}: sdl must be true when present`)
|
|
261
288
|
break
|
|
262
289
|
default:
|
|
263
290
|
problems.push(`${name}: unknown kind ${show(type['kind'])}`)
|
|
@@ -268,6 +295,25 @@ export function checkGraphQLSection(section: unknown): string[] {
|
|
|
268
295
|
return problems
|
|
269
296
|
}
|
|
270
297
|
|
|
298
|
+
/** `interfaces` of an object or interface type: absent, or interface type names. */
|
|
299
|
+
function checkInterfaces(
|
|
300
|
+
name: string,
|
|
301
|
+
interfaces: unknown,
|
|
302
|
+
kinds: ReadonlyMap<string, string>,
|
|
303
|
+
problems: string[],
|
|
304
|
+
): void {
|
|
305
|
+
if (interfaces === undefined) return
|
|
306
|
+
if (!Array.isArray(interfaces)) {
|
|
307
|
+
problems.push(`${name}: interfaces must be an array of interface type names (received ${show(interfaces)})`)
|
|
308
|
+
return
|
|
309
|
+
}
|
|
310
|
+
for (const entry of interfaces) {
|
|
311
|
+
if (typeof entry !== 'string' || kinds.get(entry) !== 'interface') {
|
|
312
|
+
problems.push(`${name}: ${show(entry)} is not an interface type`)
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
271
317
|
function checkBinding(coordinate: string, binding: unknown, problems: string[]): void {
|
|
272
318
|
if (!isRecord(binding)) {
|
|
273
319
|
problems.push(`${coordinate}: resolver must be an object (received ${show(binding)})`)
|
|
@@ -393,8 +439,8 @@ export function invalidSchema(problems: readonly string[]): never {
|
|
|
393
439
|
)
|
|
394
440
|
}
|
|
395
441
|
|
|
396
|
-
/**
|
|
397
|
-
function typeResolver(discriminator: GraphQLDiscriminator):
|
|
442
|
+
/** The `resolveType` of a union or an interface, from its discriminator: data read once, at boot. */
|
|
443
|
+
export function typeResolver(discriminator: GraphQLDiscriminator): (value: unknown) => string | undefined {
|
|
398
444
|
switch (discriminator.by) {
|
|
399
445
|
case 'property': {
|
|
400
446
|
const property = discriminator.property
|
|
@@ -454,7 +500,7 @@ export function buildGraphQLSchema(section: GraphQLMetadata, functions: FieldFun
|
|
|
454
500
|
...(value.defaultValue === undefined ? {} : { default: { value: value.defaultValue } }),
|
|
455
501
|
})
|
|
456
502
|
const fieldConfig = (
|
|
457
|
-
type: GraphQLObjectTypeDefinition,
|
|
503
|
+
type: GraphQLObjectTypeDefinition | GraphQLInterfaceTypeDefinition,
|
|
458
504
|
field: GraphQLFieldDefinition,
|
|
459
505
|
): GraphQLFieldConfig<unknown, unknown> => {
|
|
460
506
|
const config: GraphQLFieldConfig<unknown, unknown> = {
|
|
@@ -465,7 +511,7 @@ export function buildGraphQLSchema(section: GraphQLMetadata, functions: FieldFun
|
|
|
465
511
|
? {}
|
|
466
512
|
: { args: Object.fromEntries(field.args.map(arg => [arg.name, inputValue(arg)])) }),
|
|
467
513
|
}
|
|
468
|
-
if (field.resolver === undefined) return config
|
|
514
|
+
if (field.resolver === undefined || type.kind === 'interface') return config
|
|
469
515
|
const resolved = functions(field.resolver, `${type.name}.${field.name}`, field, type)
|
|
470
516
|
return {
|
|
471
517
|
...config,
|
|
@@ -502,7 +548,7 @@ export function buildGraphQLSchema(section: GraphQLMetadata, functions: FieldFun
|
|
|
502
548
|
function createType(
|
|
503
549
|
type: GraphQLTypeDefinition,
|
|
504
550
|
fieldConfig: (
|
|
505
|
-
type: GraphQLObjectTypeDefinition,
|
|
551
|
+
type: GraphQLObjectTypeDefinition | GraphQLInterfaceTypeDefinition,
|
|
506
552
|
field: GraphQLFieldDefinition,
|
|
507
553
|
) => GraphQLFieldConfig<unknown, unknown>,
|
|
508
554
|
inputValue: (value: GraphQLArgumentDefinition) => GraphQLInputFieldConfig,
|
|
@@ -514,9 +560,18 @@ function createType(
|
|
|
514
560
|
return new GraphQLObjectType({
|
|
515
561
|
name: type.name,
|
|
516
562
|
...description,
|
|
563
|
+
...interfacesOf(type, lookup),
|
|
517
564
|
// thunk: object types reference each other (cycles)
|
|
518
565
|
fields: () => Object.fromEntries(type.fields.map(field => [field.name, fieldConfig(type, field)])),
|
|
519
566
|
})
|
|
567
|
+
case 'interface':
|
|
568
|
+
return new GraphQLInterfaceType({
|
|
569
|
+
name: type.name,
|
|
570
|
+
...description,
|
|
571
|
+
...interfacesOf(type, lookup),
|
|
572
|
+
fields: () => Object.fromEntries(type.fields.map(field => [field.name, fieldConfig(type, field)])),
|
|
573
|
+
resolveType: typeResolver(type.discriminator),
|
|
574
|
+
})
|
|
520
575
|
case 'input':
|
|
521
576
|
return new GraphQLInputObjectType({
|
|
522
577
|
name: type.name,
|
|
@@ -548,10 +603,33 @@ function createType(
|
|
|
548
603
|
})
|
|
549
604
|
default: {
|
|
550
605
|
if ('builtin' in type) return builtinScalar(type.name) as GraphQLScalarType
|
|
606
|
+
// `sdl`: the entry's description and specifiedByURL replace the definition's, absent ones too
|
|
607
|
+
const sdl = type.sdl === true
|
|
608
|
+
const overrides = {
|
|
609
|
+
...(type.description === undefined && !sdl ? {} : { description: type.description }),
|
|
610
|
+
...(type.specifiedByURL === undefined && !sdl ? {} : { specifiedByURL: type.specifiedByURL }),
|
|
611
|
+
}
|
|
551
612
|
const scalar = type.scalar
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
613
|
+
if (scalar instanceof GraphQLScalarType) {
|
|
614
|
+
if (Object.keys(overrides).length === 0) return scalar
|
|
615
|
+
const config = scalar.toConfig()
|
|
616
|
+
return new GraphQLScalarType({
|
|
617
|
+
...config,
|
|
618
|
+
description: 'description' in overrides ? overrides.description : config.description,
|
|
619
|
+
specifiedByURL: 'specifiedByURL' in overrides ? overrides.specifiedByURL : config.specifiedByURL,
|
|
620
|
+
})
|
|
621
|
+
}
|
|
622
|
+
return definitionScalar(type.name, scalar as ScalarDefinition<unknown>, overrides)
|
|
555
623
|
}
|
|
556
624
|
}
|
|
557
625
|
}
|
|
626
|
+
|
|
627
|
+
/** The `interfaces` thunk of an object or interface type (none: no key, as before RFC 0007). */
|
|
628
|
+
function interfacesOf(
|
|
629
|
+
type: GraphQLObjectTypeDefinition | GraphQLInterfaceTypeDefinition,
|
|
630
|
+
lookup: (name: string) => GraphQLNamedType,
|
|
631
|
+
): { interfaces?: () => GraphQLInterfaceType[] } {
|
|
632
|
+
const names = type.interfaces
|
|
633
|
+
if (names === undefined || names.length === 0) return {}
|
|
634
|
+
return { interfaces: () => names.map(name => lookup(name) as GraphQLInterfaceType) }
|
|
635
|
+
}
|
package/src/sdl.ts
CHANGED
|
@@ -36,7 +36,7 @@ export interface SdlRoots {
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
/** A named type of the schema. */
|
|
39
|
-
export type SdlType = SdlObjectType | SdlInputType | SdlEnumType | SdlUnionType | SdlScalarType
|
|
39
|
+
export type SdlType = SdlObjectType | SdlInterfaceType | SdlInputType | SdlEnumType | SdlUnionType | SdlScalarType
|
|
40
40
|
|
|
41
41
|
/** What every named type has. */
|
|
42
42
|
export interface SdlNamedType {
|
|
@@ -47,6 +47,15 @@ export interface SdlNamedType {
|
|
|
47
47
|
/** An object type (`type Book { ... }`). */
|
|
48
48
|
export interface SdlObjectType extends SdlNamedType {
|
|
49
49
|
readonly kind: 'object'
|
|
50
|
+
/** Implemented interfaces, in declaration order. */
|
|
51
|
+
readonly interfaces?: readonly string[]
|
|
52
|
+
readonly fields: readonly SdlField[]
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** An interface type (`interface Node { ... }`). */
|
|
56
|
+
export interface SdlInterfaceType extends SdlNamedType {
|
|
57
|
+
readonly kind: 'interface'
|
|
58
|
+
readonly interfaces?: readonly string[]
|
|
50
59
|
readonly fields: readonly SdlField[]
|
|
51
60
|
}
|
|
52
61
|
|
|
@@ -168,7 +177,8 @@ class SdlPrinter {
|
|
|
168
177
|
const head = printDescription(type.description, '', true)
|
|
169
178
|
switch (type.kind) {
|
|
170
179
|
case 'object':
|
|
171
|
-
|
|
180
|
+
case 'interface':
|
|
181
|
+
return `${head}${type.kind === 'object' ? 'type' : 'interface'} ${type.name}${printImplements(type.interfaces)}${printBlock(type.fields.map((field, i) => this.#field(type.name, field, i)))}`
|
|
172
182
|
case 'input':
|
|
173
183
|
return `${head}input ${type.name}${printBlock(
|
|
174
184
|
type.fields.map(
|
|
@@ -277,6 +287,11 @@ class SdlPrinter {
|
|
|
277
287
|
}
|
|
278
288
|
}
|
|
279
289
|
|
|
290
|
+
/** graphql-js `printImplementedInterfaces`. */
|
|
291
|
+
function printImplements(interfaces: readonly string[] | undefined): string {
|
|
292
|
+
return interfaces === undefined || interfaces.length === 0 ? '' : ` implements ${interfaces.join(' & ')}`
|
|
293
|
+
}
|
|
294
|
+
|
|
280
295
|
/** The `valueToLiteral` of the specified scalars, and graphql-js' default one for the others. */
|
|
281
296
|
function scalarLiteral(value: unknown, name: string): Literal | undefined {
|
|
282
297
|
switch (name) {
|
package/src/types.ts
CHANGED
|
@@ -171,6 +171,41 @@ export interface GraphQLErrorMapper {
|
|
|
171
171
|
/** A class producing {@link GraphQLErrorMapper}s. */
|
|
172
172
|
export type GraphQLErrorMapperClass = abstract new (...args: never[]) => GraphQLErrorMapper
|
|
173
173
|
|
|
174
|
+
/**
|
|
175
|
+
* Turns the errors of a field into values of the field (errors as data, RFC 0008). It applies to
|
|
176
|
+
* the resolver-backed fields whose type is a union holding one of the object types it declares
|
|
177
|
+
* (`static types`), decided at boot; every other field keeps its errors in `errors[]`.
|
|
178
|
+
*
|
|
179
|
+
* ```ts
|
|
180
|
+
* @Injectable()
|
|
181
|
+
* export class Rejections implements GraphQLResultMapper {
|
|
182
|
+
* static readonly types = ['NotFoundRejection']
|
|
183
|
+
*
|
|
184
|
+
* toResult(error: unknown): NotFoundRejection | undefined {
|
|
185
|
+
* if (error instanceof NotFoundException) return { __typename: 'NotFoundRejection', message: error.message }
|
|
186
|
+
* return undefined
|
|
187
|
+
* }
|
|
188
|
+
* }
|
|
189
|
+
* ```
|
|
190
|
+
*/
|
|
191
|
+
export interface GraphQLResultMapper {
|
|
192
|
+
/**
|
|
193
|
+
* The value of the field for `error`: an object of a member of the field's union, or `undefined`
|
|
194
|
+
* to leave the error to the next mapper, then to `errors[]`. Synchronous. `error` is the raw
|
|
195
|
+
* error, a `GraphQLException` included (a guard returning `false` is `FORBIDDEN`, a Standard
|
|
196
|
+
* Schema failure of the arguments `BAD_USER_INPUT` with `extensions.issues`).
|
|
197
|
+
*/
|
|
198
|
+
toResult(error: unknown, ctx: ResolverContext): unknown
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/** The object types a result mapper may produce: names of object types of the schema. */
|
|
202
|
+
export interface GraphQLResultTypes {
|
|
203
|
+
readonly types: readonly string[]
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** A class producing {@link GraphQLResultMapper}s, with the object types it produces (`static types`). */
|
|
207
|
+
export type GraphQLResultMapperClass = (abstract new (...args: never[]) => GraphQLResultMapper) & GraphQLResultTypes
|
|
208
|
+
|
|
174
209
|
/**
|
|
175
210
|
* The implementation of a custom scalar (§5.7), given to `scalar<T>(name, definition)`. The
|
|
176
211
|
* runtime maps it to graphql-js 17 `GraphQLScalarType` coercers: `serialize` → `coerceOutputValue`,
|