@znight/graphql 0.0.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.
Files changed (69) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1346 -0
  3. package/dist/compiler.d.mts +114 -0
  4. package/dist/compiler.mjs +4599 -0
  5. package/dist/compiler.mjs.map +1 -0
  6. package/dist/index.d.mts +583 -0
  7. package/dist/index.mjs +134 -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-CR_wVfRU.mjs +3445 -0
  12. package/dist/plugin-CR_wVfRU.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-DQ713UgI.d.mts +266 -0
  17. package/package.json +97 -1
  18. package/src/compiler/analyze.ts +727 -0
  19. package/src/compiler/builder.ts +1561 -0
  20. package/src/compiler/checks.ts +105 -0
  21. package/src/compiler/conformance.ts +1068 -0
  22. package/src/compiler/constants.ts +80 -0
  23. package/src/compiler/data.ts +83 -0
  24. package/src/compiler/defaults.ts +92 -0
  25. package/src/compiler/discriminators.ts +53 -0
  26. package/src/compiler/docs.ts +55 -0
  27. package/src/compiler/generate.ts +419 -0
  28. package/src/compiler/graphql-js.ts +42 -0
  29. package/src/compiler/ids.ts +128 -0
  30. package/src/compiler/index.ts +98 -0
  31. package/src/compiler/locate.ts +82 -0
  32. package/src/compiler/model.ts +137 -0
  33. package/src/compiler/options.ts +85 -0
  34. package/src/compiler/scalars.ts +138 -0
  35. package/src/compiler/schema-first.ts +595 -0
  36. package/src/compiler/sdl-source.ts +529 -0
  37. package/src/compiler/sdl-view.ts +61 -0
  38. package/src/compiler/suggest.ts +39 -0
  39. package/src/context.ts +181 -0
  40. package/src/decisions.ts +169 -0
  41. package/src/decorators.ts +202 -0
  42. package/src/errors.ts +89 -0
  43. package/src/execution.ts +1064 -0
  44. package/src/format.ts +285 -0
  45. package/src/index.ts +89 -0
  46. package/src/limits.ts +177 -0
  47. package/src/loaders.ts +149 -0
  48. package/src/memory-pubsub.ts +133 -0
  49. package/src/metadata.ts +222 -0
  50. package/src/names.ts +47 -0
  51. package/src/options.ts +331 -0
  52. package/src/pipeline.ts +338 -0
  53. package/src/plugin.ts +579 -0
  54. package/src/pubsub.ts +36 -0
  55. package/src/results.ts +206 -0
  56. package/src/scalars.ts +138 -0
  57. package/src/schema-hash.ts +18 -0
  58. package/src/schema.ts +635 -0
  59. package/src/sdl.ts +530 -0
  60. package/src/testing.ts +454 -0
  61. package/src/tokens.ts +22 -0
  62. package/src/transport/abort.ts +80 -0
  63. package/src/transport/accept.ts +155 -0
  64. package/src/transport/graphiql.ts +106 -0
  65. package/src/transport/http.ts +373 -0
  66. package/src/transport/sse.ts +45 -0
  67. package/src/type-ref.ts +89 -0
  68. package/src/types.ts +225 -0
  69. package/src/validate-args.ts +48 -0
@@ -0,0 +1,4599 @@
1
+ import { a as SPECIFIED_SCALARS, c as printTypeRef, i as ROOT_TYPE_NAMES, n as DEFAULT_DEPRECATION_REASON, o as isGraphQLName, r as RESERVED_ENUM_VALUES, s as parseTypeRef, t as BUILTIN_SCALARS } from "./names-BlzDM5cF.mjs";
2
+ import { createRequire } from "node:module";
3
+ import { createHash } from "node:crypto";
4
+ import { effectiveRouteMeta, readRouteMeta, requiredRouteGroups } from "@znight/http/compiler";
5
+ // src/compiler/ids.ts
6
+ /** Name of the extension, also the key of `metadata.extensions.graphql`. */
7
+ const EXTENSION_NAME = "graphql";
8
+ /** Module the generated code imports its runtime helpers and types from. */
9
+ const RUNTIME_MODULE = "@znight/graphql";
10
+ /** Canonical identities of the exports of `@znight/graphql` the compiler reads. */
11
+ const GraphQLIds = Object.freeze({
12
+ Resolver: "@znight/graphql#Resolver",
13
+ Query: "@znight/graphql#Query",
14
+ Mutation: "@znight/graphql#Mutation",
15
+ Subscription: "@znight/graphql#Subscription",
16
+ ResolveField: "@znight/graphql#ResolveField",
17
+ Args: "@znight/graphql#Args",
18
+ Parent: "@znight/graphql#Parent",
19
+ Ctx: "@znight/graphql#Ctx",
20
+ Info: "@znight/graphql#Info",
21
+ Loader: "@znight/graphql#Loader",
22
+ UseGuard: "@znight/graphql#UseGuard",
23
+ scalar: "@znight/graphql#scalar",
24
+ ID: "@znight/graphql#ID",
25
+ Int: "@znight/graphql#Int",
26
+ Float: "@znight/graphql#Float",
27
+ JSONValue: "@znight/graphql#JSONValue",
28
+ BatchLoad: "@znight/graphql#BatchLoad",
29
+ GraphQLContext: "@znight/graphql#GraphQLContext",
30
+ ResolverContext: "@znight/graphql#ResolverContext",
31
+ PubSub: "@znight/graphql#PubSub",
32
+ GraphQLPlugin: "@znight/graphql#GraphQLPlugin"
33
+ });
34
+ /** Field decorators → the kind of field they declare (`GraphQLResolverBinding.operation`). */
35
+ const FIELD_DECORATORS = /* @__PURE__ */ new Map([
36
+ [GraphQLIds.Query, "query"],
37
+ [GraphQLIds.Mutation, "mutation"],
38
+ [GraphQLIds.Subscription, "subscription"],
39
+ [GraphQLIds.ResolveField, "field"]
40
+ ]);
41
+ /** Parameter decorators → what the generated `invoke` passes (§7.3). */
42
+ const PARAMETER_DECORATORS = /* @__PURE__ */ new Map([
43
+ [GraphQLIds.Args, "args"],
44
+ [GraphQLIds.Parent, "parent"],
45
+ [GraphQLIds.Ctx, "ctx"],
46
+ [GraphQLIds.Info, "info"],
47
+ [GraphQLIds.Loader, "loader"]
48
+ ]);
49
+ /** Scalar aliases of `@znight/graphql` → their GraphQL scalar (§6.2). */
50
+ const SCALAR_ALIASES = /* @__PURE__ */ new Map([
51
+ [GraphQLIds.ID, "ID"],
52
+ [GraphQLIds.Int, "Int"],
53
+ [GraphQLIds.Float, "Float"],
54
+ [GraphQLIds.JSONValue, "JSON"]
55
+ ]);
56
+ /**
57
+ * Decorators of `@znight/http` that look like GraphQL ones, on a resolver: ZNIGHT005 with the
58
+ * `@znight/graphql` decorator to import instead (§6.5).
59
+ */
60
+ const HTTP_LOOK_ALIKES = /* @__PURE__ */ new Map([
61
+ ["@znight/http#Ctx", GraphQLIds.Ctx],
62
+ ["@znight/http#Query", GraphQLIds.Args],
63
+ ["@znight/http#Param", GraphQLIds.Args],
64
+ ["@znight/http#Body", GraphQLIds.Args],
65
+ ["@znight/http#UseGuard", GraphQLIds.UseGuard]
66
+ ]);
67
+ /** `@Controller` of `@znight/http`: a class cannot be both a controller and a resolver (ZNIGHT005). */
68
+ const HTTP_CONTROLLER = "@znight/http#Controller";
69
+ // src/compiler/constants.ts
70
+ /** Diagnostic codes the extension reports (RFC 0005 §6.6 and the reused framework codes). */
71
+ const Codes = Object.freeze({
72
+ InvalidDecorator: "ZNIGHT005",
73
+ MissingRouteDecision: "ZNIGHT023",
74
+ InvalidType: "ZNIGHT060",
75
+ NameConflict: "ZNIGHT061",
76
+ InvalidResolver: "ZNIGHT062",
77
+ InvalidInput: "ZNIGHT063",
78
+ UnresolvableAbstractType: "ZNIGHT064",
79
+ InvalidLoader: "ZNIGHT065",
80
+ InvalidSdl: "ZNIGHT067",
81
+ SchemaMismatch: "ZNIGHT068"
82
+ });
83
+ /** Root reason given to `AnalyzeContext.addRoot()`. */
84
+ const ROOT_REASON = "GraphQL resolver";
85
+ /** Async iterables: the return type of `@Subscription` methods (§6.2). */
86
+ const ASYNC_ITERABLES = /* @__PURE__ */ new Set([
87
+ "globalThis#AsyncIterable",
88
+ "globalThis#AsyncIterableIterator",
89
+ "globalThis#AsyncGenerator"
90
+ ]);
91
+ /** Iterables mapped to lists in output position (§6.2). */
92
+ const OUTPUT_ITERABLES = /* @__PURE__ */ new Set([
93
+ "globalThis#Set",
94
+ "globalThis#ReadonlySet",
95
+ "globalThis#Iterable"
96
+ ]);
97
+ /**
98
+ * Identities the shapes keep as references (`ShapeOptions.keep`), before the identities of the
99
+ * `scalar<T>()` declarations of the application.
100
+ */
101
+ const BASE_KEEP = Object.freeze([
102
+ ...ASYNC_ITERABLES,
103
+ ...OUTPUT_ITERABLES,
104
+ GraphQLIds.ID,
105
+ GraphQLIds.Int,
106
+ GraphQLIds.Float,
107
+ GraphQLIds.JSONValue,
108
+ GraphQLIds.Loader
109
+ ]);
110
+ /** Deterministic string comparison (code-unit order, never `localeCompare`). */
111
+ function compareStrings(a, b) {
112
+ if (a === b) return 0;
113
+ return a < b ? -1 : 1;
114
+ }
115
+ // src/compiler/analyze.ts
116
+ const SCOPES = [
117
+ "singleton",
118
+ "request",
119
+ "transient"
120
+ ];
121
+ /** Shape options reading `Loader<L>` parameters: the alias is kept, `L` stays a named shape. */
122
+ const LOADER_SHAPE = Object.freeze({
123
+ keep: Object.freeze([GraphQLIds.Loader]),
124
+ jsdoc: false
125
+ });
126
+ /**
127
+ * Analyses a class carrying at least one graphql decorator (class, method or parameter). In
128
+ * schema-first mode (RFC 0007), `@Resolver('Type')` names the SDL type of the field resolvers and
129
+ * `@Parent()` is optional.
130
+ */
131
+ function analyzeGraphQLClass(cls, ctx, options) {
132
+ const resolvers = cls.decorators.filter((decorator) => decorator.identity === GraphQLIds.Resolver);
133
+ const resolver = resolvers[0];
134
+ if (resolver === void 0) {
135
+ reportOutsideResolver(cls, ctx);
136
+ storeClaims(ctx, cls, void 0, options);
137
+ return;
138
+ }
139
+ for (const extra of resolvers.slice(1)) error(ctx, Codes.InvalidDecorator, extra.location, `@Resolver is applied twice to ${cls.displayName}`, ["Apply it once"]);
140
+ const controller = cls.decorators.find((decorator) => decorator.identity === HTTP_CONTROLLER);
141
+ if (controller !== void 0) {
142
+ error(ctx, Codes.InvalidDecorator, resolver.location, `${cls.displayName} cannot be both a @Resolver and a @Controller`, ["A class is the provider of one extension: move the GraphQL fields to their own @Resolver() class (both may call the same service)"], [{
143
+ message: "@Controller is applied here",
144
+ location: controller.location
145
+ }]);
146
+ storeClaims(ctx, cls, resolver, options);
147
+ return;
148
+ }
149
+ if (ctx.options.library) {
150
+ error(ctx, Codes.InvalidResolver, resolver.location, `${cls.displayName} is a @Resolver in a compiled provider library: the schema is derived from the sources`, ["Publish the package as a source library (without library: true), so that applications compile its resolvers"]);
151
+ storeClaims(ctx, cls, resolver, options);
152
+ return;
153
+ }
154
+ if (cls.abstract || cls.typeParameters.length > 0) {
155
+ error(ctx, Codes.InvalidDecorator, resolver.location, cls.abstract ? `@Resolver cannot be applied to the abstract class ${cls.displayName}` : `@Resolver cannot be applied to the generic class ${cls.displayName}<${cls.typeParameters.join(", ")}>`, [cls.abstract ? "Resolvers are instantiated: remove `abstract`" : "Remove the type parameters of the resolver"]);
156
+ storeClaims(ctx, cls, resolver, options);
157
+ return;
158
+ }
159
+ const { scope, typeName } = resolverArguments(ctx, resolver, options.schema !== false);
160
+ ctx.registerProvider(cls, { scope });
161
+ ctx.addRoot(cls, {
162
+ reason: ROOT_REASON,
163
+ location: resolver.location
164
+ });
165
+ const classGuards = guardsOf(ctx, cls, cls.decorators);
166
+ const classMeta = readRouteMeta(ctx, cls);
167
+ const fields = [];
168
+ for (const method of cls.methods) {
169
+ const decorators = method.decorators.filter(isFieldDecorator);
170
+ if (decorators.length === 0) {
171
+ reportStrayMethodDecorators(cls, method, ctx);
172
+ continue;
173
+ }
174
+ fields.push(...analyzeMethod(ctx, cls, method, decorators, classMeta, options.schema !== false));
175
+ }
176
+ const data = {
177
+ kind: "resolver",
178
+ host: cls,
179
+ scope,
180
+ location: resolver.location,
181
+ guards: classGuards,
182
+ fields,
183
+ ...typeName === void 0 ? {} : { typeName }
184
+ };
185
+ if (options.schema !== false && typeName === void 0) for (const field of fields) {
186
+ if (field.operation !== "field") continue;
187
+ error(ctx, Codes.InvalidResolver, field.location, `@ResolveField ${cls.displayName}.${field.method} needs the type it resolves a field of: name it in @Resolver (schema-first)`, [`@Resolver('Event') on ${cls.displayName}, where Event is the object type of the SDL declaring '${field.name}'`]);
188
+ }
189
+ ctx.store(data);
190
+ }
191
+ /**
192
+ * Schema-first: a class rejected above (no `@Resolver`, abstract, a controller...) still names the
193
+ * fields its methods resolve, stored as an `invalid` resolver, so that these fields are not also
194
+ * reported as missing a resolver (one diagnostic per mistake).
195
+ */
196
+ function storeClaims(ctx, cls, resolver, options) {
197
+ if (options.schema === false) return;
198
+ const typeArg = resolver?.args[0];
199
+ const typeName = typeArg?.evaluated === true && typeof typeArg.value === "string" ? {
200
+ name: typeArg.value,
201
+ location: typeArg.location
202
+ } : void 0;
203
+ const fields = [];
204
+ for (const method of cls.methods) for (const decorator of method.decorators.filter(isFieldDecorator)) {
205
+ const arg = decorator.args[0];
206
+ fields.push({
207
+ method: method.name,
208
+ operation: FIELD_DECORATORS.get(decorator.identity),
209
+ name: arg?.evaluated === true && typeof arg.value === "string" ? arg.value : method.name,
210
+ location: decorator.location,
211
+ methodLocation: method.location,
212
+ guards: [],
213
+ meta: [],
214
+ params: [],
215
+ complete: false
216
+ });
217
+ }
218
+ if (fields.length === 0) return;
219
+ ctx.store({
220
+ kind: "resolver",
221
+ host: cls,
222
+ scope: "singleton",
223
+ location: resolver?.location ?? cls.location,
224
+ guards: [],
225
+ fields,
226
+ invalid: true,
227
+ ...typeName === void 0 ? {} : { typeName }
228
+ });
229
+ }
230
+ function isFieldDecorator(decorator) {
231
+ return decorator.identity !== void 0 && FIELD_DECORATORS.has(decorator.identity);
232
+ }
233
+ function isParameterDecorator(decorator) {
234
+ return decorator.identity !== void 0 && PARAMETER_DECORATORS.has(decorator.identity);
235
+ }
236
+ /** `@Resolver()`, `@Resolver({ scope })`, `@Resolver('Event')`, `@Resolver('Event', { scope })` (schema-first). */
237
+ function resolverArguments(ctx, decorator, schemaFirst) {
238
+ const [first, second] = decorator.args;
239
+ if (first?.evaluated !== true || typeof first.value !== "string") {
240
+ if (second !== void 0) error(ctx, Codes.InvalidDecorator, second.location, "@Resolver takes the options as its only argument here", ["@Resolver({ scope: 'request' }), or @Resolver('Event', { scope: 'request' }) in schema-first mode"]);
241
+ return { scope: resolverScope(ctx, first) };
242
+ }
243
+ const scope = resolverScope(ctx, second);
244
+ const name = first.value;
245
+ if (!schemaFirst) {
246
+ error(ctx, Codes.InvalidDecorator, first.location, `@Resolver('${name}'): naming the type is schema-first only, the code-first schema is derived from the types`, ["Remove the type name: @ResolveField methods add fields to the type of their @Parent() parameter", "Or switch to schema-first: graphql.configure({ schema: 'src/**/*.graphql' }) in znight.config.ts"]);
247
+ return { scope };
248
+ }
249
+ if (!isGraphQLName(name)) {
250
+ error(ctx, Codes.NameConflict, first.location, `'${name}' is not a valid GraphQL type name (@Resolver)`, ["GraphQL names match /^[_A-Za-z][_0-9A-Za-z]*$/ and do not start with __"]);
251
+ return { scope };
252
+ }
253
+ return {
254
+ scope,
255
+ typeName: {
256
+ name,
257
+ location: first.location
258
+ }
259
+ };
260
+ }
261
+ /** The `{ scope }` options of `@Resolver`. */
262
+ function resolverScope(ctx, arg) {
263
+ if (arg === void 0 || !arg.evaluated || arg.value === void 0) return "singleton";
264
+ const value = arg.value;
265
+ if (!isRecord(value)) {
266
+ error(ctx, Codes.InvalidDecorator, arg.location, `@Resolver expects { scope? }, got ${describe$1(value)}`, ["@Resolver() or @Resolver({ scope: 'request' })"]);
267
+ return "singleton";
268
+ }
269
+ let scope = "singleton";
270
+ for (const [key, option] of Object.entries(value)) if (key !== "scope") error(ctx, Codes.InvalidDecorator, arg.location, `@Resolver: unknown option "${key}"`, ["Options: { scope? }"]);
271
+ else if (SCOPES.includes(option)) scope = option;
272
+ else if (option !== void 0) error(ctx, Codes.InvalidDecorator, arg.location, `@Resolver: invalid scope ${describe$1(option)}`, [`Use one of ${SCOPES.map((candidate) => `'${candidate}'`).join(", ")}`]);
273
+ return scope;
274
+ }
275
+ /** `@UseGuard(A, B)` decorators of a target: guard classes bound with lazy edges (C3). */
276
+ function guardsOf(ctx, cls, decorators) {
277
+ const guards = [];
278
+ for (const decorator of decorators) {
279
+ if (decorator.identity !== GraphQLIds.UseGuard) continue;
280
+ if (decorator.args.length === 0) error(ctx, Codes.InvalidDecorator, decorator.location, "@UseGuard needs at least one guard class", ["@UseGuard(AccessGuard)"]);
281
+ for (const arg of decorator.args) {
282
+ const ref = ctx.resolveClassRef(arg, {
283
+ from: cls,
284
+ role: "guard",
285
+ lazy: true
286
+ });
287
+ if (ref !== void 0) guards.push(ref);
288
+ }
289
+ }
290
+ return guards;
291
+ }
292
+ function analyzeMethod(ctx, cls, method, decorators, classMeta, schemaFirst) {
293
+ const where = `${cls.displayName}.${method.name}`;
294
+ const primary = decorators[0];
295
+ let complete = checkMethodShape(ctx, method, primary, where);
296
+ complete = checkForeignHandler(ctx, method, primary, where) && complete;
297
+ const params = [];
298
+ for (const param of method.params) {
299
+ const data = analyzeParameter(ctx, cls, param, where);
300
+ params.push(data);
301
+ if (data === void 0) complete = false;
302
+ }
303
+ const parents = params.filter((param) => param?.kind === "parent");
304
+ for (const extra of parents.slice(1)) {
305
+ error(ctx, Codes.InvalidResolver, extra.location, `${where} has several @Parent() parameters`, ["Declare the parent object once"]);
306
+ complete = false;
307
+ }
308
+ const guards = guardsOf(ctx, cls, method.decorators);
309
+ const meta = effectiveRouteMeta(classMeta, readRouteMeta(ctx, cls, method));
310
+ const fields = [];
311
+ const seen = /* @__PURE__ */ new Set();
312
+ for (const decorator of decorators) {
313
+ const operation = FIELD_DECORATORS.get(decorator.identity);
314
+ const explicit = fieldName(ctx, decorator, method, where);
315
+ const name = explicit ?? method.name;
316
+ const key = `${operation}\0${name}`;
317
+ if (seen.has(key)) {
318
+ error(ctx, Codes.InvalidDecorator, decorator.location, `@${decorator.name}('${name}') is applied twice to ${where}`, ["Apply each field decorator once"]);
319
+ continue;
320
+ }
321
+ seen.add(key);
322
+ let fieldComplete = complete && explicit !== void 0;
323
+ if (operation === "field" && parents.length === 0 && !schemaFirst) {
324
+ error(ctx, Codes.InvalidResolver, decorator.location, `@ResolveField ${where} has no @Parent() parameter`, [`Declare the parent object first: ${method.name}(@Parent() parent: ParentType, ...)`]);
325
+ fieldComplete = false;
326
+ } else if (operation !== "field" && parents.length > 0) {
327
+ error(ctx, Codes.InvalidResolver, parents[0].location, `@Parent() is only available in @ResolveField methods, not in the @${decorator.name} ${where}`, ["Root fields have no parent object: remove the parameter"]);
328
+ fieldComplete = false;
329
+ }
330
+ fields.push({
331
+ method: method.name,
332
+ operation,
333
+ name,
334
+ location: decorator.location,
335
+ methodLocation: method.location,
336
+ guards,
337
+ meta,
338
+ params,
339
+ complete: fieldComplete
340
+ });
341
+ }
342
+ return fields;
343
+ }
344
+ /** The field name: the static decorator argument, or the method name. */
345
+ function fieldName(ctx, decorator, method, where) {
346
+ const arg = decorator.args[0];
347
+ if (arg === void 0 || arg.evaluated && arg.value === void 0) {
348
+ if (isGraphQLName(method.name)) return method.name;
349
+ error(ctx, Codes.NameConflict, decorator.location, `'${method.name}' is not a valid GraphQL field name (${where}): name the field in the decorator`, ["GraphQL names match /^[_A-Za-z][_0-9A-Za-z]*$/ and do not start with __", `@${decorator.name}('validName')`]);
350
+ return;
351
+ }
352
+ if (!arg.evaluated) return void 0;
353
+ const value = arg.value;
354
+ if (typeof value !== "string") {
355
+ error(ctx, Codes.InvalidDecorator, arg.location, `@${decorator.name} on ${where} expects a field name, got ${describe$1(value)}`, [`@${decorator.name}('${method.name}') or @${decorator.name}()`]);
356
+ return;
357
+ }
358
+ if (!isGraphQLName(value)) {
359
+ error(ctx, Codes.NameConflict, arg.location, `'${value}' is not a valid GraphQL field name (${where})`, ["GraphQL names match /^[_A-Za-z][_0-9A-Za-z]*$/ and do not start with __"]);
360
+ return;
361
+ }
362
+ return value;
363
+ }
364
+ /** Resolver methods are called by the generated code: `resolver.method(...)`. */
365
+ function checkMethodShape(ctx, method, decorator, where) {
366
+ const name = `@${decorator.name}`;
367
+ let problem;
368
+ if (method.kind !== "method") problem = {
369
+ message: `${name} cannot be applied to the accessor ${where}`,
370
+ hint: "Use a regular method"
371
+ };
372
+ else if (method.static) problem = {
373
+ message: `${name} cannot be applied to the static method ${where}`,
374
+ hint: "Fields are resolved on the resolver instance: remove `static`"
375
+ };
376
+ else if (method.privateName) problem = {
377
+ message: `${name} cannot be applied to the #private method ${where}`,
378
+ hint: "Use a public method: the generated code calls it"
379
+ };
380
+ else if (method.visibility !== "public") problem = {
381
+ message: `${where} must be public: ${name} methods are called by the generated code`,
382
+ hint: `Remove the '${method.visibility}' modifier`
383
+ };
384
+ else if (method.computed) problem = {
385
+ message: `${name} cannot be applied to ${where}: computed method names are not supported`,
386
+ hint: "Use a plain method name"
387
+ };
388
+ else if (method.typeParameters.length > 0) problem = {
389
+ message: `${name} cannot be applied to the generic method ${where}<${method.typeParameters.join(", ")}>`,
390
+ hint: "The schema is derived from the signature: remove the type parameters"
391
+ };
392
+ if (problem === void 0) return true;
393
+ error(ctx, Codes.InvalidDecorator, decorator.location, problem.message, [problem.hint]);
394
+ return false;
395
+ }
396
+ /** A resolver method cannot also be handled by another extension (`@Get`, `@OnMessage`, `@Cron`). */
397
+ function checkForeignHandler(ctx, method, decorator, where) {
398
+ for (const other of method.decorators) {
399
+ const owner = other.owner;
400
+ if (owner === void 0 || owner === "graphql" || owner === "core" || owner === "manifest") continue;
401
+ error(ctx, Codes.InvalidDecorator, other.location, `${where} cannot be both a GraphQL field (@${decorator.name}) and a @${other.name} handler`, ["A class is the provider of one extension: let both handlers call a shared service"]);
402
+ return false;
403
+ }
404
+ return true;
405
+ }
406
+ /**
407
+ * Graphql parameter decorators of a parameter, de-duplicated by position (the compiler reports the
408
+ * decorators of a parameter with a default value twice).
409
+ */
410
+ function parameterDecorators(param) {
411
+ const seen = /* @__PURE__ */ new Set();
412
+ return param.decorators.filter((decorator) => {
413
+ if (!isParameterDecorator(decorator) || seen.has(decorator.location.start)) return false;
414
+ seen.add(decorator.location.start);
415
+ return true;
416
+ });
417
+ }
418
+ function analyzeParameter(ctx, cls, param, where) {
419
+ const decorators = parameterDecorators(param);
420
+ const decorator = decorators[0];
421
+ if (decorator === void 0) {
422
+ const lookAlike = param.decorators.find((found) => found.identity !== void 0 && HTTP_LOOK_ALIKES.has(found.identity));
423
+ if (lookAlike !== void 0) {
424
+ const replacement = HTTP_LOOK_ALIKES.get(lookAlike.identity).split("#")[1];
425
+ error(ctx, Codes.InvalidDecorator, lookAlike.location, `@${lookAlike.name} on parameter '${param.name}' of ${where} is imported from '@znight/http': resolver parameters use @${replacement}() from '${RUNTIME_MODULE}'`, [`Import it from '${RUNTIME_MODULE}': import { ${replacement} } from '${RUNTIME_MODULE}'`]);
426
+ return;
427
+ }
428
+ error(ctx, Codes.InvalidDecorator, param.location, `Parameter '${param.name}' of ${where} has no @Args(), @Parent(), @Ctx(), @Info() or @Loader() decorator`, ["Every resolver parameter is filled by GraphQL: @Args(name?, schema?) for arguments, @Parent() for the parent object, @Ctx() for the GraphQLContext, @Info() for the GraphQLResolveInfo, @Loader() for a DataLoader"]);
429
+ return;
430
+ }
431
+ if (decorators.length > 1) {
432
+ error(ctx, Codes.InvalidDecorator, decorators[1].location, `Parameter '${param.name}' of ${where} has several GraphQL decorators (${decorators.map((found) => `@${found.name}`).join(", ")})`, ["Keep a single one"]);
433
+ return;
434
+ }
435
+ if (param.rest) {
436
+ error(ctx, Codes.InvalidDecorator, decorator.location, `@${decorator.name} cannot be applied to the rest parameter '...${param.name}' of ${where}`);
437
+ return;
438
+ }
439
+ switch (PARAMETER_DECORATORS.get(decorator.identity)) {
440
+ case "args": return argsParameter(ctx, decorator, param, where);
441
+ case "parent": return {
442
+ kind: "parent",
443
+ location: decorator.location
444
+ };
445
+ case "ctx": return { kind: "ctx" };
446
+ case "info": return { kind: "info" };
447
+ default: return loaderParameter(ctx, cls, param, where);
448
+ }
449
+ }
450
+ /** `@Args()`, `@Args('name')`, `@Args('name', Schema)`, `@Args(Schema)`. */
451
+ function argsParameter(ctx, decorator, param, where) {
452
+ const [first, second, ...extra] = decorator.args;
453
+ if (extra.length > 0) {
454
+ error(ctx, Codes.InvalidDecorator, decorator.location, `@Args takes at most two arguments (${where})`, ["@Args(), @Args('name'), @Args('name', Schema) or @Args(Schema)"]);
455
+ return;
456
+ }
457
+ let name;
458
+ let schemaArg = second;
459
+ if (first !== void 0) {
460
+ if (!first.evaluated && first.ref === void 0) return void 0;
461
+ if (typeof first.value === "string") {
462
+ if (!isGraphQLName(first.value)) {
463
+ error(ctx, Codes.NameConflict, first.location, `'${first.value}' is not a valid GraphQL argument name (parameter '${param.name}' of ${where})`, ["GraphQL names match /^[_A-Za-z][_0-9A-Za-z]*$/ and do not start with __"]);
464
+ return;
465
+ }
466
+ name = first.value;
467
+ } else if (first.ref !== void 0) {
468
+ if (second !== void 0) {
469
+ error(ctx, Codes.InvalidDecorator, second.location, `@Args(Schema) takes no second argument (${where})`, ["@Args('name', Schema) validates one argument, @Args(Schema) every argument"]);
470
+ return;
471
+ }
472
+ schemaArg = first;
473
+ } else if (first.value !== void 0) {
474
+ error(ctx, Codes.InvalidDecorator, first.location, `@Args on parameter '${param.name}' of ${where} expects an argument name or a schema, got ${describe$1(first.value)}`, ["@Args('id'), @Args() or @Args('input', Schema)"]);
475
+ return;
476
+ }
477
+ }
478
+ if (schemaArg !== void 0 && !(schemaArg.evaluated && schemaArg.value === void 0)) {
479
+ if (schemaArg.ref === void 0) {
480
+ if (schemaArg.evaluated) error(ctx, Codes.InvalidDecorator, schemaArg.location, `The schema of @Args on parameter '${param.name}' of ${where} must be a reference to an exported schema`, ["Declare it with `export const Schema = z.object(...)` and pass Schema: the generated module imports it"]);
481
+ return;
482
+ }
483
+ return {
484
+ kind: "args",
485
+ name,
486
+ schema: schemaArg.ref,
487
+ location: decorator.location
488
+ };
489
+ }
490
+ return {
491
+ kind: "args",
492
+ name,
493
+ schema: void 0,
494
+ location: decorator.location
495
+ };
496
+ }
497
+ /** `@Loader() authors: Loader<AuthorsById>`: binds `AuthorsById` with a lazy edge. */
498
+ function loaderParameter(ctx, cls, param, where) {
499
+ const root = ctx.types.shape({ parameter: param }, LOADER_SHAPE);
500
+ const shape = root.root;
501
+ const argument = shape.kind === "reference" && shape.identity === GraphQLIds.Loader ? shape.typeArgs[0] : void 0;
502
+ const identity = argument?.kind === "named" ? root.named.get(argument.key)?.identity : void 0;
503
+ if (identity === void 0) {
504
+ error(ctx, Codes.InvalidLoader, param.typeLocation, `@Loader() parameter '${param.name}' of ${where} must be typed Loader<L>, with L a loader class`, [`${param.name}: Loader<AuthorsById>, where AuthorsById is a provider implementing BatchLoad<K, V> (import type { Loader } from '${RUNTIME_MODULE}')`]);
505
+ return;
506
+ }
507
+ return {
508
+ kind: "loader",
509
+ binding: ctx.bindInjection({
510
+ type: identity,
511
+ location: param.typeLocation,
512
+ label: `loader '${param.name}' of ${where}()`
513
+ }, {
514
+ from: cls,
515
+ lazy: true
516
+ }),
517
+ identity,
518
+ location: param.typeLocation
519
+ };
520
+ }
521
+ /**
522
+ * Graphql decorators on a class without `@Resolver`: field methods are not inherited (a base
523
+ * class), and parameter or guard decorators have no effect.
524
+ */
525
+ function reportOutsideResolver(cls, ctx) {
526
+ for (const decorator of cls.decorators) if (decorator.identity === GraphQLIds.UseGuard) error(ctx, Codes.InvalidDecorator, decorator.location, `@UseGuard has no effect: ${cls.displayName} is not a @Resolver`, [`Add @Resolver() to ${cls.displayName}`]);
527
+ for (const method of cls.methods) {
528
+ const fields = method.decorators.filter(isFieldDecorator);
529
+ for (const field of fields) error(ctx, Codes.InvalidResolver, field.location, `@${field.name} ${cls.displayName}.${method.name} is declared on a class that is not a @Resolver`, [cls.abstract ? `Resolver methods are not inherited: declare the field on the concrete @Resolver() class (it may call ${method.name} of the base class)` : `Add @Resolver() to ${cls.displayName} (resolver methods are not inherited from base classes)`]);
530
+ if (fields.length === 0) reportStrayMethodDecorators(cls, method, ctx);
531
+ }
532
+ }
533
+ /** `@UseGuard` or parameter decorators on a method that is not a field. */
534
+ function reportStrayMethodDecorators(cls, method, ctx) {
535
+ const where = `${cls.displayName}.${method.name}`;
536
+ for (const decorator of method.decorators) if (decorator.identity === GraphQLIds.UseGuard) error(ctx, Codes.InvalidDecorator, decorator.location, `@UseGuard has no effect: ${where} is not a GraphQL field`, ["Add @Query(), @Mutation(), @Subscription() or @ResolveField() to the method"]);
537
+ for (const param of method.params) for (const decorator of parameterDecorators(param)) error(ctx, Codes.InvalidDecorator, decorator.location, `@${decorator.name} has no effect: ${where} is not a GraphQL field`, ["Add @Query(), @Mutation(), @Subscription() or @ResolveField() to the method"]);
538
+ }
539
+ function error(ctx, code, location, message, hints = [], related = []) {
540
+ ctx.report({
541
+ code,
542
+ severity: "error",
543
+ message,
544
+ location,
545
+ ...hints.length === 0 ? {} : { hints },
546
+ ...related.length === 0 ? {} : { related }
547
+ });
548
+ }
549
+ /** An evaluated object literal (not an array, not a reference). */
550
+ function isRecord(value) {
551
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
552
+ const prototype = Object.getPrototypeOf(value);
553
+ return prototype === Object.prototype || prototype === null;
554
+ }
555
+ /** Short description of a value for messages. */
556
+ function describe$1(value) {
557
+ if (value === void 0) return "undefined";
558
+ if (value === null) return "null";
559
+ if (typeof value === "string") return JSON.stringify(value);
560
+ if (typeof value === "bigint") return `${value}n`;
561
+ if (typeof value !== "object") return String(value);
562
+ if (Array.isArray(value)) return "an array";
563
+ return isRecord(value) ? "an object" : "a reference";
564
+ }
565
+ /** First 16 hexadecimal characters of the SHA-256 of `sdl` (UTF-8). */
566
+ function schemaHash(sdl) {
567
+ return createHash("sha256").update(sdl, "utf8").digest("hex").slice(0, 16);
568
+ }
569
+ // src/sdl.ts
570
+ /**
571
+ * Deterministic SDL printer (RFC 0005 §6.7, §7.3).
572
+ *
573
+ * Prints a schema model (the data of `metadata.extensions.graphql`, without its functions) exactly
574
+ * like graphql-js 17 `printSchema()` prints the `GraphQLSchema` the runtime builds from it: same
575
+ * order, same block strings, same default value literals, byte for byte. The compiler hashes this
576
+ * output (`schemaHash`) and writes it as the optional SDL artifact; the runtime can print without
577
+ * graphql-js.
578
+ *
579
+ * Import-free (types and constants of import-free modules only): shared by the compiler extension
580
+ * and the runtime. Equivalence holds when the runtime builds the schema the way this module
581
+ * assumes:
582
+ *
583
+ * - every type of the model passed to `new GraphQLSchema({ types })` in model order (metadata types
584
+ * are sorted by name), so the type map, hence `printSchema`, follows that order;
585
+ * - default values passed as `default: { value }` (external values: enum values by name), printed
586
+ * by graphql-js through `valueToLiteral`;
587
+ * - built-in scalars (`names.ts`) without description, with their `specifiedByURL`, and no scalar
588
+ * with a custom `valueToLiteral` used in a default value.
589
+ */
590
+ /**
591
+ * Prints the SDL of a schema, without a trailing newline (like `printSchema`). Throws an `Error` on
592
+ * a malformed type reference or a default value that does not match its type (the compiler reports
593
+ * these as diagnostics before printing).
594
+ */
595
+ function printSdl(schema) {
596
+ const types = /* @__PURE__ */ new Map();
597
+ for (const type of schema.types) types.set(type.name, type);
598
+ const printer = new SdlPrinter(types);
599
+ const parts = [];
600
+ const definition = printSchemaDefinition(schema.roots, types);
601
+ if (definition !== void 0) parts.push(definition);
602
+ for (const type of schema.types) if (!SPECIFIED_SCALARS.has(type.name)) parts.push(printer.type(type));
603
+ return parts.join("\n\n");
604
+ }
605
+ /** `schema { ... }`, only when the root types do not have their default names (as graphql-js). */
606
+ function printSchemaDefinition(roots, types) {
607
+ const isDefault = (root, name) => root === void 0 ? !types.has(name) : root === name;
608
+ if (isDefault(roots.query, ROOT_TYPE_NAMES.query) && isDefault(roots.mutation, ROOT_TYPE_NAMES.mutation) && isDefault(roots.subscription, ROOT_TYPE_NAMES.subscription)) return;
609
+ return `schema {
610
+ query: ${roots.query}\n` + (roots.mutation === void 0 ? "" : ` mutation: ${roots.mutation}\n`) + (roots.subscription === void 0 ? "" : ` subscription: ${roots.subscription}\n`) + "}";
611
+ }
612
+ const GRAPHQL_MAX_INT = 2147483647;
613
+ const GRAPHQL_MIN_INT = -2147483648;
614
+ const INTEGER_STRING = /^-?(?:0|[1-9][0-9]*)$/;
615
+ /** graphql-js `print()` breaks lists and objects longer than this. */
616
+ const MAX_LINE_LENGTH = 80;
617
+ var SdlPrinter = class {
618
+ #types;
619
+ constructor(types) {
620
+ this.#types = types;
621
+ }
622
+ type(type) {
623
+ const head = printDescription(type.description, "", true);
624
+ switch (type.kind) {
625
+ case "object":
626
+ case "interface": return `${head}${type.kind === "object" ? "type" : "interface"} ${type.name}${printImplements(type.interfaces)}${printBlock(type.fields.map((field, i) => this.#field(type.name, field, i)))}`;
627
+ case "input": return `${head}input ${type.name}${printBlock(type.fields.map((field, i) => `${printDescription(field.description, " ", i === 0)} ${this.#inputValue(field, `${type.name}.${field.name}`)}`))}`;
628
+ case "enum": return `${head}enum ${type.name}${printBlock(type.values.map((value, i) => `${printDescription(value.description, " ", i === 0)} ${value.name}${printDeprecated(value.deprecationReason)}`))}`;
629
+ case "union": return `${head}union ${type.name}${type.members.length === 0 ? "" : ` = ${type.members.join(" | ")}`}`;
630
+ case "scalar": {
631
+ const url = type.specifiedByURL ?? (type.builtin === true ? BUILTIN_SCALARS.get(type.name)?.specifiedByURL : void 0);
632
+ return `${head}scalar ${type.name}${url === void 0 ? "" : ` @specifiedBy(url: ${printString(url)})`}`;
633
+ }
634
+ }
635
+ }
636
+ #field(typeName, field, index) {
637
+ const coordinate = `${typeName}.${field.name}`;
638
+ return `${printDescription(field.description, " ", index === 0)} ${field.name}` + this.#args(coordinate, field.args ?? []) + `: ${checkedTypeRef(field.type, coordinate)}${printDeprecated(field.deprecationReason)}`;
639
+ }
640
+ #args(coordinate, args) {
641
+ if (args.length === 0) return "";
642
+ if (args.every((arg) => arg.description === void 0)) return `(${args.map((arg) => this.#inputValue(arg, `${coordinate}(${arg.name}:)`)).join(", ")})`;
643
+ return `(\n${args.map((arg, i) => `${printDescription(arg.description, " ", i === 0)} ${this.#inputValue(arg, `${coordinate}(${arg.name}:)`)}`).join("\n")}\n )`;
644
+ }
645
+ #inputValue(value, coordinate) {
646
+ const ref = parseTypeRef(value.type);
647
+ if (ref === void 0) throw new Error(`Invalid type reference '${value.type}' (${coordinate})`);
648
+ let declaration = `${value.name}: ${value.type}`;
649
+ if (value.defaultValue !== void 0) {
650
+ const literal = this.#literal(value.defaultValue, ref);
651
+ if (literal === void 0) throw new Error(`Invalid default value for ${coordinate}: ${describe(value.defaultValue)}`);
652
+ declaration += ` = ${printLiteral(literal)}`;
653
+ }
654
+ return declaration + printDeprecated(value.deprecationReason);
655
+ }
656
+ /** graphql-js `valueToLiteral(value, type)`: `undefined` when the value does not fit the type. */
657
+ #literal(value, ref) {
658
+ if (ref.nonNull && value == null) return void 0;
659
+ if (value == null) return { kind: "NullValue" };
660
+ if (ref.kind === "list") {
661
+ const item = ref.of;
662
+ if (!isIterableObject(value)) return this.#literal(value, item);
663
+ const values = [];
664
+ for (const entry of value) {
665
+ const literal = this.#literal(entry, item);
666
+ if (literal === void 0) return void 0;
667
+ values.push(literal);
668
+ }
669
+ return {
670
+ kind: "ListValue",
671
+ values
672
+ };
673
+ }
674
+ const type = this.#types.get(ref.name);
675
+ if (type?.kind === "input") return this.#inputLiteral(value, type);
676
+ if (type?.kind === "enum") return typeof value === "string" && type.values.some((entry) => entry.name === value) ? {
677
+ kind: "EnumValue",
678
+ value
679
+ } : void 0;
680
+ return scalarLiteral(value, ref.name);
681
+ }
682
+ #inputLiteral(value, type) {
683
+ if (typeof value !== "object" || value === null) return void 0;
684
+ const record = value;
685
+ for (const key of Object.keys(record)) if (record[key] !== void 0 && !type.fields.some((field) => field.name === key)) return void 0;
686
+ const fields = [];
687
+ for (const field of type.fields) {
688
+ const fieldValue = Object.hasOwn(record, field.name) ? record[field.name] : void 0;
689
+ const ref = parseTypeRef(field.type);
690
+ if (ref === void 0) return void 0;
691
+ if (fieldValue === void 0) {
692
+ if (ref.nonNull && field.defaultValue === void 0) return void 0;
693
+ continue;
694
+ }
695
+ const literal = this.#literal(fieldValue, ref);
696
+ if (literal === void 0) return void 0;
697
+ fields.push([field.name, literal]);
698
+ }
699
+ return {
700
+ kind: "ObjectValue",
701
+ fields
702
+ };
703
+ }
704
+ };
705
+ /** graphql-js `printImplementedInterfaces`. */
706
+ function printImplements(interfaces) {
707
+ return interfaces === void 0 || interfaces.length === 0 ? "" : ` implements ${interfaces.join(" & ")}`;
708
+ }
709
+ /** The `valueToLiteral` of the specified scalars, and graphql-js' default one for the others. */
710
+ function scalarLiteral(value, name) {
711
+ switch (name) {
712
+ case "Int": return (typeof value === "number" && Number.isInteger(value) || typeof value === "bigint") && value <= GRAPHQL_MAX_INT && value >= GRAPHQL_MIN_INT ? {
713
+ kind: "IntValue",
714
+ value: String(value)
715
+ } : void 0;
716
+ case "Float": try {
717
+ const literal = defaultScalarLiteral(value);
718
+ return literal.kind === "FloatValue" || literal.kind === "IntValue" ? literal : void 0;
719
+ } catch {
720
+ return;
721
+ }
722
+ case "String": return typeof value === "string" ? {
723
+ kind: "StringValue",
724
+ value
725
+ } : void 0;
726
+ case "Boolean": return typeof value === "boolean" ? {
727
+ kind: "BooleanValue",
728
+ value
729
+ } : void 0;
730
+ case "ID":
731
+ if (typeof value === "string") return INTEGER_STRING.test(value) ? {
732
+ kind: "IntValue",
733
+ value
734
+ } : {
735
+ kind: "StringValue",
736
+ value
737
+ };
738
+ if (typeof value === "number") return Number.isInteger(value) ? {
739
+ kind: "IntValue",
740
+ value: String(value)
741
+ } : void 0;
742
+ return typeof value === "bigint" ? {
743
+ kind: "IntValue",
744
+ value: String(value)
745
+ } : void 0;
746
+ default: return defaultScalarLiteral(value);
747
+ }
748
+ }
749
+ /** graphql-js `defaultScalarValueToLiteral`. */
750
+ function defaultScalarLiteral(value) {
751
+ if (value == null) return { kind: "NullValue" };
752
+ switch (typeof value) {
753
+ case "boolean": return {
754
+ kind: "BooleanValue",
755
+ value
756
+ };
757
+ case "string": return {
758
+ kind: "StringValue",
759
+ value
760
+ };
761
+ case "bigint": return {
762
+ kind: "IntValue",
763
+ value: value.toString()
764
+ };
765
+ case "number": {
766
+ if (!Number.isFinite(value)) return { kind: "NullValue" };
767
+ const text = String(value);
768
+ return INTEGER_STRING.test(text) ? {
769
+ kind: "IntValue",
770
+ value: text
771
+ } : {
772
+ kind: "FloatValue",
773
+ value: text
774
+ };
775
+ }
776
+ case "object": {
777
+ if (isIterableObject(value)) return {
778
+ kind: "ListValue",
779
+ values: Array.from(value, defaultScalarLiteral)
780
+ };
781
+ const record = value;
782
+ const fields = [];
783
+ for (const key of Object.keys(record)) if (record[key] !== void 0) fields.push([key, defaultScalarLiteral(record[key])]);
784
+ return {
785
+ kind: "ObjectValue",
786
+ fields
787
+ };
788
+ }
789
+ default: throw new TypeError(`Cannot convert value to AST: ${describe(value)}.`);
790
+ }
791
+ }
792
+ function isIterableObject(value) {
793
+ return typeof value === "object" && value !== null && typeof value[Symbol.iterator] === "function";
794
+ }
795
+ /** graphql-js `print()` of a value literal. */
796
+ function printLiteral(literal) {
797
+ switch (literal.kind) {
798
+ case "IntValue":
799
+ case "FloatValue":
800
+ case "EnumValue": return literal.value;
801
+ case "StringValue": return printString(literal.value);
802
+ case "BooleanValue": return literal.value ? "true" : "false";
803
+ case "NullValue": return "null";
804
+ case "ListValue": {
805
+ const values = literal.values.map(printLiteral);
806
+ const line = `[${values.join(", ")}]`;
807
+ return line.length > MAX_LINE_LENGTH ? `[\n${indent(values.join("\n"))}\n]` : line;
808
+ }
809
+ case "ObjectValue": {
810
+ const fields = literal.fields.map(([name, value]) => `${name}: ${printLiteral(value)}`);
811
+ const line = `{ ${fields.join(", ")} }`;
812
+ if (line.length <= MAX_LINE_LENGTH) return line;
813
+ const body = indent(fields.join("\n"));
814
+ return body === "" ? "" : `{\n${body}\n}`;
815
+ }
816
+ }
817
+ }
818
+ /** graphql-js `indent()` of the printer. */
819
+ function indent(text) {
820
+ return text === "" ? "" : ` ${text.replaceAll("\n", "\n ")}`;
821
+ }
822
+ function printBlock(items) {
823
+ return items.length === 0 ? "" : ` {\n${items.join("\n")}\n}`;
824
+ }
825
+ function printDeprecated(reason) {
826
+ if (reason === void 0) return "";
827
+ return reason === "No longer supported" ? " @deprecated" : ` @deprecated(reason: ${printString(reason)})`;
828
+ }
829
+ /** graphql-js `printDescription()` of `printSchema`. */
830
+ function printDescription(description, indentation, firstInBlock) {
831
+ if (description === void 0) return "";
832
+ const text = isPrintableAsBlockString(description) ? printBlockString(description) : printString(description);
833
+ return `${indentation !== "" && !firstInBlock ? `\n${indentation}` : indentation}${text.replaceAll("\n", `\n${indentation}`)}\n`;
834
+ }
835
+ function checkedTypeRef(type, coordinate) {
836
+ if (parseTypeRef(type) === void 0) throw new Error(`Invalid type reference '${type}' (${coordinate})`);
837
+ return type;
838
+ }
839
+ function describe(value) {
840
+ if (value === void 0) return "undefined";
841
+ if (typeof value === "bigint") return `${value}n`;
842
+ if (typeof value === "number" && !Number.isFinite(value)) return String(value);
843
+ try {
844
+ return JSON.stringify(value) ?? String(value);
845
+ } catch {
846
+ return String(value);
847
+ }
848
+ }
849
+ /** `\t` and space: the GraphQL `WhiteSpace` characters. */
850
+ function isWhiteSpace(code) {
851
+ return code === 9 || code === 32;
852
+ }
853
+ /** Escape sequence of each code unit graphql-js escapes in a string value. */
854
+ const ESCAPES = Array.from({ length: 160 }, (_, code) => {
855
+ switch (code) {
856
+ case 8: return "\\b";
857
+ case 9: return "\\t";
858
+ case 10: return "\\n";
859
+ case 12: return "\\f";
860
+ case 13: return "\\r";
861
+ case 34: return "\\\"";
862
+ case 92: return "\\\\";
863
+ default: return code < 32 || code >= 127 ? `\\u${code.toString(16).toUpperCase().padStart(4, "0")}` : "";
864
+ }
865
+ });
866
+ const ESCAPED = /[\x00-\x1f\x22\x5c\x7f-\x9f]/g;
867
+ /** A GraphQL string value (`"..."`), escaped like graphql-js `printString`. */
868
+ function printString(value) {
869
+ return `"${value.replace(ESCAPED, (char) => ESCAPES[char.charCodeAt(0)] ?? char)}"`;
870
+ }
871
+ /** graphql-js `isPrintableAsBlockString`. */
872
+ function isPrintableAsBlockString(value) {
873
+ if (value === "") return true;
874
+ let isEmptyLine = true;
875
+ let hasIndent = false;
876
+ let hasCommonIndent = true;
877
+ let seenNonEmptyLine = false;
878
+ for (let i = 0; i < value.length; ++i) switch (value.codePointAt(i)) {
879
+ case 0:
880
+ case 1:
881
+ case 2:
882
+ case 3:
883
+ case 4:
884
+ case 5:
885
+ case 6:
886
+ case 7:
887
+ case 8:
888
+ case 11:
889
+ case 12:
890
+ case 14:
891
+ case 15:
892
+ case 13: return false;
893
+ case 10:
894
+ if (isEmptyLine && !seenNonEmptyLine) return false;
895
+ seenNonEmptyLine = true;
896
+ isEmptyLine = true;
897
+ hasIndent = false;
898
+ break;
899
+ case 9:
900
+ case 32:
901
+ hasIndent ||= isEmptyLine;
902
+ break;
903
+ default:
904
+ hasCommonIndent &&= hasIndent;
905
+ isEmptyLine = false;
906
+ }
907
+ if (isEmptyLine) return false;
908
+ if (hasCommonIndent && seenNonEmptyLine) return false;
909
+ return true;
910
+ }
911
+ /** A GraphQL block string (`"""..."""`), laid out like graphql-js `printBlockString`. */
912
+ function printBlockString(value) {
913
+ const escaped = value.replaceAll("\"\"\"", "\\\"\"\"");
914
+ const lines = escaped.split(/\r\n|[\n\r]/g);
915
+ const isSingleLine = lines.length === 1;
916
+ const forceLeadingNewLine = lines.length > 1 && lines.slice(1).every((line) => line.length === 0 || isWhiteSpace(line.charCodeAt(0)));
917
+ const hasTrailingTripleQuotes = escaped.endsWith("\\\"\"\"");
918
+ const hasTrailingQuote = value.endsWith("\"") && !hasTrailingTripleQuotes;
919
+ const hasTrailingSlash = value.endsWith("\\");
920
+ const forceTrailingNewline = hasTrailingQuote || hasTrailingSlash;
921
+ const printAsMultipleLines = !isSingleLine || value.length > 70 || forceTrailingNewline || forceLeadingNewLine || hasTrailingTripleQuotes;
922
+ let result = "";
923
+ const skipLeadingNewLine = isSingleLine && isWhiteSpace(value.charCodeAt(0));
924
+ if (printAsMultipleLines && !skipLeadingNewLine || forceLeadingNewLine) result += "\n";
925
+ result += escaped;
926
+ if (printAsMultipleLines || forceTrailingNewline) result += "\n";
927
+ return `"""${result}"""`;
928
+ }
929
+ // src/compiler/checks.ts
930
+ /** `Loader<L>`: `L` must be a loader provider, implementing `BatchLoad<K, V>` (ZNIGHT065). */
931
+ function checkLoaders(ctx, resolvers, report) {
932
+ const seen = /* @__PURE__ */ new Set();
933
+ for (const resolver of resolvers) for (const field of resolver.fields) for (const param of field.params) {
934
+ if (param?.kind !== "loader" || seen.has(param.binding.id)) continue;
935
+ seen.add(param.binding.id);
936
+ const token = tokenOfRef(ctx.binding(param.binding));
937
+ const provider = token === void 0 ? void 0 : ctx.provider(token);
938
+ if (provider === void 0 || provider.heritage.some((type) => type.identity === GraphQLIds.BatchLoad)) continue;
939
+ if (provider.kind !== "class" || hasLoadMethod(ctx, provider.classIdentity)) continue;
940
+ report({
941
+ code: Codes.InvalidLoader,
942
+ severity: "error",
943
+ message: `${provider.displayName} is not a loader: Loader<${provider.displayName}> needs a provider implementing BatchLoad<K, V> (${resolver.host.displayName}.${field.method})`,
944
+ location: param.location,
945
+ hints: [`export class ${provider.displayName} implements BatchLoad<ID, Value> { async load(keys: readonly ID[]): Promise<readonly (Value | Error)[]> { ... } }`]
946
+ });
947
+ }
948
+ }
949
+ /**
950
+ * RFC 0001: every root field decides every required route decorator group (ZNIGHT023). `roots`
951
+ * names the root types (`schema { query: RootQuery }` in schema-first).
952
+ */
953
+ function checkDecisions(ctx, resolvers, report, roots = ROOT_TYPE_NAMES) {
954
+ const applied = [];
955
+ for (const resolver of resolvers) for (const field of resolver.fields) applied.push(...field.meta);
956
+ const required = requiredRouteGroups(ctx, applied);
957
+ if (required.length === 0) return;
958
+ for (const resolver of resolvers) for (const field of resolver.fields) {
959
+ if (field.operation === "field") continue;
960
+ const decided = new Set(field.meta.map((entry) => entry.group));
961
+ for (const group of required) {
962
+ if (decided.has(group.group)) continue;
963
+ const root = roots[field.operation] ?? ROOT_TYPE_NAMES[field.operation];
964
+ report({
965
+ code: Codes.MissingRouteDecision,
966
+ severity: "error",
967
+ message: `Root field ${root}.${field.name} (${resolver.host.displayName}.${field.method}) has no "${group.group}" decision`,
968
+ location: field.methodLocation,
969
+ hints: [`Add ${group.suggestion} to the method or to ${resolver.host.displayName}`],
970
+ related: [{
971
+ message: `The "${group.group}" group is required`,
972
+ location: group.decorators[0].definition
973
+ }]
974
+ });
975
+ }
976
+ }
977
+ }
978
+ /** The class (or one of its base classes) declares an instance `load` method. */
979
+ function hasLoadMethod(ctx, identity) {
980
+ const seen = /* @__PURE__ */ new Set();
981
+ let current = identity;
982
+ while (current !== void 0 && !seen.has(current)) {
983
+ seen.add(current);
984
+ const cls = ctx.classInfo(current);
985
+ if (cls === void 0) return true;
986
+ if (cls.methods.some((method) => method.name === "load" && method.kind === "method" && !method.static)) return true;
987
+ if (cls.properties.some((property) => property.name === "load" && !property.static)) return true;
988
+ current = cls.superClass?.identity;
989
+ }
990
+ return false;
991
+ }
992
+ /** The token of a binding result (`undefined` when binding failed). */
993
+ function tokenOfRef(ref) {
994
+ if (typeof ref === "string") return ref;
995
+ return ref !== void 0 && "token" in ref ? ref.token : void 0;
996
+ }
997
+ // src/compiler/defaults.ts
998
+ const GRAPHQL_INT_MAX = 2147483647;
999
+ const GRAPHQL_INT_MIN = -2147483648;
1000
+ /** `value` (an internal value) as a GraphQL external value of `ref`; `types` resolves enums and inputs. */
1001
+ function externalValue(value, ref, types) {
1002
+ if (value === null) return ref.nonNull ? { error: "null for a non-null type" } : { value: null };
1003
+ if (value === void 0) return { error: "undefined" };
1004
+ if (ref.kind === "list") {
1005
+ if (!Array.isArray(value)) return externalValue(value, ref.of, types);
1006
+ const items = [];
1007
+ for (const item of value) {
1008
+ const converted = externalValue(item, ref.of, types);
1009
+ if ("error" in converted) return converted;
1010
+ items.push(converted.value);
1011
+ }
1012
+ return { value: items };
1013
+ }
1014
+ switch (ref.name) {
1015
+ case "String": return typeof value === "string" ? { value } : { error: "expected a string" };
1016
+ case "ID": return typeof value === "string" || typeof value === "number" && Number.isInteger(value) ? { value } : { error: "expected a string or an integer" };
1017
+ case "Int": return typeof value === "number" && Number.isInteger(value) && value <= GRAPHQL_INT_MAX && value >= GRAPHQL_INT_MIN ? { value } : { error: "expected a 32-bit integer" };
1018
+ case "Float": return typeof value === "number" && Number.isFinite(value) ? { value } : { error: "expected a finite number" };
1019
+ case "Boolean": return typeof value === "boolean" ? { value } : { error: "expected a boolean" };
1020
+ case "JSON": return isJson(value) ? { value } : { error: "expected a JSON value" };
1021
+ }
1022
+ const type = types.get(ref.name);
1023
+ if (type?.kind === "enum") {
1024
+ const found = type.values.find((entry) => entry.value === value) ?? (typeof value === "string" ? type.values.find((entry) => entry.name === value) : void 0);
1025
+ return found === void 0 ? { error: `expected one of ${type.values.map((entry) => entry.name).join(", ")}` } : { value: found.name };
1026
+ }
1027
+ if (type?.kind === "input") {
1028
+ if (typeof value !== "object" || Array.isArray(value)) return { error: `expected an object of type ${type.name}` };
1029
+ const record = value;
1030
+ for (const key of Object.keys(record)) if (!type.fields.some((field) => field.name === key)) return { error: `${type.name} has no field '${key}'` };
1031
+ const result = {};
1032
+ for (const field of type.fields) {
1033
+ if (!Object.hasOwn(record, field.name) || record[field.name] === void 0) {
1034
+ if (field.ref.nonNull && field.defaultValue === void 0) return { error: `${type.name}.${field.name} is required` };
1035
+ continue;
1036
+ }
1037
+ const converted = externalValue(record[field.name], field.ref, types);
1038
+ if ("error" in converted) return { error: `${type.name}.${field.name}: ${converted.error}` };
1039
+ result[field.name] = converted.value;
1040
+ }
1041
+ return { value: result };
1042
+ }
1043
+ return { error: `defaults of the scalar ${ref.name} are not supported` };
1044
+ }
1045
+ /** A JSON-compatible value (the `JSON` scalar). */
1046
+ function isJson(value) {
1047
+ if (value === null || typeof value === "string" || typeof value === "boolean") return true;
1048
+ if (typeof value === "number") return Number.isFinite(value);
1049
+ if (Array.isArray(value)) return value.every(isJson);
1050
+ if (typeof value !== "object") return false;
1051
+ return Object.values(value).every(isJson);
1052
+ }
1053
+ // src/compiler/discriminators.ts
1054
+ /**
1055
+ * `instanceof` when every member is an exported class (subclasses first), `__typename` when every
1056
+ * member declares it, else the first property of the first member whose literal value differs in
1057
+ * every member; `undefined` when none applies (ZNIGHT064).
1058
+ */
1059
+ function discriminatorOf(members, classInfo) {
1060
+ if (members.every((member) => member.classIdentity !== void 0 && classInfo(member.classIdentity)?.exported)) {
1061
+ const depth = (identity) => classInfo(identity)?.heritage.length ?? 0;
1062
+ return {
1063
+ by: "class",
1064
+ classes: members.map((member) => ({
1065
+ identity: member.classIdentity,
1066
+ name: member.name
1067
+ })).sort((a, b) => depth(b.identity) - depth(a.identity) || compareStrings(a.name, b.name))
1068
+ };
1069
+ }
1070
+ if (members.every((member) => member.properties.some((property) => property.name === "__typename"))) return { by: "typename" };
1071
+ for (const candidate of members[0]?.properties ?? []) {
1072
+ if (candidate.optional || candidate.literal === void 0) continue;
1073
+ const values = literalValues(members, candidate.name);
1074
+ if (values !== void 0) return {
1075
+ by: "property",
1076
+ property: candidate.name,
1077
+ values
1078
+ };
1079
+ }
1080
+ }
1081
+ /** Literal value → member name when `property` is a required literal with a distinct value in every member. */
1082
+ function literalValues(members, property) {
1083
+ const values = /* @__PURE__ */ new Map();
1084
+ for (const member of members) {
1085
+ const found = member.properties.find((entry) => entry.name === property);
1086
+ if (found === void 0 || found.optional || found.literal === void 0) return void 0;
1087
+ const key = String(found.literal);
1088
+ if (values.has(key)) return void 0;
1089
+ values.set(key, member.name);
1090
+ }
1091
+ const sorted = {};
1092
+ for (const key of [...values.keys()].sort(compareStrings)) sorted[key] = values.get(key);
1093
+ return sorted;
1094
+ }
1095
+ // src/compiler/docs.ts
1096
+ /** The comment text (before the first tag), `undefined` when empty. */
1097
+ function descriptionOf(doc) {
1098
+ return doc === void 0 || doc.text === "" ? void 0 : doc.text;
1099
+ }
1100
+ /** The first tag named `name`. */
1101
+ function tagOf(doc, name) {
1102
+ return doc?.tags.find((tag) => tag.name === name);
1103
+ }
1104
+ /** `@deprecated reason`: the reason (graphql-js' default one without text), `undefined` without the tag. */
1105
+ function deprecationOf(doc) {
1106
+ const tag = tagOf(doc, "deprecated");
1107
+ if (tag === void 0) return void 0;
1108
+ const reason = tag.text.trim();
1109
+ return reason === "" ? DEFAULT_DEPRECATION_REASON : reason;
1110
+ }
1111
+ /** `true` with a `@gqlHidden` tag. */
1112
+ function isHidden(doc) {
1113
+ return tagOf(doc, "gqlHidden") !== void 0;
1114
+ }
1115
+ /**
1116
+ * The text of `@param name text` (also `@param {Type} name - text`), `undefined` when there is no
1117
+ * such tag or it has no text.
1118
+ */
1119
+ function paramDescription(doc, name) {
1120
+ for (const tag of doc?.tags ?? []) {
1121
+ if (tag.name !== "param") continue;
1122
+ const match = /^(?:\{[^}]*\}\s*)?\[?([\w$]+)(?:=[^\]]*)?\]?(?:\s+-)?(?:\s+([\s\S]*))?$/.exec(tag.text.trim());
1123
+ if (match?.[1] !== name) continue;
1124
+ const text = match[2]?.trim();
1125
+ return text === void 0 || text === "" ? void 0 : text;
1126
+ }
1127
+ }
1128
+ /** A `@default` value: JSON when it parses, the text otherwise (`@default title`). */
1129
+ function defaultTagValue(tag) {
1130
+ const text = tag.text.trim();
1131
+ try {
1132
+ return JSON.parse(text);
1133
+ } catch {
1134
+ return text;
1135
+ }
1136
+ }
1137
+ // src/compiler/locate.ts
1138
+ /** From the member name: `name?: ` or `name(): ` (a getter), up to the annotation. */
1139
+ const MEMBER_HEAD = /^(?:[\w$]+|'[^']*'|"[^"]*")\s*\??\s*(?:\(\s*\))?\s*:\s*/;
1140
+ const OPENING = /* @__PURE__ */ new Set([
1141
+ "(",
1142
+ "[",
1143
+ "{",
1144
+ "<"
1145
+ ]);
1146
+ const CLOSING = /* @__PURE__ */ new Set([
1147
+ ")",
1148
+ "]",
1149
+ "}",
1150
+ ">"
1151
+ ]);
1152
+ /**
1153
+ * The type annotation of a property, a property signature or a getter (`get x(): T`), from the
1154
+ * location of the member (its name); the member itself when the annotation cannot be found.
1155
+ */
1156
+ function annotationLocation(project, member) {
1157
+ if (member === void 0) return void 0;
1158
+ const file = project.getFile(member.file);
1159
+ if (file === void 0) return member;
1160
+ const text = file.text;
1161
+ const head = MEMBER_HEAD.exec(text.slice(member.start, member.start + 256));
1162
+ if (head === null) return member;
1163
+ const start = member.start + head[0].length;
1164
+ let depth = 0;
1165
+ let end = start;
1166
+ for (let index = start; index < text.length; index++) {
1167
+ const char = text[index];
1168
+ if (char === "=" && text[index + 1] === ">") {
1169
+ index++;
1170
+ continue;
1171
+ }
1172
+ if (depth === 0 && end > start && (char === "{" || char === "=")) break;
1173
+ if (OPENING.has(char)) depth++;
1174
+ else if (CLOSING.has(char)) {
1175
+ if (depth === 0) break;
1176
+ depth--;
1177
+ } else if (depth === 0 && (char === ";" || char === ",")) break;
1178
+ else if (depth === 0 && char === "\n" && !/^\s*[|&]/.test(text.slice(index + 1, index + 64))) break;
1179
+ if (!/\s/.test(char)) end = index + 1;
1180
+ }
1181
+ return end > start ? file.location({
1182
+ start,
1183
+ end
1184
+ }) : member;
1185
+ }
1186
+ /**
1187
+ * The return type annotation of a method, from the location of its name: after the parameter list
1188
+ * (balanced brackets), `: T` up to the body; `undefined` when it cannot be found.
1189
+ */
1190
+ function returnTypeLocation(project, method) {
1191
+ const file = project.getFile(method.location.file);
1192
+ if (file === void 0) return void 0;
1193
+ const text = file.text;
1194
+ let index = text.indexOf("(", method.location.end);
1195
+ if (index === -1) return void 0;
1196
+ let depth = 0;
1197
+ for (; index < text.length; index++) {
1198
+ const char = text[index];
1199
+ if (OPENING.has(char)) depth++;
1200
+ else if (CLOSING.has(char) && --depth === 0) break;
1201
+ }
1202
+ const head = /^\s*:\s*/.exec(text.slice(index + 1, index + 64));
1203
+ if (head === null) return void 0;
1204
+ const start = index + 1 + head[0].length;
1205
+ let end = start;
1206
+ depth = 0;
1207
+ for (let cursor = start; cursor < text.length; cursor++) {
1208
+ const char = text[cursor];
1209
+ if (char === "=" && text[cursor + 1] === ">") {
1210
+ cursor++;
1211
+ continue;
1212
+ }
1213
+ if (depth === 0 && (char === "{" || char === ";") && end > start) break;
1214
+ if (OPENING.has(char)) depth++;
1215
+ else if (CLOSING.has(char)) {
1216
+ if (depth === 0) break;
1217
+ depth--;
1218
+ }
1219
+ if (!/\s/.test(char)) end = cursor + 1;
1220
+ }
1221
+ return end > start ? file.location({
1222
+ start,
1223
+ end
1224
+ }) : void 0;
1225
+ }
1226
+ // src/compiler/scalars.ts
1227
+ /** Valid declarations by the identity of their type; invalid ones are reported. */
1228
+ function readScalars(ctx, report) {
1229
+ const byType = /* @__PURE__ */ new Map();
1230
+ const byName = /* @__PURE__ */ new Map();
1231
+ for (const definition of ctx.definitionsOf(GraphQLIds.scalar)) {
1232
+ const fail = (message, hints, related = []) => report({
1233
+ code: Codes.InvalidDecorator,
1234
+ severity: "error",
1235
+ message,
1236
+ location: definition.call,
1237
+ hints,
1238
+ related
1239
+ });
1240
+ const [nameArg, definitionArg] = definition.args;
1241
+ const name = nameArg?.evaluated === true ? nameArg.value : void 0;
1242
+ if (typeof name !== "string") {
1243
+ fail(`scalar() of ${definition.name} needs a static name`, [`scalar<T>('Name', { ... })`]);
1244
+ continue;
1245
+ }
1246
+ if (!isGraphQLName(name) || SPECIFIED_SCALARS.has(name)) {
1247
+ report({
1248
+ code: Codes.NameConflict,
1249
+ severity: "error",
1250
+ message: SPECIFIED_SCALARS.has(name) ? `'${name}' is a specified GraphQL scalar: scalar() cannot redefine it (${definition.name})` : `'${name}' is not a valid GraphQL scalar name (${definition.name})`,
1251
+ location: nameArg?.location ?? definition.call,
1252
+ hints: ["GraphQL names match /^[_A-Za-z][_0-9A-Za-z]*$/ and do not start with __"]
1253
+ });
1254
+ continue;
1255
+ }
1256
+ const type = definition.typeArgIdentities?.[0];
1257
+ if (type === void 0) {
1258
+ report({
1259
+ code: Codes.InvalidType,
1260
+ severity: "error",
1261
+ message: `scalar('${name}') of ${definition.name} needs a named type argument: scalar<T>('${name}', ...)`,
1262
+ location: definition.call,
1263
+ hints: [`Name the TypeScript type of the scalar: export type ${name} = string & { readonly __brand: '${name}' }, then scalar<${name}>('${name}', ...)`]
1264
+ });
1265
+ continue;
1266
+ }
1267
+ if (SCALAR_ALIASES.has(type)) {
1268
+ fail(`scalar('${name}') of ${definition.name} cannot map ${type.slice(type.indexOf("#") + 1)}: it is a built-in scalar alias`, ["Declare a branded type for the scalar: export type Name = string & { readonly __brand: \"Name\" }"]);
1269
+ continue;
1270
+ }
1271
+ if (!definition.exported) {
1272
+ fail(`The scalar ${definition.name} must be exported: the generated module imports it`, [`export const ${definition.name} = scalar<...>('${name}', ...)`]);
1273
+ continue;
1274
+ }
1275
+ const declaration = {
1276
+ name,
1277
+ identity: definition.identity,
1278
+ type,
1279
+ description: staticProperty(ctx, definitionArg, "description"),
1280
+ specifiedByURL: staticProperty(ctx, definitionArg, "specifiedByURL"),
1281
+ location: definition.location
1282
+ };
1283
+ const sameType = byType.get(type);
1284
+ if (sameType !== void 0) {
1285
+ report({
1286
+ code: Codes.NameConflict,
1287
+ severity: "error",
1288
+ message: `${definition.name} and ${sameType.identity} both map ${type.slice(type.indexOf("#") + 1)} to a scalar`,
1289
+ location: definition.location,
1290
+ related: [{
1291
+ message: "the other scalar",
1292
+ location: sameType.location
1293
+ }],
1294
+ hints: ["Keep one scalar per TypeScript type"]
1295
+ });
1296
+ continue;
1297
+ }
1298
+ const sameName = byName.get(name);
1299
+ if (sameName !== void 0) {
1300
+ report({
1301
+ code: Codes.NameConflict,
1302
+ severity: "error",
1303
+ message: `Two scalars are named '${name}': ${definition.identity} and ${sameName.identity}`,
1304
+ location: definition.location,
1305
+ related: [{
1306
+ message: "the other scalar",
1307
+ location: sameName.location
1308
+ }],
1309
+ hints: ["Rename one of them"]
1310
+ });
1311
+ continue;
1312
+ }
1313
+ byType.set(type, declaration);
1314
+ byName.set(name, declaration);
1315
+ }
1316
+ return byType;
1317
+ }
1318
+ /** A string property of an object literal argument, when statically evaluable. */
1319
+ function staticProperty(ctx, arg, key) {
1320
+ if (arg === void 0) return void 0;
1321
+ if (arg.evaluated && typeof arg.value === "object" && arg.value !== null && !Array.isArray(arg.value)) {
1322
+ const value = arg.value[key];
1323
+ return typeof value === "string" ? value : void 0;
1324
+ }
1325
+ const node = arg.node;
1326
+ if (node.type !== "ObjectExpression") return void 0;
1327
+ for (const property of node.properties) {
1328
+ if (property.type !== "Property" || property.computed) continue;
1329
+ if ((property.key.type === "Identifier" ? property.key.name : void 0) !== key) continue;
1330
+ const result = ctx.project.evaluate(arg.location.file, property.value, { refs: "none" });
1331
+ return result.kind === "value" && typeof result.value === "string" ? result.value : void 0;
1332
+ }
1333
+ }
1334
+ // src/compiler/sdl-view.ts
1335
+ /** The SDL view of a model type. */
1336
+ function sdlType(model) {
1337
+ const base = model.description === void 0 ? {} : { description: model.description };
1338
+ switch (model.kind) {
1339
+ case "object":
1340
+ case "interface": return {
1341
+ kind: model.kind,
1342
+ name: model.name,
1343
+ ...base,
1344
+ ...model.interfaces.length === 0 ? {} : { interfaces: model.interfaces },
1345
+ fields: model.fields.map((field) => ({
1346
+ name: field.name,
1347
+ type: field.type,
1348
+ ...field.description === void 0 ? {} : { description: field.description },
1349
+ ...field.deprecationReason === void 0 ? {} : { deprecationReason: field.deprecationReason },
1350
+ ...field.args.length === 0 ? {} : { args: field.args.map(sdlInputValue) }
1351
+ }))
1352
+ };
1353
+ case "input": return {
1354
+ kind: "input",
1355
+ name: model.name,
1356
+ ...base,
1357
+ fields: model.fields.map(sdlInputValue)
1358
+ };
1359
+ case "enum": return {
1360
+ kind: "enum",
1361
+ name: model.name,
1362
+ ...base,
1363
+ values: model.values.map((value) => ({
1364
+ name: value.name,
1365
+ ...value.description === void 0 ? {} : { description: value.description },
1366
+ ...value.deprecationReason === void 0 ? {} : { deprecationReason: value.deprecationReason }
1367
+ }))
1368
+ };
1369
+ case "union": return {
1370
+ kind: "union",
1371
+ name: model.name,
1372
+ ...base,
1373
+ members: model.members
1374
+ };
1375
+ case "scalar": return {
1376
+ kind: "scalar",
1377
+ name: model.name,
1378
+ ...base,
1379
+ ...model.definition === void 0 ? { builtin: true } : {},
1380
+ ...model.specifiedByURL === void 0 ? {} : { specifiedByURL: model.specifiedByURL }
1381
+ };
1382
+ }
1383
+ }
1384
+ function sdlInputValue(arg) {
1385
+ return {
1386
+ name: arg.name,
1387
+ type: arg.type,
1388
+ ...arg.description === void 0 ? {} : { description: arg.description },
1389
+ ...arg.deprecationReason === void 0 ? {} : { deprecationReason: arg.deprecationReason },
1390
+ ...arg.defaultValue === void 0 ? {} : { defaultValue: arg.defaultValue }
1391
+ };
1392
+ }
1393
+ // src/compiler/builder.ts
1394
+ const NAME_HINT = "GraphQL names match /^[_A-Za-z][_0-9A-Za-z]*$/ and do not start with __";
1395
+ const RECORD_HINT = "GraphQL objects have fixed fields: declare an interface, or use JSONValue (import type { JSONValue } from '@znight/graphql')";
1396
+ const ROOT_NAMES = new Set(Object.values(ROOT_TYPE_NAMES));
1397
+ /** Builds the model; `undefined` when the application has no resolver. */
1398
+ function buildModel(ctx, options) {
1399
+ const resolvers = [...ctx.stored].sort((a, b) => compareStrings(a.host.identity, b.host.identity));
1400
+ if (resolvers.length === 0) return void 0;
1401
+ return new SchemaBuilder(ctx, options).build(resolvers);
1402
+ }
1403
+ var SchemaBuilder = class {
1404
+ #ctx;
1405
+ #number;
1406
+ #shapeOptions;
1407
+ /** `scalar<T>()` declarations by the identity of `T`. */
1408
+ #scalars;
1409
+ /** Every named shape seen, by key. */
1410
+ #named = /* @__PURE__ */ new Map();
1411
+ /** Models by GraphQL name (the first claimant). */
1412
+ #types = /* @__PURE__ */ new Map();
1413
+ /** Models by declaration key. */
1414
+ #models = /* @__PURE__ */ new Map();
1415
+ /** GraphQL names by declaration key. */
1416
+ #names = /* @__PURE__ */ new Map();
1417
+ #positions = /* @__PURE__ */ new Map();
1418
+ #reported = /* @__PURE__ */ new Set();
1419
+ #fieldResolvers = /* @__PURE__ */ new Map();
1420
+ #defaults = [];
1421
+ #methods = /* @__PURE__ */ new Map();
1422
+ #errors = 0;
1423
+ constructor(ctx, options) {
1424
+ this.#ctx = ctx;
1425
+ this.#number = options.number;
1426
+ const declarations = readScalars(ctx, (diagnostic) => this.#report(diagnostic));
1427
+ this.#scalars = declarations;
1428
+ const keep = [.../* @__PURE__ */ new Set([...BASE_KEEP, ...declarations.keys()])].sort(compareStrings);
1429
+ this.#shapeOptions = Object.freeze({
1430
+ keep: Object.freeze(keep),
1431
+ members: "instance",
1432
+ jsdoc: true
1433
+ });
1434
+ }
1435
+ build(resolvers) {
1436
+ checkLoaders(this.#ctx, resolvers, (diagnostic) => this.#report(diagnostic));
1437
+ this.#indexFieldResolvers(resolvers);
1438
+ const roots = /* @__PURE__ */ new Map();
1439
+ const rootFields = /* @__PURE__ */ new Map();
1440
+ let hasQuery = false;
1441
+ for (const resolver of resolvers) for (const field of resolver.fields) {
1442
+ if (field.operation === "field") continue;
1443
+ if (field.operation === "query") hasQuery = true;
1444
+ if (!field.complete) continue;
1445
+ const method = this.#method(resolver, field);
1446
+ if (method === void 0) continue;
1447
+ const root = this.#rootModel(roots, field.operation);
1448
+ const coordinate = `${root.name}.${field.name}`;
1449
+ const previous = rootFields.get(coordinate);
1450
+ if (previous !== void 0) {
1451
+ this.#report({
1452
+ code: Codes.NameConflict,
1453
+ severity: "error",
1454
+ message: `Two root fields are named ${coordinate}: ${resolver.host.displayName}.${field.method} and ${previous.resolver.host.displayName}.${previous.field.method}`,
1455
+ location: field.location,
1456
+ related: [{
1457
+ message: "the other field",
1458
+ location: previous.field.location
1459
+ }],
1460
+ hints: [`Rename one of them: @${decoratorName(field.operation)}('otherName')`]
1461
+ });
1462
+ continue;
1463
+ }
1464
+ rootFields.set(coordinate, {
1465
+ field,
1466
+ resolver
1467
+ });
1468
+ root.fields.push(this.#resolverField(root, resolver, field, method));
1469
+ }
1470
+ if (!hasQuery) {
1471
+ const first = resolvers[0];
1472
+ this.#report({
1473
+ code: Codes.InvalidResolver,
1474
+ severity: "error",
1475
+ message: "The application has GraphQL resolvers but no @Query field: GraphQL requires a query root type",
1476
+ location: first.location,
1477
+ hints: [`Add a @Query() method to a resolver (${first.host.displayName}, for instance)`]
1478
+ });
1479
+ }
1480
+ this.#reportUnreachableFieldResolvers();
1481
+ this.#checkDefaults();
1482
+ this.#checkInputCycles();
1483
+ checkDecisions(this.#ctx, resolvers, (diagnostic) => this.#report(diagnostic));
1484
+ if (this.#errors > 0 || !hasQuery) return void 0;
1485
+ return this.#assemble(roots, resolvers);
1486
+ }
1487
+ #report(diagnostic) {
1488
+ if (diagnostic.severity === "error") this.#errors++;
1489
+ this.#ctx.report(diagnostic);
1490
+ }
1491
+ /** Reports once per `key`. */
1492
+ #reportOnce(key, diagnostic) {
1493
+ if (this.#reported.has(key)) return;
1494
+ this.#reported.add(key);
1495
+ this.#report(diagnostic);
1496
+ }
1497
+ #fail(code, use, message, hints) {
1498
+ const location = use.locate();
1499
+ this.#report({
1500
+ code,
1501
+ severity: "error",
1502
+ message,
1503
+ ...location === void 0 ? {} : { location },
1504
+ ...hints.length === 0 ? {} : { hints },
1505
+ related: reachedFrom(use)
1506
+ });
1507
+ }
1508
+ #method(resolver, field) {
1509
+ const key = `${resolver.host.identity}#${field.method}`;
1510
+ if (!this.#methods.has(key)) {
1511
+ const cls = this.#ctx.classInfo(resolver.host.identity);
1512
+ this.#methods.set(key, cls?.methods.find((method) => method.name === field.method && method.kind === "method"));
1513
+ }
1514
+ return this.#methods.get(key);
1515
+ }
1516
+ #shape(target) {
1517
+ const result = this.#ctx.types.shape(target, this.#shapeOptions);
1518
+ for (const named of result.named.values()) this.#named.set(named.key, named);
1519
+ return result;
1520
+ }
1521
+ #rootModel(roots, operation) {
1522
+ let root = roots.get(operation);
1523
+ if (root === void 0) {
1524
+ const name = ROOT_TYPE_NAMES[operation];
1525
+ root = {
1526
+ kind: "object",
1527
+ name,
1528
+ key: `#root:${operation}`,
1529
+ description: void 0,
1530
+ location: void 0,
1531
+ fields: [],
1532
+ trail: name,
1533
+ root: void 0,
1534
+ classIdentity: void 0,
1535
+ properties: [],
1536
+ interfaces: []
1537
+ };
1538
+ roots.set(operation, root);
1539
+ this.#claim(root, void 0);
1540
+ }
1541
+ return root;
1542
+ }
1543
+ /** A field backed by a resolver method (root field or field resolver). */
1544
+ #resolverField(owner, resolver, field, method) {
1545
+ const coordinate = `${owner.name}.${field.name}`;
1546
+ const isRoot = field.operation !== "field";
1547
+ const use = {
1548
+ position: "output",
1549
+ coordinate,
1550
+ trail: isRoot ? coordinate : `${owner.trail}.${field.name}`,
1551
+ reached: isRoot ? void 0 : owner.trail,
1552
+ root: isRoot ? method.location : owner.root,
1553
+ locate: () => method.location
1554
+ };
1555
+ const type = this.#returnType(resolver, field, method, use);
1556
+ return {
1557
+ name: field.name,
1558
+ type: type === void 0 ? "String" : printTypeRef(type.ref),
1559
+ description: descriptionOf(method.doc),
1560
+ deprecationReason: deprecationOf(method.doc),
1561
+ args: this.#arguments(field, method, use),
1562
+ binding: {
1563
+ resolver,
1564
+ field
1565
+ },
1566
+ location: field.location
1567
+ };
1568
+ }
1569
+ #returnType(resolver, field, method, use) {
1570
+ const where = `${resolver.host.displayName}.${field.method}`;
1571
+ if (method.returnType === void 0) return this.#fail(Codes.InvalidType, use, `${where} has no return type annotation (${use.coordinate})`, [`Annotate the return type: the schema is derived from it (${field.method}(...): Promise<Book>)`]);
1572
+ const result = this.#shape({ returnOf: method });
1573
+ const rootIssue = result.issues.find((issue) => issue.named === void 0);
1574
+ const located = {
1575
+ ...use,
1576
+ locate: () => rootIssue?.location ?? method.location
1577
+ };
1578
+ const root = result.root;
1579
+ const iterable = root.kind === "reference" && ASYNC_ITERABLES.has(root.identity) ? root : void 0;
1580
+ if (field.operation === "subscription") {
1581
+ if (iterable === void 0) {
1582
+ this.#report({
1583
+ code: Codes.InvalidResolver,
1584
+ severity: "error",
1585
+ message: `@Subscription ${where} must return an async iterable, not ${describeShape(root)}`,
1586
+ location: method.location,
1587
+ hints: [`Declare it as an async generator (async *${field.method}(): AsyncGenerator<Event>) or return pubsub.subscribe(...) (AsyncIterable<Event>)`]
1588
+ });
1589
+ return;
1590
+ }
1591
+ const item = iterable.typeArgs[0];
1592
+ if (item === void 0) return this.#fail(Codes.InvalidType, located, `${where} must declare the type of its events (${use.coordinate})`, ["AsyncIterable<Event>"]);
1593
+ return this.#convert(item, false, located);
1594
+ }
1595
+ if (iterable !== void 0) return this.#fail(Codes.InvalidType, located, `${where} returns an async iterable: only @Subscription fields stream values (${use.coordinate})`, ["Use @Subscription() for a stream of events, or return a list (Promise<readonly Book[]>)"]);
1596
+ if (root.kind === "undefined") return this.#fail(Codes.InvalidType, located, `${where} returns void: GraphQL fields must return a value (${use.coordinate})`, ["Return a payload type (the changed object) or a boolean"]);
1597
+ return this.#convert(root, false, located);
1598
+ }
1599
+ /** Arguments of a resolver field: `@Args('name')` parameters and the properties of `@Args()` types. */
1600
+ #arguments(field, method, owner) {
1601
+ const args = [];
1602
+ const seen = /* @__PURE__ */ new Map();
1603
+ const add = (arg, location) => {
1604
+ if (seen.has(arg.name)) {
1605
+ const first = seen.get(arg.name);
1606
+ this.#report({
1607
+ code: Codes.NameConflict,
1608
+ severity: "error",
1609
+ message: `Argument '${arg.name}' of ${owner.coordinate} is declared twice`,
1610
+ ...location === void 0 ? {} : { location },
1611
+ ...first === void 0 ? {} : { related: [{
1612
+ message: "first declared here",
1613
+ location: first
1614
+ }] },
1615
+ hints: ["Argument names must be unique: rename one of them"]
1616
+ });
1617
+ return;
1618
+ }
1619
+ seen.set(arg.name, location);
1620
+ args.push(arg);
1621
+ };
1622
+ field.params.forEach((data, index) => {
1623
+ if (data?.kind !== "args") return;
1624
+ const param = method.params[index];
1625
+ const result = this.#shape({ parameter: param });
1626
+ if (data.name !== void 0) {
1627
+ const use = {
1628
+ position: "input",
1629
+ coordinate: `${owner.coordinate}(${data.name}:)`,
1630
+ trail: `${owner.trail}(${data.name})`,
1631
+ reached: owner.reached,
1632
+ root: owner.root,
1633
+ locate: () => param.typeLocation
1634
+ };
1635
+ const arg = this.#parameterArgument(data.name, param, method, result, use);
1636
+ if (arg !== void 0) add(arg, param.location);
1637
+ return;
1638
+ }
1639
+ const properties = this.#propertiesOf(result.root);
1640
+ if (properties === "record") {
1641
+ this.#fail(Codes.InvalidType, {
1642
+ ...owner,
1643
+ locate: () => param.typeLocation
1644
+ }, `@Args() parameter '${param.name}' of ${owner.coordinate} cannot be a record or an index signature: GraphQL arguments are fixed`, [RECORD_HINT, `@Args('${param.name}') declares one argument named '${param.name}'`]);
1645
+ return;
1646
+ }
1647
+ if (properties === void 0) {
1648
+ this.#fail(Codes.InvalidType, {
1649
+ ...owner,
1650
+ locate: () => param.typeLocation
1651
+ }, `@Args() parameter '${param.name}' of ${owner.coordinate} must have an object type: its properties are the arguments`, [`@Args('${param.name}') declares one argument named '${param.name}'`]);
1652
+ return;
1653
+ }
1654
+ for (const property of properties) {
1655
+ if (isHidden(property.doc)) continue;
1656
+ const use = {
1657
+ position: "input",
1658
+ coordinate: `${owner.coordinate}(${property.name}:)`,
1659
+ trail: `${owner.trail}(${property.name})`,
1660
+ reached: owner.reached,
1661
+ root: owner.root,
1662
+ locate: () => annotationLocation(this.#ctx.project, property.location) ?? param.typeLocation
1663
+ };
1664
+ const arg = this.#inputValue(property, use);
1665
+ if (arg !== void 0) add(arg, property.location ?? param.location);
1666
+ }
1667
+ });
1668
+ return args;
1669
+ }
1670
+ /** `@Args('limit') limit: Int = 20`. */
1671
+ #parameterArgument(name, param, method, result, use) {
1672
+ if (!isGraphQLName(name)) {
1673
+ this.#fail(Codes.NameConflict, use, `'${name}' is not a valid GraphQL argument name (${use.coordinate})`, [NAME_HINT]);
1674
+ return;
1675
+ }
1676
+ const converted = this.#convert(result.root, param.optional, use);
1677
+ if (converted === void 0) return void 0;
1678
+ let ref = converted.ref;
1679
+ const arg = {
1680
+ name,
1681
+ type: "",
1682
+ ref,
1683
+ description: descriptionOf(param.doc) ?? paramDescription(method.doc, param.name),
1684
+ deprecationReason: deprecationOf(param.doc),
1685
+ defaultValue: void 0,
1686
+ nullAsUndefined: false,
1687
+ location: param.location
1688
+ };
1689
+ const initializer = param.initializer;
1690
+ let hasDefault = false;
1691
+ if (param.hasDefault) {
1692
+ if (initializer?.evaluated === true && initializer.ref === void 0 && !containsRef(initializer.value)) {
1693
+ this.#defaults.push({
1694
+ target: arg,
1695
+ value: initializer.value,
1696
+ use,
1697
+ location: initializer.location
1698
+ });
1699
+ hasDefault = true;
1700
+ } else ref = {
1701
+ ...ref,
1702
+ nonNull: false
1703
+ };
1704
+ }
1705
+ arg.ref = ref;
1706
+ arg.type = printTypeRef(ref);
1707
+ arg.nullAsUndefined = !ref.nonNull && !converted.acceptsNull;
1708
+ this.#checkDeprecatedInput(arg, hasDefault, use);
1709
+ return arg;
1710
+ }
1711
+ /** A property as an argument or an input field (§6.3): `@default`, `@deprecated`, null handling. */
1712
+ #inputValue(property, use) {
1713
+ if (!isGraphQLName(property.name)) {
1714
+ this.#fail(Codes.NameConflict, {
1715
+ ...use,
1716
+ locate: () => property.location
1717
+ }, `'${property.name}' is not a valid GraphQL name (${use.coordinate})`, [NAME_HINT, "Hide the property with /** @gqlHidden */"]);
1718
+ return;
1719
+ }
1720
+ if (property.kind === "getter") {
1721
+ this.#fail(Codes.InvalidInput, use, `${use.coordinate} is a getter: input values are plain data`, ["Declare a property instead, or hide the getter with /** @gqlHidden */"]);
1722
+ return;
1723
+ }
1724
+ const converted = this.#convert(property.shape, property.optional, use);
1725
+ if (converted === void 0) return void 0;
1726
+ const arg = {
1727
+ name: property.name,
1728
+ type: printTypeRef(converted.ref),
1729
+ ref: converted.ref,
1730
+ description: descriptionOf(property.doc),
1731
+ deprecationReason: deprecationOf(property.doc),
1732
+ defaultValue: void 0,
1733
+ nullAsUndefined: !converted.ref.nonNull && !converted.acceptsNull,
1734
+ location: property.location
1735
+ };
1736
+ const tag = tagOf(property.doc, "default");
1737
+ if (tag !== void 0) this.#defaults.push({
1738
+ target: arg,
1739
+ value: defaultTagValue(tag),
1740
+ use,
1741
+ location: tag.location
1742
+ });
1743
+ this.#checkDeprecatedInput(arg, tag !== void 0, use);
1744
+ return arg;
1745
+ }
1746
+ /** graphql-js rejects a deprecated argument or input field that is required. */
1747
+ #checkDeprecatedInput(arg, hasDefault, use) {
1748
+ if (arg.deprecationReason === void 0 || !arg.ref.nonNull || hasDefault) return;
1749
+ this.#fail(Codes.InvalidInput, use, `${use.coordinate} is required and cannot be deprecated`, ["Make it optional (?: T or T | undefined) or give it a default value before deprecating it"]);
1750
+ }
1751
+ #indexFieldResolvers(resolvers) {
1752
+ for (const resolver of resolvers) for (const field of resolver.fields) {
1753
+ if (field.operation !== "field" || !field.complete) continue;
1754
+ const method = this.#method(resolver, field);
1755
+ if (method === void 0) continue;
1756
+ const index = field.params.findIndex((param) => param?.kind === "parent");
1757
+ const param = method.params[index];
1758
+ if (param === void 0) continue;
1759
+ const result = this.#shape({ parameter: param });
1760
+ const key = this.#objectKeyOf(result.root);
1761
+ if (key === void 0) {
1762
+ this.#report({
1763
+ code: Codes.InvalidResolver,
1764
+ severity: "error",
1765
+ message: `The @Parent() parameter '${param.name}' of ${resolver.host.displayName}.${field.method} must have an object type, not ${describeShape(result.root)}`,
1766
+ location: param.typeLocation,
1767
+ hints: ["The parent type is the object type the field is added to: an interface, a class or a named alias of an object type"]
1768
+ });
1769
+ continue;
1770
+ }
1771
+ let list = this.#fieldResolvers.get(key);
1772
+ if (list === void 0) {
1773
+ list = [];
1774
+ this.#fieldResolvers.set(key, list);
1775
+ }
1776
+ list.push({
1777
+ resolver,
1778
+ field,
1779
+ method,
1780
+ attached: false
1781
+ });
1782
+ }
1783
+ for (const list of this.#fieldResolvers.values()) list.sort((a, b) => compareStrings(a.resolver.host.identity, b.resolver.host.identity) || a.field.location.start - b.field.location.start);
1784
+ }
1785
+ #attachFieldResolvers(model) {
1786
+ const pending = this.#fieldResolvers.get(model.key);
1787
+ if (pending === void 0) return;
1788
+ for (const entry of pending) {
1789
+ entry.attached = true;
1790
+ const field = this.#resolverField(model, entry.resolver, entry.field, entry.method);
1791
+ const index = model.fields.findIndex((existing) => existing.name === field.name);
1792
+ if (index === -1) {
1793
+ model.fields.push(field);
1794
+ continue;
1795
+ }
1796
+ const existing = model.fields[index];
1797
+ const where = `${entry.resolver.host.displayName}.${entry.field.method}`;
1798
+ if (existing.binding !== void 0) {
1799
+ this.#report({
1800
+ code: Codes.NameConflict,
1801
+ severity: "error",
1802
+ message: `Two field resolvers add ${model.name}.${field.name}: ${where} and ${existing.binding.resolver.host.displayName}.${existing.binding.field.method}`,
1803
+ location: entry.field.location,
1804
+ related: [{
1805
+ message: "the other field resolver",
1806
+ location: existing.binding.field.location
1807
+ }],
1808
+ hints: ["Keep one of them, or rename one: @ResolveField('otherName')"]
1809
+ });
1810
+ continue;
1811
+ }
1812
+ if (existing.type !== field.type) {
1813
+ this.#report({
1814
+ code: Codes.NameConflict,
1815
+ severity: "error",
1816
+ message: `@ResolveField ${where} returns ${field.type}, but the property ${model.name}.${field.name} is ${existing.type}`,
1817
+ location: entry.field.location,
1818
+ ...existing.location === void 0 ? {} : { related: [{
1819
+ message: "the property",
1820
+ location: existing.location
1821
+ }] },
1822
+ hints: ["A field resolver replacing a property must resolve the same GraphQL type: change its return type, or rename the field"]
1823
+ });
1824
+ continue;
1825
+ }
1826
+ model.fields[index] = {
1827
+ ...field,
1828
+ description: field.description ?? existing.description,
1829
+ deprecationReason: field.deprecationReason ?? existing.deprecationReason
1830
+ };
1831
+ }
1832
+ }
1833
+ #reportUnreachableFieldResolvers() {
1834
+ for (const [key, list] of this.#fieldResolvers) for (const entry of list) {
1835
+ if (entry.attached) continue;
1836
+ const name = this.#named.get(key)?.name ?? key;
1837
+ this.#report({
1838
+ code: Codes.InvalidResolver,
1839
+ severity: "warning",
1840
+ message: `@ResolveField ${entry.resolver.host.displayName}.${entry.field.method} adds a field to ${name}, which no root field reaches as an output type: it is not part of the schema`,
1841
+ location: entry.field.location,
1842
+ hints: [`Return ${name} from a @Query (or another reachable field), or remove the field resolver`]
1843
+ });
1844
+ }
1845
+ }
1846
+ /**
1847
+ * The GraphQL type of a shape: `null`, `undefined` and `optional` make it nullable, everything
1848
+ * else is non-null (§6.2).
1849
+ */
1850
+ #convert(shape, optional, use) {
1851
+ let nullable = optional;
1852
+ let acceptsNull = false;
1853
+ let inner = shape;
1854
+ if (shape.kind === "union") {
1855
+ const rest = shape.members.filter((member) => member.kind !== "null" && member.kind !== "undefined");
1856
+ if (rest.length !== shape.members.length) {
1857
+ nullable = true;
1858
+ acceptsNull = shape.members.some((member) => member.kind === "null");
1859
+ }
1860
+ if (rest.length === 0) return this.#fail(Codes.InvalidType, use, `${use.coordinate} is always null or undefined`, ["Declare the type of the value"]);
1861
+ inner = rest.length === 1 ? rest[0] : {
1862
+ kind: "union",
1863
+ members: rest
1864
+ };
1865
+ }
1866
+ const converted = this.#inner(inner, use);
1867
+ if (converted === void 0) return void 0;
1868
+ const isNullable = nullable || converted.nullable;
1869
+ return {
1870
+ ref: {
1871
+ ...converted.ref,
1872
+ nonNull: !isNullable
1873
+ },
1874
+ acceptsNull: acceptsNull || converted.nullable
1875
+ };
1876
+ }
1877
+ #inner(shape, use) {
1878
+ switch (shape.kind) {
1879
+ case "string": return refTo("String");
1880
+ case "number": return refTo(this.#number);
1881
+ case "boolean": return refTo("Boolean");
1882
+ case "literal":
1883
+ if (typeof shape.value === "string") return refTo("String");
1884
+ return refTo(typeof shape.value === "number" ? this.#number : "Boolean");
1885
+ case "date": return this.#builtin("DateTime", use);
1886
+ case "bigint": return this.#builtin("BigInt", use);
1887
+ case "array": {
1888
+ const item = this.#convert(shape.items, false, use);
1889
+ return item === void 0 ? void 0 : {
1890
+ ref: {
1891
+ kind: "list",
1892
+ of: item.ref,
1893
+ nonNull: false
1894
+ },
1895
+ nullable: false
1896
+ };
1897
+ }
1898
+ case "named": return this.#namedType(shape.key, use);
1899
+ case "reference": return this.#reference(shape, use);
1900
+ case "url": return this.#fail(Codes.InvalidType, use, `Cannot map 'URL' to a GraphQL type (${use.coordinate})`, ["Expose a string (url.href), or declare a scalar: export const UrlScalar = scalar<URL>('URL', { ... })"]);
1901
+ case "binary": return this.#fail(Codes.InvalidType, use, `Cannot map binary data to a GraphQL type (${use.coordinate})`, ["Expose a URL (coverUrl: string), hide the property with /** @gqlHidden */, or declare a scalar: export const Base64 = scalar<Uint8Array>('Base64', { ... })"]);
1902
+ case "unknown": return this.#fail(Codes.InvalidType, use, `Cannot map 'unknown' or 'any' to a GraphQL type (${use.coordinate})`, ["Declare the type, or use JSONValue (import type { JSONValue } from '@znight/graphql') for arbitrary JSON"]);
1903
+ case "never":
1904
+ case "null":
1905
+ case "undefined": return this.#fail(Codes.InvalidType, use, `Cannot map '${shape.kind}' to a GraphQL type (${use.coordinate})`, ["Declare the type of the value"]);
1906
+ case "tuple": return this.#fail(Codes.InvalidType, use, `Cannot map a tuple to a GraphQL type (${use.coordinate})`, ["Use an array (readonly T[]) or a named object type"]);
1907
+ case "object":
1908
+ if (shape.additional !== void 0) return this.#fail(Codes.InvalidType, use, `Cannot map a record or an index signature to a GraphQL type (${use.coordinate})`, [RECORD_HINT]);
1909
+ return this.#fail(Codes.InvalidType, use, `Cannot map an anonymous object type to a GraphQL type (${use.coordinate})`, ["GraphQL types have names: declare it (export interface BookStats { ... }) and use it"]);
1910
+ case "intersection": return this.#fail(Codes.InvalidType, use, `Cannot map an anonymous intersection to a GraphQL type (${use.coordinate})`, ["Name it: export type BookWithAuthor = Book & { readonly author: Author }"]);
1911
+ case "union": return this.#anonymousUnion(shape.members, use);
1912
+ case "schema": return this.#fail(Codes.InvalidType, use, `Cannot map a schema type to a GraphQL type (${use.coordinate})`, ["Declare the TypeScript type: the annotation is the GraphQL type"]);
1913
+ case "opaque": return this.#opaque(shape, use);
1914
+ }
1915
+ }
1916
+ #anonymousUnion(members, use) {
1917
+ if (members.every((member) => member.kind === "literal" && typeof member.value === "string")) {
1918
+ const values = members.map((member) => `'${member.value}'`).join(" | ");
1919
+ return this.#fail(Codes.InvalidType, use, `Cannot map the anonymous union ${values} to a GraphQL type (${use.coordinate})`, [`Name it to make it an enum: export type Sort = ${values}`]);
1920
+ }
1921
+ if (members.every((member) => member.kind === "named")) return this.#fail(Codes.InvalidType, use, `Cannot map an anonymous union to a GraphQL type (${use.coordinate})`, [`GraphQL unions have names: export type SearchResult = ${members.map((member) => this.#named.get(member.key)?.name ?? "?").join(" | ")}`]);
1922
+ return this.#fail(Codes.InvalidType, use, `Cannot map the union ${members.map(describeShape).join(" | ")} to a GraphQL type (${use.coordinate})`, ["GraphQL unions only contain object types: declare one type, or an object type per case"]);
1923
+ }
1924
+ #opaque(shape, use) {
1925
+ const text = shape.text;
1926
+ let hints;
1927
+ switch (shape.reason) {
1928
+ case "non-json":
1929
+ if (text.startsWith("Promise")) hints = ["A Promise is only unwrapped at the top of a resolver return type: declare the resolved type"];
1930
+ else if (text.startsWith("Map") || text.startsWith("WeakMap")) hints = ["Use an array of entries, a named object type, or JSONValue (import type { JSONValue } from '@znight/graphql')"];
1931
+ else hints = ["Declare a plain data type (an interface, an array, a scalar)"];
1932
+ break;
1933
+ case "missing-annotation":
1934
+ hints = ["Annotate the type: the compiler reads annotations only"];
1935
+ break;
1936
+ case "function":
1937
+ hints = ["Functions are not data: return the value, or hide the member with /** @gqlHidden */"];
1938
+ break;
1939
+ case "type-parameter":
1940
+ hints = ["Give the type arguments explicitly"];
1941
+ break;
1942
+ default: hints = ["Declare the structure with an interface or a named alias"];
1943
+ }
1944
+ return this.#fail(Codes.InvalidType, use, `Cannot map '${text}' to a GraphQL type (${use.coordinate})`, hints);
1945
+ }
1946
+ #reference(shape, use) {
1947
+ const alias = SCALAR_ALIASES.get(shape.identity);
1948
+ if (alias === "JSON") {
1949
+ const json = this.#builtin("JSON", use);
1950
+ return json === void 0 ? void 0 : {
1951
+ ref: json.ref,
1952
+ nullable: true
1953
+ };
1954
+ }
1955
+ if (alias !== void 0) return refTo(alias === "Float" ? "Float" : alias);
1956
+ const scalar = this.#scalars.get(shape.identity);
1957
+ if (scalar !== void 0) return this.#customScalar(scalar, use);
1958
+ if (OUTPUT_ITERABLES.has(shape.identity)) {
1959
+ if (use.position === "input") return this.#fail(Codes.InvalidInput, use, `${use.coordinate} is a ${shape.name}: input lists are arrays`, ["Declare an array (readonly T[]) for input values"]);
1960
+ const item = shape.typeArgs[0];
1961
+ if (item === void 0) return this.#fail(Codes.InvalidType, use, `${use.coordinate}: ${shape.name} needs a type argument`, []);
1962
+ const converted = this.#convert(item, false, use);
1963
+ return converted === void 0 ? void 0 : {
1964
+ ref: {
1965
+ kind: "list",
1966
+ of: converted.ref,
1967
+ nonNull: false
1968
+ },
1969
+ nullable: false
1970
+ };
1971
+ }
1972
+ if (ASYNC_ITERABLES.has(shape.identity)) return this.#fail(Codes.InvalidType, use, `Cannot map '${shape.name}' to a GraphQL type (${use.coordinate})`, ["Async iterables are only the return type of @Subscription methods"]);
1973
+ return this.#fail(Codes.InvalidType, use, `Cannot map '${shape.name}' to a GraphQL type (${use.coordinate})`, ["Declare a plain data type (an interface, an array, a scalar)"]);
1974
+ }
1975
+ #builtin(name, use) {
1976
+ const key = `#builtin:${name}`;
1977
+ if (!this.#models.has(key)) {
1978
+ const model = {
1979
+ kind: "scalar",
1980
+ name,
1981
+ key,
1982
+ description: void 0,
1983
+ location: void 0,
1984
+ definition: void 0,
1985
+ specifiedByURL: void 0
1986
+ };
1987
+ this.#models.set(key, model);
1988
+ if (!this.#claim(model, use)) return void 0;
1989
+ }
1990
+ return refTo(name);
1991
+ }
1992
+ #customScalar(scalar, use) {
1993
+ const key = `#scalar:${scalar.identity}`;
1994
+ if (!this.#models.has(key)) {
1995
+ const model = {
1996
+ kind: "scalar",
1997
+ name: scalar.name,
1998
+ key,
1999
+ description: scalar.description,
2000
+ location: scalar.location,
2001
+ definition: scalar.identity,
2002
+ specifiedByURL: scalar.specifiedByURL
2003
+ };
2004
+ this.#models.set(key, model);
2005
+ if (!this.#claim(model, use)) return void 0;
2006
+ }
2007
+ return refTo(scalar.name);
2008
+ }
2009
+ #namedType(key, use) {
2010
+ const named = this.#named.get(key);
2011
+ if (named === void 0) return this.#fail(Codes.InvalidType, use, `Cannot resolve the type of ${use.coordinate}`, []);
2012
+ switch (named.declaration) {
2013
+ case "enum": return this.#enum(named, use);
2014
+ case "alias": return this.#alias(named, use);
2015
+ default: return this.#object(named, named, use);
2016
+ }
2017
+ }
2018
+ /**
2019
+ * A named alias: an object type (object literal, intersection, utility type, generic instance),
2020
+ * an enum (string literals), a union (object types), or transparent (`type Email = string`,
2021
+ * `type Books = Book[]`, `type Author = Person`).
2022
+ */
2023
+ #alias(alias, use) {
2024
+ const shape = alias.shape;
2025
+ switch (shape.kind) {
2026
+ case "object":
2027
+ case "intersection": return this.#object(alias, alias, use);
2028
+ case "named": {
2029
+ const target = this.#named.get(shape.key);
2030
+ if (target !== void 0 && target.declaration !== "alias" && target.declaration !== "enum" && target.typeArgs.length > 0) return this.#object(alias, target, use);
2031
+ return this.#namedType(shape.key, use);
2032
+ }
2033
+ case "union": {
2034
+ const rest = shape.members.filter((member) => member.kind !== "null" && member.kind !== "undefined");
2035
+ const nullable = rest.length !== shape.members.length;
2036
+ if (rest.length === 1) {
2037
+ const inner = this.#inner(rest[0], use);
2038
+ return inner === void 0 ? void 0 : {
2039
+ ref: inner.ref,
2040
+ nullable: inner.nullable || nullable
2041
+ };
2042
+ }
2043
+ let converted;
2044
+ if (rest.every((member) => member.kind === "literal")) converted = this.#literalEnum(alias, rest, use);
2045
+ else if (rest.length > 0 && rest.every((member) => member.kind === "named")) converted = this.#union(alias, rest, use);
2046
+ else converted = this.#anonymousUnion(rest, use);
2047
+ return converted === void 0 ? void 0 : {
2048
+ ref: converted.ref,
2049
+ nullable
2050
+ };
2051
+ }
2052
+ default: return this.#inner(shape, use);
2053
+ }
2054
+ }
2055
+ /**
2056
+ * The declaration key of the object type a shape maps to in output position (the parent of a
2057
+ * field resolver), `undefined` when it is not an object type. Mirrors {@link #alias}.
2058
+ */
2059
+ #objectKeyOf(shape) {
2060
+ if (shape.kind === "union") {
2061
+ const rest = shape.members.filter((member) => member.kind !== "null" && member.kind !== "undefined");
2062
+ return rest.length === 1 ? this.#objectKeyOf(rest[0]) : void 0;
2063
+ }
2064
+ if (shape.kind !== "named") return void 0;
2065
+ const named = this.#named.get(shape.key);
2066
+ if (named === void 0 || named.declaration === "enum") return void 0;
2067
+ if (named.declaration !== "alias") return named.key;
2068
+ const target = named.shape;
2069
+ if (target.kind === "object" || target.kind === "intersection") return named.key;
2070
+ if (target.kind === "named") {
2071
+ const instance = this.#named.get(target.key);
2072
+ if (instance !== void 0 && instance.declaration !== "alias" && instance.declaration !== "enum" && instance.typeArgs.length > 0) return named.key;
2073
+ }
2074
+ return this.#objectKeyOf(target);
2075
+ }
2076
+ /** An object type (output) or an input object type (input) for `named`, structured as `structure`. */
2077
+ #object(named, structure, use) {
2078
+ const key = named.key;
2079
+ if (!this.#checkPosition(named, use)) return void 0;
2080
+ const existing = this.#models.get(key);
2081
+ if (existing !== void 0) return this.#known(existing);
2082
+ const name = this.#typeName(named, use);
2083
+ if (name === void 0) return void 0;
2084
+ const properties = this.#propertiesOf(structure.shape);
2085
+ if (properties === "record") return this.#fail(Codes.InvalidType, use, `Cannot map ${named.name}, a record or an index signature, to a GraphQL type (${use.coordinate})`, [RECORD_HINT]);
2086
+ if (properties === void 0) return this.#fail(Codes.InvalidType, use, `Cannot map ${named.name} to a GraphQL object type (${use.coordinate})`, ["Declare an interface, a class or a named object type"]);
2087
+ const description = descriptionOf(named.doc) ?? (structure === named ? void 0 : descriptionOf(structure.doc));
2088
+ const trail = `${use.trail} → ${name}`;
2089
+ if (use.position === "input") {
2090
+ const model = {
2091
+ kind: "input",
2092
+ name,
2093
+ key,
2094
+ description,
2095
+ location: named.location,
2096
+ fields: []
2097
+ };
2098
+ this.#models.set(key, model);
2099
+ if (!this.#claim(model, use)) return void 0;
2100
+ for (const property of properties) {
2101
+ if (isHidden(property.doc)) continue;
2102
+ const fieldUse = {
2103
+ position: "input",
2104
+ coordinate: `${name}.${property.name}`,
2105
+ trail: `${trail}.${property.name}`,
2106
+ reached: trail,
2107
+ root: use.root,
2108
+ locate: () => annotationLocation(this.#ctx.project, property.location)
2109
+ };
2110
+ const field = this.#inputValue(property, fieldUse);
2111
+ if (field !== void 0) model.fields.push(field);
2112
+ }
2113
+ return refTo(name);
2114
+ }
2115
+ const model = {
2116
+ kind: "object",
2117
+ name,
2118
+ key,
2119
+ description,
2120
+ location: named.location,
2121
+ fields: [],
2122
+ trail,
2123
+ root: use.root,
2124
+ classIdentity: structure.declaration === "class" && structure.typeArgs.length === 0 && structure === named ? structure.identity : void 0,
2125
+ properties: properties.map((property) => this.#discriminant(property)),
2126
+ interfaces: []
2127
+ };
2128
+ this.#models.set(key, model);
2129
+ if (!this.#claim(model, use)) return void 0;
2130
+ for (const property of properties) {
2131
+ const field = this.#outputField(model, property);
2132
+ if (field !== void 0) model.fields.push(field);
2133
+ }
2134
+ this.#attachFieldResolvers(model);
2135
+ return refTo(name);
2136
+ }
2137
+ /** A property as a union discriminator candidate: its literal value, through aliases (`kind: BookKind`). */
2138
+ #discriminant(property) {
2139
+ let shape = property.shape;
2140
+ for (let depth = 0; shape.kind === "named" && depth < 32; depth++) {
2141
+ const named = this.#named.get(shape.key);
2142
+ if (named?.declaration !== "alias") break;
2143
+ shape = named.shape;
2144
+ }
2145
+ return {
2146
+ name: property.name,
2147
+ optional: property.optional,
2148
+ literal: shape.kind === "literal" ? shape.value : void 0
2149
+ };
2150
+ }
2151
+ #outputField(model, property) {
2152
+ if (isHidden(property.doc) || property.name === "__typename") return void 0;
2153
+ if (property.shape.kind === "opaque" && property.shape.reason === "function") return void 0;
2154
+ const use = {
2155
+ position: "output",
2156
+ coordinate: `${model.name}.${property.name}`,
2157
+ trail: `${model.trail}.${property.name}`,
2158
+ reached: model.trail,
2159
+ root: model.root,
2160
+ locate: () => annotationLocation(this.#ctx.project, property.location)
2161
+ };
2162
+ if (!isGraphQLName(property.name)) return this.#fail(Codes.NameConflict, {
2163
+ ...use,
2164
+ locate: () => property.location
2165
+ }, `'${property.name}' is not a valid GraphQL field name (${use.coordinate})`, [NAME_HINT, "Hide the property with /** @gqlHidden */"]);
2166
+ const converted = this.#convert(property.shape, property.optional, use);
2167
+ if (converted === void 0) return void 0;
2168
+ return {
2169
+ name: property.name,
2170
+ type: printTypeRef(converted.ref),
2171
+ description: descriptionOf(property.doc),
2172
+ deprecationReason: deprecationOf(property.doc),
2173
+ args: [],
2174
+ binding: void 0,
2175
+ location: property.location
2176
+ };
2177
+ }
2178
+ /**
2179
+ * The properties of an object structure (intersections merged, later members win), `'record'` for
2180
+ * a record or an index signature (ZNIGHT060: GraphQL objects have fixed fields).
2181
+ */
2182
+ #propertiesOf(shape) {
2183
+ switch (shape.kind) {
2184
+ case "object": return shape.additional === void 0 ? shape.properties : "record";
2185
+ case "named": {
2186
+ const named = this.#named.get(shape.key);
2187
+ return named === void 0 || named.declaration === "enum" ? void 0 : this.#propertiesOf(named.shape);
2188
+ }
2189
+ case "intersection": {
2190
+ const merged = /* @__PURE__ */ new Map();
2191
+ for (const member of shape.members) {
2192
+ const properties = this.#propertiesOf(member);
2193
+ if (properties === void 0 || properties === "record") return properties;
2194
+ for (const property of properties) {
2195
+ merged.delete(property.name);
2196
+ merged.set(property.name, property);
2197
+ }
2198
+ }
2199
+ return [...merged.values()];
2200
+ }
2201
+ default: return;
2202
+ }
2203
+ }
2204
+ /** A declaration is used in one position: input or output (ZNIGHT063 otherwise). */
2205
+ #checkPosition(named, use) {
2206
+ let uses = this.#positions.get(named.key);
2207
+ if (uses === void 0) {
2208
+ uses = {};
2209
+ this.#positions.set(named.key, uses);
2210
+ }
2211
+ const other = use.position === "input" ? uses.output : uses.input;
2212
+ if (uses[use.position] === void 0) uses[use.position] = {
2213
+ coordinate: use.coordinate,
2214
+ location: use.locate()
2215
+ };
2216
+ if (other === void 0) return true;
2217
+ const output = use.position === "output" ? use.coordinate : other.coordinate;
2218
+ const input = use.position === "input" ? use.coordinate : other.coordinate;
2219
+ const inputOwner = input.includes("(") ? input.slice(0, input.indexOf("(")) : input.split(".")[0];
2220
+ this.#reportOnce(`position\0${named.key}`, {
2221
+ code: Codes.InvalidInput,
2222
+ severity: "error",
2223
+ message: `'${named.name}' is used as an output type (${output}) and as an input type (${input})`,
2224
+ location: named.location,
2225
+ hints: [`GraphQL needs two types: declare \`export interface ${named.name}Input extends ${named.name} {}\` and use it in ${inputOwner}`],
2226
+ related: [...uses.output?.location === void 0 ? [] : [{
2227
+ message: `used as output: ${output}`,
2228
+ location: uses.output.location
2229
+ }], ...uses.input?.location === void 0 ? [] : [{
2230
+ message: `used as input: ${input}`,
2231
+ location: uses.input.location
2232
+ }]]
2233
+ });
2234
+ return false;
2235
+ }
2236
+ /**
2237
+ * The GraphQL name of a declaration (§6.4): `@gqlName`, the declaration name, or for a generic
2238
+ * instance the names of its type arguments followed by the declaration name (`BookPage`).
2239
+ */
2240
+ #typeName(named, use) {
2241
+ if (this.#names.has(named.key)) return this.#names.get(named.key);
2242
+ let name;
2243
+ const explicit = tagOf(named.doc, "gqlName")?.text.trim();
2244
+ if (explicit !== void 0 && explicit !== "") name = explicit;
2245
+ else if (named.typeArgs.length === 0) name = named.name;
2246
+ else {
2247
+ const args = named.typeArgs.map((arg) => this.#argumentName(arg));
2248
+ const missing = args.findIndex((arg) => arg === void 0);
2249
+ if (missing === -1) name = `${args.join("")}${named.name}`;
2250
+ else this.#reportOnce(`argument\0${named.key}`, {
2251
+ code: Codes.InvalidType,
2252
+ severity: "error",
2253
+ message: `Cannot name the GraphQL type of ${named.name}<...> (${use.coordinate}): its type argument ${describeShape(named.typeArgs[missing])} has no GraphQL name`,
2254
+ location: named.location,
2255
+ related: reachedFrom(use),
2256
+ hints: [`Name the instance with an alias (export type BookPages = ${named.name}<...>) or with /** @gqlName Name */`]
2257
+ });
2258
+ }
2259
+ this.#names.set(named.key, name);
2260
+ return name;
2261
+ }
2262
+ /** The GraphQL name a type argument contributes to a generic instance name. */
2263
+ #argumentName(shape) {
2264
+ switch (shape.kind) {
2265
+ case "string": return "String";
2266
+ case "number": return this.#number;
2267
+ case "boolean": return "Boolean";
2268
+ case "date": return "DateTime";
2269
+ case "bigint": return "BigInt";
2270
+ case "reference": {
2271
+ const alias = SCALAR_ALIASES.get(shape.identity);
2272
+ if (alias !== void 0) return alias;
2273
+ return this.#scalars.get(shape.identity)?.name;
2274
+ }
2275
+ case "union": {
2276
+ const rest = shape.members.filter((member) => member.kind !== "null" && member.kind !== "undefined");
2277
+ return rest.length === 1 ? this.#argumentName(rest[0]) : void 0;
2278
+ }
2279
+ case "named": {
2280
+ const named = this.#named.get(shape.key);
2281
+ if (named === void 0) return void 0;
2282
+ if (named.declaration === "alias") {
2283
+ const target = named.shape;
2284
+ const instance = target.kind === "named" ? this.#named.get(target.key) : void 0;
2285
+ const isObject = target.kind === "object" || target.kind === "intersection" || instance !== void 0 && instance.declaration !== "alias" && instance.declaration !== "enum" && instance.typeArgs.length > 0;
2286
+ const isEnum = target.kind === "union" && target.members.every((member) => member.kind === "literal");
2287
+ const isUnion = target.kind === "union" && target.members.filter((member) => member.kind === "named").length > 1;
2288
+ if (!isObject && !isEnum && !isUnion) return this.#argumentName(target);
2289
+ }
2290
+ return this.#typeName(named, {
2291
+ position: "output",
2292
+ coordinate: named.name,
2293
+ trail: named.name,
2294
+ reached: void 0,
2295
+ root: void 0,
2296
+ locate: () => named.location
2297
+ });
2298
+ }
2299
+ default: return;
2300
+ }
2301
+ }
2302
+ /**
2303
+ * Registers a model under its GraphQL name: the name must be valid, not reserved, and not taken
2304
+ * by another declaration (ZNIGHT061).
2305
+ */
2306
+ #claim(model, use) {
2307
+ const name = model.name;
2308
+ const isRoot = model.key.startsWith("#root:");
2309
+ const location = model.location ?? use?.locate();
2310
+ let problem;
2311
+ if (!isGraphQLName(name)) problem = `'${name}' is not a valid GraphQL type name`;
2312
+ else if (!isRoot && ROOT_NAMES.has(name)) problem = `'${name}' is the name of a root operation type`;
2313
+ else if (SPECIFIED_SCALARS.has(name)) problem = `'${name}' is the name of a specified GraphQL scalar`;
2314
+ if (problem !== void 0) {
2315
+ this.#reportOnce(`name\0${model.key}`, {
2316
+ code: Codes.NameConflict,
2317
+ severity: "error",
2318
+ message: `${problem} (${model.key.startsWith("#") ? name : model.key})`,
2319
+ ...location === void 0 ? {} : { location },
2320
+ related: use === void 0 ? [] : reachedFrom(use),
2321
+ hints: [isGraphQLName(name) ? "Rename the declaration, or name it with /** @gqlName OtherName */" : NAME_HINT]
2322
+ });
2323
+ return false;
2324
+ }
2325
+ const existing = this.#types.get(name);
2326
+ if (existing === void 0) {
2327
+ this.#types.set(name, model);
2328
+ return true;
2329
+ }
2330
+ if (existing === model) return true;
2331
+ const pair = [existing.key, model.key].sort(compareStrings).join("\0");
2332
+ this.#reportOnce(`conflict\0${pair}`, {
2333
+ code: Codes.NameConflict,
2334
+ severity: "error",
2335
+ message: `Two types are named '${name}' in the GraphQL schema: ${describeKey(model)} and ${describeKey(existing)}`,
2336
+ ...location === void 0 ? {} : { location },
2337
+ related: [...existing.location === void 0 ? [] : [{
2338
+ message: "the other declaration",
2339
+ location: existing.location
2340
+ }], ...use === void 0 ? [] : reachedFrom(use)],
2341
+ hints: ["Rename one of them, or name one with /** @gqlName OtherName */"]
2342
+ });
2343
+ return false;
2344
+ }
2345
+ /** A model met again: its reference, unless its name was refused (already reported). */
2346
+ #known(model) {
2347
+ return this.#types.get(model.name) === model ? refTo(model.name) : void 0;
2348
+ }
2349
+ /** A TypeScript `enum`: names = member names, internal values = member values. */
2350
+ #enum(named, use) {
2351
+ const existing = this.#models.get(named.key);
2352
+ if (existing !== void 0) return this.#known(existing);
2353
+ const name = this.#typeName(named, use);
2354
+ if (name === void 0) return void 0;
2355
+ const members = named.enumMembers;
2356
+ if (members === void 0 || members.length === 0) return this.#fail(Codes.InvalidType, use, `The values of the enum ${named.name} cannot be evaluated (${use.coordinate})`, ["Use literal member values (A = \"a\", B = 1)"]);
2357
+ const values = [];
2358
+ for (const member of members) {
2359
+ if (!this.#checkEnumValueName(member.name, named, member.location, use)) continue;
2360
+ values.push({
2361
+ name: member.name,
2362
+ value: member.value,
2363
+ description: descriptionOf(member.doc),
2364
+ deprecationReason: deprecationOf(member.doc)
2365
+ });
2366
+ }
2367
+ return this.#registerEnum(named, name, values, use);
2368
+ }
2369
+ /** A named alias of a union of string literals: names = values = the literals, in source order. */
2370
+ #literalEnum(named, members, use) {
2371
+ const existing = this.#models.get(named.key);
2372
+ if (existing !== void 0) return this.#known(existing);
2373
+ const literals = named.literals ?? members.map((member) => member.value);
2374
+ if (!literals.every((value) => typeof value === "string")) return this.#fail(Codes.InvalidType, use, `Cannot map ${named.name} (${literals.map((value) => JSON.stringify(value)).join(" | ")}) to a GraphQL type (${use.coordinate}): only unions of string literals are enums`, ["Declare a TypeScript enum (enum Level { Low = 1, High = 2 }), or use Int"]);
2375
+ const name = this.#typeName(named, use);
2376
+ if (name === void 0) return void 0;
2377
+ const values = [];
2378
+ for (const literal of literals) {
2379
+ if (!this.#checkEnumValueName(literal, named, named.location, use)) continue;
2380
+ values.push({
2381
+ name: literal,
2382
+ value: literal,
2383
+ description: void 0,
2384
+ deprecationReason: void 0
2385
+ });
2386
+ }
2387
+ return this.#registerEnum(named, name, values, use);
2388
+ }
2389
+ #checkEnumValueName(value, named, location, use) {
2390
+ if (isGraphQLName(value) && !RESERVED_ENUM_VALUES.has(value)) return true;
2391
+ this.#reportOnce(`enum\0${named.key}\0${value}`, {
2392
+ code: Codes.NameConflict,
2393
+ severity: "error",
2394
+ message: `'${value}' of ${named.name} is not a valid GraphQL enum value`,
2395
+ location,
2396
+ related: reachedFrom(use),
2397
+ hints: [`${NAME_HINT}; true, false and null are reserved`]
2398
+ });
2399
+ return false;
2400
+ }
2401
+ #registerEnum(named, name, values, use) {
2402
+ const model = {
2403
+ kind: "enum",
2404
+ name,
2405
+ key: named.key,
2406
+ description: descriptionOf(named.doc),
2407
+ location: named.location,
2408
+ values
2409
+ };
2410
+ this.#models.set(named.key, model);
2411
+ if (!this.#claim(model, use)) return void 0;
2412
+ return refTo(name);
2413
+ }
2414
+ /** A named alias of a union of object types (output only), with its runtime discriminator. */
2415
+ #union(alias, members, use) {
2416
+ if (use.position === "input") return this.#fail(Codes.InvalidInput, use, `${use.coordinate} is the union ${alias.name}: unions cannot be input values`, ["Declare one input type with optional fields, or one argument per case"]);
2417
+ const existing = this.#models.get(alias.key);
2418
+ if (existing !== void 0) return this.#known(existing);
2419
+ const name = this.#typeName(alias, use);
2420
+ if (name === void 0) return void 0;
2421
+ const model = {
2422
+ kind: "union",
2423
+ name,
2424
+ key: alias.key,
2425
+ description: descriptionOf(alias.doc),
2426
+ location: alias.location,
2427
+ members: [],
2428
+ discriminator: void 0
2429
+ };
2430
+ this.#models.set(alias.key, model);
2431
+ if (!this.#claim(model, use)) return void 0;
2432
+ const trail = `${use.trail} → ${name}`;
2433
+ const objects = [];
2434
+ let failed = false;
2435
+ for (const member of members) {
2436
+ const memberUse = {
2437
+ ...use,
2438
+ coordinate: name,
2439
+ trail,
2440
+ reached: trail,
2441
+ locate: () => alias.location
2442
+ };
2443
+ const converted = this.#inner(member, memberUse);
2444
+ if (converted === void 0) {
2445
+ failed = true;
2446
+ continue;
2447
+ }
2448
+ const target = converted.ref.kind === "named" ? this.#types.get(converted.ref.name) : void 0;
2449
+ if (target?.kind !== "object") {
2450
+ failed = true;
2451
+ this.#fail(Codes.InvalidType, memberUse, `The union ${name} contains ${describeShape(member)}, which is not an object type`, ["GraphQL unions only contain object types"]);
2452
+ continue;
2453
+ }
2454
+ if (!objects.includes(target)) objects.push(target);
2455
+ }
2456
+ objects.sort((a, b) => compareStrings(a.name, b.name));
2457
+ model.members = objects.map((object) => object.name);
2458
+ if (!failed) model.discriminator = this.#discriminator(model, objects);
2459
+ return refTo(name);
2460
+ }
2461
+ /**
2462
+ * How the runtime tells the members apart (§5.7): `instanceof` when every member is an exported
2463
+ * class, a `__typename` property, a literal property with a distinct value per member, else
2464
+ * ZNIGHT064.
2465
+ */
2466
+ #discriminator(union, members) {
2467
+ const found = discriminatorOf(members, (identity) => this.#ctx.classInfo(identity));
2468
+ if (found !== void 0) return found;
2469
+ const names = members.map((member) => member.name);
2470
+ const one = names.length === 1;
2471
+ const listed = one ? names[0] : `${names.slice(0, -1).join(", ")} and ${names.at(-1)}`;
2472
+ const allClasses = members.every((member) => member.classIdentity !== void 0);
2473
+ let why;
2474
+ if (allClasses) why = one ? "is a class that is not exported" : "are classes that are not all exported";
2475
+ else why = one ? "has no literal property" : "have no common literal property";
2476
+ this.#report({
2477
+ code: Codes.UnresolvableAbstractType,
2478
+ severity: "error",
2479
+ message: `The members of union ${union.name} cannot be told apart at runtime: ${listed} ${why}`,
2480
+ ...union.location === void 0 ? {} : { location: union.location },
2481
+ hints: [allClasses ? "Export the member classes: the generated module imports them for instanceof" : `Add a literal property to each member (${members.map((member) => `readonly kind: '${lowerFirst(member.name)}'`).join(" / ")}), or make the members classes`]
2482
+ });
2483
+ }
2484
+ /** Defaults (`@default`, parameter defaults) against their types (ZNIGHT063), as external values. */
2485
+ #checkDefaults() {
2486
+ for (const pending of this.#defaults) {
2487
+ const result = externalValue(pending.value, pending.target.ref, this.#types);
2488
+ if ("error" in result) {
2489
+ const location = pending.location ?? pending.use.locate();
2490
+ this.#report({
2491
+ code: Codes.InvalidInput,
2492
+ severity: "error",
2493
+ message: `Invalid default value ${describeValue(pending.value)} for ${pending.use.coordinate} (${pending.target.type}): ${result.error}`,
2494
+ ...location === void 0 ? {} : { location },
2495
+ related: reachedFrom(pending.use),
2496
+ hints: ["Defaults are JSON values of the argument type (enum values as their member value or name)"]
2497
+ });
2498
+ continue;
2499
+ }
2500
+ pending.target.defaultValue = result.value;
2501
+ if (pending.target.ref.nonNull) pending.target.nullAsUndefined = false;
2502
+ }
2503
+ }
2504
+ /** GraphQL forbids a cycle of non-null input fields (ZNIGHT063). */
2505
+ #checkInputCycles() {
2506
+ const inputs = [...this.#models.values()].filter((model) => model.kind === "input").sort((a, b) => compareStrings(a.name, b.name));
2507
+ const state = /* @__PURE__ */ new Map();
2508
+ const stack = [];
2509
+ const visit = (model) => {
2510
+ state.set(model.name, "visiting");
2511
+ stack.push(model.name);
2512
+ for (const field of model.fields) {
2513
+ if (field.ref.kind !== "named" || !field.ref.nonNull) continue;
2514
+ const target = this.#types.get(field.ref.name);
2515
+ if (target?.kind !== "input") continue;
2516
+ const status = state.get(target.name);
2517
+ if (status === "visiting") {
2518
+ const cycle = [...stack.slice(stack.indexOf(target.name)), target.name];
2519
+ this.#report({
2520
+ code: Codes.InvalidInput,
2521
+ severity: "error",
2522
+ message: `The input types ${cycle.join(" → ")} reference each other through non-null fields: no value can be written`,
2523
+ ...target.location === void 0 ? {} : { location: target.location },
2524
+ hints: ["Make one of the fields of the cycle optional (?: T) or a list"]
2525
+ });
2526
+ } else if (status === void 0) visit(target);
2527
+ }
2528
+ stack.pop();
2529
+ state.set(model.name, "done");
2530
+ };
2531
+ for (const model of inputs) if (!state.has(model.name)) visit(model);
2532
+ }
2533
+ #assemble(roots, resolvers) {
2534
+ const types = [...this.#types.values()].sort((a, b) => compareStrings(a.name, b.name));
2535
+ const meta = [];
2536
+ for (const resolver of resolvers) for (const field of resolver.fields) meta.push(...field.meta);
2537
+ const rootNames = {
2538
+ query: ROOT_TYPE_NAMES.query,
2539
+ ...roots.has("mutation") ? { mutation: ROOT_TYPE_NAMES.mutation } : {},
2540
+ ...roots.has("subscription") ? { subscription: ROOT_TYPE_NAMES.subscription } : {}
2541
+ };
2542
+ const sdl = printSdl({
2543
+ roots: rootNames,
2544
+ types: types.map(sdlType)
2545
+ });
2546
+ return {
2547
+ roots: rootNames,
2548
+ types,
2549
+ meta,
2550
+ sdl,
2551
+ schemaHash: schemaHash(sdl)
2552
+ };
2553
+ }
2554
+ };
2555
+ /** A (nullable, until `#convert` decides) reference to a named type. */
2556
+ function refTo(name) {
2557
+ return {
2558
+ ref: {
2559
+ kind: "named",
2560
+ name,
2561
+ nonNull: false
2562
+ },
2563
+ nullable: false
2564
+ };
2565
+ }
2566
+ function reachedFrom(use) {
2567
+ return use.reached === void 0 || use.root === void 0 ? [] : [{
2568
+ message: `reached from ${use.reached}`,
2569
+ location: use.root
2570
+ }];
2571
+ }
2572
+ function decoratorName(operation) {
2573
+ return ROOT_TYPE_NAMES[operation];
2574
+ }
2575
+ function describeKey(model) {
2576
+ return model.key.startsWith("#") ? `the ${model.kind} ${model.name}` : model.key;
2577
+ }
2578
+ /** Short description of a shape for messages (`string`, `Book`, `Map<string, number>`). */
2579
+ function describeShape(shape) {
2580
+ switch (shape.kind) {
2581
+ case "literal": return JSON.stringify(shape.value);
2582
+ case "named": return shape.key.slice(shape.key.indexOf("#") + 1);
2583
+ case "reference": return shape.name;
2584
+ case "opaque": return shape.text;
2585
+ case "array": return `${describeShape(shape.items)}[]`;
2586
+ case "union": return shape.members.map(describeShape).join(" | ");
2587
+ case "object": return "an object literal type";
2588
+ default: return shape.kind;
2589
+ }
2590
+ }
2591
+ function describeValue(value) {
2592
+ try {
2593
+ return JSON.stringify(value) ?? String(value);
2594
+ } catch {
2595
+ return String(value);
2596
+ }
2597
+ }
2598
+ function containsRef(value) {
2599
+ if (typeof value !== "object" || value === null) return false;
2600
+ if (Array.isArray(value)) return value.some(containsRef);
2601
+ const prototype = Object.getPrototypeOf(value);
2602
+ if (prototype !== Object.prototype && prototype !== null) return true;
2603
+ return Object.values(value).some(containsRef);
2604
+ }
2605
+ function lowerFirst(text) {
2606
+ return `${text.charAt(0).toLowerCase()}${text.slice(1)}`;
2607
+ }
2608
+ // src/compiler/generate.ts
2609
+ /** Module of `isThenable` (a required peer of `@znight/graphql`). */
2610
+ const HTTP_MODULE = "@znight/http";
2611
+ /** Builds the graphql section; `undefined` (no section) when the application has no resolver. */
2612
+ function generateGraphQL(ctx, model, options) {
2613
+ if (model === void 0) return void 0;
2614
+ if (options.sdl !== false) ctx.emitFile(options.sdl, `${model.sdl}\n`);
2615
+ const imports = createImports(ctx);
2616
+ const entries = [
2617
+ ["version", ctx.lit(1)],
2618
+ ["schemaHash", model.schemaHash],
2619
+ ["roots", ctx.obj(Object.entries(model.roots))],
2620
+ ["types", ctx.arr(model.types.map((type) => typeNode(ctx, imports, type)))]
2621
+ ];
2622
+ const groups = groupsNode(ctx, model.meta);
2623
+ if (groups !== void 0) entries.push(["groups", groups]);
2624
+ return ctx.obj(entries);
2625
+ }
2626
+ function createImports(ctx) {
2627
+ let args;
2628
+ let context;
2629
+ let info;
2630
+ let validateArgs;
2631
+ let isThenable;
2632
+ const hosts = /* @__PURE__ */ new Map();
2633
+ return {
2634
+ argsType: () => {
2635
+ args ??= ctx.importFrom(RUNTIME_MODULE, "GraphQLArgs", { type: true });
2636
+ return args;
2637
+ },
2638
+ contextType: () => {
2639
+ context ??= ctx.importFrom(RUNTIME_MODULE, "GraphQLContext", { type: true });
2640
+ return context;
2641
+ },
2642
+ infoType: () => {
2643
+ info ??= ctx.importFrom("graphql", "GraphQLResolveInfo", { type: true });
2644
+ return info;
2645
+ },
2646
+ validateArgs: () => {
2647
+ validateArgs ??= ctx.importFrom(RUNTIME_MODULE, "validateArgs");
2648
+ return validateArgs;
2649
+ },
2650
+ isThenable: () => {
2651
+ isThenable ??= ctx.importFrom(HTTP_MODULE, "isThenable");
2652
+ return isThenable;
2653
+ },
2654
+ hostType: (identity) => {
2655
+ let node = hosts.get(identity);
2656
+ if (node === void 0) {
2657
+ node = ctx.importType(identity);
2658
+ hosts.set(identity, node);
2659
+ }
2660
+ return node;
2661
+ }
2662
+ };
2663
+ }
2664
+ function typeNode(ctx, imports, type) {
2665
+ const head = [["kind", type.kind], ["name", type.name]];
2666
+ if (type.description !== void 0 && type.kind !== "scalar") head.push(["description", type.description]);
2667
+ switch (type.kind) {
2668
+ case "object":
2669
+ case "interface": {
2670
+ const entries = [...head];
2671
+ if (type.interfaces.length > 0) entries.push(["interfaces", ctx.lit([...type.interfaces])]);
2672
+ entries.push(["fields", ctx.arr(type.fields.map((field) => fieldNode(ctx, imports, field)))]);
2673
+ if (type.kind === "interface") entries.push(["discriminator", discriminatorNode(ctx, type.discriminator)]);
2674
+ return ctx.obj(entries);
2675
+ }
2676
+ case "input": return ctx.obj([...head, ["fields", ctx.arr(type.fields.map((field) => inputValueNode(ctx, field)))]]);
2677
+ case "enum": return ctx.obj([...head, ["values", ctx.arr(type.values.map((value) => {
2678
+ const entries = [["name", value.name], ["value", value.value]];
2679
+ if (value.description !== void 0) entries.push(["description", value.description]);
2680
+ if (value.deprecationReason !== void 0) entries.push(["deprecationReason", value.deprecationReason]);
2681
+ return ctx.obj(entries);
2682
+ }))]]);
2683
+ case "union": return ctx.obj([
2684
+ ...head,
2685
+ ["members", ctx.lit([...type.members])],
2686
+ ["discriminator", discriminatorNode(ctx, type.discriminator)]
2687
+ ]);
2688
+ case "scalar": {
2689
+ if (type.definition === void 0) return ctx.obj([...head, ["builtin", ctx.lit(true)]]);
2690
+ const entries = [...head, ["scalar", ctx.importValue(type.definition)]];
2691
+ if (type.fromSdl === true) {
2692
+ if (type.description !== void 0) entries.push(["description", type.description]);
2693
+ if (type.specifiedByURL !== void 0) entries.push(["specifiedByURL", type.specifiedByURL]);
2694
+ entries.push(["sdl", ctx.lit(true)]);
2695
+ }
2696
+ return ctx.obj(entries);
2697
+ }
2698
+ }
2699
+ }
2700
+ /** How the runtime tells the members of a union or the implementations of an interface apart. */
2701
+ function discriminatorNode(ctx, discriminator) {
2702
+ if (discriminator === void 0 || discriminator.by === "typename") return ctx.obj([["by", "typename"]]);
2703
+ if (discriminator.by === "property") return ctx.obj([
2704
+ ["by", "property"],
2705
+ ["property", discriminator.property],
2706
+ ["values", ctx.obj(Object.entries(discriminator.values))]
2707
+ ]);
2708
+ return ctx.obj([["by", "class"], ["classes", ctx.arr(discriminator.classes.map((entry) => ctx.arr([ctx.importValue(entry.identity), entry.name])))]]);
2709
+ }
2710
+ function fieldNode(ctx, imports, field) {
2711
+ const entries = [["name", field.name], ["type", field.type]];
2712
+ if (field.description !== void 0) entries.push(["description", field.description]);
2713
+ if (field.deprecationReason !== void 0) entries.push(["deprecationReason", field.deprecationReason]);
2714
+ if (field.args.length > 0) entries.push(["args", ctx.arr(field.args.map((arg) => inputValueNode(ctx, arg)))]);
2715
+ if (field.binding !== void 0) entries.push(["resolver", resolverNode(ctx, imports, field.binding.resolver, field.binding.field)]);
2716
+ return ctx.obj(entries);
2717
+ }
2718
+ function inputValueNode(ctx, arg) {
2719
+ const entries = [["name", arg.name], ["type", arg.type]];
2720
+ if (arg.description !== void 0) entries.push(["description", arg.description]);
2721
+ if (arg.deprecationReason !== void 0) entries.push(["deprecationReason", arg.deprecationReason]);
2722
+ if (arg.defaultValue !== void 0) entries.push(["defaultValue", ctx.lit(arg.defaultValue)]);
2723
+ if (arg.nullAsUndefined) entries.push(["nullAsUndefined", ctx.lit(true)]);
2724
+ return ctx.obj(entries);
2725
+ }
2726
+ function resolverNode(ctx, imports, resolver, field) {
2727
+ const guards = [...resolver.guards, ...field.guards].map((ref) => ctx.tokenOf(ref) ?? ref.token);
2728
+ const entries = [
2729
+ ["token", ctx.tokenOf(resolver.host) ?? resolver.host.identity],
2730
+ ["method", field.method],
2731
+ ["operation", field.operation],
2732
+ ["guards", ctx.lit(guards)],
2733
+ ["meta", ctx.obj(field.meta.map((entry) => [entry.key, ctx.lit(entry.value)]))]
2734
+ ];
2735
+ const loaders = field.params.flatMap((param) => param?.kind === "loader" ? [loaderToken(ctx, param)] : []);
2736
+ if (loaders.length > 0) entries.push(["loaders", ctx.lit(loaders)]);
2737
+ entries.push(["invoke", invokeNode(ctx, imports, resolver, field)]);
2738
+ return ctx.obj(entries);
2739
+ }
2740
+ /** The provider token a loader parameter is bound to. */
2741
+ function loaderToken(ctx, param) {
2742
+ const ref = ctx.binding(param.binding);
2743
+ if (typeof ref === "string") return ref;
2744
+ if (ref !== void 0 && "token" in ref) return ref.token;
2745
+ return param.identity;
2746
+ }
2747
+ /** Which `invoke` parameters a field uses: `(r, p, a, c, i)`. */
2748
+ function usedParameters(field) {
2749
+ let p = false;
2750
+ let a = false;
2751
+ let c = false;
2752
+ let i = false;
2753
+ for (const param of field.params) switch (param?.kind) {
2754
+ case "parent":
2755
+ p = true;
2756
+ break;
2757
+ case "args":
2758
+ a = true;
2759
+ break;
2760
+ case "ctx":
2761
+ case "loader":
2762
+ c = true;
2763
+ break;
2764
+ case "info": i = true;
2765
+ }
2766
+ return [
2767
+ p,
2768
+ a,
2769
+ c,
2770
+ i
2771
+ ];
2772
+ }
2773
+ /**
2774
+ * `(r: Resolver, p: unknown, a: GraphQLArgs, c: GraphQLContext, i: GraphQLResolveInfo) => r.m(...)`:
2775
+ * unused middle parameters are `_`-prefixed, trailing ones omitted (`noUnusedParameters`).
2776
+ */
2777
+ function invokeNode(ctx, imports, resolver, field) {
2778
+ const used = usedParameters(field);
2779
+ const last = used.lastIndexOf(true);
2780
+ const types = [
2781
+ () => ctx.code`unknown`,
2782
+ imports.argsType,
2783
+ imports.contextType,
2784
+ imports.infoType
2785
+ ];
2786
+ const names = [
2787
+ "p",
2788
+ "a",
2789
+ "c",
2790
+ "i"
2791
+ ];
2792
+ const params = [["r", imports.hostType(resolver.host.identity)]];
2793
+ for (let index = 0; index <= last; index++) params.push([`${used[index] ? "" : "_"}${names[index]}`, types[index]()]);
2794
+ const validated = field.params.flatMap((param, index) => param?.kind === "args" && param.schema !== void 0 ? [index] : []);
2795
+ return ctx.arrow(params, (r, ...rest) => {
2796
+ const [p, a, c, i] = rest;
2797
+ const scope = {
2798
+ p,
2799
+ a,
2800
+ c,
2801
+ i
2802
+ };
2803
+ if (validated.length === 0) return call(ctx, r, field, (index) => argument(ctx, scope, field, index));
2804
+ return validatingBlock(ctx, imports, r, scope, field, validated);
2805
+ });
2806
+ }
2807
+ /** `r.method(args)`. */
2808
+ function call(ctx, r, field, argumentOf) {
2809
+ return ctx.call(ctx.member(r, field.method), field.params.map((_, index) => argumentOf(index)));
2810
+ }
2811
+ /** The expression passed for the parameter `index` (validation not applied). */
2812
+ function argument(ctx, scope, field, index) {
2813
+ const param = field.params[index];
2814
+ switch (param?.kind) {
2815
+ case "parent": return ctx.code`${scope.p} as never`;
2816
+ case "ctx": return scope.c;
2817
+ case "info": return scope.i;
2818
+ case "loader": return ctx.code`${ctx.call(ctx.member(scope.c, "loader"), [loaderToken(ctx, param)])} as never`;
2819
+ case "args": return ctx.code`${argsValue(ctx, scope, param.name)} as never`;
2820
+ default: return ctx.lit(void 0);
2821
+ }
2822
+ }
2823
+ /** `a['name']`, or `a` for `@Args()`. */
2824
+ function argsValue(ctx, scope, name) {
2825
+ const a = scope.a;
2826
+ return name === void 0 ? a : ctx.code`${a}[${name}]`;
2827
+ }
2828
+ /** `validateArgs(Schema, a['input'], 'input')`. */
2829
+ function validation(ctx, imports, scope, param) {
2830
+ return ctx.call(imports.validateArgs(), [
2831
+ ctx.ref(param.schema),
2832
+ argsValue(ctx, scope, param.name),
2833
+ param.name ?? "args"
2834
+ ]);
2835
+ }
2836
+ /**
2837
+ * The body of the `invoke` of a field validating arguments: each validation is evaluated in order
2838
+ * into a `const`; while they return values the method is called synchronously, the first thenable
2839
+ * resumes in an `async` arrow that awaits it and the later validations (the order of evaluation and
2840
+ * the error reported first are those of an `async` invoke awaiting every validation).
2841
+ *
2842
+ * ```ts
2843
+ * (r: BookResolver, _p: unknown, a: GraphQLArgs) => {
2844
+ * const v0 = validateArgs(NewBookInput, a['input'], 'input')
2845
+ * if (isThenable(v0)) return (async () => r.addBook(await v0 as never))()
2846
+ * return r.addBook(v0 as never)
2847
+ * }
2848
+ * ```
2849
+ */
2850
+ function validatingBlock(ctx, imports, r, scope, field, validated) {
2851
+ return ctx.block(validated.map((index) => `v${index}`), (...locals) => {
2852
+ const local = new Map(validated.map((index, position) => [index, locals[position]]));
2853
+ const statements = [];
2854
+ for (const index of validated) {
2855
+ const name = local.get(index);
2856
+ const param = field.params[index];
2857
+ statements.push(ctx.code`const ${name} = ${validation(ctx, imports, scope, param)}`);
2858
+ const resume = ctx.arrow([], () => call(ctx, r, field, (position) => {
2859
+ const value = local.get(position);
2860
+ if (value === void 0) return argument(ctx, scope, field, position);
2861
+ if (position < index) return ctx.code`${value} as never`;
2862
+ if (position === index) return ctx.code`await ${value} as never`;
2863
+ const later = field.params[position];
2864
+ return ctx.code`await ${validation(ctx, imports, scope, later)} as never`;
2865
+ }), { async: true });
2866
+ statements.push(ctx.code`if (${ctx.call(imports.isThenable(), [name])}) return (${resume})()`);
2867
+ }
2868
+ statements.push(ctx.code`return ${call(ctx, r, field, (position) => {
2869
+ const value = local.get(position);
2870
+ return value === void 0 ? argument(ctx, scope, field, position) : ctx.code`${value} as never`;
2871
+ })}`);
2872
+ return statements;
2873
+ });
2874
+ }
2875
+ /**
2876
+ * `groups`: the route decorator groups the runtime needs (required, holding several keys, or named
2877
+ * differently from their key), sorted; `undefined` when there is none (RFC 0001, as `extensions.http`).
2878
+ */
2879
+ function groupsNode(ctx, meta) {
2880
+ const groups = /* @__PURE__ */ new Map();
2881
+ for (const entry of meta) {
2882
+ const group = groups.get(entry.group);
2883
+ if (group === void 0) groups.set(entry.group, {
2884
+ keys: /* @__PURE__ */ new Set([entry.key]),
2885
+ required: entry.required
2886
+ });
2887
+ else {
2888
+ group.keys.add(entry.key);
2889
+ group.required ||= entry.required;
2890
+ }
2891
+ }
2892
+ const needed = [...groups].filter(([name, group]) => group.required || group.keys.size > 1 || !group.keys.has(name)).sort(([a], [b]) => compareStrings(a, b));
2893
+ if (needed.length === 0) return void 0;
2894
+ return ctx.obj(needed.map(([name, group]) => [name, ctx.obj([["keys", ctx.lit([...group.keys].sort(compareStrings))], ["required", group.required]])]));
2895
+ }
2896
+ // src/compiler/options.ts
2897
+ /** The defaults. */
2898
+ const DEFAULT_OPTIONS = Object.freeze({
2899
+ sdl: false,
2900
+ number: "Float",
2901
+ schema: false
2902
+ });
2903
+ const KNOWN = /* @__PURE__ */ new Set([
2904
+ "sdl",
2905
+ "number",
2906
+ "schema"
2907
+ ]);
2908
+ /**
2909
+ * Merges `options` onto `base`. Throws a `TypeError` naming the option on an invalid value: options
2910
+ * are written in `znight.config.ts`, the error surfaces when the configuration is loaded.
2911
+ */
2912
+ function resolveOptions(base, options) {
2913
+ if (typeof options !== "object" || options === null || Array.isArray(options)) throw new TypeError("[znight] graphql.configure() expects an options object: { sdl?, number?, schema? }");
2914
+ for (const key of Object.keys(options)) if (!KNOWN.has(key)) throw new TypeError(`[znight] graphql.configure(): unknown option "${key}" (options: sdl, number, schema)`);
2915
+ const sdl = options.sdl === void 0 ? base.sdl : options.sdl;
2916
+ if (sdl !== false && (typeof sdl !== "string" || sdl.trim() === "")) throw new TypeError("[znight] graphql.configure(): \"sdl\" must be a path relative to the project root ('schema.graphql') or false");
2917
+ const number = options.number === void 0 ? base.number : options.number;
2918
+ if (number !== "Float" && number !== "Int") throw new TypeError(`[znight] graphql.configure(): "number" must be 'Float' or 'Int'`);
2919
+ return Object.freeze({
2920
+ sdl,
2921
+ number,
2922
+ schema: resolveSchema(base.schema, options.schema)
2923
+ });
2924
+ }
2925
+ function resolveSchema(base, schema) {
2926
+ if (schema === void 0) return base;
2927
+ if (schema === false) return false;
2928
+ const globs = typeof schema === "string" ? [schema] : schema;
2929
+ if (!Array.isArray(globs) || globs.length === 0 || globs.some((glob) => typeof glob !== "string" || glob.trim() === "")) throw new TypeError("[znight] graphql.configure(): \"schema\" must be a glob or a non-empty list of globs relative to the project root ('src/**/*.graphql'), or false");
2930
+ return Object.freeze([...globs]);
2931
+ }
2932
+ Object.freeze([
2933
+ "graphql/language",
2934
+ "graphql/type",
2935
+ "graphql/utilities"
2936
+ ]);
2937
+ let loaded;
2938
+ /** The graphql-js modules, loaded on first use. */
2939
+ function graphqlModules() {
2940
+ if (loaded === void 0) {
2941
+ const load = createRequire(import.meta.url);
2942
+ loaded = Object.freeze({
2943
+ language: load("graphql/language"),
2944
+ type: load("graphql/type"),
2945
+ utilities: load("graphql/utilities")
2946
+ });
2947
+ }
2948
+ return loaded;
2949
+ }
2950
+ // src/compiler/conformance.ts
2951
+ const MAX_DEPTH = 64;
2952
+ /** Walks every binding; reports the mismatches. */
2953
+ function checkConformance(input) {
2954
+ return new Conformance(input).run();
2955
+ }
2956
+ var Conformance = class {
2957
+ #config;
2958
+ #ctx;
2959
+ #options;
2960
+ #named = /* @__PURE__ */ new Map();
2961
+ #visited = /* @__PURE__ */ new Set();
2962
+ #anonymous = /* @__PURE__ */ new WeakMap();
2963
+ #reported = /* @__PURE__ */ new Set();
2964
+ #votes = /* @__PURE__ */ new Map();
2965
+ /** Abstract type → member object type → class identities (`''`: not an exported class) returned for it. */
2966
+ #members = /* @__PURE__ */ new Map();
2967
+ /** TypeScript members of abstract types that cannot be told apart (ZNIGHT064 unless every member is a class). */
2968
+ #untellable = /* @__PURE__ */ new Map();
2969
+ #predicates = graphqlModules().type;
2970
+ /** Identities of the `T` of the `scalar<T>()` declarations. */
2971
+ #scalarTypes;
2972
+ constructor(input) {
2973
+ this.#config = input;
2974
+ this.#ctx = input.ctx;
2975
+ const keep = new Set(BASE_KEEP);
2976
+ for (const scalar of input.scalars.values()) keep.add(scalar.type);
2977
+ this.#scalarTypes = new Set([...input.scalars.values()].map((scalar) => scalar.type));
2978
+ this.#options = Object.freeze({
2979
+ keep: Object.freeze([...keep].sort(compareStrings)),
2980
+ members: "instance",
2981
+ jsdoc: false
2982
+ });
2983
+ }
2984
+ run() {
2985
+ for (const binding of this.#config.bindings) this.#binding(binding);
2986
+ const nullAsUndefined = /* @__PURE__ */ new Set();
2987
+ for (const [coordinate, vote] of [...this.#votes].sort(([a], [b]) => compareStrings(a, b))) {
2988
+ if (vote.rejects.length === 0) continue;
2989
+ if (vote.accepts === 0) {
2990
+ nullAsUndefined.add(coordinate);
2991
+ continue;
2992
+ }
2993
+ for (const site of vote.rejects) this.#mismatch(site, `${site.label} does not accept null, but another use of ${coordinate} does: null is passed as is`, ["Accept null (| null) in every TypeScript type of the value, or none of them"]);
2994
+ }
2995
+ return {
2996
+ nullAsUndefined,
2997
+ discriminators: this.#discriminators()
2998
+ };
2999
+ }
3000
+ #binding(binding) {
3001
+ const { resolver, field, method, type, sdlField } = binding;
3002
+ const where = `${resolver.host.displayName}.${method.name}`;
3003
+ const coordinate = `${type.name}.${sdlField.name}`;
3004
+ const sdl = this.#config.locate(sdlField.astNode);
3005
+ if (method.returnType !== void 0) {
3006
+ const result = this.#shape({ returnOf: method });
3007
+ const site = {
3008
+ label: `${where} returns`,
3009
+ origin: `returned by ${where}`,
3010
+ coordinate,
3011
+ locate: () => returnTypeLocation(this.#ctx.project, method) ?? method.location,
3012
+ sdl,
3013
+ optional: false
3014
+ };
3015
+ const root = result.root;
3016
+ const iterable = root.kind === "reference" && ASYNC_ITERABLES.has(root.identity) ? root : void 0;
3017
+ if (field.operation === "subscription") {
3018
+ if (iterable !== void 0) this.#output(sdlField.type, iterable.typeArgs[0] ?? { kind: "unknown" }, site);
3019
+ else if (!this.#parts(root).unchecked) this.#report({
3020
+ code: Codes.InvalidResolver,
3021
+ severity: "error",
3022
+ message: `@Subscription ${where} must return an async iterable (${coordinate})`,
3023
+ location: site.locate() ?? method.location,
3024
+ hints: [`Declare it as an async generator (async *${method.name}(): AsyncGenerator<Event>) or return pubsub.subscribe(...) (AsyncIterable<Event>)`]
3025
+ });
3026
+ } else if (iterable === void 0) this.#output(sdlField.type, root, site);
3027
+ else this.#mismatch(site, `${where} returns an async iterable: only Subscription fields stream values`, [`Use @Subscription() for a stream of events, or return the value of ${coordinate}`]);
3028
+ }
3029
+ field.params.forEach((data, index) => {
3030
+ const param = method.params[index];
3031
+ if (param === void 0) return;
3032
+ if (data?.kind === "args" && data.schema === void 0) this.#arguments(binding, data.name, param, where);
3033
+ else if (data?.kind === "parent") this.#parent(type, param, where);
3034
+ });
3035
+ }
3036
+ /** `@Args('name')` (one argument) or `@Args()` (every argument, an object type). */
3037
+ #arguments(binding, name, param, where) {
3038
+ const { type, sdlField } = binding;
3039
+ const coordinate = `${type.name}.${sdlField.name}`;
3040
+ const result = this.#shape({ parameter: param });
3041
+ if (name !== void 0) {
3042
+ const arg = sdlField.args.find((candidate) => candidate.name === name);
3043
+ if (arg === void 0) return;
3044
+ this.#input(arg.type, result.root, {
3045
+ optional: param.optional || param.hasDefault,
3046
+ sdlDefault: arg.default !== void 0,
3047
+ vote: `${coordinate}(${name}:)`
3048
+ }, {
3049
+ label: `parameter '${param.name}' of ${where}`,
3050
+ origin: `parameter '${param.name}' of ${where}`,
3051
+ coordinate: `${coordinate}(${name}:)`,
3052
+ locate: () => param.typeLocation,
3053
+ sdl: this.#config.locate(arg.astNode),
3054
+ optional: false
3055
+ });
3056
+ return;
3057
+ }
3058
+ const site = {
3059
+ label: `@Args() parameter '${param.name}' of ${where}`,
3060
+ origin: `@Args() parameter '${param.name}' of ${where}`,
3061
+ coordinate,
3062
+ locate: () => param.typeLocation,
3063
+ sdl: this.#config.locate(sdlField.astNode),
3064
+ optional: false
3065
+ };
3066
+ const parts = this.#parts(result.root);
3067
+ if (parts.unchecked) return;
3068
+ const object = parts.members.length === 1 ? parts.members[0] : void 0;
3069
+ const properties = object === void 0 ? void 0 : this.#propertiesOf(object);
3070
+ if (properties === "record") return;
3071
+ if (properties === void 0) {
3072
+ this.#mismatch(site, `${site.label} must be an object type: it receives every argument of ${coordinate}`, [`@Args('name') receives one argument`]);
3073
+ return;
3074
+ }
3075
+ for (const property of properties) {
3076
+ const arg = sdlField.args.find((candidate) => candidate.name === property.name);
3077
+ const propertySite = {
3078
+ label: `property '${property.name}' of the @Args() parameter of ${where}`,
3079
+ origin: site.origin,
3080
+ coordinate: `${coordinate}(${property.name}:)`,
3081
+ locate: () => annotationLocation(this.#ctx.project, property.location) ?? param.typeLocation,
3082
+ sdl: this.#config.locate(arg?.astNode ?? sdlField.astNode),
3083
+ optional: false
3084
+ };
3085
+ if (arg === void 0) {
3086
+ const names = sdlField.args.map((candidate) => candidate.name);
3087
+ this.#mismatch({
3088
+ ...propertySite,
3089
+ locate: () => property.location ?? param.typeLocation
3090
+ }, `${coordinate} has no argument '${property.name}' (${propertySite.label})`, [names.length === 0 ? `${coordinate} takes no argument` : `Its arguments: ${names.join(", ")}`]);
3091
+ continue;
3092
+ }
3093
+ this.#input(arg.type, property.shape, {
3094
+ optional: property.optional,
3095
+ sdlDefault: arg.default !== void 0,
3096
+ vote: propertySite.coordinate
3097
+ }, propertySite);
3098
+ }
3099
+ }
3100
+ /** `@Parent()`: its properties named like the unbound fields of the type conform to them. */
3101
+ #parent(type, param, where) {
3102
+ const result = this.#shape({ parameter: param });
3103
+ const site = {
3104
+ label: `@Parent() parameter '${param.name}' of ${where}`,
3105
+ origin: `@Parent() parameter of ${where}`,
3106
+ coordinate: type.name,
3107
+ locate: () => param.typeLocation,
3108
+ sdl: this.#config.locate(type.astNode),
3109
+ optional: false
3110
+ };
3111
+ const parts = this.#parts(result.root);
3112
+ if (parts.unchecked) return;
3113
+ for (const member of parts.members) {
3114
+ const properties = this.#propertiesOf(member);
3115
+ if (properties === "record") continue;
3116
+ if (properties === void 0) {
3117
+ this.#mismatch(site, `${site.label} is ${this.#describe(member)}, not an object type (${type.name})`, [`Type it with the TypeScript type of ${type.name}`]);
3118
+ continue;
3119
+ }
3120
+ const fields = type.getFields();
3121
+ for (const property of properties) {
3122
+ const field = fields[property.name];
3123
+ if (field === void 0 || this.#config.bound.has(`${type.name}.${field.name}`)) continue;
3124
+ this.#output(field.type, property.shape, this.#propertySite(site, type, field, property, member));
3125
+ }
3126
+ }
3127
+ }
3128
+ #output(type, shape, site, depth = 0) {
3129
+ if (depth > MAX_DEPTH) return;
3130
+ const parts = this.#parts(shape);
3131
+ if (parts.unchecked) return;
3132
+ const { isNonNullType, isListType, isScalarType, isEnumType, isObjectType } = this.#predicates;
3133
+ let inner = type;
3134
+ if (isNonNullType(inner)) {
3135
+ if (parts.hasNull || parts.hasUndefined || site.optional) {
3136
+ const what = parts.hasNull ? "null" : "undefined";
3137
+ const described = `${site.label} ${this.#describeParts(parts, site.optional)}, which may be ${what}`;
3138
+ if (site.list === void 0) this.#mismatch(site, `${described}, but ${site.coordinate} is ${String(type)} (non-null)`, [`Return a value in every case, or make ${site.coordinate} nullable in the SDL (${String(inner.ofType)})`]);
3139
+ else this.#mismatch(site, `${described}, but the items of ${site.coordinate} (${site.list}) are ${String(type)} (non-null)`, [`Return non-null items, or make the items of ${site.coordinate} nullable in the SDL`]);
3140
+ }
3141
+ inner = inner.ofType;
3142
+ }
3143
+ if (parts.members.length === 0) return;
3144
+ if (isListType(inner)) {
3145
+ for (const member of parts.members) {
3146
+ const item = this.#listItem(member, "output");
3147
+ if (item === void 0) {
3148
+ this.#mismatch(site, `${site.label} ${this.#describe(member)}, not a list, but ${site.coordinate} is ${String(type)}`, ["Return an array (readonly T[]), a Set or an Iterable"]);
3149
+ continue;
3150
+ }
3151
+ for (const shape of item) this.#output(inner.ofType, shape, {
3152
+ ...site,
3153
+ label: `${site.label} a list of`,
3154
+ optional: false,
3155
+ list: site.list ?? String(type)
3156
+ }, depth + 1);
3157
+ }
3158
+ return;
3159
+ }
3160
+ if (isScalarType(inner)) {
3161
+ for (const member of parts.members) {
3162
+ if (this.#scalarAccepts(inner.name, member, "output")) continue;
3163
+ this.#mismatch(site, `${site.label} ${this.#describe(member)}, which does not conform to ${inner.name} (${site.coordinate})`, [this.#scalarHint(inner.name)]);
3164
+ }
3165
+ return;
3166
+ }
3167
+ if (isEnumType(inner)) {
3168
+ const values = new Set(inner.getValues().map((value) => value.name));
3169
+ for (const member of parts.members) {
3170
+ const literals = this.#literals(member);
3171
+ if (literals === void 0) {
3172
+ this.#mismatch(site, `${site.label} ${this.#describe(member)}, wider than the enum ${inner.name} (${site.coordinate})`, [`Return the values of ${inner.name}: ${[...values].join(", ")} (a TypeScript enum or a union of literals)`]);
3173
+ continue;
3174
+ }
3175
+ const unknown = literals.filter((literal) => !values.has(String(literal)));
3176
+ if (unknown.length > 0) this.#mismatch(site, `${site.label} ${unknown.map((literal) => JSON.stringify(literal)).join(", ")}, not ${unknown.length === 1 ? "a value" : "values"} of the enum ${inner.name} (${site.coordinate})`, [`Enum values are compared by value: ${[...values].join(", ")}`]);
3177
+ }
3178
+ return;
3179
+ }
3180
+ if (isObjectType(inner)) {
3181
+ for (const member of parts.members) this.#object(inner, member, site, depth);
3182
+ return;
3183
+ }
3184
+ this.#abstract(inner, parts.members, site, depth);
3185
+ }
3186
+ /** An object type returned as `shape`: every field without resolver is a conforming property. */
3187
+ #object(type, shape, site, depth) {
3188
+ const properties = this.#propertiesOf(shape);
3189
+ if (properties === "record") return;
3190
+ if (properties === void 0) {
3191
+ this.#mismatch(site, `${site.label} ${this.#describe(shape)}, not an object type, but ${site.coordinate} is ${type.name}`, [`Return the TypeScript type of ${type.name} (an interface, a class or an object type)`]);
3192
+ return;
3193
+ }
3194
+ if (!this.#visit(`output\0${type.name}`, shape)) return;
3195
+ const named = shape.kind === "named" ? this.#named.get(shape.key) : void 0;
3196
+ const tsName = named?.name ?? "the returned object type";
3197
+ const byName = new Map(properties.map((property) => [property.name, property]));
3198
+ for (const field of Object.values(type.getFields())) {
3199
+ const coordinate = `${type.name}.${field.name}`;
3200
+ if (this.#config.bound.has(coordinate)) continue;
3201
+ const property = byName.get(field.name);
3202
+ if (property === void 0) {
3203
+ const location = this.#config.locate(field.astNode);
3204
+ const declaration = named?.location ?? site.locate();
3205
+ this.#reportOnce(`provide\0${coordinate}\0${named?.key ?? site.label}`, {
3206
+ code: Codes.SchemaMismatch,
3207
+ severity: "error",
3208
+ message: `${coordinate} has no resolver, and ${tsName} (${site.origin}) has no property '${field.name}'`,
3209
+ ...location === void 0 ? {} : { location },
3210
+ hints: [`Add a field resolver: @ResolveField('${field.name}') in a @Resolver('${type.name}') class`, `Or add '${field.name}' to ${tsName}`],
3211
+ ...declaration === void 0 ? {} : { related: [{
3212
+ message: `${tsName} is declared here`,
3213
+ location: declaration
3214
+ }] }
3215
+ });
3216
+ continue;
3217
+ }
3218
+ this.#output(field.type, property.shape, this.#propertySite(site, type, field, property, shape), depth + 1);
3219
+ }
3220
+ }
3221
+ /** A union or an interface: each TypeScript member is matched to a possible type. */
3222
+ #abstract(type, members, site, depth) {
3223
+ const names = this.#config.schema.getPossibleTypes(type).map((object) => object.name);
3224
+ for (const member of members) {
3225
+ const named = member.kind === "named" ? this.#named.get(member.key) : void 0;
3226
+ const properties = this.#propertiesOf(member);
3227
+ if (properties === void 0) {
3228
+ this.#mismatch(site, `${site.label} ${this.#describe(member)}, not an object type, but ${site.coordinate} is ${type.name}`, [`Return one of ${names.join(", ")}`]);
3229
+ continue;
3230
+ }
3231
+ if (properties === "record") continue;
3232
+ if (named?.name === type.name && this.#predicates.isInterfaceType(type)) {
3233
+ if (!this.#visit(`interface\0${type.name}`, member)) continue;
3234
+ const byName = new Map(properties.map((property) => [property.name, property]));
3235
+ for (const field of Object.values(type.getFields())) {
3236
+ const property = byName.get(field.name);
3237
+ if (property !== void 0) this.#output(field.type, property.shape, this.#propertySite(site, type, field, property, member), depth + 1);
3238
+ }
3239
+ continue;
3240
+ }
3241
+ const typename = properties.find((property) => property.name === "__typename");
3242
+ const literal = typename?.shape.kind === "literal" ? typename.shape.value : void 0;
3243
+ let target = typeof literal === "string" ? literal : void 0;
3244
+ if (target === void 0 && named !== void 0 && names.includes(named.name)) target = named.name;
3245
+ if (target === void 0 || !names.includes(target)) {
3246
+ const what = named?.name ?? this.#describe(member);
3247
+ if (names.length === 0) {
3248
+ this.#mismatch(site, `${site.label} ${what}, but no object type implements ${type.name} in the SDL`, [`Declare an implementation: type ${what} implements ${type.name} { ... }`]);
3249
+ continue;
3250
+ }
3251
+ this.#mismatch(site, `${site.label} ${what}, which is not ${names.length === 1 ? "the member" : "a member"} of ${type.name} (${names.join(", ")})`, [`Return ${names.join(" | ")}: a member is recognised by its __typename literal (__typename: '${names[0] ?? "Member"}'), else by its name`]);
3252
+ continue;
3253
+ }
3254
+ const object = this.#config.schema.getType(target);
3255
+ this.#object(object, member, site, depth + 1);
3256
+ const identity = named?.declaration === "class" && named.typeArgs.length === 0 && this.#ctx.classInfo(named.identity)?.exported === true ? named.identity : "";
3257
+ let byMember = this.#members.get(type.name);
3258
+ if (byMember === void 0) {
3259
+ byMember = /* @__PURE__ */ new Map();
3260
+ this.#members.set(type.name, byMember);
3261
+ }
3262
+ let identities = byMember.get(target);
3263
+ if (identities === void 0) {
3264
+ identities = /* @__PURE__ */ new Set();
3265
+ byMember.set(target, identities);
3266
+ }
3267
+ identities.add(identity);
3268
+ if (typename === void 0 && identity === "") {
3269
+ let list = this.#untellable.get(type.name);
3270
+ if (list === void 0) {
3271
+ list = [];
3272
+ this.#untellable.set(type.name, list);
3273
+ }
3274
+ list.push({
3275
+ name: named?.name ?? target,
3276
+ site,
3277
+ ...named === void 0 ? {} : { location: named.location }
3278
+ });
3279
+ }
3280
+ }
3281
+ }
3282
+ #input(type, shape, position, site, depth = 0) {
3283
+ if (depth > MAX_DEPTH) return;
3284
+ const parts = this.#parts(shape);
3285
+ const { isNonNullType, isListType, isScalarType, isEnumType } = this.#predicates;
3286
+ let inner = type;
3287
+ if (isNonNullType(inner)) inner = inner.ofType;
3288
+ else {
3289
+ const acceptsNull = parts.hasNull || parts.unchecked;
3290
+ const acceptsUndefined = position.item !== true && (parts.hasUndefined || position.optional || parts.unchecked);
3291
+ if (position.item === true) {
3292
+ if (!acceptsNull) this.#mismatch(site, `${site.label} does not accept null, but the items of ${site.coordinate} (${site.list ?? String(type)}) may be null`, [`Accept null items ((T | null)[]), or make the items non-null in the SDL (${String(type)}!)`]);
3293
+ } else if (!position.sdlDefault && !acceptsUndefined) this.#mismatch(site, `${site.label} does not accept undefined, but ${site.coordinate} (${String(type)}) may be omitted`, ["Make it optional (?: or | undefined), or make the SDL argument non-null or give it a default value"]);
3294
+ else if (!acceptsNull && !acceptsUndefined) this.#mismatch(site, `${site.label} does not accept null, but ${site.coordinate} (${String(type)}) may be null`, [`Accept null (| null) or undefined, or make it non-null in the SDL (${String(type)}!)`]);
3295
+ if (position.vote !== void 0 && !parts.unchecked) {
3296
+ let vote = this.#votes.get(position.vote);
3297
+ if (vote === void 0) {
3298
+ vote = {
3299
+ accepts: 0,
3300
+ rejects: []
3301
+ };
3302
+ this.#votes.set(position.vote, vote);
3303
+ }
3304
+ if (acceptsNull) vote.accepts++;
3305
+ else if (acceptsUndefined) vote.rejects.push(site);
3306
+ }
3307
+ }
3308
+ if (parts.unchecked) return;
3309
+ if (parts.members.length === 0) {
3310
+ this.#mismatch(site, `${site.label} accepts no ${String(inner)} value (${site.coordinate})`, [`Type it with the TypeScript type of ${String(inner)}`]);
3311
+ return;
3312
+ }
3313
+ if (isListType(inner)) {
3314
+ const lists = parts.members.map((member) => this.#listItem(member, "input")).filter((item) => item !== void 0);
3315
+ if (lists.length === 0) {
3316
+ this.#mismatch(site, `${site.label} is ${this.#describeParts(parts, false)}, not an array, but ${site.coordinate} is ${String(type)}`, ["Accept an array (readonly T[]): graphql-js always passes lists as arrays"]);
3317
+ return;
3318
+ }
3319
+ for (const items of lists) for (const item of items) this.#input(inner.ofType, item, {
3320
+ optional: false,
3321
+ sdlDefault: true,
3322
+ item: true
3323
+ }, {
3324
+ ...site,
3325
+ label: `${site.label} (items)`,
3326
+ list: site.list ?? String(type)
3327
+ }, depth + 1);
3328
+ return;
3329
+ }
3330
+ if (isScalarType(inner)) {
3331
+ if (parts.members.some((member) => this.#scalarAccepts(inner.name, member, "input"))) return;
3332
+ this.#mismatch(site, `${site.label} is ${this.#describeParts(parts, false)}, which does not accept every ${inner.name} (${site.coordinate})`, [this.#scalarHint(inner.name)]);
3333
+ return;
3334
+ }
3335
+ if (isEnumType(inner)) {
3336
+ const accepted = /* @__PURE__ */ new Set();
3337
+ for (const member of parts.members) {
3338
+ if (member.kind === "string") return;
3339
+ for (const literal of this.#literals(member) ?? []) accepted.add(String(literal));
3340
+ }
3341
+ const missing = inner.getValues().filter((value) => !accepted.has(value.name));
3342
+ if (missing.length > 0) this.#mismatch(site, `${site.label} is ${this.#describeParts(parts, false)}, which does not accept ${missing.map((value) => value.name).join(", ")} of the enum ${inner.name} (${site.coordinate})`, [`Accept every value of ${inner.name} (graphql-codegen's enum type, or a union of every value)`]);
3343
+ return;
3344
+ }
3345
+ const objects = parts.members.filter((member) => this.#propertiesOf(member) !== void 0);
3346
+ if (objects.length === 0) {
3347
+ this.#mismatch(site, `${site.label} is ${this.#describeParts(parts, false)}, not an object type, but ${site.coordinate} is ${inner.name}`, [`Type it with the TypeScript type of the input ${inner.name}`]);
3348
+ return;
3349
+ }
3350
+ for (const object of objects) this.#inputObject(inner, object, site, depth);
3351
+ }
3352
+ /** An input object received as `shape`: each property is a field of the input, accepting its values. */
3353
+ #inputObject(type, shape, site, depth) {
3354
+ const properties = this.#propertiesOf(shape);
3355
+ if (properties === void 0 || properties === "record") return;
3356
+ if (!this.#visit(`input\0${type.name}`, shape)) return;
3357
+ const fields = type.getFields();
3358
+ for (const property of properties) {
3359
+ if (property.name === "__typename") continue;
3360
+ const field = fields[property.name];
3361
+ const propertySite = {
3362
+ label: this.#tsName(shape) === void 0 ? `property '${property.name}'` : `${this.#tsName(shape)}.${property.name}`,
3363
+ origin: site.origin,
3364
+ coordinate: `${type.name}.${property.name}`,
3365
+ locate: () => annotationLocation(this.#ctx.project, property.location) ?? site.locate(),
3366
+ sdl: this.#config.locate(field?.astNode ?? type.astNode),
3367
+ optional: false
3368
+ };
3369
+ if (field === void 0) {
3370
+ this.#mismatch({
3371
+ ...propertySite,
3372
+ locate: () => property.location ?? site.locate()
3373
+ }, `${propertySite.label} is not a field of the input ${type.name}: it is always undefined`, [`Remove it, or declare it in the SDL (input ${type.name} { ${property.name}: ... })`]);
3374
+ continue;
3375
+ }
3376
+ this.#input(field.type, property.shape, {
3377
+ optional: property.optional,
3378
+ sdlDefault: field.default !== void 0,
3379
+ vote: propertySite.coordinate
3380
+ }, propertySite, depth + 1);
3381
+ }
3382
+ }
3383
+ /** A member (not nullish, not a union) conforms to a scalar: output, or accepts every value: input. */
3384
+ #scalarAccepts(name, shape, position) {
3385
+ const custom = this.#config.scalars.get(name);
3386
+ if (custom !== void 0) return this.#hasReference(shape, custom.type);
3387
+ if ((name === "String" || name === "ID") && shape.kind === "reference" && this.#scalarTypes.has(shape.identity)) return true;
3388
+ const output = position === "output";
3389
+ switch (name) {
3390
+ case "String": return this.#isString(shape, output);
3391
+ case "ID": return this.#isString(shape, output) || output && this.#isNumber(shape, true);
3392
+ case "Int":
3393
+ case "Float": return this.#isNumber(shape, output);
3394
+ case "Boolean": return shape.kind === "boolean" || output && shape.kind === "literal" && typeof shape.value === "boolean";
3395
+ case "DateTime": return shape.kind === "date";
3396
+ case "BigInt": return shape.kind === "bigint" || output && this.#isNumber(shape, true);
3397
+ default: return true;
3398
+ }
3399
+ }
3400
+ #scalarHint(name) {
3401
+ const custom = this.#config.scalars.get(name);
3402
+ if (custom !== void 0) return `${name} is declared with scalar<${custom.type.slice(custom.type.indexOf("#") + 1)}>(): use that type (graphql-codegen: scalars: { ${name}: '...' })`;
3403
+ switch (name) {
3404
+ case "Int":
3405
+ case "Float": return "Use number (or the Int / Float aliases of @znight/graphql)";
3406
+ case "Boolean": return "Use boolean";
3407
+ case "DateTime": return "The built-in DateTime scalar is a Date";
3408
+ case "BigInt": return "The built-in BigInt scalar is a bigint";
3409
+ default: return "Use string (string literals, string enums and branded strings conform in output positions)";
3410
+ }
3411
+ }
3412
+ /** `string`, a branded string, the `ID` alias; output: also string literals and string enums. */
3413
+ #isString(shape, output) {
3414
+ switch (shape.kind) {
3415
+ case "string": return true;
3416
+ case "literal": return output && typeof shape.value === "string";
3417
+ case "reference": return shape.identity === GraphQLIds.ID;
3418
+ case "intersection": return shape.members.some((member) => member.kind === "string" || member.kind === "reference" && member.identity === GraphQLIds.ID);
3419
+ case "named": {
3420
+ const named = this.#named.get(shape.key);
3421
+ return output && named?.declaration === "enum" && (this.#literals(shape) ?? []).every((value) => typeof value === "string");
3422
+ }
3423
+ default: return false;
3424
+ }
3425
+ }
3426
+ /** `number`, a branded number, the `Int` / `Float` aliases; output: also number literals and numeric enums. */
3427
+ #isNumber(shape, output) {
3428
+ const alias = (candidate) => candidate.kind === "reference" && (candidate.identity === GraphQLIds.Int || candidate.identity === GraphQLIds.Float);
3429
+ switch (shape.kind) {
3430
+ case "number": return true;
3431
+ case "literal": return output && typeof shape.value === "number";
3432
+ case "reference": return alias(shape);
3433
+ case "intersection": return shape.members.some((member) => member.kind === "number" || alias(member));
3434
+ case "named": {
3435
+ const named = this.#named.get(shape.key);
3436
+ return output && named?.declaration === "enum" && (this.#literals(shape) ?? []).every((value) => typeof value === "number");
3437
+ }
3438
+ default: return false;
3439
+ }
3440
+ }
3441
+ #hasReference(shape, identity) {
3442
+ if (shape.kind === "reference") return shape.identity === identity;
3443
+ if (shape.kind === "intersection") return shape.members.some((member) => this.#hasReference(member, identity));
3444
+ return false;
3445
+ }
3446
+ #shape(target) {
3447
+ const result = this.#ctx.types.shape(target, this.#options);
3448
+ for (const named of result.named.values()) this.#named.set(named.key, named);
3449
+ return result;
3450
+ }
3451
+ /** The non-nullish members of a shape, through unions and aliases (object aliases stay named). */
3452
+ #parts(shape) {
3453
+ const parts = {
3454
+ members: [],
3455
+ hasNull: false,
3456
+ hasUndefined: false,
3457
+ unchecked: false
3458
+ };
3459
+ const visit = (current, depth) => {
3460
+ if (depth > MAX_DEPTH) return;
3461
+ switch (current.kind) {
3462
+ case "null":
3463
+ parts.hasNull = true;
3464
+ return;
3465
+ case "undefined":
3466
+ parts.hasUndefined = true;
3467
+ return;
3468
+ case "unknown":
3469
+ case "opaque":
3470
+ case "schema":
3471
+ parts.unchecked = true;
3472
+ return;
3473
+ case "never": return;
3474
+ case "union":
3475
+ for (const member of current.members) visit(member, depth + 1);
3476
+ return;
3477
+ case "named": {
3478
+ const named = this.#named.get(current.key);
3479
+ if (named === void 0) {
3480
+ parts.unchecked = true;
3481
+ return;
3482
+ }
3483
+ const target = named.shape;
3484
+ const isObject = target.kind === "object" || target.kind === "intersection" && this.#propertiesOf(target) !== void 0 || target.kind === "named" && this.#named.get(target.key)?.declaration !== "alias";
3485
+ if (named.declaration !== "alias" || isObject) parts.members.push(current);
3486
+ else visit(target, depth + 1);
3487
+ return;
3488
+ }
3489
+ default: parts.members.push(current);
3490
+ }
3491
+ };
3492
+ visit(shape, 0);
3493
+ return parts;
3494
+ }
3495
+ /** The item shapes of a list member (array, tuple, iterable); `undefined`: not a list. */
3496
+ #listItem(shape, position) {
3497
+ if (shape.kind === "array") return [shape.items];
3498
+ if (shape.kind === "tuple") return position === "output" ? [...shape.items, ...shape.rest === void 0 ? [] : [shape.rest]] : void 0;
3499
+ if (shape.kind === "reference" && position === "output" && OUTPUT_ITERABLES.has(shape.identity)) return [shape.typeArgs[0] ?? { kind: "unknown" }];
3500
+ }
3501
+ /** The literal values of a member: a literal, an enum; `undefined`: not literals. */
3502
+ #literals(shape) {
3503
+ if (shape.kind === "literal") return [shape.value];
3504
+ if (shape.kind !== "named") return void 0;
3505
+ const named = this.#named.get(shape.key);
3506
+ if (named?.declaration !== "enum") return void 0;
3507
+ if (named.enumMembers !== void 0) return named.enumMembers.map((member) => member.value);
3508
+ const target = named.shape;
3509
+ if (target.kind === "literal") return [target.value];
3510
+ if (target.kind === "union" && target.members.every((member) => member.kind === "literal")) return target.members.map((member) => member.value);
3511
+ }
3512
+ /** The properties of an object structure (intersections merged, later members win), `'record'` for index signatures. */
3513
+ #propertiesOf(shape, depth = 0) {
3514
+ if (depth > MAX_DEPTH) return void 0;
3515
+ switch (shape.kind) {
3516
+ case "object": return shape.additional === void 0 ? shape.properties : "record";
3517
+ case "named": {
3518
+ const named = this.#named.get(shape.key);
3519
+ return named === void 0 || named.declaration === "enum" ? void 0 : this.#propertiesOf(named.shape, depth + 1);
3520
+ }
3521
+ case "intersection": {
3522
+ const merged = /* @__PURE__ */ new Map();
3523
+ for (const member of shape.members) {
3524
+ const properties = this.#propertiesOf(member, depth + 1);
3525
+ if (properties === void 0 || properties === "record") return properties;
3526
+ for (const property of properties) {
3527
+ merged.delete(property.name);
3528
+ merged.set(property.name, property);
3529
+ }
3530
+ }
3531
+ return [...merged.values()];
3532
+ }
3533
+ default: return;
3534
+ }
3535
+ }
3536
+ /** `true` the first time `(namespace, shape)` is visited: named shapes by key, anonymous ones by identity. */
3537
+ #visit(namespace, shape) {
3538
+ if (shape.kind === "named") {
3539
+ const key = `${namespace}\0${shape.key}`;
3540
+ if (this.#visited.has(key)) return false;
3541
+ this.#visited.add(key);
3542
+ return true;
3543
+ }
3544
+ let seen = this.#anonymous.get(shape);
3545
+ if (seen === void 0) {
3546
+ seen = /* @__PURE__ */ new Set();
3547
+ this.#anonymous.set(shape, seen);
3548
+ }
3549
+ if (seen.has(namespace)) return false;
3550
+ seen.add(namespace);
3551
+ return true;
3552
+ }
3553
+ #propertySite(site, type, field, property, owner) {
3554
+ const name = this.#tsName(owner);
3555
+ return {
3556
+ label: `${name === void 0 ? `property '${property.name}'` : `${name}.${property.name}`} is`,
3557
+ origin: `type of ${name === void 0 ? `property '${property.name}'` : `${name}.${property.name}`}, ${site.origin}`,
3558
+ coordinate: `${type.name}.${field.name}`,
3559
+ locate: () => annotationLocation(this.#ctx.project, property.location) ?? site.locate(),
3560
+ sdl: this.#config.locate(field.astNode),
3561
+ optional: property.optional
3562
+ };
3563
+ }
3564
+ #tsName(shape) {
3565
+ return shape.kind === "named" ? this.#named.get(shape.key)?.name : void 0;
3566
+ }
3567
+ /**
3568
+ * `instanceof` when every TypeScript member returned for an abstract type is an exported class
3569
+ * (one per member), else `__typename`; members that are neither are ZNIGHT064.
3570
+ */
3571
+ #discriminators() {
3572
+ const result = /* @__PURE__ */ new Map();
3573
+ for (const [name, byMember] of [...this.#members].sort(([a], [b]) => compareStrings(a, b))) {
3574
+ const classes = [];
3575
+ for (const [member, identities] of byMember) for (const identity of identities) if (identity !== "") classes.push({
3576
+ identity,
3577
+ name: member,
3578
+ depth: this.#classDepth(identity)
3579
+ });
3580
+ if (classes.length > 0) {
3581
+ classes.sort((a, b) => b.depth - a.depth || compareStrings(a.name, b.name) || compareStrings(a.identity, b.identity));
3582
+ result.set(name, {
3583
+ by: "class",
3584
+ classes: classes.map(({ identity, name: member }) => ({
3585
+ identity,
3586
+ name: member
3587
+ }))
3588
+ });
3589
+ }
3590
+ for (const entry of this.#untellable.get(name) ?? []) {
3591
+ const location = entry.site.locate();
3592
+ this.#reportOnce(`untellable\0${name}\0${entry.name}`, {
3593
+ code: Codes.UnresolvableAbstractType,
3594
+ severity: "error",
3595
+ message: `${entry.name} cannot be told apart among the members of ${name} at runtime: it declares no __typename and is not an exported class`,
3596
+ ...location === void 0 ? {} : { location },
3597
+ hints: [`Return objects carrying __typename (declare __typename: '${entry.name}' in the TypeScript type; graphql-codegen declares it), or return exported class instances`],
3598
+ ...entry.location === void 0 ? {} : { related: [{
3599
+ message: `${entry.name} is declared here`,
3600
+ location: entry.location
3601
+ }] }
3602
+ });
3603
+ }
3604
+ }
3605
+ return result;
3606
+ }
3607
+ #classDepth(identity) {
3608
+ let depth = 0;
3609
+ let current = this.#ctx.classInfo(identity)?.superClass?.identity;
3610
+ const seen = /* @__PURE__ */ new Set();
3611
+ while (current !== void 0 && !seen.has(current) && depth < MAX_DEPTH) {
3612
+ seen.add(current);
3613
+ depth++;
3614
+ current = this.#ctx.classInfo(current)?.superClass?.identity;
3615
+ }
3616
+ return depth;
3617
+ }
3618
+ #report(diagnostic) {
3619
+ this.#config.report(diagnostic);
3620
+ }
3621
+ #reportOnce(key, diagnostic) {
3622
+ if (this.#reported.has(key)) return;
3623
+ this.#reported.add(key);
3624
+ this.#report(diagnostic);
3625
+ }
3626
+ #mismatch(site, message, hints) {
3627
+ const location = site.locate();
3628
+ const key = `${location?.file}:${location?.start}\0${message}`;
3629
+ this.#reportOnce(key, {
3630
+ code: Codes.SchemaMismatch,
3631
+ severity: "error",
3632
+ message,
3633
+ ...location === void 0 ? {} : { location },
3634
+ ...hints.length === 0 ? {} : { hints },
3635
+ ...site.sdl === void 0 ? {} : { related: [{
3636
+ message: `${site.coordinate} is declared here`,
3637
+ location: site.sdl
3638
+ }] }
3639
+ });
3640
+ }
3641
+ #describeParts(parts, optional) {
3642
+ const members = parts.members.map((member) => this.#describe(member));
3643
+ if (parts.hasNull) members.push("null");
3644
+ if (parts.hasUndefined || optional) members.push("undefined");
3645
+ return members.join(" | ");
3646
+ }
3647
+ /** Short description of a shape for messages (`string`, `Book`, `'a'`, `Book[]`). */
3648
+ #describe(shape) {
3649
+ switch (shape.kind) {
3650
+ case "literal": return JSON.stringify(shape.value);
3651
+ case "named": return this.#named.get(shape.key)?.name ?? shape.key.slice(shape.key.indexOf("#") + 1);
3652
+ case "reference": return shape.name;
3653
+ case "opaque": return shape.text;
3654
+ case "array": return `${this.#describe(shape.items)}[]`;
3655
+ case "union":
3656
+ case "intersection": return shape.members.map((member) => this.#describe(member)).join(shape.kind === "union" ? " | " : " & ");
3657
+ case "object": return "an object literal type";
3658
+ case "tuple": return "a tuple";
3659
+ default: return shape.kind;
3660
+ }
3661
+ }
3662
+ };
3663
+ // src/compiler/suggest.ts
3664
+ /**
3665
+ * "Did you mean" suggestions of the schema-first diagnostics (RFC 0007 §5.3). Import-free.
3666
+ */
3667
+ /**
3668
+ * The candidate closest to `name` (Levenshtein distance, case differences counting half), when it
3669
+ * is close enough to be a typo: at most a third of the name's length, at least 1. Ties keep the
3670
+ * first candidate, so callers pass them sorted.
3671
+ */
3672
+ function closest(name, candidates) {
3673
+ const limit = Math.max(1, Math.floor(name.length / 3));
3674
+ let best;
3675
+ let bestDistance = Number.POSITIVE_INFINITY;
3676
+ for (const candidate of candidates) {
3677
+ if (candidate === name) continue;
3678
+ const distance = candidate.toLowerCase() === name.toLowerCase() ? .5 : levenshtein(name, candidate);
3679
+ if (distance <= limit && distance < bestDistance) {
3680
+ best = candidate;
3681
+ bestDistance = distance;
3682
+ }
3683
+ }
3684
+ return best;
3685
+ }
3686
+ function levenshtein(a, b) {
3687
+ if (Math.abs(a.length - b.length) > Math.max(a.length, b.length) / 2) return Number.POSITIVE_INFINITY;
3688
+ let previous = Array.from({ length: b.length + 1 }, (_, index) => index);
3689
+ for (let i = 1; i <= a.length; i++) {
3690
+ const current = [i];
3691
+ for (let j = 1; j <= b.length; j++) {
3692
+ const cost = a.charCodeAt(i - 1) === b.charCodeAt(j - 1) ? 0 : 1;
3693
+ current.push(Math.min(previous[j] + 1, current[j - 1] + 1, previous[j - 1] + cost));
3694
+ }
3695
+ previous = current;
3696
+ }
3697
+ return previous[b.length];
3698
+ }
3699
+ // src/compiler/sdl-source.ts
3700
+ /** Directives graphql-js knows (specified, or `@defer`/`@stream` of graphql 17). */
3701
+ const KNOWN_DIRECTIVES = /* @__PURE__ */ new Set([
3702
+ "deprecated",
3703
+ "specifiedBy",
3704
+ "oneOf",
3705
+ "include",
3706
+ "skip",
3707
+ "defer",
3708
+ "stream"
3709
+ ]);
3710
+ /** The built-in directives allowed in an SDL (the others are executable directives). */
3711
+ const SDL_DIRECTIVES = /* @__PURE__ */ new Map([
3712
+ ["deprecated", {
3713
+ locations: /* @__PURE__ */ new Set([
3714
+ "FIELD_DEFINITION",
3715
+ "ARGUMENT_DEFINITION",
3716
+ "INPUT_FIELD_DEFINITION",
3717
+ "ENUM_VALUE"
3718
+ ]),
3719
+ args: /* @__PURE__ */ new Map([["reason", false]]),
3720
+ repeatable: false
3721
+ }],
3722
+ ["specifiedBy", {
3723
+ locations: /* @__PURE__ */ new Set(["SCALAR"]),
3724
+ args: /* @__PURE__ */ new Map([["url", true]]),
3725
+ repeatable: false
3726
+ }],
3727
+ ["oneOf", {
3728
+ locations: /* @__PURE__ */ new Set(["INPUT_OBJECT"]),
3729
+ args: /* @__PURE__ */ new Map(),
3730
+ repeatable: false
3731
+ }]
3732
+ ]);
3733
+ /** The directive location of each kind of type definition and extension. */
3734
+ const TYPE_LOCATIONS = {
3735
+ type: "OBJECT",
3736
+ interface: "INTERFACE",
3737
+ union: "UNION",
3738
+ enum: "ENUM",
3739
+ input: "INPUT_OBJECT",
3740
+ scalar: "SCALAR"
3741
+ };
3742
+ /** Names of the types graphql-js always provides. */
3743
+ const PROVIDED_TYPES = /* @__PURE__ */ new Set([
3744
+ "String",
3745
+ "Int",
3746
+ "Float",
3747
+ "Boolean",
3748
+ "ID"
3749
+ ]);
3750
+ /** Directive locations in messages. */
3751
+ const LOCATION_NAMES = {
3752
+ SCHEMA: "the schema",
3753
+ SCALAR: "scalars",
3754
+ OBJECT: "object types",
3755
+ FIELD_DEFINITION: "fields",
3756
+ ARGUMENT_DEFINITION: "arguments",
3757
+ INTERFACE: "interfaces",
3758
+ UNION: "unions",
3759
+ ENUM: "enums",
3760
+ ENUM_VALUE: "enum values",
3761
+ INPUT_OBJECT: "input objects",
3762
+ INPUT_FIELD_DEFINITION: "input fields"
3763
+ };
3764
+ const TYPE_KINDS = {
3765
+ ObjectTypeDefinition: "type",
3766
+ ObjectTypeExtension: "type",
3767
+ InterfaceTypeDefinition: "interface",
3768
+ InterfaceTypeExtension: "interface",
3769
+ UnionTypeDefinition: "union",
3770
+ UnionTypeExtension: "union",
3771
+ EnumTypeDefinition: "enum",
3772
+ EnumTypeExtension: "enum",
3773
+ InputObjectTypeDefinition: "input",
3774
+ InputObjectTypeExtension: "input",
3775
+ ScalarTypeDefinition: "scalar",
3776
+ ScalarTypeExtension: "scalar"
3777
+ };
3778
+ /**
3779
+ * Reads, parses and validates the SDL; `undefined` after an error (reported). Warnings (custom
3780
+ * directives) do not stop the build.
3781
+ */
3782
+ function loadSdl(ctx, patterns, report) {
3783
+ const files = ctx.inputFiles(patterns);
3784
+ if (files.length === 0) {
3785
+ report({
3786
+ code: Codes.InvalidSdl,
3787
+ severity: "error",
3788
+ message: `No GraphQL schema file matches ${patterns.map((pattern) => `'${pattern}'`).join(", ")} (schema-first)`,
3789
+ hints: ["The globs of graphql.configure({ schema }) are relative to the project root: 'src/**/*.graphql'", "Remove the schema option to derive the schema from the TypeScript types (code-first)"]
3790
+ });
3791
+ return;
3792
+ }
3793
+ const { language, type, utilities } = graphqlModules();
3794
+ const locator = new Locator(new Map(files.map((file) => [file.file, file.text])));
3795
+ let errors = 0;
3796
+ const fail = (message, node, hints = [], related) => {
3797
+ errors++;
3798
+ const location = locator.locate(node);
3799
+ const other = related === void 0 ? void 0 : locator.locate(related);
3800
+ report({
3801
+ code: Codes.InvalidSdl,
3802
+ severity: "error",
3803
+ message,
3804
+ ...location === void 0 ? {} : { location },
3805
+ ...hints.length === 0 ? {} : { hints },
3806
+ ...other === void 0 ? {} : { related: [{
3807
+ message: "first declared here",
3808
+ location: other
3809
+ }] }
3810
+ });
3811
+ };
3812
+ const documents = [];
3813
+ for (const file of files) {
3814
+ if (/^(?:\s|,|#[^\n\r]*)*$/.test(file.text.replace(/^\uFEFF/, ""))) continue;
3815
+ try {
3816
+ documents.push(language.parse(new language.Source(file.text, file.file)));
3817
+ } catch (error) {
3818
+ const position = error.positions?.[0];
3819
+ errors++;
3820
+ const location = position === void 0 ? void 0 : locator.at(file.file, position, position + 1);
3821
+ report({
3822
+ code: Codes.InvalidSdl,
3823
+ severity: "error",
3824
+ message: `${error.message} (${file.relativeFile})`,
3825
+ ...location === void 0 ? {} : { location }
3826
+ });
3827
+ }
3828
+ }
3829
+ if (errors > 0) return void 0;
3830
+ if (documents.length === 0) {
3831
+ report({
3832
+ code: Codes.InvalidSdl,
3833
+ severity: "error",
3834
+ message: `The GraphQL schema files matching ${patterns.map((pattern) => `'${pattern}'`).join(", ")} define nothing (schema-first)`,
3835
+ hints: ["Declare the types of the schema, starting with type Query { ... }"]
3836
+ });
3837
+ return;
3838
+ }
3839
+ const document = utilities.concatAST(documents);
3840
+ const customDirectives = checkStructure(document, fail, (message, node) => {
3841
+ const location = locator.locate(node);
3842
+ report({
3843
+ code: Codes.InvalidSdl,
3844
+ severity: "warning",
3845
+ message,
3846
+ ...location === void 0 ? {} : { location },
3847
+ hints: ["Znight serves the schema without custom directives (and so does the SDL artifact): remove it, or keep it for other tools"]
3848
+ });
3849
+ });
3850
+ if (errors > 0) return void 0;
3851
+ let schema;
3852
+ try {
3853
+ schema = utilities.buildASTSchema(document, { assumeValidSDL: true });
3854
+ } catch (error) {
3855
+ fail(`Invalid schema: ${error.message}`, error.nodes?.[0]);
3856
+ return;
3857
+ }
3858
+ for (const problem of type.validateSchema(schema)) {
3859
+ const [node, ...others] = (problem.nodes ?? []).filter((candidate) => candidate.loc !== void 0);
3860
+ errors++;
3861
+ const location = locator.locate(node);
3862
+ const related = others.flatMap((other) => {
3863
+ const at = locator.locate(other);
3864
+ return at === void 0 ? [] : [{
3865
+ message: "see also",
3866
+ location: at
3867
+ }];
3868
+ });
3869
+ report({
3870
+ code: Codes.InvalidSdl,
3871
+ severity: "error",
3872
+ message: `Invalid schema: ${problem.message}`,
3873
+ ...location === void 0 ? {} : { location },
3874
+ ...related.length === 0 ? {} : { related },
3875
+ ...node === void 0 && schema.getQueryType() == null ? { hints: ["Declare the query root type in one of the files: type Query { ... }"] } : {}
3876
+ });
3877
+ }
3878
+ if (errors > 0) return void 0;
3879
+ return {
3880
+ schema,
3881
+ customDirectives,
3882
+ locate: (node) => locator.locate(node)
3883
+ };
3884
+ }
3885
+ /**
3886
+ * The checks graphql-js leaves to its SDL validation (not exported): unique type, field, value and
3887
+ * argument names, known types and directives, extensions of existing types of the same kind, one
3888
+ * schema definition. Returns the names of the custom directives (each reported as a warning).
3889
+ */
3890
+ function checkStructure(document, fail, warn) {
3891
+ const definitions = /* @__PURE__ */ new Map();
3892
+ const directives = /* @__PURE__ */ new Map();
3893
+ const specs = new Map(SDL_DIRECTIVES);
3894
+ let schemaDefinition;
3895
+ for (const definition of document.definitions) {
3896
+ if (definition.kind === "SchemaDefinition") {
3897
+ if (schemaDefinition === void 0) schemaDefinition = definition;
3898
+ else fail("The schema is defined twice (schema { ... })", definition, ["Keep one schema definition"], schemaDefinition);
3899
+ continue;
3900
+ }
3901
+ if (definition.kind === "DirectiveDefinition") {
3902
+ const name = definition.name.value;
3903
+ const previous = directives.get(name);
3904
+ if (previous !== void 0) fail(`The directive @${name} is defined twice`, definition.name, [], previous);
3905
+ else if (KNOWN_DIRECTIVES.has(name)) fail(`@${name} is a built-in directive: it cannot be redefined`, definition.name);
3906
+ else {
3907
+ directives.set(name, definition.name);
3908
+ specs.set(name, {
3909
+ locations: new Set(definition.locations.map((location) => location.value)),
3910
+ args: new Map((definition.arguments ?? []).map((arg) => [arg.name.value, arg.type.kind === "NonNullType" && arg.defaultValue === void 0])),
3911
+ repeatable: definition.repeatable
3912
+ });
3913
+ warn(`The custom directive @${name} is ignored`, definition.name);
3914
+ }
3915
+ continue;
3916
+ }
3917
+ const kind = TYPE_KINDS[definition.kind];
3918
+ if (kind === void 0 || definition.kind.endsWith("Extension")) continue;
3919
+ const node = definition;
3920
+ const previous = definitions.get(node.name.value);
3921
+ if (previous !== void 0) {
3922
+ fail(`The type ${node.name.value} is defined twice`, node.name, [kind === "scalar" ? "Keep one of them, or rename one" : `Extend it instead (${declaration(`extend ${kind}`, kind, node.name.value)}), or rename one of them`], previous.name);
3923
+ continue;
3924
+ }
3925
+ definitions.set(node.name.value, node);
3926
+ }
3927
+ const typeNames = [...definitions.keys(), ...PROVIDED_TYPES].sort(compareStrings);
3928
+ const known = (name, where) => {
3929
+ const value = name.name.value;
3930
+ if (definitions.has(value) || PROVIDED_TYPES.has(value)) return;
3931
+ const suggestion = closest(value, typeNames);
3932
+ fail(`Unknown type ${value} (${where})`, name, suggestion === void 0 ? ["Define it, or fix the name"] : [`Did you mean ${suggestion}?`]);
3933
+ };
3934
+ /** The directives applied at `location`: known, allowed there, once unless repeatable, valid arguments. */
3935
+ const directivesOf = (nodes, location) => {
3936
+ const seen = /* @__PURE__ */ new Map();
3937
+ for (const directive of nodes ?? []) {
3938
+ const name = directive.name.value;
3939
+ const spec = specs.get(name);
3940
+ if (spec === void 0) {
3941
+ fail(KNOWN_DIRECTIVES.has(name) ? `@${name} is an executable directive: it cannot be used in a schema` : `Unknown directive @${name}`, directive.name, KNOWN_DIRECTIVES.has(name) ? ["Remove it"] : [`Define it (directive @${name} on ${location}), or remove it`]);
3942
+ continue;
3943
+ }
3944
+ if (!spec.locations.has(location)) fail(`@${name} cannot be used on ${LOCATION_NAMES[location] ?? location}`, directive.name, [`@${name} applies to ${[...spec.locations].map((where) => LOCATION_NAMES[where] ?? where).join(", ")}`]);
3945
+ const previous = seen.get(name);
3946
+ if (previous !== void 0 && !spec.repeatable) fail(`@${name} is applied twice`, directive.name, ["Apply it once"], previous);
3947
+ seen.set(name, directive.name);
3948
+ const given = /* @__PURE__ */ new Set();
3949
+ for (const arg of directive.arguments ?? []) {
3950
+ given.add(arg.name.value);
3951
+ if (!spec.args.has(arg.name.value)) {
3952
+ const names = [...spec.args.keys()];
3953
+ fail(`@${name} has no argument '${arg.name.value}'`, arg.name, [names.length === 0 ? `@${name} takes no argument` : `Its arguments: ${names.join(", ")}`]);
3954
+ }
3955
+ }
3956
+ for (const [arg, required] of spec.args) if (required && !given.has(arg)) fail(`@${name} needs its argument '${arg}'`, directive.name, [`@${name}(${arg}: ...)`]);
3957
+ }
3958
+ };
3959
+ /** Members of a type: its definition and extensions, by name (fields, values, input fields). */
3960
+ const members = /* @__PURE__ */ new Map();
3961
+ const member = (typeName, kind, name) => {
3962
+ let seen = members.get(typeName);
3963
+ if (seen === void 0) {
3964
+ seen = /* @__PURE__ */ new Map();
3965
+ members.set(typeName, seen);
3966
+ }
3967
+ const previous = seen.get(name.value);
3968
+ if (previous !== void 0) {
3969
+ fail(`${typeName}.${name.value} is declared twice`, name, [`Keep one ${kind} named ${name.value}`], previous);
3970
+ return;
3971
+ }
3972
+ seen.set(name.value, name);
3973
+ };
3974
+ const inputValues = (owner, values) => {
3975
+ const seen = /* @__PURE__ */ new Map();
3976
+ for (const value of values ?? []) {
3977
+ const previous = seen.get(value.name.value);
3978
+ if (previous !== void 0) fail(`Argument ${owner}(${value.name.value}:) is declared twice`, value.name, [], previous);
3979
+ seen.set(value.name.value, value.name);
3980
+ known(namedType(value.type), `${owner}(${value.name.value}:)`);
3981
+ directivesOf(value.directives, "ARGUMENT_DEFINITION");
3982
+ }
3983
+ };
3984
+ const fields = (owner, list) => {
3985
+ for (const field of list ?? []) {
3986
+ member(owner, "field", field.name);
3987
+ known(namedType(field.type), `${owner}.${field.name.value}`);
3988
+ inputValues(`${owner}.${field.name.value}`, field.arguments);
3989
+ directivesOf(field.directives, "FIELD_DEFINITION");
3990
+ }
3991
+ };
3992
+ const operations = /* @__PURE__ */ new Map();
3993
+ for (const definition of document.definitions) {
3994
+ if (definition.kind === "SchemaDefinition" || definition.kind === "SchemaExtension") {
3995
+ for (const operation of definition.operationTypes ?? []) {
3996
+ known(operation.type, `schema ${operation.operation}`);
3997
+ const previous = operations.get(operation.operation);
3998
+ if (previous !== void 0) fail(`The ${operation.operation} root type is declared twice`, operation, ["Keep one"], previous);
3999
+ operations.set(operation.operation, operation);
4000
+ }
4001
+ directivesOf(definition.directives, "SCHEMA");
4002
+ continue;
4003
+ }
4004
+ if (definition.kind === "DirectiveDefinition") {
4005
+ inputValues(`@${definition.name.value}`, definition.arguments);
4006
+ continue;
4007
+ }
4008
+ const kind = TYPE_KINDS[definition.kind];
4009
+ if (kind === void 0) continue;
4010
+ const node = definition;
4011
+ const name = node.name.value;
4012
+ if (definition.kind.endsWith("Extension")) {
4013
+ const base = definitions.get(name);
4014
+ if (base === void 0) {
4015
+ if (!PROVIDED_TYPES.has(name)) {
4016
+ const suggestion = closest(name, typeNames);
4017
+ fail(`Cannot extend ${kind} ${name}: it is not defined`, node.name, [suggestion === void 0 ? `Define it: ${declaration(kind, kind, name)}` : `Did you mean ${suggestion}?`]);
4018
+ }
4019
+ continue;
4020
+ }
4021
+ if (TYPE_KINDS[base.kind] !== kind) {
4022
+ const actual = TYPE_KINDS[base.kind];
4023
+ fail(`Cannot extend ${name} as ${article(kind)} ${kind}: it is ${article(actual)} ${actual}`, node.name, [`Use ${declaration(`extend ${actual}`, actual, name)}`], base.name);
4024
+ continue;
4025
+ }
4026
+ }
4027
+ directivesOf(node.directives, TYPE_LOCATIONS[kind]);
4028
+ for (const implemented of node.interfaces ?? []) known(implemented, `${name} implements`);
4029
+ for (const target of node.types ?? []) known(target, `union ${name}`);
4030
+ for (const value of node.values ?? []) {
4031
+ member(name, "value", value.name);
4032
+ directivesOf(value.directives, "ENUM_VALUE");
4033
+ }
4034
+ if (kind === "input") for (const field of node.fields ?? []) {
4035
+ member(name, "field", field.name);
4036
+ known(namedType(field.type), `${name}.${field.name.value}`);
4037
+ directivesOf(field.directives, "INPUT_FIELD_DEFINITION");
4038
+ }
4039
+ else fields(name, node.fields);
4040
+ }
4041
+ return new Set(directives.keys());
4042
+ }
4043
+ /** `extend type Book { ... }`, `union Hit = ...`, `scalar Url`: valid SDL for a hint. */
4044
+ function declaration(keyword, kind, name) {
4045
+ if (kind === "scalar") return `${keyword} ${name}`;
4046
+ if (kind === "union") return `${keyword} ${name} = ...`;
4047
+ return `${keyword} ${name} { ... }`;
4048
+ }
4049
+ function article(word) {
4050
+ return /^[aeiou]/.test(word) ? "an" : "a";
4051
+ }
4052
+ function namedType(type) {
4053
+ let current = type;
4054
+ while (current.kind !== "NamedType") current = current.type;
4055
+ return current;
4056
+ }
4057
+ /** Source locations of AST nodes (graphql-js `Source.name` is the absolute path of the file). */
4058
+ var Locator = class {
4059
+ #texts;
4060
+ #lines = /* @__PURE__ */ new Map();
4061
+ constructor(texts) {
4062
+ this.#texts = texts;
4063
+ }
4064
+ locate(node) {
4065
+ if (node === null || node === void 0) return void 0;
4066
+ const loc = ("name" in node && node.name !== void 0 && typeof node.name === "object" ? node.name : node).loc ?? node.loc;
4067
+ if (loc === void 0) return void 0;
4068
+ return this.at(loc.source.name, loc.start, loc.end);
4069
+ }
4070
+ at(file, start, end) {
4071
+ const text = this.#texts.get(file);
4072
+ if (text === void 0) return void 0;
4073
+ const lines = this.#lineStarts(file, text);
4074
+ const first = position(lines, start);
4075
+ const last = position(lines, Math.min(end, text.length));
4076
+ return {
4077
+ file,
4078
+ line: first.line,
4079
+ column: first.column,
4080
+ endLine: last.line,
4081
+ endColumn: last.column,
4082
+ start,
4083
+ end
4084
+ };
4085
+ }
4086
+ #lineStarts(file, text) {
4087
+ let lines = this.#lines.get(file);
4088
+ if (lines === void 0) {
4089
+ lines = [0];
4090
+ for (let index = 0; index < text.length; index++) {
4091
+ const code = text.charCodeAt(index);
4092
+ if (code === 10) lines.push(index + 1);
4093
+ else if (code === 13) {
4094
+ if (text.charCodeAt(index + 1) === 10) index++;
4095
+ lines.push(index + 1);
4096
+ }
4097
+ }
4098
+ this.#lines.set(file, lines);
4099
+ }
4100
+ return lines;
4101
+ }
4102
+ };
4103
+ /** 1-based line and column (UTF-16 code units) of an offset. */
4104
+ function position(lines, offset) {
4105
+ let low = 0;
4106
+ let high = lines.length - 1;
4107
+ while (low < high) {
4108
+ const middle = low + high + 1 >> 1;
4109
+ if (lines[middle] <= offset) low = middle;
4110
+ else high = middle - 1;
4111
+ }
4112
+ return {
4113
+ line: low + 1,
4114
+ column: offset - lines[low] + 1
4115
+ };
4116
+ }
4117
+ // src/compiler/schema-first.ts
4118
+ const DECORATORS = {
4119
+ query: "@Query",
4120
+ mutation: "@Mutation",
4121
+ subscription: "@Subscription",
4122
+ field: "@ResolveField"
4123
+ };
4124
+ /**
4125
+ * Builds the model from the SDL files matching `patterns`; `undefined` after an error. `artifact`:
4126
+ * the path of the SDL artifact (option `sdl`), never read back as an input.
4127
+ */
4128
+ function buildSchemaFirstModel(ctx, patterns, artifact) {
4129
+ return new SchemaFirstBuilder(ctx).build(artifact === false ? patterns : [...patterns, `!${artifact}`]);
4130
+ }
4131
+ var SchemaFirstBuilder = class {
4132
+ #ctx;
4133
+ #errors = 0;
4134
+ #methods = /* @__PURE__ */ new Map();
4135
+ constructor(ctx) {
4136
+ this.#ctx = ctx;
4137
+ }
4138
+ build(patterns) {
4139
+ const report = (diagnostic) => this.#report(diagnostic);
4140
+ const stored = [...this.#ctx.stored].sort((a, b) => compareStrings(a.host.identity, b.host.identity));
4141
+ const resolvers = stored.filter((resolver) => resolver.invalid !== true);
4142
+ const rejected = stored.filter((resolver) => resolver.invalid === true);
4143
+ checkLoaders(this.#ctx, resolvers, report);
4144
+ const scalarDeclarations = readScalars(this.#ctx, report);
4145
+ const sdl = loadSdl(this.#ctx, patterns, report);
4146
+ if (sdl === void 0) {
4147
+ checkDecisions(this.#ctx, resolvers, report);
4148
+ return;
4149
+ }
4150
+ const schema = sdl.schema;
4151
+ const roots = {
4152
+ query: schema.getQueryType().name,
4153
+ ...schema.getMutationType() ? { mutation: schema.getMutationType().name } : {},
4154
+ ...schema.getSubscriptionType() ? { subscription: schema.getSubscriptionType().name } : {}
4155
+ };
4156
+ checkDecisions(this.#ctx, resolvers, report, roots);
4157
+ const types = Object.values(schema.getTypeMap()).filter((type) => !type.name.startsWith("__") && !SPECIFIED_SCALARS.has(type.name)).sort((a, b) => compareStrings(a.name, b.name));
4158
+ this.#checkUnsupported(sdl, types);
4159
+ const scalars = this.#scalars(sdl, types, scalarDeclarations);
4160
+ const { bindings, claimed: bound } = this.#bind(sdl, resolvers, rejected);
4161
+ const conformance = checkConformance({
4162
+ ctx: this.#ctx,
4163
+ schema,
4164
+ locate: (node) => sdl.locate(node),
4165
+ scalars: new Map([...scalarDeclarations.values()].map((declaration) => [declaration.name, declaration])),
4166
+ bindings,
4167
+ bound,
4168
+ report
4169
+ });
4170
+ if (this.#errors > 0) return void 0;
4171
+ const byCoordinate = new Map(bindings.map((binding) => [`${binding.type.name}.${binding.sdlField.name}`, binding]));
4172
+ const models = [];
4173
+ for (const type of types) {
4174
+ const model = this.#model(sdl, type, byCoordinate, scalars, conformance);
4175
+ if (model !== void 0) models.push(model);
4176
+ }
4177
+ const meta = resolvers.flatMap((resolver) => resolver.fields.flatMap((field) => field.meta));
4178
+ const printed = printSdl({
4179
+ roots,
4180
+ types: models.map(sdlType)
4181
+ });
4182
+ return {
4183
+ roots,
4184
+ types: models,
4185
+ meta,
4186
+ sdl: printed,
4187
+ schemaHash: schemaHash(printed)
4188
+ };
4189
+ }
4190
+ #report(diagnostic) {
4191
+ if (diagnostic.severity === "error") this.#errors++;
4192
+ this.#ctx.report(diagnostic);
4193
+ }
4194
+ #mismatch(location, message, hints, related) {
4195
+ this.#report({
4196
+ code: Codes.SchemaMismatch,
4197
+ severity: "error",
4198
+ message,
4199
+ ...location === void 0 ? {} : { location },
4200
+ ...hints.length === 0 ? {} : { hints },
4201
+ ...related === void 0 || related.length === 0 ? {} : { related }
4202
+ });
4203
+ }
4204
+ /** `@oneOf` input objects (the runtime does not model them). */
4205
+ #checkUnsupported(sdl, types) {
4206
+ const { isInputObjectType } = graphqlModules().type;
4207
+ for (const type of types) {
4208
+ if (!isInputObjectType(type) || !type.isOneOf) continue;
4209
+ const location = sdl.locate(type.astNode);
4210
+ this.#report({
4211
+ code: Codes.InvalidSdl,
4212
+ severity: "error",
4213
+ message: `@oneOf input objects are not supported yet (${type.name})`,
4214
+ ...location === void 0 ? {} : { location },
4215
+ hints: ["Declare one optional field per case and check that exactly one is set in the resolver"]
4216
+ });
4217
+ }
4218
+ }
4219
+ /** The scalar models: `scalar<T>()` declarations by name, the built-in scalars, else ZNIGHT067. */
4220
+ #scalars(sdl, types, declarations) {
4221
+ const { isScalarType } = graphqlModules().type;
4222
+ const byName = new Map([...declarations.values()].map((declaration) => [declaration.name, declaration]));
4223
+ const models = /* @__PURE__ */ new Map();
4224
+ for (const type of types) {
4225
+ if (!isScalarType(type)) continue;
4226
+ const location = sdl.locate(type.astNode);
4227
+ const declaration = byName.get(type.name);
4228
+ const description = type.description ?? void 0;
4229
+ const specifiedByURL = type.specifiedByURL ?? void 0;
4230
+ if (declaration !== void 0) {
4231
+ models.set(type.name, {
4232
+ kind: "scalar",
4233
+ name: type.name,
4234
+ key: `#scalar:${declaration.identity}`,
4235
+ description,
4236
+ location,
4237
+ definition: declaration.identity,
4238
+ specifiedByURL,
4239
+ fromSdl: true
4240
+ });
4241
+ continue;
4242
+ }
4243
+ const builtin = BUILTIN_SCALARS.get(type.name);
4244
+ if (builtin !== void 0) {
4245
+ if (description !== void 0 || specifiedByURL !== void 0 && specifiedByURL !== builtin.specifiedByURL) this.#report({
4246
+ code: Codes.InvalidSdl,
4247
+ severity: "warning",
4248
+ message: `scalar ${type.name} is the built-in scalar of @znight/graphql: its description and @specifiedBy in the SDL are ignored`,
4249
+ ...location === void 0 ? {} : { location },
4250
+ hints: [`Remove them, or implement the scalar yourself: export const ${type.name}Scalar = scalar<T>('${type.name}', { ... })`]
4251
+ });
4252
+ models.set(type.name, {
4253
+ kind: "scalar",
4254
+ name: type.name,
4255
+ key: `#builtin:${type.name}`,
4256
+ description: void 0,
4257
+ location,
4258
+ definition: void 0,
4259
+ specifiedByURL: void 0
4260
+ });
4261
+ continue;
4262
+ }
4263
+ this.#report({
4264
+ code: Codes.InvalidSdl,
4265
+ severity: "error",
4266
+ message: `scalar ${type.name} has no implementation: declare it with scalar<T>('${type.name}', ...)`,
4267
+ ...location === void 0 ? {} : { location },
4268
+ hints: [`export const ${type.name}Scalar = scalar<${type.name}>('${type.name}', { serialize: value => value as ${type.name}, parseValue: value => value as ${type.name} }), with ${type.name} the TypeScript type of its values (the one graphql-codegen maps it to)`]
4269
+ });
4270
+ }
4271
+ return models;
4272
+ }
4273
+ /**
4274
+ * Binds every resolver method to its SDL field (coverage, RFC 0007 §5.3). `claimed`: the
4275
+ * coordinates a resolver method names, bound or not (a method with an analysis error already
4276
+ * reported), so that their fields are never also reported as missing a resolver.
4277
+ */
4278
+ #bind(sdl, resolvers, rejected) {
4279
+ const schema = sdl.schema;
4280
+ const bindings = [];
4281
+ const claimed = /* @__PURE__ */ new Set();
4282
+ for (const resolver of rejected) for (const field of resolver.fields) {
4283
+ const owner = field.operation === "field" ? resolver.typeName?.name : rootType(schema, field.operation)?.name;
4284
+ if (owner !== void 0) claimed.add(`${owner}.${field.name}`);
4285
+ }
4286
+ const byCoordinate = /* @__PURE__ */ new Map();
4287
+ for (const resolver of resolvers) {
4288
+ const target = this.#resolverType(sdl, resolver);
4289
+ for (const field of resolver.fields) {
4290
+ const owner = field.operation === "field" ? resolver.typeName?.name : rootType(schema, field.operation)?.name;
4291
+ if (owner !== void 0) claimed.add(`${owner}.${field.name}`);
4292
+ if (!field.complete) continue;
4293
+ const method = this.#method(resolver, field);
4294
+ if (method === void 0) continue;
4295
+ const where = `${resolver.host.displayName}.${field.method}`;
4296
+ let type;
4297
+ if (field.operation === "field") {
4298
+ type = target;
4299
+ if (type === void 0) continue;
4300
+ } else {
4301
+ type = rootType(schema, field.operation);
4302
+ if (type === void 0) {
4303
+ const name = ROOTS[field.operation];
4304
+ this.#mismatch(field.location, `${DECORATORS[field.operation]} ${where}: the schema has no ${name} type`, [`Declare it in the SDL: type ${name} { ${field.name}: ... }`]);
4305
+ continue;
4306
+ }
4307
+ }
4308
+ const sdlField = type.getFields()[field.name];
4309
+ if (sdlField === void 0) {
4310
+ this.#unknownField(schema, type, field, where);
4311
+ continue;
4312
+ }
4313
+ const coordinate = `${type.name}.${field.name}`;
4314
+ const previous = byCoordinate.get(coordinate);
4315
+ if (previous !== void 0) {
4316
+ this.#report({
4317
+ code: Codes.NameConflict,
4318
+ severity: "error",
4319
+ message: `Two resolvers bind ${coordinate}: ${where} and ${previous.resolver.host.displayName}.${previous.field.method}`,
4320
+ location: field.location,
4321
+ related: [{
4322
+ message: "the other resolver",
4323
+ location: previous.field.location
4324
+ }],
4325
+ hints: ["Keep one of them"]
4326
+ });
4327
+ continue;
4328
+ }
4329
+ this.#checkArguments(sdl, field, method, sdlField, coordinate, where);
4330
+ const binding = {
4331
+ resolver,
4332
+ field,
4333
+ method,
4334
+ type,
4335
+ sdlField
4336
+ };
4337
+ byCoordinate.set(coordinate, binding);
4338
+ bindings.push(binding);
4339
+ }
4340
+ }
4341
+ for (const operation of [
4342
+ "query",
4343
+ "mutation",
4344
+ "subscription"
4345
+ ]) {
4346
+ const root = rootType(schema, operation);
4347
+ if (root === void 0) continue;
4348
+ for (const field of Object.values(root.getFields())) {
4349
+ if (claimed.has(`${root.name}.${field.name}`)) continue;
4350
+ this.#mismatch(sdl.locate(field.astNode), `Root field ${root.name}.${field.name} has no resolver`, [`Add a method to a @Resolver() class: ${DECORATORS[operation]}() ${field.name}(...) (or ${DECORATORS[operation]}('${field.name}') on another method)`]);
4351
+ }
4352
+ }
4353
+ return {
4354
+ bindings,
4355
+ claimed
4356
+ };
4357
+ }
4358
+ /** The object type of `@Resolver('Type')`, reported when it is not one. */
4359
+ #resolverType(sdl, resolver) {
4360
+ const typeName = resolver.typeName;
4361
+ if (typeName === void 0) return void 0;
4362
+ const { isObjectType, isInterfaceType } = graphqlModules().type;
4363
+ const schema = sdl.schema;
4364
+ const type = schema.getType(typeName.name);
4365
+ const owner = resolver.host.displayName;
4366
+ if (type === void 0) {
4367
+ const objects = Object.values(schema.getTypeMap()).filter((candidate) => isObjectType(candidate) && !candidate.name.startsWith("__")).map((candidate) => candidate.name).sort(compareStrings);
4368
+ const suggestion = closest(typeName.name, objects);
4369
+ this.#mismatch(typeName.location, `@Resolver('${typeName.name}') of ${owner}: the schema has no type ${typeName.name}`, [suggestion === void 0 ? `Declare it in the SDL: type ${typeName.name} { ... }` : `Did you mean '${suggestion}'?`]);
4370
+ return;
4371
+ }
4372
+ const declared = sdl.locate(type.astNode);
4373
+ const related = declared === void 0 ? [] : [{
4374
+ message: `${type.name} is declared here`,
4375
+ location: declared
4376
+ }];
4377
+ if (isInterfaceType(type)) {
4378
+ const implementations = schema.getPossibleTypes(type).map((object) => object.name);
4379
+ this.#mismatch(typeName.location, `@Resolver('${type.name}') of ${owner}: ${type.name} is an interface, field resolvers bind the fields of object types`, [implementations.length === 0 ? `Declare an object type implementing ${type.name} and bind the field on it` : `Bind the field on each implementation: @Resolver('${implementations[0]}')${implementations.length > 1 ? ` (and ${implementations.slice(1).join(", ")})` : ""}`], related);
4380
+ return;
4381
+ }
4382
+ if (!isObjectType(type)) {
4383
+ this.#mismatch(typeName.location, `@Resolver('${type.name}') of ${owner}: ${type.name} is not an object type, it has no field to resolve`, ["Name an object type of the SDL (type ... { ... })"], related);
4384
+ return;
4385
+ }
4386
+ const operation = [
4387
+ "query",
4388
+ "mutation",
4389
+ "subscription"
4390
+ ].find((candidate) => rootType(sdl.schema, candidate) === type);
4391
+ if (operation !== void 0 && resolver.fields.some((field) => field.operation === "field")) {
4392
+ this.#mismatch(typeName.location, `@Resolver('${type.name}') of ${owner}: ${type.name} is the ${operation} root type, its fields are bound with ${DECORATORS[operation]}()`, [`Replace @ResolveField() with ${DECORATORS[operation]}() and use @Resolver()`], related);
4393
+ return;
4394
+ }
4395
+ return type;
4396
+ }
4397
+ #unknownField(schema, type, field, where) {
4398
+ const hints = [];
4399
+ if (field.operation !== "field") {
4400
+ const elsewhere = [
4401
+ "query",
4402
+ "mutation",
4403
+ "subscription"
4404
+ ].find((operation) => operation !== field.operation && rootType(schema, operation)?.getFields()[field.name] !== void 0);
4405
+ if (elsewhere !== void 0) hints.push(`${ROOTS[elsewhere]}.${field.name} exists: use ${DECORATORS[elsewhere]}()`);
4406
+ }
4407
+ if (hints.length === 0) {
4408
+ const suggestion = closest(field.name, Object.keys(type.getFields()).sort(compareStrings));
4409
+ if (suggestion !== void 0) hints.push(`Did you mean '${suggestion}'? ${DECORATORS[field.operation]}('${suggestion}')`);
4410
+ }
4411
+ hints.push(`Declare it in the SDL (${field.operation === "field" ? `extend type ${type.name}` : `extend type ${type.name}`} { ${field.name}: ... }), or remove the method`);
4412
+ this.#mismatch(field.location, `${DECORATORS[field.operation]} ${where}: ${type.name} has no field '${field.name}'`, hints);
4413
+ }
4414
+ /** `@Args('name')`: the argument must exist. */
4415
+ #checkArguments(sdl, field, method, sdlField, coordinate, where) {
4416
+ const names = sdlField.args.map((arg) => arg.name);
4417
+ field.params.forEach((param, index) => {
4418
+ if (param?.kind !== "args" || param.name === void 0 || names.includes(param.name)) return;
4419
+ const suggestion = closest(param.name, [...names].sort(compareStrings));
4420
+ const declared = sdl.locate(sdlField.astNode);
4421
+ let hint = `Its arguments: ${names.join(", ")}`;
4422
+ if (suggestion !== void 0) hint = `Did you mean '${suggestion}'?`;
4423
+ else if (names.length === 0) hint = `${coordinate} takes no argument: declare it in the SDL, or remove the parameter`;
4424
+ this.#mismatch(param.location, `@Args('${param.name}') on parameter '${method.params[index]?.name ?? param.name}' of ${where}: ${coordinate} has no argument '${param.name}'`, [hint], declared === void 0 ? [] : [{
4425
+ message: `${coordinate} is declared here`,
4426
+ location: declared
4427
+ }]);
4428
+ });
4429
+ }
4430
+ #method(resolver, field) {
4431
+ const key = `${resolver.host.identity}#${field.method}`;
4432
+ if (!this.#methods.has(key)) {
4433
+ const cls = this.#ctx.classInfo(resolver.host.identity);
4434
+ this.#methods.set(key, cls?.methods.find((method) => method.name === field.method && method.kind === "method"));
4435
+ }
4436
+ return this.#methods.get(key);
4437
+ }
4438
+ #model(sdl, type, bindings, scalars, conformance) {
4439
+ const { isObjectType, isInterfaceType, isInputObjectType, isEnumType, isUnionType } = graphqlModules().type;
4440
+ const base = {
4441
+ name: type.name,
4442
+ key: `#sdl:${type.name}`,
4443
+ description: type.description ?? void 0,
4444
+ location: sdl.locate(type.astNode)
4445
+ };
4446
+ const nulls = conformance.nullAsUndefined;
4447
+ if (isObjectType(type) || isInterfaceType(type)) {
4448
+ const fields = Object.values(type.getFields()).map((field) => this.#field(sdl, type, field, bindings.get(`${type.name}.${field.name}`), nulls));
4449
+ const interfaces = type.getInterfaces().map((implemented) => implemented.name);
4450
+ if (isInterfaceType(type)) return {
4451
+ ...base,
4452
+ kind: "interface",
4453
+ fields,
4454
+ interfaces,
4455
+ discriminator: conformance.discriminators.get(type.name)
4456
+ };
4457
+ return {
4458
+ ...base,
4459
+ kind: "object",
4460
+ fields,
4461
+ trail: type.name,
4462
+ root: void 0,
4463
+ classIdentity: void 0,
4464
+ properties: [],
4465
+ interfaces
4466
+ };
4467
+ }
4468
+ if (isInputObjectType(type)) return {
4469
+ ...base,
4470
+ kind: "input",
4471
+ fields: Object.values(type.getFields()).map((field) => inputValue(sdl, field, `${type.name}.${field.name}`, nulls))
4472
+ };
4473
+ if (isEnumType(type)) return {
4474
+ ...base,
4475
+ kind: "enum",
4476
+ values: type.getValues().map((value) => ({
4477
+ name: value.name,
4478
+ value: value.name,
4479
+ description: value.description ?? void 0,
4480
+ deprecationReason: value.deprecationReason ?? void 0
4481
+ }))
4482
+ };
4483
+ if (isUnionType(type)) return {
4484
+ ...base,
4485
+ kind: "union",
4486
+ members: type.getTypes().map((member) => member.name).sort(compareStrings),
4487
+ discriminator: conformance.discriminators.get(type.name)
4488
+ };
4489
+ return scalars.get(type.name);
4490
+ }
4491
+ #field(sdl, type, field, binding, nulls) {
4492
+ const coordinate = `${type.name}.${field.name}`;
4493
+ return {
4494
+ name: field.name,
4495
+ type: String(field.type),
4496
+ description: field.description ?? void 0,
4497
+ deprecationReason: field.deprecationReason ?? void 0,
4498
+ args: field.args.map((arg) => inputValue(sdl, arg, `${coordinate}(${arg.name}:)`, nulls)),
4499
+ binding: binding === void 0 ? void 0 : {
4500
+ resolver: binding.resolver,
4501
+ field: binding.field
4502
+ },
4503
+ location: sdl.locate(field.astNode)
4504
+ };
4505
+ }
4506
+ };
4507
+ const ROOTS = {
4508
+ query: "Query",
4509
+ mutation: "Mutation",
4510
+ subscription: "Subscription"
4511
+ };
4512
+ function rootType(schema, operation) {
4513
+ switch (operation) {
4514
+ case "query": return schema.getQueryType() ?? void 0;
4515
+ case "mutation": return schema.getMutationType() ?? void 0;
4516
+ default: return schema.getSubscriptionType() ?? void 0;
4517
+ }
4518
+ }
4519
+ /** An argument or an input field: its default as an external value (enum values by name). */
4520
+ function inputValue(sdl, value, coordinate, nulls) {
4521
+ const type = String(value.type);
4522
+ const ref = parseTypeRef(type);
4523
+ const defaultInput = value.default;
4524
+ let defaultValue;
4525
+ if (defaultInput !== void 0) defaultValue = defaultInput.literal === void 0 ? defaultInput.value : graphqlModules().utilities.valueFromASTUntyped(defaultInput.literal);
4526
+ return {
4527
+ name: value.name,
4528
+ type,
4529
+ ref,
4530
+ description: value.description ?? void 0,
4531
+ deprecationReason: value.deprecationReason ?? void 0,
4532
+ defaultValue,
4533
+ nullAsUndefined: !ref.nonNull && nulls.has(coordinate),
4534
+ location: sdl.locate(value.astNode)
4535
+ };
4536
+ }
4537
+ // src/compiler/index.ts
4538
+ const FIELD = {
4539
+ targets: ["method"],
4540
+ params: ["static"]
4541
+ };
4542
+ const NO_ARGUMENT = {
4543
+ targets: ["parameter"],
4544
+ params: []
4545
+ };
4546
+ /** Decorators owned by the extension, by canonical identity (static / value arguments, §4.6). */
4547
+ const decorators = Object.freeze({
4548
+ [GraphQLIds.Resolver]: {
4549
+ targets: ["class"],
4550
+ params: ["static", "static"]
4551
+ },
4552
+ [GraphQLIds.Query]: FIELD,
4553
+ [GraphQLIds.Mutation]: FIELD,
4554
+ [GraphQLIds.Subscription]: FIELD,
4555
+ [GraphQLIds.ResolveField]: FIELD,
4556
+ [GraphQLIds.Args]: {
4557
+ targets: ["parameter"],
4558
+ params: ["value", "value"]
4559
+ },
4560
+ [GraphQLIds.Parent]: NO_ARGUMENT,
4561
+ [GraphQLIds.Ctx]: NO_ARGUMENT,
4562
+ [GraphQLIds.Info]: NO_ARGUMENT,
4563
+ [GraphQLIds.Loader]: NO_ARGUMENT,
4564
+ [GraphQLIds.UseGuard]: {
4565
+ targets: ["class", "method"],
4566
+ params: [],
4567
+ rest: "value"
4568
+ }
4569
+ });
4570
+ function createExtension(options) {
4571
+ /**
4572
+ * Models by compile, built by `validate`, read by `generate`. The key is `graph.extensions`: the
4573
+ * validate and generate contexts of one compile get different graph objects sharing this array.
4574
+ */
4575
+ const models = /* @__PURE__ */ new WeakMap();
4576
+ const modelOf = (graph) => models.get(graph.extensions);
4577
+ return Object.freeze({
4578
+ apiVersion: 1,
4579
+ name: EXTENSION_NAME,
4580
+ decorators,
4581
+ options,
4582
+ analyzeClass: (cls, ctx) => analyzeGraphQLClass(cls, ctx, options),
4583
+ validate(ctx) {
4584
+ const schema = options.schema;
4585
+ models.set(ctx.graph.extensions, schema === false ? buildModel(ctx, options) : buildSchemaFirstModel(ctx, schema, options.sdl));
4586
+ },
4587
+ generate(ctx) {
4588
+ return generateGraphQL(ctx, modelOf(ctx.graph), options);
4589
+ },
4590
+ configure(next) {
4591
+ return createExtension(resolveOptions(options, next));
4592
+ }
4593
+ });
4594
+ }
4595
+ /** The `@znight/graphql` compiler extension with its default options. */
4596
+ const graphqlCompilerExtension = createExtension(DEFAULT_OPTIONS);
4597
+ export { graphqlCompilerExtension as default, graphqlCompilerExtension };
4598
+
4599
+ //# sourceMappingURL=compiler.mjs.map