@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.
Files changed (42) hide show
  1. package/README.md +307 -11
  2. package/dist/compiler.d.mts +24 -3
  3. package/dist/compiler.mjs +1876 -109
  4. package/dist/compiler.mjs.map +1 -1
  5. package/dist/index.d.mts +49 -8
  6. package/dist/index.mjs +10 -4
  7. package/dist/index.mjs.map +1 -1
  8. package/dist/names-BlzDM5cF.mjs.map +1 -1
  9. package/dist/{plugin-BdBxr5NQ.mjs → plugin-CR_wVfRU.mjs} +247 -24
  10. package/dist/plugin-CR_wVfRU.mjs.map +1 -0
  11. package/dist/testing.d.mts +1 -1
  12. package/dist/testing.mjs +1 -1
  13. package/dist/{types-CgbV9LjD.d.mts → types-DQ713UgI.d.mts} +34 -2
  14. package/package.json +14 -9
  15. package/src/compiler/analyze.ts +129 -10
  16. package/src/compiler/builder.ts +6 -85
  17. package/src/compiler/checks.ts +105 -0
  18. package/src/compiler/conformance.ts +1068 -0
  19. package/src/compiler/constants.ts +6 -0
  20. package/src/compiler/data.ts +10 -0
  21. package/src/compiler/generate.ts +41 -27
  22. package/src/compiler/graphql-js.ts +42 -0
  23. package/src/compiler/index.ts +8 -3
  24. package/src/compiler/locate.ts +39 -1
  25. package/src/compiler/model.ts +16 -1
  26. package/src/compiler/options.ts +39 -8
  27. package/src/compiler/schema-first.ts +595 -0
  28. package/src/compiler/sdl-source.ts +529 -0
  29. package/src/compiler/sdl-view.ts +3 -1
  30. package/src/compiler/suggest.ts +39 -0
  31. package/src/decorators.ts +9 -3
  32. package/src/index.ts +5 -0
  33. package/src/metadata.ts +28 -1
  34. package/src/options.ts +23 -1
  35. package/src/pipeline.ts +66 -5
  36. package/src/plugin.ts +27 -5
  37. package/src/results.ts +206 -0
  38. package/src/scalars.ts +10 -3
  39. package/src/schema.ts +90 -12
  40. package/src/sdl.ts +17 -2
  41. package/src/types.ts +35 -0
  42. 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
- if (binding.operation === "subscription") return {
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
- ...definition.description === void 0 ? {} : { description: definition.description },
1838
- ...definition.specifiedByURL === void 0 ? {} : { specifiedByURL: definition.specifiedByURL },
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"] !== void 0) checkBinding(coordinate, field["resolver"], problems);
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
- /** A union's `resolveType`, from its discriminator: data read once, at boot. */
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
- return scalar instanceof GraphQLScalarType ? scalar : definitionScalar(type.name, scalar);
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 `errorMappers` options, 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 ["guards", "errorMappers"]) {
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-BdBxr5NQ.mjs.map
3445
+ //# sourceMappingURL=plugin-CR_wVfRU.mjs.map