@znight/graphql 0.2.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +307 -11
- package/dist/compiler.d.mts +24 -3
- package/dist/compiler.mjs +1876 -109
- package/dist/compiler.mjs.map +1 -1
- package/dist/index.d.mts +49 -8
- package/dist/index.mjs +10 -4
- package/dist/index.mjs.map +1 -1
- package/dist/names-BlzDM5cF.mjs.map +1 -1
- package/dist/{plugin-BdBxr5NQ.mjs → plugin-CR_wVfRU.mjs} +247 -24
- package/dist/plugin-CR_wVfRU.mjs.map +1 -0
- package/dist/testing.d.mts +1 -1
- package/dist/testing.mjs +1 -1
- package/dist/{types-CgbV9LjD.d.mts → types-DQ713UgI.d.mts} +34 -2
- package/package.json +14 -9
- package/src/compiler/analyze.ts +129 -10
- package/src/compiler/builder.ts +6 -85
- package/src/compiler/checks.ts +105 -0
- package/src/compiler/conformance.ts +1068 -0
- package/src/compiler/constants.ts +6 -0
- package/src/compiler/data.ts +10 -0
- package/src/compiler/generate.ts +41 -27
- package/src/compiler/graphql-js.ts +42 -0
- package/src/compiler/index.ts +8 -3
- package/src/compiler/locate.ts +39 -1
- package/src/compiler/model.ts +16 -1
- package/src/compiler/options.ts +39 -8
- package/src/compiler/schema-first.ts +595 -0
- package/src/compiler/sdl-source.ts +529 -0
- package/src/compiler/sdl-view.ts +3 -1
- package/src/compiler/suggest.ts +39 -0
- package/src/decorators.ts +9 -3
- package/src/index.ts +5 -0
- package/src/metadata.ts +28 -1
- package/src/options.ts +23 -1
- package/src/pipeline.ts +66 -5
- package/src/plugin.ts +27 -5
- package/src/results.ts +206 -0
- package/src/scalars.ts +10 -3
- package/src/schema.ts +90 -12
- package/src/sdl.ts +17 -2
- package/src/types.ts +35 -0
- package/dist/plugin-BdBxr5NQ.mjs.map +0 -1
package/dist/compiler.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
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";
|
|
2
3
|
import { createHash } from "node:crypto";
|
|
3
4
|
import { effectiveRouteMeta, readRouteMeta, requiredRouteGroups } from "@znight/http/compiler";
|
|
4
5
|
// src/compiler/ids.ts
|
|
@@ -75,7 +76,9 @@ const Codes = Object.freeze({
|
|
|
75
76
|
InvalidResolver: "ZNIGHT062",
|
|
76
77
|
InvalidInput: "ZNIGHT063",
|
|
77
78
|
UnresolvableAbstractType: "ZNIGHT064",
|
|
78
|
-
InvalidLoader: "ZNIGHT065"
|
|
79
|
+
InvalidLoader: "ZNIGHT065",
|
|
80
|
+
InvalidSdl: "ZNIGHT067",
|
|
81
|
+
SchemaMismatch: "ZNIGHT068"
|
|
79
82
|
});
|
|
80
83
|
/** Root reason given to `AnalyzeContext.addRoot()`. */
|
|
81
84
|
const ROOT_REASON = "GraphQL resolver";
|
|
@@ -120,12 +123,17 @@ const LOADER_SHAPE = Object.freeze({
|
|
|
120
123
|
keep: Object.freeze([GraphQLIds.Loader]),
|
|
121
124
|
jsdoc: false
|
|
122
125
|
});
|
|
123
|
-
/**
|
|
124
|
-
|
|
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) {
|
|
125
132
|
const resolvers = cls.decorators.filter((decorator) => decorator.identity === GraphQLIds.Resolver);
|
|
126
133
|
const resolver = resolvers[0];
|
|
127
134
|
if (resolver === void 0) {
|
|
128
135
|
reportOutsideResolver(cls, ctx);
|
|
136
|
+
storeClaims(ctx, cls, void 0, options);
|
|
129
137
|
return;
|
|
130
138
|
}
|
|
131
139
|
for (const extra of resolvers.slice(1)) error(ctx, Codes.InvalidDecorator, extra.location, `@Resolver is applied twice to ${cls.displayName}`, ["Apply it once"]);
|
|
@@ -135,17 +143,20 @@ function analyzeGraphQLClass(cls, ctx) {
|
|
|
135
143
|
message: "@Controller is applied here",
|
|
136
144
|
location: controller.location
|
|
137
145
|
}]);
|
|
146
|
+
storeClaims(ctx, cls, resolver, options);
|
|
138
147
|
return;
|
|
139
148
|
}
|
|
140
149
|
if (ctx.options.library) {
|
|
141
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);
|
|
142
152
|
return;
|
|
143
153
|
}
|
|
144
154
|
if (cls.abstract || cls.typeParameters.length > 0) {
|
|
145
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);
|
|
146
157
|
return;
|
|
147
158
|
}
|
|
148
|
-
const scope =
|
|
159
|
+
const { scope, typeName } = resolverArguments(ctx, resolver, options.schema !== false);
|
|
149
160
|
ctx.registerProvider(cls, { scope });
|
|
150
161
|
ctx.addRoot(cls, {
|
|
151
162
|
reason: ROOT_REASON,
|
|
@@ -160,15 +171,60 @@ function analyzeGraphQLClass(cls, ctx) {
|
|
|
160
171
|
reportStrayMethodDecorators(cls, method, ctx);
|
|
161
172
|
continue;
|
|
162
173
|
}
|
|
163
|
-
fields.push(...analyzeMethod(ctx, cls, method, decorators, classMeta));
|
|
174
|
+
fields.push(...analyzeMethod(ctx, cls, method, decorators, classMeta, options.schema !== false));
|
|
164
175
|
}
|
|
165
|
-
|
|
176
|
+
const data = {
|
|
166
177
|
kind: "resolver",
|
|
167
178
|
host: cls,
|
|
168
179
|
scope,
|
|
169
180
|
location: resolver.location,
|
|
170
181
|
guards: classGuards,
|
|
171
|
-
fields
|
|
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 }
|
|
172
228
|
});
|
|
173
229
|
}
|
|
174
230
|
function isFieldDecorator(decorator) {
|
|
@@ -177,9 +233,33 @@ function isFieldDecorator(decorator) {
|
|
|
177
233
|
function isParameterDecorator(decorator) {
|
|
178
234
|
return decorator.identity !== void 0 && PARAMETER_DECORATORS.has(decorator.identity);
|
|
179
235
|
}
|
|
180
|
-
/** `@Resolver({ scope })
|
|
181
|
-
function
|
|
182
|
-
const
|
|
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) {
|
|
183
263
|
if (arg === void 0 || !arg.evaluated || arg.value === void 0) return "singleton";
|
|
184
264
|
const value = arg.value;
|
|
185
265
|
if (!isRecord(value)) {
|
|
@@ -209,7 +289,7 @@ function guardsOf(ctx, cls, decorators) {
|
|
|
209
289
|
}
|
|
210
290
|
return guards;
|
|
211
291
|
}
|
|
212
|
-
function analyzeMethod(ctx, cls, method, decorators, classMeta) {
|
|
292
|
+
function analyzeMethod(ctx, cls, method, decorators, classMeta, schemaFirst) {
|
|
213
293
|
const where = `${cls.displayName}.${method.name}`;
|
|
214
294
|
const primary = decorators[0];
|
|
215
295
|
let complete = checkMethodShape(ctx, method, primary, where);
|
|
@@ -240,7 +320,7 @@ function analyzeMethod(ctx, cls, method, decorators, classMeta) {
|
|
|
240
320
|
}
|
|
241
321
|
seen.add(key);
|
|
242
322
|
let fieldComplete = complete && explicit !== void 0;
|
|
243
|
-
if (operation === "field" && parents.length === 0) {
|
|
323
|
+
if (operation === "field" && parents.length === 0 && !schemaFirst) {
|
|
244
324
|
error(ctx, Codes.InvalidResolver, decorator.location, `@ResolveField ${where} has no @Parent() parameter`, [`Declare the parent object first: ${method.name}(@Parent() parent: ParentType, ...)`]);
|
|
245
325
|
fieldComplete = false;
|
|
246
326
|
} else if (operation !== "field" && parents.length > 0) {
|
|
@@ -542,7 +622,8 @@ var SdlPrinter = class {
|
|
|
542
622
|
type(type) {
|
|
543
623
|
const head = printDescription(type.description, "", true);
|
|
544
624
|
switch (type.kind) {
|
|
545
|
-
case "object":
|
|
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)))}`;
|
|
546
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}`)}`))}`;
|
|
547
628
|
case "enum": return `${head}enum ${type.name}${printBlock(type.values.map((value, i) => `${printDescription(value.description, " ", i === 0)} ${value.name}${printDeprecated(value.deprecationReason)}`))}`;
|
|
548
629
|
case "union": return `${head}union ${type.name}${type.members.length === 0 ? "" : ` = ${type.members.join(" | ")}`}`;
|
|
@@ -621,6 +702,10 @@ var SdlPrinter = class {
|
|
|
621
702
|
};
|
|
622
703
|
}
|
|
623
704
|
};
|
|
705
|
+
/** graphql-js `printImplementedInterfaces`. */
|
|
706
|
+
function printImplements(interfaces) {
|
|
707
|
+
return interfaces === void 0 || interfaces.length === 0 ? "" : ` implements ${interfaces.join(" & ")}`;
|
|
708
|
+
}
|
|
624
709
|
/** The `valueToLiteral` of the specified scalars, and graphql-js' default one for the others. */
|
|
625
710
|
function scalarLiteral(value, name) {
|
|
626
711
|
switch (name) {
|
|
@@ -841,6 +926,74 @@ function printBlockString(value) {
|
|
|
841
926
|
if (printAsMultipleLines || forceTrailingNewline) result += "\n";
|
|
842
927
|
return `"""${result}"""`;
|
|
843
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
|
+
}
|
|
844
997
|
// src/compiler/defaults.ts
|
|
845
998
|
const GRAPHQL_INT_MAX = 2147483647;
|
|
846
999
|
const GRAPHQL_INT_MIN = -2147483648;
|
|
@@ -1030,6 +1183,46 @@ function annotationLocation(project, member) {
|
|
|
1030
1183
|
end
|
|
1031
1184
|
}) : member;
|
|
1032
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
|
+
}
|
|
1033
1226
|
// src/compiler/scalars.ts
|
|
1034
1227
|
/** Valid declarations by the identity of their type; invalid ones are reported. */
|
|
1035
1228
|
function readScalars(ctx, report) {
|
|
@@ -1143,10 +1336,12 @@ function staticProperty(ctx, arg, key) {
|
|
|
1143
1336
|
function sdlType(model) {
|
|
1144
1337
|
const base = model.description === void 0 ? {} : { description: model.description };
|
|
1145
1338
|
switch (model.kind) {
|
|
1146
|
-
case "object":
|
|
1147
|
-
|
|
1339
|
+
case "object":
|
|
1340
|
+
case "interface": return {
|
|
1341
|
+
kind: model.kind,
|
|
1148
1342
|
name: model.name,
|
|
1149
1343
|
...base,
|
|
1344
|
+
...model.interfaces.length === 0 ? {} : { interfaces: model.interfaces },
|
|
1150
1345
|
fields: model.fields.map((field) => ({
|
|
1151
1346
|
name: field.name,
|
|
1152
1347
|
type: field.type,
|
|
@@ -1238,7 +1433,7 @@ var SchemaBuilder = class {
|
|
|
1238
1433
|
});
|
|
1239
1434
|
}
|
|
1240
1435
|
build(resolvers) {
|
|
1241
|
-
this.#
|
|
1436
|
+
checkLoaders(this.#ctx, resolvers, (diagnostic) => this.#report(diagnostic));
|
|
1242
1437
|
this.#indexFieldResolvers(resolvers);
|
|
1243
1438
|
const roots = /* @__PURE__ */ new Map();
|
|
1244
1439
|
const rootFields = /* @__PURE__ */ new Map();
|
|
@@ -1285,7 +1480,7 @@ var SchemaBuilder = class {
|
|
|
1285
1480
|
this.#reportUnreachableFieldResolvers();
|
|
1286
1481
|
this.#checkDefaults();
|
|
1287
1482
|
this.#checkInputCycles();
|
|
1288
|
-
this.#
|
|
1483
|
+
checkDecisions(this.#ctx, resolvers, (diagnostic) => this.#report(diagnostic));
|
|
1289
1484
|
if (this.#errors > 0 || !hasQuery) return void 0;
|
|
1290
1485
|
return this.#assemble(roots, resolvers);
|
|
1291
1486
|
}
|
|
@@ -1337,7 +1532,8 @@ var SchemaBuilder = class {
|
|
|
1337
1532
|
trail: name,
|
|
1338
1533
|
root: void 0,
|
|
1339
1534
|
classIdentity: void 0,
|
|
1340
|
-
properties: []
|
|
1535
|
+
properties: [],
|
|
1536
|
+
interfaces: []
|
|
1341
1537
|
};
|
|
1342
1538
|
roots.set(operation, root);
|
|
1343
1539
|
this.#claim(root, void 0);
|
|
@@ -1552,25 +1748,6 @@ var SchemaBuilder = class {
|
|
|
1552
1748
|
if (arg.deprecationReason === void 0 || !arg.ref.nonNull || hasDefault) return;
|
|
1553
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"]);
|
|
1554
1750
|
}
|
|
1555
|
-
/** `Loader<L>`: `L` must be a loader provider, implementing `BatchLoad<K, V>` (ZNIGHT065). */
|
|
1556
|
-
#checkLoaders(resolvers) {
|
|
1557
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1558
|
-
for (const resolver of resolvers) for (const field of resolver.fields) for (const param of field.params) {
|
|
1559
|
-
if (param?.kind !== "loader" || seen.has(param.binding.id)) continue;
|
|
1560
|
-
seen.add(param.binding.id);
|
|
1561
|
-
const token = tokenOfRef(this.#ctx.binding(param.binding));
|
|
1562
|
-
const provider = token === void 0 ? void 0 : this.#ctx.provider(token);
|
|
1563
|
-
if (provider === void 0 || provider.heritage.some((type) => type.identity === GraphQLIds.BatchLoad)) continue;
|
|
1564
|
-
if (provider.kind !== "class" || hasLoadMethod(this.#ctx, provider.classIdentity)) continue;
|
|
1565
|
-
this.#report({
|
|
1566
|
-
code: Codes.InvalidLoader,
|
|
1567
|
-
severity: "error",
|
|
1568
|
-
message: `${provider.displayName} is not a loader: Loader<${provider.displayName}> needs a provider implementing BatchLoad<K, V> (${resolver.host.displayName}.${field.method})`,
|
|
1569
|
-
location: param.location,
|
|
1570
|
-
hints: [`export class ${provider.displayName} implements BatchLoad<ID, Value> { async load(keys: readonly ID[]): Promise<readonly (Value | Error)[]> { ... } }`]
|
|
1571
|
-
});
|
|
1572
|
-
}
|
|
1573
|
-
}
|
|
1574
1751
|
#indexFieldResolvers(resolvers) {
|
|
1575
1752
|
for (const resolver of resolvers) for (const field of resolver.fields) {
|
|
1576
1753
|
if (field.operation !== "field" || !field.complete) continue;
|
|
@@ -1945,7 +2122,8 @@ var SchemaBuilder = class {
|
|
|
1945
2122
|
trail,
|
|
1946
2123
|
root: use.root,
|
|
1947
2124
|
classIdentity: structure.declaration === "class" && structure.typeArgs.length === 0 && structure === named ? structure.identity : void 0,
|
|
1948
|
-
properties: properties.map((property) => this.#discriminant(property))
|
|
2125
|
+
properties: properties.map((property) => this.#discriminant(property)),
|
|
2126
|
+
interfaces: []
|
|
1949
2127
|
};
|
|
1950
2128
|
this.#models.set(key, model);
|
|
1951
2129
|
if (!this.#claim(model, use)) return void 0;
|
|
@@ -2352,32 +2530,6 @@ var SchemaBuilder = class {
|
|
|
2352
2530
|
};
|
|
2353
2531
|
for (const model of inputs) if (!state.has(model.name)) visit(model);
|
|
2354
2532
|
}
|
|
2355
|
-
/** RFC 0001: every root field decides every required route decorator group (ZNIGHT023). */
|
|
2356
|
-
#checkDecisions(resolvers) {
|
|
2357
|
-
const applied = [];
|
|
2358
|
-
for (const resolver of resolvers) for (const field of resolver.fields) applied.push(...field.meta);
|
|
2359
|
-
const required = requiredRouteGroups(this.#ctx, applied);
|
|
2360
|
-
if (required.length === 0) return;
|
|
2361
|
-
for (const resolver of resolvers) for (const field of resolver.fields) {
|
|
2362
|
-
if (field.operation === "field") continue;
|
|
2363
|
-
const decided = new Set(field.meta.map((entry) => entry.group));
|
|
2364
|
-
for (const group of required) {
|
|
2365
|
-
if (decided.has(group.group)) continue;
|
|
2366
|
-
const root = ROOT_TYPE_NAMES[field.operation];
|
|
2367
|
-
this.#report({
|
|
2368
|
-
code: Codes.MissingRouteDecision,
|
|
2369
|
-
severity: "error",
|
|
2370
|
-
message: `Root field ${root}.${field.name} (${resolver.host.displayName}.${field.method}) has no "${group.group}" decision`,
|
|
2371
|
-
location: field.methodLocation,
|
|
2372
|
-
hints: [`Add ${group.suggestion} to the method or to ${resolver.host.displayName}`],
|
|
2373
|
-
related: [{
|
|
2374
|
-
message: `The "${group.group}" group is required`,
|
|
2375
|
-
location: group.decorators[0].definition
|
|
2376
|
-
}]
|
|
2377
|
-
});
|
|
2378
|
-
}
|
|
2379
|
-
}
|
|
2380
|
-
}
|
|
2381
2533
|
#assemble(roots, resolvers) {
|
|
2382
2534
|
const types = [...this.#types.values()].sort((a, b) => compareStrings(a.name, b.name));
|
|
2383
2535
|
const meta = [];
|
|
@@ -2400,20 +2552,6 @@ var SchemaBuilder = class {
|
|
|
2400
2552
|
};
|
|
2401
2553
|
}
|
|
2402
2554
|
};
|
|
2403
|
-
/** The class (or one of its base classes) declares an instance `load` method. */
|
|
2404
|
-
function hasLoadMethod(ctx, identity) {
|
|
2405
|
-
const seen = /* @__PURE__ */ new Set();
|
|
2406
|
-
let current = identity;
|
|
2407
|
-
while (current !== void 0 && !seen.has(current)) {
|
|
2408
|
-
seen.add(current);
|
|
2409
|
-
const cls = ctx.classInfo(current);
|
|
2410
|
-
if (cls === void 0) return true;
|
|
2411
|
-
if (cls.methods.some((method) => method.name === "load" && method.kind === "method" && !method.static)) return true;
|
|
2412
|
-
if (cls.properties.some((property) => property.name === "load" && !property.static)) return true;
|
|
2413
|
-
current = cls.superClass?.identity;
|
|
2414
|
-
}
|
|
2415
|
-
return false;
|
|
2416
|
-
}
|
|
2417
2555
|
/** A (nullable, until `#convert` decides) reference to a named type. */
|
|
2418
2556
|
function refTo(name) {
|
|
2419
2557
|
return {
|
|
@@ -2434,11 +2572,6 @@ function reachedFrom(use) {
|
|
|
2434
2572
|
function decoratorName(operation) {
|
|
2435
2573
|
return ROOT_TYPE_NAMES[operation];
|
|
2436
2574
|
}
|
|
2437
|
-
/** The token of a binding result (`undefined` when binding failed). */
|
|
2438
|
-
function tokenOfRef(ref) {
|
|
2439
|
-
if (typeof ref === "string") return ref;
|
|
2440
|
-
return ref !== void 0 && "token" in ref ? ref.token : void 0;
|
|
2441
|
-
}
|
|
2442
2575
|
function describeKey(model) {
|
|
2443
2576
|
return model.key.startsWith("#") ? `the ${model.kind} ${model.name}` : model.key;
|
|
2444
2577
|
}
|
|
@@ -2532,7 +2665,14 @@ function typeNode(ctx, imports, type) {
|
|
|
2532
2665
|
const head = [["kind", type.kind], ["name", type.name]];
|
|
2533
2666
|
if (type.description !== void 0 && type.kind !== "scalar") head.push(["description", type.description]);
|
|
2534
2667
|
switch (type.kind) {
|
|
2535
|
-
case "object":
|
|
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
|
+
}
|
|
2536
2676
|
case "input": return ctx.obj([...head, ["fields", ctx.arr(type.fields.map((field) => inputValueNode(ctx, field)))]]);
|
|
2537
2677
|
case "enum": return ctx.obj([...head, ["values", ctx.arr(type.values.map((value) => {
|
|
2538
2678
|
const entries = [["name", value.name], ["value", value.value]];
|
|
@@ -2540,25 +2680,33 @@ function typeNode(ctx, imports, type) {
|
|
|
2540
2680
|
if (value.deprecationReason !== void 0) entries.push(["deprecationReason", value.deprecationReason]);
|
|
2541
2681
|
return ctx.obj(entries);
|
|
2542
2682
|
}))]]);
|
|
2543
|
-
case "union":
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
}
|
|
2559
|
-
case "scalar": return type.definition === void 0 ? ctx.obj([...head, ["builtin", ctx.lit(true)]]) : ctx.obj([...head, ["scalar", ctx.importValue(type.definition)]]);
|
|
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
|
+
}
|
|
2560
2698
|
}
|
|
2561
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
|
+
}
|
|
2562
2710
|
function fieldNode(ctx, imports, field) {
|
|
2563
2711
|
const entries = [["name", field.name], ["type", field.type]];
|
|
2564
2712
|
if (field.description !== void 0) entries.push(["description", field.description]);
|
|
@@ -2749,25 +2897,1643 @@ function groupsNode(ctx, meta) {
|
|
|
2749
2897
|
/** The defaults. */
|
|
2750
2898
|
const DEFAULT_OPTIONS = Object.freeze({
|
|
2751
2899
|
sdl: false,
|
|
2752
|
-
number: "Float"
|
|
2900
|
+
number: "Float",
|
|
2901
|
+
schema: false
|
|
2753
2902
|
});
|
|
2754
|
-
const KNOWN = /* @__PURE__ */ new Set([
|
|
2903
|
+
const KNOWN = /* @__PURE__ */ new Set([
|
|
2904
|
+
"sdl",
|
|
2905
|
+
"number",
|
|
2906
|
+
"schema"
|
|
2907
|
+
]);
|
|
2755
2908
|
/**
|
|
2756
2909
|
* Merges `options` onto `base`. Throws a `TypeError` naming the option on an invalid value: options
|
|
2757
2910
|
* are written in `znight.config.ts`, the error surfaces when the configuration is loaded.
|
|
2758
2911
|
*/
|
|
2759
2912
|
function resolveOptions(base, options) {
|
|
2760
|
-
if (typeof options !== "object" || options === null || Array.isArray(options)) throw new TypeError("[znight] graphql.configure() expects an options object: { sdl?, number? }");
|
|
2761
|
-
for (const key of Object.keys(options)) if (!KNOWN.has(key)) throw new TypeError(`[znight] graphql.configure(): unknown option "${key}" (options: sdl, number)`);
|
|
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)`);
|
|
2762
2915
|
const sdl = options.sdl === void 0 ? base.sdl : options.sdl;
|
|
2763
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");
|
|
2764
2917
|
const number = options.number === void 0 ? base.number : options.number;
|
|
2765
2918
|
if (number !== "Float" && number !== "Int") throw new TypeError(`[znight] graphql.configure(): "number" must be 'Float' or 'Int'`);
|
|
2766
2919
|
return Object.freeze({
|
|
2767
2920
|
sdl,
|
|
2768
|
-
number
|
|
2921
|
+
number,
|
|
2922
|
+
schema: resolveSchema(base.schema, options.schema)
|
|
2769
2923
|
});
|
|
2770
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
|
+
}
|
|
2771
4537
|
// src/compiler/index.ts
|
|
2772
4538
|
const FIELD = {
|
|
2773
4539
|
targets: ["method"],
|
|
@@ -2781,7 +4547,7 @@ const NO_ARGUMENT = {
|
|
|
2781
4547
|
const decorators = Object.freeze({
|
|
2782
4548
|
[GraphQLIds.Resolver]: {
|
|
2783
4549
|
targets: ["class"],
|
|
2784
|
-
params: ["static"]
|
|
4550
|
+
params: ["static", "static"]
|
|
2785
4551
|
},
|
|
2786
4552
|
[GraphQLIds.Query]: FIELD,
|
|
2787
4553
|
[GraphQLIds.Mutation]: FIELD,
|
|
@@ -2813,9 +4579,10 @@ function createExtension(options) {
|
|
|
2813
4579
|
name: EXTENSION_NAME,
|
|
2814
4580
|
decorators,
|
|
2815
4581
|
options,
|
|
2816
|
-
analyzeClass: analyzeGraphQLClass,
|
|
4582
|
+
analyzeClass: (cls, ctx) => analyzeGraphQLClass(cls, ctx, options),
|
|
2817
4583
|
validate(ctx) {
|
|
2818
|
-
|
|
4584
|
+
const schema = options.schema;
|
|
4585
|
+
models.set(ctx.graph.extensions, schema === false ? buildModel(ctx, options) : buildSchemaFirstModel(ctx, schema, options.sdl));
|
|
2819
4586
|
},
|
|
2820
4587
|
generate(ctx) {
|
|
2821
4588
|
return generateGraphQL(ctx, modelOf(ctx.graph), options);
|