@znight/graphql 0.0.0-stage → 0.2.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.
Files changed (62) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1062 -2
  3. package/dist/compiler.d.mts +93 -0
  4. package/dist/compiler.mjs +2832 -0
  5. package/dist/compiler.mjs.map +1 -0
  6. package/dist/index.d.mts +542 -0
  7. package/dist/index.mjs +128 -0
  8. package/dist/index.mjs.map +1 -0
  9. package/dist/names-BlzDM5cF.mjs +111 -0
  10. package/dist/names-BlzDM5cF.mjs.map +1 -0
  11. package/dist/plugin-BdBxr5NQ.mjs +3222 -0
  12. package/dist/plugin-BdBxr5NQ.mjs.map +1 -0
  13. package/dist/testing.d.mts +101 -0
  14. package/dist/testing.mjs +306 -0
  15. package/dist/testing.mjs.map +1 -0
  16. package/dist/types-CgbV9LjD.d.mts +234 -0
  17. package/package.json +95 -4
  18. package/src/compiler/analyze.ts +608 -0
  19. package/src/compiler/builder.ts +1640 -0
  20. package/src/compiler/constants.ts +74 -0
  21. package/src/compiler/data.ts +73 -0
  22. package/src/compiler/defaults.ts +92 -0
  23. package/src/compiler/discriminators.ts +53 -0
  24. package/src/compiler/docs.ts +55 -0
  25. package/src/compiler/generate.ts +405 -0
  26. package/src/compiler/ids.ts +128 -0
  27. package/src/compiler/index.ts +93 -0
  28. package/src/compiler/locate.ts +44 -0
  29. package/src/compiler/model.ts +122 -0
  30. package/src/compiler/options.ts +54 -0
  31. package/src/compiler/scalars.ts +138 -0
  32. package/src/compiler/sdl-view.ts +59 -0
  33. package/src/context.ts +181 -0
  34. package/src/decisions.ts +169 -0
  35. package/src/decorators.ts +196 -0
  36. package/src/errors.ts +89 -0
  37. package/src/execution.ts +1064 -0
  38. package/src/format.ts +285 -0
  39. package/src/index.ts +84 -0
  40. package/src/limits.ts +177 -0
  41. package/src/loaders.ts +149 -0
  42. package/src/memory-pubsub.ts +133 -0
  43. package/src/metadata.ts +195 -0
  44. package/src/names.ts +47 -0
  45. package/src/options.ts +309 -0
  46. package/src/pipeline.ts +277 -0
  47. package/src/plugin.ts +557 -0
  48. package/src/pubsub.ts +36 -0
  49. package/src/scalars.ts +131 -0
  50. package/src/schema-hash.ts +18 -0
  51. package/src/schema.ts +557 -0
  52. package/src/sdl.ts +515 -0
  53. package/src/testing.ts +454 -0
  54. package/src/tokens.ts +22 -0
  55. package/src/transport/abort.ts +80 -0
  56. package/src/transport/accept.ts +155 -0
  57. package/src/transport/graphiql.ts +106 -0
  58. package/src/transport/http.ts +373 -0
  59. package/src/transport/sse.ts +45 -0
  60. package/src/type-ref.ts +89 -0
  61. package/src/types.ts +190 -0
  62. package/src/validate-args.ts +48 -0
package/src/schema.ts ADDED
@@ -0,0 +1,557 @@
1
+ /**
2
+ * The `GraphQLSchema` of `metadata.extensions.graphql` (RFC 0005 §7.3, §8.1): structural checks of
3
+ * the section (it can be hand-written), then graphql-js 17 constructors (thunks for cycles, every
4
+ * type passed in metadata order so that `printSchema()` matches `printSdl()`), then
5
+ * `validateSchema`. Every problem is collected and reported as one ZNIGHT066 error.
6
+ *
7
+ * Resolve functions are not decided here: the caller supplies them per resolver-backed field
8
+ * (`pipeline.ts`), so this module stays about the shape of the schema.
9
+ */
10
+ import type { ZnightMetadata } from '@znight/core'
11
+ import type {
12
+ GraphQLArgumentConfig,
13
+ GraphQLEnumValueConfig,
14
+ GraphQLFieldConfig,
15
+ GraphQLFieldResolver,
16
+ GraphQLInputFieldConfig,
17
+ GraphQLInputType,
18
+ GraphQLNamedType,
19
+ GraphQLOutputType,
20
+ GraphQLTypeResolver,
21
+ } from 'graphql'
22
+ import type {
23
+ GraphQLArgumentDefinition,
24
+ GraphQLDiscriminator,
25
+ GraphQLFieldDefinition,
26
+ GraphQLInputTypeDefinition,
27
+ GraphQLMetadata,
28
+ GraphQLObjectTypeDefinition,
29
+ GraphQLResolverBinding,
30
+ GraphQLTypeDefinition,
31
+ } from './metadata.ts'
32
+ import type { GraphQLTypeRef } from './type-ref.ts'
33
+ import type { ScalarDefinition } from './types.ts'
34
+
35
+ import { ErrorCode, ZnightError } from '@znight/core'
36
+ import {
37
+ GraphQLBoolean,
38
+ GraphQLEnumType,
39
+ GraphQLFloat,
40
+ GraphQLID,
41
+ GraphQLInputObjectType,
42
+ GraphQLInt,
43
+ GraphQLList,
44
+ GraphQLNonNull,
45
+ GraphQLObjectType,
46
+ GraphQLScalarType,
47
+ GraphQLSchema,
48
+ GraphQLString,
49
+ GraphQLUnionType,
50
+ validateSchema,
51
+ } from 'graphql'
52
+
53
+ import { GRAPHQL_METADATA_VERSION } from './metadata.ts'
54
+ import { BUILTIN_SCALARS, isGraphQLName, RESERVED_ENUM_VALUES, SPECIFIED_SCALARS } from './names.ts'
55
+ import { builtinScalar, definitionScalar } from './scalars.ts'
56
+ import { parseTypeRef } from './type-ref.ts'
57
+
58
+ /** The resolve functions of a resolver-backed field, decided by the caller (`pipeline.ts`). */
59
+ export interface FieldFunctions {
60
+ readonly resolve?: GraphQLFieldResolver<unknown, unknown>
61
+ /** Root subscription fields: the source event stream. */
62
+ readonly subscribe?: GraphQLFieldResolver<unknown, unknown>
63
+ }
64
+
65
+ /** Builds the resolve functions of the field `coordinate` (`Query.books`) backed by `binding`. */
66
+ export type FieldFunctionsFactory = (
67
+ binding: GraphQLResolverBinding,
68
+ coordinate: string,
69
+ field: GraphQLFieldDefinition,
70
+ parent: GraphQLObjectTypeDefinition,
71
+ ) => FieldFunctions
72
+
73
+ /** The graphql section of the metadata, or ZNIGHT066 when the application has none. */
74
+ export function graphqlSection(metadata: ZnightMetadata): GraphQLMetadata {
75
+ const section: unknown = metadata.extensions?.graphql
76
+ if (section === undefined) {
77
+ throw new ZnightError(
78
+ ErrorCode.InvalidGraphQLSetup,
79
+ 'GraphQLPlugin: the metadata has no GraphQL schema (metadata.extensions.graphql)',
80
+ {
81
+ hints: [
82
+ 'Declare at least one @Resolver() class with a @Query() method',
83
+ 'Regenerate the metadata (znight generate) when resolvers exist',
84
+ ],
85
+ },
86
+ )
87
+ }
88
+ return section as GraphQLMetadata
89
+ }
90
+
91
+ const OPERATIONS: ReadonlySet<string> = new Set(['query', 'mutation', 'subscription', 'field'])
92
+ const INPUT_KINDS: ReadonlySet<string> = new Set(['input', 'enum', 'scalar'])
93
+ const OUTPUT_KINDS: ReadonlySet<string> = new Set(['object', 'enum', 'scalar', 'union'])
94
+
95
+ function isRecord(value: unknown): value is Record<string, unknown> {
96
+ return value !== null && typeof value === 'object' && !Array.isArray(value)
97
+ }
98
+
99
+ function show(value: unknown): string {
100
+ if (typeof value === 'function') return 'a function'
101
+ try {
102
+ return JSON.stringify(value) ?? String(value)
103
+ } catch {
104
+ return typeof value
105
+ }
106
+ }
107
+
108
+ /**
109
+ * The structural problems of a graphql section (empty when it can be built): version, roots,
110
+ * names, kinds, type references, resolver bindings, discriminators, scalars and groups.
111
+ */
112
+ export function checkGraphQLSection(section: unknown): string[] {
113
+ const problems: string[] = []
114
+ if (!isRecord(section)) return [`expected an object (received ${show(section)})`]
115
+ if (section['version'] !== GRAPHQL_METADATA_VERSION) {
116
+ problems.push(
117
+ `unsupported version ${show(section['version'])}: this @znight/graphql reads version ${GRAPHQL_METADATA_VERSION} (rebuild the application with the same @znight/graphql version)`,
118
+ )
119
+ return problems
120
+ }
121
+ if (typeof section['schemaHash'] !== 'string') problems.push('schemaHash must be a string')
122
+ const types = section['types']
123
+ if (!Array.isArray(types)) {
124
+ problems.push('types must be an array')
125
+ return problems
126
+ }
127
+ const kinds = new Map<string, string>()
128
+ for (const [index, type] of types.entries()) {
129
+ if (!isRecord(type) || typeof type['name'] !== 'string' || typeof type['kind'] !== 'string') {
130
+ problems.push(`types[${index}] must be { kind, name, ... } (received ${show(type)})`)
131
+ continue
132
+ }
133
+ const name = type['name']
134
+ if (!isGraphQLName(name)) problems.push(`invalid type name ${show(name)}`)
135
+ else if (SPECIFIED_SCALARS.has(name)) problems.push(`the specified scalar ${name} must not be declared`)
136
+ else if (kinds.has(name)) problems.push(`the type ${name} is declared twice`)
137
+ kinds.set(name, type['kind'])
138
+ }
139
+ const kindOf = (name: string): string | undefined => (SPECIFIED_SCALARS.has(name) ? 'scalar' : kinds.get(name))
140
+ const checkRef = (where: string, text: unknown, allowed: ReadonlySet<string>, position: string): void => {
141
+ const ref = typeof text === 'string' ? parseTypeRef(text) : undefined
142
+ if (ref === undefined) {
143
+ problems.push(`${where}: invalid type reference ${show(text)}`)
144
+ return
145
+ }
146
+ let named: GraphQLTypeRef = ref
147
+ while (named.kind === 'list') named = named.of
148
+ const kind = kindOf(named.name)
149
+ if (kind === undefined) problems.push(`${where}: unknown type ${named.name}`)
150
+ else if (!allowed.has(kind))
151
+ problems.push(`${where}: ${named.name} (${kind}) cannot be used as an ${position} type`)
152
+ }
153
+ const checkInputValues = (where: string, values: unknown, required: boolean): void => {
154
+ if (!Array.isArray(values) || (required && values.length === 0)) {
155
+ problems.push(`${where} must be a ${required ? 'non-empty ' : ''}array`)
156
+ return
157
+ }
158
+ const seen = new Set<string>()
159
+ for (const value of values) {
160
+ if (!isRecord(value) || typeof value['name'] !== 'string') {
161
+ problems.push(`${where}: expected { name, type } (received ${show(value)})`)
162
+ continue
163
+ }
164
+ const name = value['name']
165
+ if (!isGraphQLName(name)) problems.push(`${where}: invalid name ${show(name)}`)
166
+ if (seen.has(name)) problems.push(`${where}: ${name} is declared twice`)
167
+ seen.add(name)
168
+ checkRef(`${where}.${name}`, value['type'], INPUT_KINDS, 'input')
169
+ }
170
+ }
171
+ for (const type of types) {
172
+ if (!isRecord(type) || typeof type['name'] !== 'string') continue
173
+ const name = type['name']
174
+ switch (type['kind']) {
175
+ case 'object': {
176
+ const fields = type['fields']
177
+ if (!Array.isArray(fields) || fields.length === 0) {
178
+ problems.push(`${name}: fields must be a non-empty array`)
179
+ break
180
+ }
181
+ const seen = new Set<string>()
182
+ for (const field of fields) {
183
+ if (!isRecord(field) || typeof field['name'] !== 'string') {
184
+ problems.push(`${name}: expected fields { name, type } (received ${show(field)})`)
185
+ continue
186
+ }
187
+ const coordinate = `${name}.${field['name']}`
188
+ if (!isGraphQLName(field['name'])) problems.push(`${name}: invalid field name ${show(field['name'])}`)
189
+ if (seen.has(field['name'])) problems.push(`${coordinate} is declared twice`)
190
+ seen.add(field['name'])
191
+ checkRef(coordinate, field['type'], OUTPUT_KINDS, 'output')
192
+ if (field['args'] !== undefined) checkInputValues(`${coordinate} arguments`, field['args'], false)
193
+ if (field['resolver'] !== undefined) checkBinding(coordinate, field['resolver'], problems)
194
+ }
195
+ break
196
+ }
197
+ case 'input':
198
+ checkInputValues(`${name} fields`, type['fields'], true)
199
+ break
200
+ case 'enum': {
201
+ const values = type['values']
202
+ if (!Array.isArray(values) || values.length === 0) {
203
+ problems.push(`${name}: values must be a non-empty array`)
204
+ break
205
+ }
206
+ const seen = new Set<string>()
207
+ for (const value of values) {
208
+ const valueName = isRecord(value) ? value['name'] : undefined
209
+ if (
210
+ !isRecord(value) ||
211
+ typeof valueName !== 'string' ||
212
+ !isGraphQLName(valueName) ||
213
+ RESERVED_ENUM_VALUES.has(valueName) ||
214
+ (typeof value['value'] !== 'string' && typeof value['value'] !== 'number')
215
+ ) {
216
+ problems.push(`${name}: invalid enum value ${show(value)} (expected { name, value: string | number })`)
217
+ continue
218
+ }
219
+ if (seen.has(valueName)) problems.push(`${name}.${valueName} is declared twice`)
220
+ seen.add(valueName)
221
+ }
222
+ break
223
+ }
224
+ case 'union': {
225
+ const members = type['members']
226
+ if (!Array.isArray(members) || members.length === 0) {
227
+ problems.push(`${name}: members must be a non-empty array`)
228
+ break
229
+ }
230
+ for (const member of members) {
231
+ if (typeof member !== 'string' || kinds.get(member) !== 'object') {
232
+ problems.push(`${name}: the member ${show(member)} is not an object type`)
233
+ }
234
+ }
235
+ checkDiscriminator(name, type['discriminator'], members, problems)
236
+ break
237
+ }
238
+ case 'scalar':
239
+ if (type['builtin'] === true) {
240
+ if (!BUILTIN_SCALARS.has(name))
241
+ problems.push(`${name} is not a built-in scalar (${[...BUILTIN_SCALARS.keys()].join(', ')})`)
242
+ } else {
243
+ const scalar = type['scalar']
244
+ if (scalar instanceof GraphQLScalarType) {
245
+ if (scalar.name !== name) {
246
+ problems.push(
247
+ `the scalar ${name} is implemented by the graphql-js scalar ${scalar.name} (names must match)`,
248
+ )
249
+ }
250
+ } else if (
251
+ !isRecord(scalar) ||
252
+ typeof scalar['serialize'] !== 'function' ||
253
+ typeof scalar['parseValue'] !== 'function' ||
254
+ (scalar['parseLiteral'] !== undefined && typeof scalar['parseLiteral'] !== 'function')
255
+ ) {
256
+ problems.push(
257
+ `the scalar ${name} needs a definition { serialize, parseValue, parseLiteral? } or a GraphQLScalarType (received ${show(scalar)})`,
258
+ )
259
+ }
260
+ }
261
+ break
262
+ default:
263
+ problems.push(`${name}: unknown kind ${show(type['kind'])}`)
264
+ }
265
+ }
266
+ checkRoots(section['roots'], kinds, types, problems)
267
+ checkGroups(section['groups'], problems)
268
+ return problems
269
+ }
270
+
271
+ function checkBinding(coordinate: string, binding: unknown, problems: string[]): void {
272
+ if (!isRecord(binding)) {
273
+ problems.push(`${coordinate}: resolver must be an object (received ${show(binding)})`)
274
+ return
275
+ }
276
+ const invalid: string[] = []
277
+ if (typeof binding['token'] !== 'string' || binding['token'] === '') invalid.push('token')
278
+ if (typeof binding['method'] !== 'string') invalid.push('method')
279
+ if (typeof binding['operation'] !== 'string' || !OPERATIONS.has(binding['operation'])) invalid.push('operation')
280
+ const guards = binding['guards']
281
+ if (!Array.isArray(guards) || !guards.every(guard => typeof guard === 'string' && guard !== ''))
282
+ invalid.push('guards')
283
+ if (!isRecord(binding['meta'])) invalid.push('meta')
284
+ if (typeof binding['invoke'] !== 'function') invalid.push('invoke')
285
+ if (invalid.length > 0) problems.push(`${coordinate}: invalid resolver binding (${invalid.join(', ')})`)
286
+ }
287
+
288
+ function checkDiscriminator(
289
+ name: string,
290
+ discriminator: unknown,
291
+ members: readonly unknown[],
292
+ problems: string[],
293
+ ): void {
294
+ const memberSet = new Set(members)
295
+ if (!isRecord(discriminator)) {
296
+ problems.push(`${name}: discriminator must be an object (received ${show(discriminator)})`)
297
+ return
298
+ }
299
+ switch (discriminator['by']) {
300
+ case 'typename':
301
+ return
302
+ case 'property': {
303
+ const values = discriminator['values']
304
+ if (typeof discriminator['property'] !== 'string' || !isRecord(values)) {
305
+ problems.push(`${name}: a property discriminator needs { property, values }`)
306
+ return
307
+ }
308
+ for (const member of Object.values(values)) {
309
+ if (!memberSet.has(member)) problems.push(`${name}: the discriminator names ${show(member)}, not a member`)
310
+ }
311
+ return
312
+ }
313
+ case 'class': {
314
+ const classes = discriminator['classes']
315
+ if (!Array.isArray(classes)) {
316
+ problems.push(`${name}: a class discriminator needs classes: [Class, member][]`)
317
+ return
318
+ }
319
+ for (const entry of classes) {
320
+ if (!Array.isArray(entry) || typeof entry[0] !== 'function' || !memberSet.has(entry[1])) {
321
+ problems.push(`${name}: invalid class discriminator entry ${show(entry)} (expected [Class, member])`)
322
+ }
323
+ }
324
+ return
325
+ }
326
+ default:
327
+ problems.push(`${name}: unknown discriminator ${show(discriminator['by'])}`)
328
+ }
329
+ }
330
+
331
+ function checkRoots(
332
+ roots: unknown,
333
+ kinds: ReadonlyMap<string, string>,
334
+ types: readonly unknown[],
335
+ problems: string[],
336
+ ): void {
337
+ if (!isRecord(roots) || typeof roots['query'] !== 'string') {
338
+ problems.push(`roots must be { query, mutation?, subscription? } (received ${show(roots)})`)
339
+ return
340
+ }
341
+ for (const operation of ['query', 'mutation', 'subscription'] as const) {
342
+ const root = roots[operation]
343
+ if (root === undefined) continue
344
+ if (typeof root !== 'string' || kinds.get(root) !== 'object') {
345
+ problems.push(`the ${operation} root ${show(root)} is not an object type`)
346
+ }
347
+ }
348
+ const subscription = roots['subscription']
349
+ if (typeof subscription !== 'string') return
350
+ for (const type of types) {
351
+ if (!isRecord(type) || type['name'] !== subscription || !Array.isArray(type['fields'])) continue
352
+ for (const field of type['fields'] as unknown[]) {
353
+ if (!isRecord(field)) continue
354
+ const binding = field['resolver']
355
+ if (!isRecord(binding) || binding['operation'] !== 'subscription') {
356
+ problems.push(`${subscription}.${show(field['name'])} needs a subscription resolver (its source event stream)`)
357
+ }
358
+ }
359
+ }
360
+ }
361
+
362
+ function checkGroups(groups: unknown, problems: string[]): void {
363
+ if (groups === undefined) return
364
+ if (!isRecord(groups)) {
365
+ problems.push(`groups must be an object (received ${show(groups)})`)
366
+ return
367
+ }
368
+ for (const [group, entry] of Object.entries(groups)) {
369
+ const keys = isRecord(entry) ? entry['keys'] : undefined
370
+ if (
371
+ !isRecord(entry) ||
372
+ !Array.isArray(keys) ||
373
+ keys.length === 0 ||
374
+ !keys.every(key => typeof key === 'string' && key !== '') ||
375
+ typeof entry['required'] !== 'boolean'
376
+ ) {
377
+ problems.push(`group ${show(group)} must be { keys: [non-empty strings], required: boolean }`)
378
+ }
379
+ }
380
+ }
381
+
382
+ /** Throws one ZNIGHT066 error listing `problems`. */
383
+ export function invalidSchema(problems: readonly string[]): never {
384
+ throw new ZnightError(
385
+ ErrorCode.InvalidGraphQLSetup,
386
+ `GraphQLPlugin: invalid GraphQL schema (metadata.extensions.graphql), ${problems.length} ${problems.length === 1 ? 'problem' : 'problems'}:\n${problems.map(problem => ` - ${problem}`).join('\n')}`,
387
+ {
388
+ hints: [
389
+ 'Regenerate the metadata with the @znight/graphql version the application runs (znight generate)',
390
+ 'Hand-written metadata must follow GraphQLMetadata (@znight/graphql, RFC 0005 §7.1)',
391
+ ],
392
+ },
393
+ )
394
+ }
395
+
396
+ /** A union's `resolveType`, from its discriminator: data read once, at boot. */
397
+ function typeResolver(discriminator: GraphQLDiscriminator): GraphQLTypeResolver<unknown, unknown> {
398
+ switch (discriminator.by) {
399
+ case 'property': {
400
+ const property = discriminator.property
401
+ const values = new Map(Object.entries(discriminator.values))
402
+ return value =>
403
+ value !== null && typeof value === 'object'
404
+ ? (values.get(String((value as Record<string, unknown>)[property])) ?? typenameOf(value))
405
+ : undefined
406
+ }
407
+ case 'class': {
408
+ const classes = discriminator.classes
409
+ return value => {
410
+ for (const [cls, name] of classes) if (value instanceof cls) return name
411
+ return typenameOf(value)
412
+ }
413
+ }
414
+ default:
415
+ return typenameOf
416
+ }
417
+ }
418
+
419
+ function typenameOf(value: unknown): string | undefined {
420
+ if (value === null || typeof value !== 'object') return undefined
421
+ const name = (value as { __typename?: unknown }).__typename
422
+ return typeof name === 'string' ? name : undefined
423
+ }
424
+
425
+ /**
426
+ * Builds the schema of a section. `functions` decides the resolve functions of every
427
+ * resolver-backed field; plain property fields keep the graphql-js default resolver.
428
+ *
429
+ * @throws ZnightError ZNIGHT066 listing every structural, construction and `validateSchema` problem.
430
+ */
431
+ export function buildGraphQLSchema(section: GraphQLMetadata, functions: FieldFunctionsFactory): GraphQLSchema {
432
+ const structural = checkGraphQLSection(section)
433
+ if (structural.length > 0) invalidSchema(structural)
434
+ const named = new Map<string, GraphQLNamedType>([
435
+ ['String', GraphQLString],
436
+ ['Int', GraphQLInt],
437
+ ['Float', GraphQLFloat],
438
+ ['Boolean', GraphQLBoolean],
439
+ ['ID', GraphQLID],
440
+ ])
441
+ const problems: string[] = []
442
+ const lookup = (name: string): GraphQLNamedType => named.get(name) as GraphQLNamedType
443
+ const typeOf = (ref: GraphQLTypeRef): GraphQLInputType & GraphQLOutputType => {
444
+ const inner = ref.kind === 'named' ? lookup(ref.name) : new GraphQLList(typeOf(ref.of))
445
+ return (ref.nonNull ? new GraphQLNonNull(inner) : inner) as GraphQLInputType & GraphQLOutputType
446
+ }
447
+ // references were checked by checkGraphQLSection
448
+ const typeRef = (text: string): GraphQLInputType & GraphQLOutputType => typeOf(parseTypeRef(text) as GraphQLTypeRef)
449
+ const inputValue = (value: GraphQLArgumentDefinition): GraphQLArgumentConfig & GraphQLInputFieldConfig => ({
450
+ type: typeRef(value.type),
451
+ ...(value.description === undefined ? {} : { description: value.description }),
452
+ ...(value.deprecationReason === undefined ? {} : { deprecationReason: value.deprecationReason }),
453
+ // external value (enum values by name): what printSdl() assumes (sdl.ts)
454
+ ...(value.defaultValue === undefined ? {} : { default: { value: value.defaultValue } }),
455
+ })
456
+ const fieldConfig = (
457
+ type: GraphQLObjectTypeDefinition,
458
+ field: GraphQLFieldDefinition,
459
+ ): GraphQLFieldConfig<unknown, unknown> => {
460
+ const config: GraphQLFieldConfig<unknown, unknown> = {
461
+ type: typeRef(field.type),
462
+ ...(field.description === undefined ? {} : { description: field.description }),
463
+ ...(field.deprecationReason === undefined ? {} : { deprecationReason: field.deprecationReason }),
464
+ ...(field.args === undefined || field.args.length === 0
465
+ ? {}
466
+ : { args: Object.fromEntries(field.args.map(arg => [arg.name, inputValue(arg)])) }),
467
+ }
468
+ if (field.resolver === undefined) return config
469
+ const resolved = functions(field.resolver, `${type.name}.${field.name}`, field, type)
470
+ return {
471
+ ...config,
472
+ ...(resolved.resolve === undefined ? {} : { resolve: resolved.resolve }),
473
+ ...(resolved.subscribe === undefined ? {} : { subscribe: resolved.subscribe }),
474
+ }
475
+ }
476
+ try {
477
+ for (const type of section.types) named.set(type.name, createType(type, fieldConfig, inputValue, lookup))
478
+ } catch (error) {
479
+ invalidSchema([`cannot build the schema: ${error instanceof Error ? error.message : String(error)}`])
480
+ }
481
+ const root = (name: string | undefined): GraphQLObjectType | undefined =>
482
+ name === undefined ? undefined : (lookup(name) as GraphQLObjectType)
483
+ let schema: GraphQLSchema | undefined
484
+ try {
485
+ schema = new GraphQLSchema({
486
+ query: root(section.roots.query),
487
+ mutation: root(section.roots.mutation),
488
+ subscription: root(section.roots.subscription),
489
+ // metadata order (sorted by name): printSchema() follows it, like printSdl()
490
+ types: section.types.map(type => lookup(type.name)),
491
+ })
492
+ for (const error of validateSchema(schema)) problems.push(error.message)
493
+ } catch (error) {
494
+ // thunks run here: a resolver factory (a missing provider keeps its own code) or a constructor
495
+ if (error instanceof ZnightError) throw error
496
+ problems.push(error instanceof Error ? error.message : String(error))
497
+ }
498
+ if (problems.length > 0 || schema === undefined) invalidSchema(problems)
499
+ return schema
500
+ }
501
+
502
+ function createType(
503
+ type: GraphQLTypeDefinition,
504
+ fieldConfig: (
505
+ type: GraphQLObjectTypeDefinition,
506
+ field: GraphQLFieldDefinition,
507
+ ) => GraphQLFieldConfig<unknown, unknown>,
508
+ inputValue: (value: GraphQLArgumentDefinition) => GraphQLInputFieldConfig,
509
+ lookup: (name: string) => GraphQLNamedType,
510
+ ): GraphQLNamedType {
511
+ const description = 'description' in type && type.description !== undefined ? { description: type.description } : {}
512
+ switch (type.kind) {
513
+ case 'object':
514
+ return new GraphQLObjectType({
515
+ name: type.name,
516
+ ...description,
517
+ // thunk: object types reference each other (cycles)
518
+ fields: () => Object.fromEntries(type.fields.map(field => [field.name, fieldConfig(type, field)])),
519
+ })
520
+ case 'input':
521
+ return new GraphQLInputObjectType({
522
+ name: type.name,
523
+ ...description,
524
+ fields: () =>
525
+ Object.fromEntries((type as GraphQLInputTypeDefinition).fields.map(field => [field.name, inputValue(field)])),
526
+ })
527
+ case 'enum':
528
+ return new GraphQLEnumType({
529
+ name: type.name,
530
+ ...description,
531
+ values: Object.fromEntries(
532
+ type.values.map((value): [string, GraphQLEnumValueConfig] => [
533
+ value.name,
534
+ {
535
+ value: value.value,
536
+ ...(value.description === undefined ? {} : { description: value.description }),
537
+ ...(value.deprecationReason === undefined ? {} : { deprecationReason: value.deprecationReason }),
538
+ },
539
+ ]),
540
+ ),
541
+ })
542
+ case 'union':
543
+ return new GraphQLUnionType({
544
+ name: type.name,
545
+ ...description,
546
+ types: () => type.members.map(member => lookup(member) as GraphQLObjectType),
547
+ resolveType: typeResolver(type.discriminator),
548
+ })
549
+ default: {
550
+ if ('builtin' in type) return builtinScalar(type.name) as GraphQLScalarType
551
+ const scalar = type.scalar
552
+ return scalar instanceof GraphQLScalarType
553
+ ? scalar
554
+ : definitionScalar(type.name, scalar as ScalarDefinition<unknown>)
555
+ }
556
+ }
557
+ }