@znight/graphql 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +288 -5
- package/dist/compiler.d.mts +24 -3
- package/dist/compiler.mjs +1876 -109
- package/dist/compiler.mjs.map +1 -1
- package/dist/index.d.mts +49 -8
- package/dist/index.mjs +10 -4
- package/dist/index.mjs.map +1 -1
- package/dist/names-BlzDM5cF.mjs.map +1 -1
- package/dist/{plugin-BdBxr5NQ.mjs → plugin-CR_wVfRU.mjs} +247 -24
- package/dist/plugin-CR_wVfRU.mjs.map +1 -0
- package/dist/testing.d.mts +1 -1
- package/dist/testing.mjs +1 -1
- package/dist/{types-CgbV9LjD.d.mts → types-DQ713UgI.d.mts} +34 -2
- package/package.json +8 -8
- package/src/compiler/analyze.ts +129 -10
- package/src/compiler/builder.ts +6 -85
- package/src/compiler/checks.ts +105 -0
- package/src/compiler/conformance.ts +1068 -0
- package/src/compiler/constants.ts +6 -0
- package/src/compiler/data.ts +10 -0
- package/src/compiler/generate.ts +41 -27
- package/src/compiler/graphql-js.ts +42 -0
- package/src/compiler/index.ts +8 -3
- package/src/compiler/locate.ts +39 -1
- package/src/compiler/model.ts +16 -1
- package/src/compiler/options.ts +39 -8
- package/src/compiler/schema-first.ts +595 -0
- package/src/compiler/sdl-source.ts +529 -0
- package/src/compiler/sdl-view.ts +3 -1
- package/src/compiler/suggest.ts +39 -0
- package/src/decorators.ts +9 -3
- package/src/index.ts +5 -0
- package/src/metadata.ts +28 -1
- package/src/options.ts +23 -1
- package/src/pipeline.ts +66 -5
- package/src/plugin.ts +27 -5
- package/src/results.ts +206 -0
- package/src/scalars.ts +10 -3
- package/src/schema.ts +90 -12
- package/src/sdl.ts +17 -2
- package/src/types.ts +35 -0
- package/dist/plugin-BdBxr5NQ.mjs.map +0 -1
|
@@ -2,7 +2,7 @@ import { a as SPECIFIED_SCALARS, o as isGraphQLName, r as RESERVED_ENUM_VALUES,
|
|
|
2
2
|
import { ErrorCode, ZnightError, displayName, resolveOptions } from "@znight/core";
|
|
3
3
|
import DataLoader from "dataloader";
|
|
4
4
|
import { EventChannel, HttpResponse, HttpTokens, ValidationException, isHttpException, isThenable, parseByteSize, parseMediaType } from "@znight/http";
|
|
5
|
-
import { AbortedGraphQLExecutionError, GraphQLBoolean, GraphQLEnumType, GraphQLError, GraphQLFloat, GraphQLID, GraphQLInputObjectType, GraphQLInt, GraphQLList, GraphQLNonNull, GraphQLObjectType, GraphQLScalarType, GraphQLSchema, GraphQLString, GraphQLUnionType, Kind, NoSchemaIntrospectionCustomRule, createSourceEventStream, execute, executeSubscriptionEvent, getNamedType, isObjectType, parse, printSchema, specifiedRules, validate, validateSchema, validateSubscriptionArgs, valueFromASTUntyped } from "graphql";
|
|
5
|
+
import { AbortedGraphQLExecutionError, GraphQLBoolean, GraphQLEnumType, GraphQLError, GraphQLFloat, GraphQLID, GraphQLInputObjectType, GraphQLInt, GraphQLInterfaceType, GraphQLList, GraphQLNonNull, GraphQLObjectType, GraphQLScalarType, GraphQLSchema, GraphQLString, GraphQLUnionType, Kind, NoSchemaIntrospectionCustomRule, createSourceEventStream, execute, executeSubscriptionEvent, getNamedType, isObjectType, parse, printSchema, specifiedRules, validate, validateSchema, validateSubscriptionArgs, valueFromASTUntyped } from "graphql";
|
|
6
6
|
import { createHash, randomUUID } from "node:crypto";
|
|
7
7
|
// src/errors.ts
|
|
8
8
|
/**
|
|
@@ -1264,7 +1264,7 @@ var GraphQLExecutor = class {
|
|
|
1264
1264
|
this.#logger.error(`GraphQLPlugin: ${message}`, { err: error });
|
|
1265
1265
|
}
|
|
1266
1266
|
};
|
|
1267
|
-
function noop() {}
|
|
1267
|
+
function noop$1() {}
|
|
1268
1268
|
function badRequest(message) {
|
|
1269
1269
|
return new Rejection(400, [requestError(message, GraphQLErrorCode.BadRequest)], void 0);
|
|
1270
1270
|
}
|
|
@@ -1371,7 +1371,7 @@ var SubscriptionStream = class {
|
|
|
1371
1371
|
signal.addEventListener("abort", this.#onAbort, { once: true });
|
|
1372
1372
|
}
|
|
1373
1373
|
const first = init.run(() => this.#read());
|
|
1374
|
-
first.catch(noop);
|
|
1374
|
+
first.catch(noop$1);
|
|
1375
1375
|
this.#first = first;
|
|
1376
1376
|
if (signal?.aborted === true) this.return();
|
|
1377
1377
|
}
|
|
@@ -1430,7 +1430,7 @@ var SubscriptionStream = class {
|
|
|
1430
1430
|
const reading = this.#reading > 0;
|
|
1431
1431
|
try {
|
|
1432
1432
|
const returned = Promise.resolve(this.#init.source.return?.());
|
|
1433
|
-
if (reading) returned.catch(noop);
|
|
1433
|
+
if (reading) returned.catch(noop$1);
|
|
1434
1434
|
else await returned;
|
|
1435
1435
|
} catch {}
|
|
1436
1436
|
return DONE$1;
|
|
@@ -1460,6 +1460,7 @@ const KEYS = /* @__PURE__ */ new Set([
|
|
|
1460
1460
|
"http",
|
|
1461
1461
|
"guards",
|
|
1462
1462
|
"errorMappers",
|
|
1463
|
+
"resultMappers",
|
|
1463
1464
|
"exposeErrors",
|
|
1464
1465
|
"introspection",
|
|
1465
1466
|
"graphiql",
|
|
@@ -1567,6 +1568,7 @@ function resolveGraphQLOptions(options, env = process.env) {
|
|
|
1567
1568
|
http,
|
|
1568
1569
|
guards: refs("guards", options["guards"], "canResolve"),
|
|
1569
1570
|
errorMappers: refs("errorMappers", options["errorMappers"], "map"),
|
|
1571
|
+
resultMappers: refs("resultMappers", options["resultMappers"], "toResult"),
|
|
1570
1572
|
exposeErrors: boolean("exposeErrors", options["exposeErrors"], false),
|
|
1571
1573
|
introspection: boolean("introspection", options["introspection"], nodeEnv !== "production"),
|
|
1572
1574
|
graphiql: boolean("graphiql", options["graphiql"], nodeEnv === "development"),
|
|
@@ -1599,6 +1601,13 @@ function fieldFunctions(options, normalizers, binding, coordinate, field, parent
|
|
|
1599
1601
|
const target = options.resolver(binding, coordinate);
|
|
1600
1602
|
const guards = [...globalGuardsApply(binding) ? options.globalGuards : [], ...binding.guards.map((token) => options.guard(token, coordinate))];
|
|
1601
1603
|
const normalize = field.args === void 0 ? void 0 : normalizers.args(field.args);
|
|
1604
|
+
const recover = options.results?.(field, coordinate);
|
|
1605
|
+
const route = guards.length === 0 && recover === void 0 ? void 0 : Object.freeze({
|
|
1606
|
+
meta: binding.meta,
|
|
1607
|
+
coordinate
|
|
1608
|
+
});
|
|
1609
|
+
const parentType = parent.name;
|
|
1610
|
+
const fieldName = field.name;
|
|
1602
1611
|
let resolve;
|
|
1603
1612
|
if (guards.length === 0) {
|
|
1604
1613
|
if (target.token === void 0) {
|
|
@@ -1615,12 +1624,6 @@ function fieldFunctions(options, normalizers, binding, coordinate, field, parent
|
|
|
1615
1624
|
};
|
|
1616
1625
|
}
|
|
1617
1626
|
} else {
|
|
1618
|
-
const route = Object.freeze({
|
|
1619
|
-
meta: binding.meta,
|
|
1620
|
-
coordinate
|
|
1621
|
-
});
|
|
1622
|
-
const parentType = parent.name;
|
|
1623
|
-
const fieldName = field.name;
|
|
1624
1627
|
const forbidden = () => {
|
|
1625
1628
|
throw new GraphQLException(`Not allowed to resolve ${coordinate}`, { code: GraphQLErrorCode.Forbidden });
|
|
1626
1629
|
};
|
|
@@ -1637,7 +1640,9 @@ function fieldFunctions(options, normalizers, binding, coordinate, field, parent
|
|
|
1637
1640
|
return allowed.then((ok) => ok ? call() : forbidden());
|
|
1638
1641
|
};
|
|
1639
1642
|
}
|
|
1640
|
-
|
|
1643
|
+
const subscription = binding.operation === "subscription";
|
|
1644
|
+
if (recover !== void 0) resolve = withResults(resolve, recover, route, parentType, fieldName, subscription);
|
|
1645
|
+
if (subscription) return {
|
|
1641
1646
|
subscribe: resolve,
|
|
1642
1647
|
resolve: identity$1
|
|
1643
1648
|
};
|
|
@@ -1646,6 +1651,34 @@ function fieldFunctions(options, normalizers, binding, coordinate, field, parent
|
|
|
1646
1651
|
function identity$1(payload) {
|
|
1647
1652
|
return payload;
|
|
1648
1653
|
}
|
|
1654
|
+
/**
|
|
1655
|
+
* The resolve function of a field that carries results (RFC 0008): errors of the whole pipeline
|
|
1656
|
+
* (guards, null handling, `invoke`), thrown or rejected, go through `recover`. Synchronous results
|
|
1657
|
+
* stay synchronous; a promise gets one rejection handler. For a subscription, a value is a source
|
|
1658
|
+
* stream of that single event.
|
|
1659
|
+
*/
|
|
1660
|
+
function withResults(resolve, recover, route, parentType, fieldName, subscription) {
|
|
1661
|
+
const fail = (error, p, a, c, i) => {
|
|
1662
|
+
const value = recover(error, new FieldContext(route, c, parentType, fieldName, a, p, i));
|
|
1663
|
+
return subscription ? once(value) : value;
|
|
1664
|
+
};
|
|
1665
|
+
return (p, a, c, i) => {
|
|
1666
|
+
let result;
|
|
1667
|
+
try {
|
|
1668
|
+
result = resolve(p, a, c, i);
|
|
1669
|
+
} catch (error) {
|
|
1670
|
+
return fail(error, p, a, c, i);
|
|
1671
|
+
}
|
|
1672
|
+
return isThenable(result) ? recoverAsync(result, fail, p, a, c, i) : result;
|
|
1673
|
+
};
|
|
1674
|
+
}
|
|
1675
|
+
function recoverAsync(result, fail, p, a, c, i) {
|
|
1676
|
+
return Promise.resolve(result).then(void 0, (error) => fail(error, p, a, c, i));
|
|
1677
|
+
}
|
|
1678
|
+
/** A source event stream of one event. */
|
|
1679
|
+
async function* once(value) {
|
|
1680
|
+
yield value;
|
|
1681
|
+
}
|
|
1649
1682
|
/** Runs guards in order, synchronously until one returns a promise. */
|
|
1650
1683
|
function runGuards(container, guards, ctx, start = 0) {
|
|
1651
1684
|
for (let index = start; index < guards.length; index++) {
|
|
@@ -1830,12 +1863,14 @@ function builtinScalar(name) {
|
|
|
1830
1863
|
* `parseValue` → `coerceInputValue`, `parseLiteral` → `coerceInputLiteral` (default: `parseValue`
|
|
1831
1864
|
* of the literal's JSON value). The definition's methods are called on the definition.
|
|
1832
1865
|
*/
|
|
1833
|
-
function definitionScalar(name, definition) {
|
|
1866
|
+
function definitionScalar(name, definition, overrides = {}) {
|
|
1834
1867
|
const parseLiteral = definition.parseLiteral;
|
|
1868
|
+
const description = "description" in overrides ? overrides.description : definition.description;
|
|
1869
|
+
const specifiedByURL = "specifiedByURL" in overrides ? overrides.specifiedByURL : definition.specifiedByURL;
|
|
1835
1870
|
return new GraphQLScalarType({
|
|
1836
1871
|
name,
|
|
1837
|
-
...
|
|
1838
|
-
...
|
|
1872
|
+
...description === void 0 ? {} : { description },
|
|
1873
|
+
...specifiedByURL === void 0 ? {} : { specifiedByURL },
|
|
1839
1874
|
coerceOutputValue: (value) => definition.serialize(value),
|
|
1840
1875
|
coerceInputValue: (value) => definition.parseValue(value),
|
|
1841
1876
|
coerceInputLiteral: parseLiteral === void 0 ? (node) => definition.parseValue(valueFromASTUntyped(node)) : (node) => parseLiteral.call(definition, node)
|
|
@@ -1861,6 +1896,7 @@ const INPUT_KINDS = /* @__PURE__ */ new Set([
|
|
|
1861
1896
|
]);
|
|
1862
1897
|
const OUTPUT_KINDS = /* @__PURE__ */ new Set([
|
|
1863
1898
|
"object",
|
|
1899
|
+
"interface",
|
|
1864
1900
|
"enum",
|
|
1865
1901
|
"scalar",
|
|
1866
1902
|
"union"
|
|
@@ -1940,7 +1976,10 @@ function checkGraphQLSection(section) {
|
|
|
1940
1976
|
if (!isRecord(type) || typeof type["name"] !== "string") continue;
|
|
1941
1977
|
const name = type["name"];
|
|
1942
1978
|
switch (type["kind"]) {
|
|
1943
|
-
case "object":
|
|
1979
|
+
case "object":
|
|
1980
|
+
case "interface": {
|
|
1981
|
+
const isInterface = type["kind"] === "interface";
|
|
1982
|
+
checkInterfaces(name, type["interfaces"], kinds, problems);
|
|
1944
1983
|
const fields = type["fields"];
|
|
1945
1984
|
if (!Array.isArray(fields) || fields.length === 0) {
|
|
1946
1985
|
problems.push(`${name}: fields must be a non-empty array`);
|
|
@@ -1958,7 +1997,13 @@ function checkGraphQLSection(section) {
|
|
|
1958
1997
|
seen.add(field["name"]);
|
|
1959
1998
|
checkRef(coordinate, field["type"], OUTPUT_KINDS, "output");
|
|
1960
1999
|
if (field["args"] !== void 0) checkInputValues(`${coordinate} arguments`, field["args"], false);
|
|
1961
|
-
if (field["resolver"]
|
|
2000
|
+
if (field["resolver"] === void 0) continue;
|
|
2001
|
+
if (isInterface) problems.push(`${coordinate}: interface fields have no resolver (bind it on the object types)`);
|
|
2002
|
+
else checkBinding(coordinate, field["resolver"], problems);
|
|
2003
|
+
}
|
|
2004
|
+
if (isInterface) {
|
|
2005
|
+
const implementations = types.filter((other) => isRecord(other) && other["kind"] === "object" && Array.isArray(other["interfaces"]) && other["interfaces"].includes(name));
|
|
2006
|
+
checkDiscriminator(name, type["discriminator"], implementations.map((other) => other["name"]), problems);
|
|
1962
2007
|
}
|
|
1963
2008
|
break;
|
|
1964
2009
|
}
|
|
@@ -2002,6 +2047,8 @@ function checkGraphQLSection(section) {
|
|
|
2002
2047
|
if (scalar.name !== name) problems.push(`the scalar ${name} is implemented by the graphql-js scalar ${scalar.name} (names must match)`);
|
|
2003
2048
|
} else if (!isRecord(scalar) || typeof scalar["serialize"] !== "function" || typeof scalar["parseValue"] !== "function" || scalar["parseLiteral"] !== void 0 && typeof scalar["parseLiteral"] !== "function") problems.push(`the scalar ${name} needs a definition { serialize, parseValue, parseLiteral? } or a GraphQLScalarType (received ${show(scalar)})`);
|
|
2004
2049
|
}
|
|
2050
|
+
for (const key of ["description", "specifiedByURL"]) if (type[key] !== void 0 && typeof type[key] !== "string") problems.push(`${name}: ${key} must be a string`);
|
|
2051
|
+
if (type["sdl"] !== void 0 && type["sdl"] !== true) problems.push(`${name}: sdl must be true when present`);
|
|
2005
2052
|
break;
|
|
2006
2053
|
default: problems.push(`${name}: unknown kind ${show(type["kind"])}`);
|
|
2007
2054
|
}
|
|
@@ -2010,6 +2057,15 @@ function checkGraphQLSection(section) {
|
|
|
2010
2057
|
checkGroups(section["groups"], problems);
|
|
2011
2058
|
return problems;
|
|
2012
2059
|
}
|
|
2060
|
+
/** `interfaces` of an object or interface type: absent, or interface type names. */
|
|
2061
|
+
function checkInterfaces(name, interfaces, kinds, problems) {
|
|
2062
|
+
if (interfaces === void 0) return;
|
|
2063
|
+
if (!Array.isArray(interfaces)) {
|
|
2064
|
+
problems.push(`${name}: interfaces must be an array of interface type names (received ${show(interfaces)})`);
|
|
2065
|
+
return;
|
|
2066
|
+
}
|
|
2067
|
+
for (const entry of interfaces) if (typeof entry !== "string" || kinds.get(entry) !== "interface") problems.push(`${name}: ${show(entry)} is not an interface type`);
|
|
2068
|
+
}
|
|
2013
2069
|
function checkBinding(coordinate, binding, problems) {
|
|
2014
2070
|
if (!isRecord(binding)) {
|
|
2015
2071
|
problems.push(`${coordinate}: resolver must be an object (received ${show(binding)})`);
|
|
@@ -2094,7 +2150,7 @@ function checkGroups(groups, problems) {
|
|
|
2094
2150
|
function invalidSchema(problems) {
|
|
2095
2151
|
throw new ZnightError(ErrorCode.InvalidGraphQLSetup, `GraphQLPlugin: invalid GraphQL schema (metadata.extensions.graphql), ${problems.length} ${problems.length === 1 ? "problem" : "problems"}:\n${problems.map((problem) => ` - ${problem}`).join("\n")}`, { hints: ["Regenerate the metadata with the @znight/graphql version the application runs (znight generate)", "Hand-written metadata must follow GraphQLMetadata (@znight/graphql, RFC 0005 §7.1)"] });
|
|
2096
2152
|
}
|
|
2097
|
-
/**
|
|
2153
|
+
/** The `resolveType` of a union or an interface, from its discriminator: data read once, at boot. */
|
|
2098
2154
|
function typeResolver(discriminator) {
|
|
2099
2155
|
switch (discriminator.by) {
|
|
2100
2156
|
case "property": {
|
|
@@ -2153,7 +2209,7 @@ function buildGraphQLSchema(section, functions) {
|
|
|
2153
2209
|
...field.deprecationReason === void 0 ? {} : { deprecationReason: field.deprecationReason },
|
|
2154
2210
|
...field.args === void 0 || field.args.length === 0 ? {} : { args: Object.fromEntries(field.args.map((arg) => [arg.name, inputValue(arg)])) }
|
|
2155
2211
|
};
|
|
2156
|
-
if (field.resolver === void 0) return config;
|
|
2212
|
+
if (field.resolver === void 0 || type.kind === "interface") return config;
|
|
2157
2213
|
const resolved = functions(field.resolver, `${type.name}.${field.name}`, field, type);
|
|
2158
2214
|
return {
|
|
2159
2215
|
...config,
|
|
@@ -2189,8 +2245,16 @@ function createType(type, fieldConfig, inputValue, lookup) {
|
|
|
2189
2245
|
case "object": return new GraphQLObjectType({
|
|
2190
2246
|
name: type.name,
|
|
2191
2247
|
...description,
|
|
2248
|
+
...interfacesOf(type, lookup),
|
|
2192
2249
|
fields: () => Object.fromEntries(type.fields.map((field) => [field.name, fieldConfig(type, field)]))
|
|
2193
2250
|
});
|
|
2251
|
+
case "interface": return new GraphQLInterfaceType({
|
|
2252
|
+
name: type.name,
|
|
2253
|
+
...description,
|
|
2254
|
+
...interfacesOf(type, lookup),
|
|
2255
|
+
fields: () => Object.fromEntries(type.fields.map((field) => [field.name, fieldConfig(type, field)])),
|
|
2256
|
+
resolveType: typeResolver(type.discriminator)
|
|
2257
|
+
});
|
|
2194
2258
|
case "input": return new GraphQLInputObjectType({
|
|
2195
2259
|
name: type.name,
|
|
2196
2260
|
...description,
|
|
@@ -2213,10 +2277,153 @@ function createType(type, fieldConfig, inputValue, lookup) {
|
|
|
2213
2277
|
});
|
|
2214
2278
|
default: {
|
|
2215
2279
|
if ("builtin" in type) return builtinScalar(type.name);
|
|
2280
|
+
const sdl = type.sdl === true;
|
|
2281
|
+
const overrides = {
|
|
2282
|
+
...type.description === void 0 && !sdl ? {} : { description: type.description },
|
|
2283
|
+
...type.specifiedByURL === void 0 && !sdl ? {} : { specifiedByURL: type.specifiedByURL }
|
|
2284
|
+
};
|
|
2216
2285
|
const scalar = type.scalar;
|
|
2217
|
-
|
|
2286
|
+
if (scalar instanceof GraphQLScalarType) {
|
|
2287
|
+
if (Object.keys(overrides).length === 0) return scalar;
|
|
2288
|
+
const config = scalar.toConfig();
|
|
2289
|
+
return new GraphQLScalarType({
|
|
2290
|
+
...config,
|
|
2291
|
+
description: "description" in overrides ? overrides.description : config.description,
|
|
2292
|
+
specifiedByURL: "specifiedByURL" in overrides ? overrides.specifiedByURL : config.specifiedByURL
|
|
2293
|
+
});
|
|
2294
|
+
}
|
|
2295
|
+
return definitionScalar(type.name, scalar, overrides);
|
|
2296
|
+
}
|
|
2297
|
+
}
|
|
2298
|
+
}
|
|
2299
|
+
/** The `interfaces` thunk of an object or interface type (none: no key, as before RFC 0007). */
|
|
2300
|
+
function interfacesOf(type, lookup) {
|
|
2301
|
+
const names = type.interfaces;
|
|
2302
|
+
if (names === void 0 || names.length === 0) return {};
|
|
2303
|
+
return { interfaces: () => names.map((name) => lookup(name)) };
|
|
2304
|
+
}
|
|
2305
|
+
// src/results.ts
|
|
2306
|
+
/** The object types a result mapper ref declares: `static types` of a class; for an instance, its `types` property or its class's. */
|
|
2307
|
+
function declaredTypes(ref) {
|
|
2308
|
+
try {
|
|
2309
|
+
if (typeof ref === "function") return ref.types;
|
|
2310
|
+
const own = ref.types;
|
|
2311
|
+
if (own !== void 0) return own;
|
|
2312
|
+
return ref.constructor?.types;
|
|
2313
|
+
} catch {
|
|
2314
|
+
return;
|
|
2315
|
+
}
|
|
2316
|
+
}
|
|
2317
|
+
/**
|
|
2318
|
+
* Checks the declared types of the result mappers against the schema's types: a non-empty array of
|
|
2319
|
+
* names of object types. Every problem in one ZNIGHT066 error.
|
|
2320
|
+
*/
|
|
2321
|
+
function checkResultTypes(mappers, types) {
|
|
2322
|
+
if (mappers.length === 0) return;
|
|
2323
|
+
const kinds = new Map(types.map((type) => [type?.name, type?.kind]));
|
|
2324
|
+
const problems = [];
|
|
2325
|
+
for (const mapper of mappers) {
|
|
2326
|
+
const declared = mapper.types;
|
|
2327
|
+
if (!Array.isArray(declared) || declared.length === 0) {
|
|
2328
|
+
problems.push(`the result mapper ${mapper.name} declares no types (static readonly types = [...object type names])`);
|
|
2329
|
+
continue;
|
|
2218
2330
|
}
|
|
2331
|
+
for (const name of declared) if (typeof name !== "string" || kinds.get(name) !== "object") problems.push(`the result mapper ${mapper.name} declares ${typeof name === "string" ? name : String(name)}, which is not an object type of the schema`);
|
|
2219
2332
|
}
|
|
2333
|
+
if (problems.length === 0) return;
|
|
2334
|
+
throw new ZnightError(ErrorCode.InvalidGraphQLSetup, `GraphQLPlugin: ${problems.join("; ")}`, { hints: ["A result mapper declares the object types its values may have: static readonly types = ['NotFoundRejection'] (RFC 0008)"] });
|
|
2335
|
+
}
|
|
2336
|
+
/** Decides which fields carry results, and builds their {@link Recover} functions. */
|
|
2337
|
+
var ResultMappers = class {
|
|
2338
|
+
#container;
|
|
2339
|
+
#logger;
|
|
2340
|
+
#mappers;
|
|
2341
|
+
#types;
|
|
2342
|
+
/** Built on the first field, once the schema construction checked the section's structure. */
|
|
2343
|
+
#index;
|
|
2344
|
+
#fields = 0;
|
|
2345
|
+
constructor(container, logger, mappers, types) {
|
|
2346
|
+
this.#container = container;
|
|
2347
|
+
this.#logger = logger;
|
|
2348
|
+
this.#mappers = mappers;
|
|
2349
|
+
this.#types = types;
|
|
2350
|
+
}
|
|
2351
|
+
/** Fields that carry results so far. */
|
|
2352
|
+
get fields() {
|
|
2353
|
+
return this.#fields;
|
|
2354
|
+
}
|
|
2355
|
+
/** The {@link Recover} function of `field`, `undefined` when it carries no result. */
|
|
2356
|
+
recover(field, coordinate) {
|
|
2357
|
+
if (this.#mappers.length === 0) return void 0;
|
|
2358
|
+
const ref = parseTypeRef(field.type);
|
|
2359
|
+
if (ref?.kind !== "named") return void 0;
|
|
2360
|
+
this.#index ??= index(this.#types);
|
|
2361
|
+
const objects = this.#index.objects;
|
|
2362
|
+
const union = this.#index.unions.get(ref.name);
|
|
2363
|
+
if (union === void 0) return void 0;
|
|
2364
|
+
const applicable = this.#mappers.filter((mapper) => mapper.types.some((type) => union.members.has(type)));
|
|
2365
|
+
if (applicable.length === 0) return void 0;
|
|
2366
|
+
this.#fields++;
|
|
2367
|
+
const container = this.#container;
|
|
2368
|
+
const logger = this.#logger;
|
|
2369
|
+
return (error, ctx) => {
|
|
2370
|
+
for (const mapper of applicable) {
|
|
2371
|
+
let value;
|
|
2372
|
+
let type;
|
|
2373
|
+
try {
|
|
2374
|
+
value = resolveIn(container, mapper.resolvable).toResult(error, ctx);
|
|
2375
|
+
if (value === void 0 || value === null) continue;
|
|
2376
|
+
if (isThenable(value)) {
|
|
2377
|
+
Promise.resolve(value).catch(noop);
|
|
2378
|
+
logger.error(`GraphQLPlugin: the result mapper ${mapper.name} returned a promise: toResult() must be synchronous`, details(ctx, coordinate, error));
|
|
2379
|
+
continue;
|
|
2380
|
+
}
|
|
2381
|
+
type = union.typeOf(value);
|
|
2382
|
+
} catch (failure) {
|
|
2383
|
+
logger.error(`GraphQLPlugin: the result mapper ${mapper.name} failed`, details(ctx, coordinate, failure));
|
|
2384
|
+
continue;
|
|
2385
|
+
}
|
|
2386
|
+
if (type !== void 0 && union.members.has(type)) {
|
|
2387
|
+
logger.debug(`GraphQL: error resolved as ${type} in ${coordinate}`, {
|
|
2388
|
+
coordinate,
|
|
2389
|
+
type
|
|
2390
|
+
});
|
|
2391
|
+
return value;
|
|
2392
|
+
}
|
|
2393
|
+
if (type !== void 0 && objects.has(type)) logger.debug(`GraphQLPlugin: the result mapper ${mapper.name} returned a value of ${type}, not a member of ${union.name} (${coordinate}): skipped`, details(ctx, coordinate, error));
|
|
2394
|
+
else logger.warn(`GraphQLPlugin: the result mapper ${mapper.name} returned a value of no type of the schema (${coordinate}): skipped`, details(ctx, coordinate, error));
|
|
2395
|
+
}
|
|
2396
|
+
throw error;
|
|
2397
|
+
};
|
|
2398
|
+
}
|
|
2399
|
+
};
|
|
2400
|
+
function index(types) {
|
|
2401
|
+
const objects = /* @__PURE__ */ new Set();
|
|
2402
|
+
const unions = /* @__PURE__ */ new Map();
|
|
2403
|
+
for (const type of types) if (type.kind === "object") objects.add(type.name);
|
|
2404
|
+
else if (type.kind === "union") unions.set(type.name, {
|
|
2405
|
+
name: type.name,
|
|
2406
|
+
members: new Set(type.members),
|
|
2407
|
+
typeOf: typeResolver(type.discriminator)
|
|
2408
|
+
});
|
|
2409
|
+
return {
|
|
2410
|
+
objects,
|
|
2411
|
+
unions
|
|
2412
|
+
};
|
|
2413
|
+
}
|
|
2414
|
+
function noop() {}
|
|
2415
|
+
function details(ctx, coordinate, err) {
|
|
2416
|
+
return {
|
|
2417
|
+
err,
|
|
2418
|
+
requestId: ctx.operation.requestId,
|
|
2419
|
+
coordinate,
|
|
2420
|
+
path: pathOf(ctx.info).join(".")
|
|
2421
|
+
};
|
|
2422
|
+
}
|
|
2423
|
+
function pathOf(info) {
|
|
2424
|
+
const path = [];
|
|
2425
|
+
for (let step = info.path; step !== void 0; step = step.prev) path.push(step.key);
|
|
2426
|
+
return path.reverse();
|
|
2220
2427
|
}
|
|
2221
2428
|
// src/transport/abort.ts
|
|
2222
2429
|
/** The stand-in passed as graphql-js `abortSignal` by the HTTP transport (one per request). */
|
|
@@ -2854,14 +3061,18 @@ var GraphQLPlugin = class GraphQLPlugin {
|
|
|
2854
3061
|
return new GraphQLPlugin(options);
|
|
2855
3062
|
}
|
|
2856
3063
|
/**
|
|
2857
|
-
* Providers `TestApp` creates at boot: the classes of the `guards` and `
|
|
3064
|
+
* Providers `TestApp` creates at boot: the classes of the `guards`, `errorMappers` and `resultMappers` options, and
|
|
2858
3065
|
* the resolvers, guards and loaders of the metadata.
|
|
2859
3066
|
*/
|
|
2860
3067
|
initRoots(context) {
|
|
2861
3068
|
const roots = /* @__PURE__ */ new Set();
|
|
2862
3069
|
try {
|
|
2863
3070
|
const options = this.#options;
|
|
2864
|
-
if (options !== null && typeof options === "object") for (const key of [
|
|
3071
|
+
if (options !== null && typeof options === "object") for (const key of [
|
|
3072
|
+
"guards",
|
|
3073
|
+
"errorMappers",
|
|
3074
|
+
"resultMappers"
|
|
3075
|
+
]) {
|
|
2865
3076
|
const refs = options[key];
|
|
2866
3077
|
if (Array.isArray(refs)) {
|
|
2867
3078
|
for (const ref of refs) if (typeof ref === "function") roots.add(ref);
|
|
@@ -3045,6 +3256,10 @@ var GraphQLPlugin = class GraphQLPlugin {
|
|
|
3045
3256
|
const providers = new Providers(container, metadata);
|
|
3046
3257
|
const globalGuards = options.guards.map((ref) => providers.ref(ref, "guard", "canResolve"));
|
|
3047
3258
|
const mappers = options.errorMappers.map((ref) => providers.ref(ref, "error mapper", "map"));
|
|
3259
|
+
const resultMappers = options.resultMappers.map((ref) => ({
|
|
3260
|
+
...providers.ref(ref, "result mapper", "toResult"),
|
|
3261
|
+
types: declaredTypes(ref)
|
|
3262
|
+
}));
|
|
3048
3263
|
const inputs = /* @__PURE__ */ new Map();
|
|
3049
3264
|
const fields = [];
|
|
3050
3265
|
const types = Array.isArray(section?.types) ? section.types : [];
|
|
@@ -3070,12 +3285,19 @@ var GraphQLPlugin = class GraphQLPlugin {
|
|
|
3070
3285
|
});
|
|
3071
3286
|
}
|
|
3072
3287
|
}
|
|
3288
|
+
if (resultMappers.length > 0 && checkGraphQLSection(section).length === 0) checkResultTypes(resultMappers, types);
|
|
3289
|
+
const results = new ResultMappers(container, ctx.logger, resultMappers.map((mapper) => ({
|
|
3290
|
+
resolvable: mapper.resolvable,
|
|
3291
|
+
name: mapper.name,
|
|
3292
|
+
types: mapper.types
|
|
3293
|
+
})), types);
|
|
3073
3294
|
const schema = buildGraphQLSchema(section, pipelineFactory({
|
|
3074
3295
|
container,
|
|
3075
3296
|
globalGuards: globalGuards.map((guard) => guard.resolvable),
|
|
3076
3297
|
guard: (token, coordinate) => providers.token(token, `guard of ${coordinate}`, "canResolve"),
|
|
3077
3298
|
resolver: (binding, coordinate) => providers.token(binding.token, `resolver of ${coordinate}`, binding.method),
|
|
3078
|
-
inputs
|
|
3299
|
+
inputs,
|
|
3300
|
+
results: (field, coordinate) => results.recover(field, coordinate)
|
|
3079
3301
|
}));
|
|
3080
3302
|
checkFieldDecisions(fields, section.groups, options.enforcement, ctx.logger);
|
|
3081
3303
|
const formatter = new ErrorFormatter({
|
|
@@ -3103,7 +3325,8 @@ var GraphQLPlugin = class GraphQLPlugin {
|
|
|
3103
3325
|
});
|
|
3104
3326
|
ctx.logger.debug("GraphQL schema built", {
|
|
3105
3327
|
types: types.length,
|
|
3106
|
-
schemaHash: section.schemaHash
|
|
3328
|
+
schemaHash: section.schemaHash,
|
|
3329
|
+
...results.fields === 0 ? {} : { resultFields: results.fields }
|
|
3107
3330
|
});
|
|
3108
3331
|
return {
|
|
3109
3332
|
schema,
|
|
@@ -3219,4 +3442,4 @@ var DeferredPubSub = class extends PubSub {
|
|
|
3219
3442
|
};
|
|
3220
3443
|
export { MemoryPubSub as a, GraphQLErrorCode as c, GraphQLTokens as i, GraphQLException as l, endpointOf as n, PubSub as o, executorOf as r, orderByKeys as s, GraphQLPlugin as t };
|
|
3221
3444
|
|
|
3222
|
-
//# sourceMappingURL=plugin-
|
|
3445
|
+
//# sourceMappingURL=plugin-CR_wVfRU.mjs.map
|