@znight/http 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -236,6 +236,36 @@ the server's time zone) answer `400`. A missing required value answers `400 Miss
236
236
  `| undefined`, default value) receive `undefined` when absent, so default values apply:
237
237
  `@Query('page') page = 1`. A repeated query key for a scalar parameter is a `400`.
238
238
 
239
+ ### Custom parameter decorators
240
+
241
+ `createParamDecorator` from `@znight/core` defines your own parameter decorators, such as a
242
+ `@CurrentUser()` reading what an authentication guard stored (RFC 0010). The `http` resolver
243
+ receives the `HttpContext` and the data argument of the use:
244
+
245
+ ```ts
246
+ import { createParamDecorator } from '@znight/core'
247
+ import { UnauthorizedException } from '@znight/http'
248
+ import type { User } from './user.ts'
249
+
250
+ export const CurrentUser = createParamDecorator<User>({
251
+ http: ctx => {
252
+ const user = ctx.state.get('user') as User | undefined
253
+ if (user === undefined) throw new UnauthorizedException()
254
+ return user
255
+ },
256
+ })
257
+
258
+ // @Get('/me') me(@CurrentUser() user: User) { return user }
259
+ ```
260
+
261
+ The resolver runs inside `invoke`, after the guards and the interceptors, in parameter order with
262
+ the other arguments; what it throws is answered like a handler error. It may return a promise (the
263
+ route then awaits it), or be the class of a provider implementing `ParamResolver` (constructor
264
+ injection, any scope: a request-scoped resolver is created once per request). The compiler checks
265
+ every use: the decorator must have an `http` resolver, be the only parameter decorator of the
266
+ parameter, and inject a type the parameter accepts. Custom parameters are not documented by
267
+ `@znight/openapi`: document the header or cookie they read with `@ApiHeader` or `@ApiSecurity`.
268
+
239
269
  ## Validation
240
270
 
241
271
  Any library implementing [Standard Schema v1](https://standardschema.dev) works, without adapter:
@@ -2151,8 +2181,12 @@ Rules:
2151
2181
  stream (an `HttpResponse` or a Web `Response` is sent as usual). Only on `GET` routes; invalid
2152
2182
  values are ZNIGHT007 at boot.
2153
2183
  - Param sources (`HttpParamSource`): `param`, `query`, `body`, `headers`, `request`, `response`,
2154
- `context`, `file`, `files`, `cookie` and `signedCookie` (`name`: the cookie name; absent for
2155
- `@Cookie()`). A route with a `signedCookie` parameter fails the boot without `cookies.secrets`
2184
+ `context`, `file`, `files`, `cookie`, `signedCookie` (`name`: the cookie name; absent for
2185
+ `@Cookie()`) and `custom` (a custom parameter decorator, `name`: the decorator's name).
2186
+ - `paramResolvers` (`Token[]`, omitted when empty): the provider classes resolving custom
2187
+ parameters (`ParamResolver`), in order of first use. `invoke` then takes a third parameter `x`,
2188
+ the instances in that order; the plugin resolves them once at boot when they are all singletons,
2189
+ otherwise in the request scope of each call. A route with a `signedCookie` parameter fails the boot without `cookies.secrets`
2156
2190
  (ZNIGHT006).
2157
2191
  - Guards, interceptors and controllers referenced by tokens must be providers (the compiler
2158
2192
  registers injectable helper classes, §6.5 `resolveClassRef`). Request-scoped guards/interceptors
@@ -2212,6 +2246,14 @@ expressions of the table below are `validate(S, ...)` calls in both forms.
2212
2246
  | `@UploadedFiles('f') f: File[]` | `uploads.files(ctx, 'f')` · optional: `uploads.optionalFiles(ctx, 'f')` |
2213
2247
  | `@UploadedFile()` / `@UploadedFiles()` | the same helpers with `undefined` as field (any field) |
2214
2248
  | `@Ctx()` | `ctx` |
2249
+ | custom, function resolver | `(D.resolvers.http as ParamFunctionOf<typeof D, 'http'>)(ctx, data)` |
2250
+ | custom, class resolver | `x[i].resolve(ctx, data)` (`data` left out when `resolve` declares one param) |
2251
+
2252
+ Custom parameters (`createParamDecorator`, RFC 0010) are treated like validated arguments: the
2253
+ resolver may return a thenable, so each one is followed by the `isThenable` fallback, and the route
2254
+ stays synchronous while the resolvers return values. `data` is the literal data argument or an
2255
+ imported reference (`undefined` without argument). The cast `as never` is added when the compiler
2256
+ could not check the value type against the parameter.
2215
2257
 
2216
2258
  `coerce.X` by declared type (`T`):
2217
2259
 
@@ -403,6 +403,12 @@ interface HttpRouteInfo {
403
403
  */
404
404
  readonly meta: Readonly<Record<string, unknown>>;
405
405
  }
406
+ declare module "@znight/core" {
407
+ /** Custom parameter decorators (`createParamDecorator`) resolve HTTP parameters from the {@link HttpContext}. */
408
+ interface ParamContexts {
409
+ http: HttpContext;
410
+ }
411
+ }
406
412
  /**
407
413
  * Adapter-neutral view of the current HTTP request, shared by guards, interceptors, exception
408
414
  * mappers and handlers (`@Ctx()`), and injectable in request-scoped providers.
@@ -528,7 +534,7 @@ type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "HEAD" | "OPTION
528
534
  * `name` being the multipart field; `cookie` / `signedCookie`: `@Cookie` / `@SignedCookie`, the
529
535
  * `name` being the cookie name).
530
536
  */
531
- type HttpParamSource = "param" | "query" | "body" | "headers" | "request" | "response" | "context" | "file" | "files" | "cookie" | "signedCookie";
537
+ type HttpParamSource = "param" | "query" | "body" | "headers" | "request" | "response" | "context" | "file" | "files" | "cookie" | "signedCookie" | "custom";
532
538
  /** A handler parameter (informational: extraction, coercion and validation happen in `invoke`). */
533
539
  interface HttpParamDefinition {
534
540
  /** Position in the handler signature. */
@@ -613,10 +619,17 @@ interface HttpRouteDefinition {
613
619
  */
614
620
  readonly sse?: HttpSseDefinition;
615
621
  /**
616
- * Calls the handler: extracts, coerces and validates every argument from the context, then calls
617
- * the method on the controller instance. May return a promise.
622
+ * Provider classes resolving custom parameters (`ParamResolver`, RFC 0010), in order of first use:
623
+ * the plugin resolves them in the request scope and passes the instances to `invoke` as `x`
624
+ * (`x[i]` is the instance of `paramResolvers[i]`). Absent when the route has none.
625
+ */
626
+ readonly paramResolvers?: readonly Token[];
627
+ /**
628
+ * Calls the handler: extracts, coerces and validates every argument from the context (custom
629
+ * parameters included), then calls the method on the controller instance. May return a promise.
630
+ * `resolvers` is passed when the route has {@link paramResolvers}.
618
631
  */
619
- readonly invoke: (controller: any, ctx: HttpContext) => unknown;
632
+ readonly invoke: (controller: any, ctx: HttpContext, resolvers?: any) => unknown;
620
633
  }
621
634
  /** A controller: a provider whose methods are routes. */
622
635
  interface HttpControllerDefinition {
@@ -879,4 +892,4 @@ interface HttpAdapter {
879
892
  injectStream?(request: InjectStreamRequest): Promise<InjectStreamResponse>;
880
893
  }
881
894
  export { PayloadTooLargeException as $, UploadedFileOptions as A, BadRequestException as B, HttpUploadDefinition as C, CookieOptions as Ct, RequestAbortedError as D, parseSetCookie as Dt, RequestAbortKind as E, parseCookieHeader as Et, formatByteSize as F, HeaderValue as G, ForbiddenException as H, parseByteSize as I, HttpExceptionOptions as J, HttpErrorBody as K, QueryRecord as L, Uploads as M, uploads as N, RequestId as O, serializeClearCookie as Ot, ByteSize as P, NotImplementedException as Q, parseQuery as R, HttpSseDefinition as S, CookieDefaults as St, HttpRouteInfo as T, SetCookieInfo as Tt, GatewayTimeoutException as U, ConflictException as V, GoneException as W, MethodNotAllowedException as X, InternalServerErrorException as Y, NotFoundException as Z, HttpMethod as _, StandardSchemaV1Result as _t, AdapterRoute as a, ValidationException as at, HttpRouteDefinition as b, isStandardSchema as bt, HttpServerAddress as c, toIssueDetail as ct, InjectStreamOptions as d, StandardSchemaV1 as dt, ServiceUnavailableException as et, InjectStreamRequest as f, StandardSchemaV1FailureResult as ft, HttpMetadata as g, StandardSchemaV1Props as gt, HttpControllerDefinition as h, StandardSchemaV1PathSegment as ht, AdapterRequest as i, UnsupportedMediaTypeException as it, UploadedFilesOptions as j, UploadLimits as k, serializeCookie as kt, InjectRequest as l, InferSchemaInput as lt, ReplyBody as m, StandardSchemaV1Options as mt, AdapterHandler as n, UnauthorizedException as nt, FallbackHandler as o, ValidationIssueDetail as ot, InjectStreamResponse as p, StandardSchemaV1Issue as pt, HttpException as q, AdapterReply as r, UnprocessableEntityException as rt, HttpAdapter as s, isHttpException as st, AdapterCloseOptions as t, TooManyRequestsException as tt, InjectResponse as u, InferSchemaOutput as ut, HttpParamDefinition as v, StandardSchemaV1SuccessResult as vt, HttpContext as w, ResponseCookie as wt, HttpRouteGroupDefinition as x, ClearCookieOptions as xt, HttpParamSource as y, StandardSchemaV1Types as yt, BadGatewayException as z };
882
- //# sourceMappingURL=adapter-J0UXFnNM.d.mts.map
895
+ //# sourceMappingURL=adapter-DmhQGqAR.d.mts.map
@@ -1,4 +1,4 @@
1
- import { _ as HttpMethod, y as HttpParamSource } from "./adapter-J0UXFnNM.mjs";
1
+ import { _ as HttpMethod, y as HttpParamSource } from "./adapter-DmhQGqAR.mjs";
2
2
  import { AnalyzeContext, ClassInfo, ClassRef, CompilerExtension, Location, MethodInfo, StaticValue, ValidateContext, ValueRef } from "@znight/compiler";
3
3
  /** Primitive coercions (`coerce.<kind>`). */
4
4
  type ScalarCoercion = "string" | "number" | "integer" | "boolean" | "bigint" | "date";
@@ -46,6 +46,26 @@ interface UploadData {
46
46
  /** The injected value does not have the declared type (`node:buffer` File, `File | null`): `as never`. */
47
47
  readonly cast: boolean;
48
48
  }
49
+ /** A custom parameter (`createParamDecorator`, RFC 0010). */
50
+ interface CustomParamData {
51
+ /** Canonical identity of the decorator `const` (imported by the generated module). */
52
+ readonly identity: string;
53
+ /** Name of the decorator (`CurrentUser`). */
54
+ readonly name: string;
55
+ /** The data argument: evaluated value (it may contain references) or a reference; `undefined` without argument. */
56
+ readonly data: {
57
+ readonly value: StaticValue;
58
+ } | undefined;
59
+ /** The class resolver (`x[i].resolve(ctx, data)`); `undefined` for a function resolver. */
60
+ readonly resolver: ClassRef | undefined;
61
+ /**
62
+ * Parameters of the class resolver's `resolve` (own, inherited or an arrow function property);
63
+ * `undefined` for a function resolver, or when the compiler cannot read it (the call is cast).
64
+ */
65
+ readonly arity?: number;
66
+ /** The value type could not be checked against the parameter: `as never`. */
67
+ readonly cast: boolean;
68
+ }
49
69
  /** A handler parameter. */
50
70
  interface HttpParamData {
51
71
  /** Position in the handler signature. */
@@ -65,6 +85,8 @@ interface HttpParamData {
65
85
  readonly coercion: ParamCoercion | undefined;
66
86
  /** Upload parameter (`source` `file` / `files`). */
67
87
  readonly upload?: UploadData;
88
+ /** Custom parameter (`source` `custom`). */
89
+ readonly custom?: CustomParamData;
68
90
  /**
69
91
  * The default value of the handler parameter (`@Query('page') page = 1` → `1`), when it is
70
92
  * statically evaluable (literals, `const` references; references to exported bindings are left
package/dist/compiler.mjs CHANGED
@@ -1150,7 +1150,15 @@ function analyzeParams(ctx, cls, method, where, bodyValidation) {
1150
1150
  const params = [];
1151
1151
  let complete = true;
1152
1152
  for (const param of method.params) {
1153
- const [decorator, ...extra] = unique(param.decorators).filter((decorator) => decorator.owner === "http" && PARAM_SOURCES.has(decorator.identity ?? ""));
1153
+ const decorators = unique(param.decorators).filter((decorator) => decorator.owner === "http" && PARAM_SOURCES.has(decorator.identity ?? ""));
1154
+ const custom = typeof ctx.paramDecorator === "function" ? ctx.paramDecorator(param, "http", cls) : void 0;
1155
+ if (custom !== void 0) {
1156
+ for (const other of decorators) error(ctx, other.location, `Parameter '${param.name}' of ${where} has several parameter decorators (@${custom.decorator.name} and @${other.name})`, ["Keep a single parameter decorator"]);
1157
+ if (custom.ok && decorators.length === 0) params.push(customParam(param, custom));
1158
+ else complete = false;
1159
+ continue;
1160
+ }
1161
+ const [decorator, ...extra] = decorators;
1154
1162
  const foreign = decorator === void 0 ? foreignParameterDecorator(param) : void 0;
1155
1163
  if (foreign !== void 0) {
1156
1164
  error(ctx, foreign.decorator.location, `@${foreign.decorator.name} on parameter '${param.name}' of ${where} is imported from '${foreign.module}': route parameters use ${foreign.name} from '${HTTP_PACKAGE}'`, [`Import it from '${HTTP_PACKAGE}': import { ${foreign.name} } from '${HTTP_PACKAGE}'`]);
@@ -1177,6 +1185,29 @@ function analyzeParams(ctx, cls, method, where, bodyValidation) {
1177
1185
  complete
1178
1186
  };
1179
1187
  }
1188
+ /** A custom parameter (`source: 'custom'`, the decorator name as `name`). */
1189
+ function customParam(param, use) {
1190
+ const data = use.data;
1191
+ return {
1192
+ index: param.index,
1193
+ name: param.name,
1194
+ source: "custom",
1195
+ key: use.name,
1196
+ type: void 0,
1197
+ optional: param.optional || param.hasDefault,
1198
+ schema: void 0,
1199
+ coercion: void 0,
1200
+ custom: {
1201
+ identity: use.identity,
1202
+ name: use.name,
1203
+ data: data === void 0 ? void 0 : { value: data.ref ?? data.value },
1204
+ resolver: use.resolver.kind === "class" ? use.resolver.ref : void 0,
1205
+ ...use.resolver.kind === "class" && use.resolver.arity !== void 0 ? { arity: use.resolver.arity } : {},
1206
+ cast: use.cast
1207
+ },
1208
+ location: use.location
1209
+ };
1210
+ }
1180
1211
  function analyzeParam(ctx, cls, method, param, decorator, where, bodyValidation) {
1181
1212
  const source = PARAM_SOURCES.get(decorator.identity ?? "");
1182
1213
  if (source === void 0) return;
@@ -1730,6 +1761,7 @@ function createHelpers(ctx) {
1730
1761
  let isThenable;
1731
1762
  let uploads;
1732
1763
  let context;
1764
+ let paramFunctionOf;
1733
1765
  return {
1734
1766
  coerce: () => {
1735
1767
  coerce ??= ctx.importFrom(HTTP_PACKAGE, "coerce");
@@ -1750,6 +1782,10 @@ function createHelpers(ctx) {
1750
1782
  context: () => {
1751
1783
  context ??= ctx.importFrom(HTTP_PACKAGE, "HttpContext", { type: true });
1752
1784
  return context;
1785
+ },
1786
+ paramFunctionOf: () => {
1787
+ paramFunctionOf ??= ctx.importFrom("@znight/core", "ParamFunctionOf", { type: true });
1788
+ return paramFunctionOf;
1753
1789
  }
1754
1790
  };
1755
1791
  }
@@ -1778,7 +1814,9 @@ function routeNode(ctx, helpers, controller, route) {
1778
1814
  if (uploads.length > 0) entries.push(["uploads", ctx.arr(uploads.map((upload) => uploadNode(ctx, upload)))]);
1779
1815
  if (route.rawResponse) entries.push(["rawResponse", true]);
1780
1816
  if (route.sse !== void 0) entries.push(["sse", sseNode(ctx, route.sse)]);
1781
- entries.push(["invoke", invokeNode(ctx, helpers, controller, route)]);
1817
+ const resolvers = paramResolvers(route);
1818
+ if (resolvers.length > 0) entries.push(["paramResolvers", tokens(ctx, resolvers)]);
1819
+ entries.push(["invoke", invokeNode(ctx, helpers, controller, route, resolvers)]);
1782
1820
  return ctx.obj(entries);
1783
1821
  }
1784
1822
  /** `{ heartbeat?, retry?, maxDuration? }` (`{}` without options). */
@@ -1821,20 +1859,73 @@ function tokens(ctx, refs) {
1821
1859
  * `@Body()` parameter reads the body in `invoke`: that arrow is `async` and `await`s every
1822
1860
  * `validate()`.
1823
1861
  */
1824
- function invokeNode(ctx, helpers, controller, route) {
1862
+ function invokeNode(ctx, helpers, controller, route, resolvers) {
1825
1863
  const params = [["c", ctx.importType(controller.identity)]];
1826
- if (route.params.length > 0 || route.bodySchema !== void 0) params.push(["ctx", helpers.context()]);
1864
+ if (route.params.length > 0 || route.bodySchema !== void 0) params.push([readsContext(route) ? "ctx" : "_ctx", helpers.context()]);
1865
+ if (resolvers.length > 0) {
1866
+ const tuple = resolvers.map((ref) => ctx.importType(ref));
1867
+ params.push(["x", ctx.code`readonly [${joinNodes(ctx, tuple)}]`]);
1868
+ const index = new Map(resolvers.map((ref, position) => [ref.token, position]));
1869
+ const withResolvers = (x) => x === void 0 ? helpers : {
1870
+ ...helpers,
1871
+ resolvers: {
1872
+ x,
1873
+ index
1874
+ }
1875
+ };
1876
+ return invokeArrow(ctx, params, route, (c, http, x) => [
1877
+ c,
1878
+ http,
1879
+ withResolvers(x)
1880
+ ]);
1881
+ }
1882
+ return invokeArrow(ctx, params, route, (c, http) => [
1883
+ c,
1884
+ http,
1885
+ helpers
1886
+ ]);
1887
+ }
1888
+ /** `true` when an argument (or the body validation) reads the context: all but a `resolve()` without parameter. */
1889
+ function readsContext(route) {
1890
+ return route.bodySchema !== void 0 || route.params.some((param) => param.custom?.resolver === void 0 || param.custom.arity !== 0);
1891
+ }
1892
+ /** The provider classes resolving custom parameters, in order of first use (`x[i]`). */
1893
+ function paramResolvers(route) {
1894
+ const found = /* @__PURE__ */ new Map();
1895
+ for (const param of route.params) {
1896
+ const resolver = param.custom?.resolver;
1897
+ if (resolver !== void 0 && !found.has(resolver.token)) found.set(resolver.token, resolver);
1898
+ }
1899
+ return [...found.values()];
1900
+ }
1901
+ /** `a, b, c`. */
1902
+ function joinNodes(ctx, nodes) {
1903
+ return nodes.slice(1).reduce((joined, node) => ctx.code`${joined}, ${node}`, nodes[0]);
1904
+ }
1905
+ /** The arrow of {@link invokeNode}; `bind` maps the arrow parameters to the controller, the context and the helpers. */
1906
+ function invokeArrow(ctx, params, route, bind) {
1827
1907
  const slots = [];
1828
1908
  for (const param of route.params) slots[param.index] = param;
1829
1909
  const bodySchema = route.bodySchema;
1830
- if (bodySchema !== void 0) return ctx.arrow(params, (c, http) => {
1910
+ if (bodySchema !== void 0) return ctx.arrow(params, (...bound) => {
1911
+ const [c, http, helpers] = bind(...bound);
1831
1912
  const call = handlerCall(ctx, c, route, Array.from(slots, (param) => awaitedArgument(ctx, helpers, http, param)));
1832
1913
  const body = ctx.code`await ${ctx.call(ctx.member(http, "parseBody"))}`;
1833
1914
  const check = ctx.code`await ${validation(ctx, helpers, bodySchema, body, "body")}`;
1834
1915
  return ctx.code`{ ${check}; return ${call} }`;
1835
1916
  }, { async: true });
1836
- const last = slots.findLastIndex((param) => param?.schema !== void 0);
1837
- return ctx.arrow(params, (c, http) => last === -1 ? handlerCall(ctx, c, route, Array.from(slots, (param) => plainArgument(ctx, helpers, http, param))) : validatingBlock(ctx, helpers, route, slots, last, c, http));
1917
+ const last = slots.findLastIndex((param) => param !== void 0 && isDeferred(param));
1918
+ return ctx.arrow(params, (...bound) => {
1919
+ const [c, http, helpers] = bind(...bound);
1920
+ return last === -1 ? handlerCall(ctx, c, route, Array.from(slots, (param) => plainArgument(ctx, helpers, http, param))) : validatingBlock(ctx, helpers, route, slots, last, c, http);
1921
+ });
1922
+ }
1923
+ /**
1924
+ * `true` for an argument whose expression may return a thenable: a validation with a schema, or a
1925
+ * custom parameter (its resolver may return a promise).
1926
+ */
1927
+ function isDeferred(param) {
1928
+ return param.schema !== void 0 || param.custom !== void 0;
1838
1929
  }
1839
1930
  /** `c.handler(args)`. */
1840
1931
  function handlerCall(ctx, c, route, args) {
@@ -1873,7 +1964,7 @@ function validatingBlock(ctx, helpers, route, slots, last, c, http) {
1873
1964
  const param = slots[index];
1874
1965
  const name = local.get(index);
1875
1966
  statements.push(ctx.code`const ${name} = ${plainArgument(ctx, helpers, http, param)}`);
1876
- if (param.schema !== void 0) {
1967
+ if (isDeferred(param)) {
1877
1968
  const rest = Array.from(slots, (other, position) => {
1878
1969
  if (position < index) return evaluated(position);
1879
1970
  return position === index ? ctx.code`await ${name}` : awaitedArgument(ctx, helpers, http, other);
@@ -1890,7 +1981,7 @@ function validatingBlock(ctx, helpers, route, slots, last, c, http) {
1890
1981
  /** An argument evaluated in an `async` arrow: its validation, if any, is `await`ed. */
1891
1982
  function awaitedArgument(ctx, helpers, http, param) {
1892
1983
  const node = plainArgument(ctx, helpers, http, param);
1893
- return param?.schema === void 0 ? node : ctx.code`await ${node}`;
1984
+ return param === void 0 || !isDeferred(param) ? node : ctx.code`await ${node}`;
1894
1985
  }
1895
1986
  /** An argument as an expression (`validate()` not awaited); `undefined` for a hole. */
1896
1987
  function plainArgument(ctx, helpers, http, param) {
@@ -1943,7 +2034,33 @@ function argumentNode(ctx, helpers, http, param) {
1943
2034
  cast: false
1944
2035
  }, value, label);
1945
2036
  }
2037
+ case "custom": return customArgument(ctx, helpers, http, param.custom);
2038
+ }
2039
+ }
2040
+ /**
2041
+ * A custom parameter (RFC 0010): `(D.resolvers.http as ParamFunctionOf<typeof D, 'http'>)(ctx, data)`
2042
+ * for a function resolver, `x[i].resolve(ctx, data)` for a class resolver; cast with `as never` when
2043
+ * the value type could not be checked against the parameter.
2044
+ */
2045
+ function customArgument(ctx, helpers, http, custom) {
2046
+ const data = custom.data === void 0 ? ctx.lit(void 0) : dataNode(ctx, custom.data.value);
2047
+ let call;
2048
+ if (custom.resolver === void 0) {
2049
+ const decorator = ctx.importValue(custom.identity);
2050
+ const callee = ctx.code`${ctx.member(decorator, "resolvers", "http")} as ${helpers.paramFunctionOf()}<typeof ${decorator}, 'http'>`;
2051
+ call = ctx.call(callee, [http, data]);
2052
+ } else {
2053
+ const resolvers = helpers.resolvers;
2054
+ const index = resolvers.index.get(custom.resolver.token);
2055
+ if (custom.arity === void 0) return ctx.code`(${resolvers.x}[${index}] as { resolve(ctx: unknown, data: unknown): unknown }).resolve(${http}, ${data}) as never`;
2056
+ const args = [http, data].slice(0, custom.arity);
2057
+ call = ctx.code`${resolvers.x}[${index}].resolve(${args.length === 0 ? ctx.code`` : joinNodes(ctx, args)})`;
1946
2058
  }
2059
+ return custom.cast ? ctx.code`${call} as never` : call;
2060
+ }
2061
+ /** The data argument of a custom parameter: a literal (references imported) or a reference. */
2062
+ function dataNode(ctx, value) {
2063
+ return isValueRef(value) ? ctx.ref(value) : ctx.lit(value);
1947
2064
  }
1948
2065
  /** `validate(schema, value, location)` (the value, or a promise when the schema validates asynchronously). */
1949
2066
  function validation(ctx, helpers, schema, value, location) {