@znight/graphql 0.2.1 → 0.3.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.
@@ -0,0 +1,2165 @@
1
+ import { ErrorCode, ZnightError } from "@znight/core";
2
+ import { HttpResponse, HttpTokens, ValidationException, isHttpException, parseMediaType } from "@znight/http";
3
+ import { AbortedGraphQLExecutionError, GraphQLError, Kind, NoSchemaIntrospectionCustomRule, createSourceEventStream, execute, executeSubscriptionEvent, getNamedType, isObjectType, parse, print, specifiedRules, validate, validateSubscriptionArgs } from "graphql";
4
+ import { createHash, randomUUID } from "node:crypto";
5
+ // src/errors.ts
6
+ /**
7
+ * GraphQL errors shown to clients (RFC 0005 §5.10, §8.8): `GraphQLException` and the
8
+ * `extensions.code` values the plugin produces (Apollo conventions, which clients already know).
9
+ *
10
+ * Import-free: the exception is a plain `Error` subclass, rendered by the runtime as
11
+ * `{ message, locations, path, extensions: { code, ...extensions } }`.
12
+ */
13
+ /** `extensions.code` values produced by `@znight/graphql`. Applications may use their own codes too. */
14
+ const GraphQLErrorCode = Object.freeze({
15
+ ParseFailed: "GRAPHQL_PARSE_FAILED",
16
+ ValidationFailed: "GRAPHQL_VALIDATION_FAILED",
17
+ BadUserInput: "BAD_USER_INPUT",
18
+ QueryTooComplex: "QUERY_TOO_COMPLEX",
19
+ Unauthenticated: "UNAUTHENTICATED",
20
+ Forbidden: "FORBIDDEN",
21
+ NotFound: "NOT_FOUND",
22
+ Conflict: "CONFLICT",
23
+ TooManyRequests: "TOO_MANY_REQUESTS",
24
+ BadRequest: "BAD_REQUEST",
25
+ SubscriptionOverflow: "SUBSCRIPTION_OVERFLOW",
26
+ ServiceUnavailable: "SERVICE_UNAVAILABLE",
27
+ InternalServerError: "INTERNAL_SERVER_ERROR"
28
+ });
29
+ /**
30
+ * An error whose message and extensions are sent to the client as they are (§5.10): throw it from a
31
+ * resolver or return it from a `GraphQLErrorMapper`.
32
+ *
33
+ * ```ts
34
+ * throw new GraphQLException(`No book ${id}`, { code: 'NOT_FOUND', extensions: { bookId: id } })
35
+ * ```
36
+ */
37
+ var GraphQLException = class extends Error {
38
+ /** `extensions.code`. */
39
+ code;
40
+ /** Extra `extensions` entries. */
41
+ extensions;
42
+ constructor(message, options) {
43
+ super(message, options.cause === void 0 ? void 0 : { cause: options.cause });
44
+ this.name = "GraphQLException";
45
+ this.code = options.code;
46
+ this.extensions = options.extensions;
47
+ }
48
+ };
49
+ // src/context.ts
50
+ const NO_VARIABLES = Object.freeze({});
51
+ /**
52
+ * The `GraphQLContext` of one operation. Built from references the executor already holds (the
53
+ * operation node, the request, the options, the enclosing scope): one allocation per operation,
54
+ * every field read on demand.
55
+ */
56
+ var OperationContext = class {
57
+ document;
58
+ #runtime;
59
+ #operation;
60
+ #request;
61
+ #options;
62
+ #scope;
63
+ #requestId;
64
+ #http;
65
+ #signal;
66
+ #ended;
67
+ #state;
68
+ #loaders;
69
+ constructor(runtime, document, operation, request, options, scope) {
70
+ this.#runtime = runtime;
71
+ this.document = document;
72
+ this.#operation = operation;
73
+ this.#request = request;
74
+ this.#options = options;
75
+ this.#scope = scope;
76
+ this.#requestId = options.requestId;
77
+ }
78
+ get transport() {
79
+ return this.#options.transport ?? "direct";
80
+ }
81
+ /** The driver that received the operation: `'builtin'` for the built-in transport and direct calls. */
82
+ get driver() {
83
+ return this.#options.driver ?? "builtin";
84
+ }
85
+ get serverContext() {
86
+ return this.#options.serverContext;
87
+ }
88
+ get operationType() {
89
+ return this.#operation.operation;
90
+ }
91
+ get operationName() {
92
+ return this.#operation.name?.value;
93
+ }
94
+ get requestId() {
95
+ let id = this.#requestId;
96
+ if (id === void 0) {
97
+ const http = this.http;
98
+ const scoped = http === void 0 ? this.#scope?.get(HttpTokens.RequestId) : void 0;
99
+ id = http?.requestId ?? (typeof scoped === "string" ? scoped : randomUUID());
100
+ this.#requestId = id;
101
+ }
102
+ return id;
103
+ }
104
+ get http() {
105
+ let http = this.#http;
106
+ if (http === void 0) {
107
+ http = this.#options.http ?? this.#scope?.get(HttpTokens.Context) ?? null;
108
+ this.#http = http;
109
+ }
110
+ return http ?? void 0;
111
+ }
112
+ get variables() {
113
+ return this.#request.variables ?? NO_VARIABLES;
114
+ }
115
+ get signal() {
116
+ let signal = this.#signal;
117
+ if (signal === void 0) {
118
+ const base = this.#options.signal ?? this.http?.signal ?? this.#runtime.signal;
119
+ const ended = this.#ended;
120
+ signal = ended === void 0 ? base : AbortSignal.any([base, ended]);
121
+ this.#signal = signal;
122
+ }
123
+ return signal;
124
+ }
125
+ /**
126
+ * Ties {@link signal} to the end of a subscription: it also aborts when `ended` does (the stream
127
+ * ended by itself or with `return()`). Called before any resolver runs.
128
+ */
129
+ endsWith(ended) {
130
+ this.#ended = ended;
131
+ }
132
+ get state() {
133
+ this.#state ??= /* @__PURE__ */ new Map();
134
+ return this.#state;
135
+ }
136
+ loader(key) {
137
+ const token = typeof key === "string" ? key : this.#tokenOf(key);
138
+ let loaders = this.#loaders;
139
+ if (loaders === void 0) {
140
+ loaders = /* @__PURE__ */ new Map();
141
+ this.#loaders = loaders;
142
+ }
143
+ let loader = loaders.get(token);
144
+ if (loader === void 0) {
145
+ loader = this.#runtime.loaders.create(token);
146
+ loaders.set(token, loader);
147
+ }
148
+ return loader;
149
+ }
150
+ /** Starts a new execution (a subscription event): loaders and their caches are renewed. */
151
+ resetLoaders() {
152
+ this.#loaders = void 0;
153
+ }
154
+ #tokenOf(loader) {
155
+ const token = typeof loader === "function" ? this.#runtime.container.tokenOf(loader) : void 0;
156
+ if (token === void 0) {
157
+ const name = typeof loader === "function" ? loader.name || "<anonymous>" : String(loader);
158
+ throw new ZnightError(ErrorCode.MissingProvider, `GraphQLContext.loader(): ${name} is not a provider`, { hints: ["Add @Injectable() to the loader class (it implements BatchLoad<K, V>)"] });
159
+ }
160
+ return token;
161
+ }
162
+ };
163
+ // src/format.ts
164
+ /** Resolves a {@link Resolvable} (in the current scope for scoped providers). */
165
+ function resolveIn(container, resolvable) {
166
+ return resolvable.token === void 0 ? resolvable.instance : container.get(resolvable.token);
167
+ }
168
+ /** Message of masked errors. */
169
+ const MASKED_MESSAGE = "Unexpected error.";
170
+ /** `HttpException` status → `extensions.code` (other 4xx: `BAD_REQUEST`, 5xx: masked). */
171
+ const STATUS_CODES = /* @__PURE__ */ new Map([
172
+ [400, GraphQLErrorCode.BadUserInput],
173
+ [401, GraphQLErrorCode.Unauthenticated],
174
+ [403, GraphQLErrorCode.Forbidden],
175
+ [404, GraphQLErrorCode.NotFound],
176
+ [409, GraphQLErrorCode.Conflict],
177
+ [429, GraphQLErrorCode.TooManyRequests]
178
+ ]);
179
+ /** Formats the errors of execution results. One per plugin, built at boot. */
180
+ var ErrorFormatter = class {
181
+ #container;
182
+ #mappers;
183
+ #expose;
184
+ #logger;
185
+ #schema;
186
+ constructor(options) {
187
+ this.#container = options.container;
188
+ this.#mappers = options.mappers;
189
+ this.#expose = options.exposeErrors;
190
+ this.#logger = options.logger;
191
+ this.#schema = options.schema;
192
+ }
193
+ /** Formats the errors of an executed operation (field errors). */
194
+ format(errors, ctx) {
195
+ const result = new Array(errors.length);
196
+ let logged;
197
+ for (let index = 0; index < errors.length; index++) {
198
+ const error = errors[index];
199
+ const original = error.originalError;
200
+ if (original === void 0 || original === null) {
201
+ result[index] = withCode(error, error.extensions["code"] === void 0 ? GraphQLErrorCode.InternalServerError : void 0);
202
+ continue;
203
+ }
204
+ const exposed = this.#exposed(original, ctx);
205
+ if (exposed !== void 0) {
206
+ result[index] = render(error, exposed);
207
+ const cause = exposed.cause;
208
+ if (cause !== void 0) {
209
+ logged ??= /* @__PURE__ */ new Set();
210
+ if (!logged.has(cause)) {
211
+ logged.add(cause);
212
+ this.#log(error, cause, ctx, exposed.code);
213
+ }
214
+ }
215
+ continue;
216
+ }
217
+ if (original instanceof GraphQLError && !isCompletionError(original)) {
218
+ result[index] = {
219
+ ...original.toJSON(),
220
+ ...located(error)
221
+ };
222
+ continue;
223
+ }
224
+ logged ??= /* @__PURE__ */ new Set();
225
+ if (!logged.has(original)) {
226
+ logged.add(original);
227
+ this.#log(error, original, ctx, void 0);
228
+ }
229
+ result[index] = {
230
+ message: this.#expose && original instanceof Error ? original.message : MASKED_MESSAGE,
231
+ ...located(error),
232
+ extensions: { code: GraphQLErrorCode.InternalServerError }
233
+ };
234
+ }
235
+ return result;
236
+ }
237
+ /** The exception shown for `original`, `undefined` when it must be masked. */
238
+ #exposed(original, ctx) {
239
+ if (original instanceof GraphQLException) return original;
240
+ for (const resolvable of this.#mappers) {
241
+ let mapped;
242
+ try {
243
+ mapped = resolveIn(this.#container, resolvable).map(original, ctx);
244
+ } catch (error) {
245
+ this.#logger.error("GraphQLPlugin: an error mapper failed", {
246
+ err: error,
247
+ requestId: ctx.requestId
248
+ });
249
+ continue;
250
+ }
251
+ if (mapped !== void 0) return mapped;
252
+ }
253
+ if (isHttpException(original)) {
254
+ const status = original.status;
255
+ if (original instanceof ValidationException) return new GraphQLException(original.message, {
256
+ code: GraphQLErrorCode.BadUserInput,
257
+ extensions: { issues: original.details }
258
+ });
259
+ if (status >= 500) return void 0;
260
+ return new GraphQLException(original.message, { code: STATUS_CODES.get(status) ?? GraphQLErrorCode.BadRequest });
261
+ }
262
+ }
263
+ /** Logs an unexpected error, or the cause of a presented one (`code`), at `error` level. */
264
+ #log(error, cause, ctx, code) {
265
+ const coordinate = coordinateOf(this.#schema, ctx.document, ctx.operationName, error.path);
266
+ const what = code === void 0 ? "unexpected error" : `${code} error`;
267
+ this.#logger.error(`GraphQL: ${what}${coordinate === void 0 ? "" : ` in ${coordinate}`}`, {
268
+ err: cause,
269
+ requestId: ctx.requestId,
270
+ ...code === void 0 ? {} : { code },
271
+ ...coordinate === void 0 ? {} : { coordinate },
272
+ ...error.path === void 0 ? {} : { path: error.path.join(".") }
273
+ });
274
+ }
275
+ };
276
+ /**
277
+ * What graphql-js raises while completing a resolver's value: a built-in scalar or an enum that
278
+ * cannot represent it (`String cannot represent value: { passwordHash: ... }`), a list value that
279
+ * is not iterable, a union or interface value that resolves to no member. A server bug whose message
280
+ * holds the value, masked and logged like any unexpected error. graphql-js throws plain
281
+ * `GraphQLError`s without code there, told apart from the ones of user code by their message.
282
+ */
283
+ const COMPLETION_ERROR = /^(?:(?:Int|Float|String|Boolean|ID) cannot represent |Enum "[^"]*" cannot represent |Expected Iterable, but did not find one for field "|Abstract type "|Runtime Object type ")/;
284
+ function isCompletionError(error) {
285
+ return error.constructor === GraphQLError && error.extensions["code"] === void 0 && COMPLETION_ERROR.test(error.message);
286
+ }
287
+ function located(error) {
288
+ return {
289
+ ...error.locations === void 0 ? {} : { locations: error.locations },
290
+ ...error.path === void 0 ? {} : { path: error.path }
291
+ };
292
+ }
293
+ /** `{ message, locations, path, extensions: { ...extensions, code } }`. */
294
+ function render(error, exception) {
295
+ return {
296
+ message: exception.message,
297
+ ...located(error),
298
+ extensions: {
299
+ ...exception.extensions,
300
+ code: exception.code
301
+ }
302
+ };
303
+ }
304
+ /** A graphql-js error formatted with `extensions.code` set to `code` (kept as is without code). */
305
+ function withCode(error, code) {
306
+ const formatted = error.toJSON();
307
+ if (code === void 0) return formatted;
308
+ return {
309
+ ...formatted,
310
+ extensions: {
311
+ ...formatted.extensions,
312
+ code
313
+ }
314
+ };
315
+ }
316
+ /** A request error (no location): `{ message, extensions: { code } }`. */
317
+ function requestError(message, code, nodes) {
318
+ return withCode(new GraphQLError(message, nodes === void 0 ? {} : { nodes }), code);
319
+ }
320
+ /**
321
+ * The schema coordinate (`Book.author`) of the field at `path`, found by walking the operation's
322
+ * selections (aliases, fragments, inline fragments). Best effort, for logs: `undefined` when the
323
+ * path crosses a union without a type condition.
324
+ */
325
+ function coordinateOf(schema, document, operationName, path) {
326
+ if (path === void 0 || path.length === 0) return void 0;
327
+ const fragments = /* @__PURE__ */ new Map();
328
+ let operation;
329
+ for (const definition of document.definitions) if (definition.kind === Kind.FRAGMENT_DEFINITION) fragments.set(definition.name.value, definition);
330
+ else if (definition.kind === Kind.OPERATION_DEFINITION && (operationName === void 0 || definition.name?.value === operationName)) operation ??= definition;
331
+ if (operation === void 0) return void 0;
332
+ let type = schema.getRootType(operation.operation);
333
+ let selections = [operation.selectionSet];
334
+ let coordinate;
335
+ for (const segment of path) {
336
+ if (typeof segment === "number") continue;
337
+ const found = findField(selections, segment, fragments, type?.name);
338
+ if (found === void 0 || type === void 0 || type === null) return void 0;
339
+ const parent = found.typeCondition === void 0 ? type : schema.getType(found.typeCondition);
340
+ if (!isObjectType(parent)) return void 0;
341
+ const field = parent.getFields()[found.field.name.value];
342
+ coordinate = `${parent.name}.${found.field.name.value}`;
343
+ if (field === void 0) return coordinate;
344
+ type = getNamedType(field.type);
345
+ selections = found.field.selectionSet === void 0 ? [] : [found.field.selectionSet];
346
+ }
347
+ return coordinate;
348
+ }
349
+ function findField(sets, key, fragments, typeCondition) {
350
+ for (const set of sets) for (const selection of set.selections) if (selection.kind === Kind.FIELD) {
351
+ if ((selection.alias ?? selection.name).value === key) return {
352
+ field: selection,
353
+ typeCondition
354
+ };
355
+ } else {
356
+ const nested = selection.kind === Kind.INLINE_FRAGMENT ? selection : fragments.get(selection.name.value);
357
+ if (nested === void 0) continue;
358
+ const found = findField([nested.selectionSet], key, fragments, nested.typeCondition?.name.value ?? typeCondition);
359
+ if (found !== void 0) return found;
360
+ }
361
+ }
362
+ // src/limits.ts
363
+ /** The limit errors of `document` (`QUERY_TOO_COMPLEX`), `undefined` when it is within its limits. */
364
+ function checkLimits(document, limits) {
365
+ const { maxDepth, maxAliases, maxDirectives } = limits;
366
+ if (maxDepth === void 0 && maxAliases === void 0 && maxDirectives === void 0) return void 0;
367
+ const counter = {
368
+ aliases: 0,
369
+ directives: 0,
370
+ spreads: 0
371
+ };
372
+ const fragments = /* @__PURE__ */ new Map();
373
+ for (const definition of document.definitions) if (definition.kind === Kind.FRAGMENT_DEFINITION) fragments.set(definition.name.value, definition);
374
+ let errors;
375
+ const fail = (message) => {
376
+ errors ??= [];
377
+ errors.push(new GraphQLError(message, { extensions: { code: GraphQLErrorCode.QueryTooComplex } }));
378
+ };
379
+ for (const definition of document.definitions) if (definition.kind === Kind.OPERATION_DEFINITION || definition.kind === Kind.FRAGMENT_DEFINITION) {
380
+ countDirectives(definition.directives, counter);
381
+ if (definition.kind === Kind.OPERATION_DEFINITION) for (const variable of definition.variableDefinitions ?? []) countDirectives(variable.directives, counter);
382
+ count(definition.selectionSet, counter);
383
+ }
384
+ if (maxAliases !== void 0) {
385
+ if (counter.aliases > maxAliases) fail(`The document has ${counter.aliases} aliases, more than the limit of ${maxAliases}.`);
386
+ else if (counter.spreads > 0) {
387
+ const memo = /* @__PURE__ */ new Map();
388
+ for (const definition of document.definitions) {
389
+ if (definition.kind !== Kind.OPERATION_DEFINITION) continue;
390
+ if (aliasesOf(definition.selectionSet, fragments, memo, /* @__PURE__ */ new Set(), maxAliases + 1) > maxAliases) {
391
+ fail(`${definition.name === void 0 ? "The operation" : `The operation ${definition.name.value}`} has more than ${maxAliases} aliases once its fragments are expanded.`);
392
+ break;
393
+ }
394
+ }
395
+ }
396
+ }
397
+ if (maxDirectives !== void 0 && counter.directives > maxDirectives) fail(`The document has ${counter.directives} directives, more than the limit of ${maxDirectives}.`);
398
+ if (maxDepth !== void 0) {
399
+ const depths = /* @__PURE__ */ new Map();
400
+ for (const definition of document.definitions) {
401
+ if (definition.kind !== Kind.OPERATION_DEFINITION) continue;
402
+ const depth = depthOf(definition.selectionSet, fragments, depths, /* @__PURE__ */ new Set());
403
+ if (depth > maxDepth) fail(`${definition.name === void 0 ? "The operation" : `The operation ${definition.name.value}`} is ${depth} levels deep, more than the limit of ${maxDepth}.`);
404
+ }
405
+ }
406
+ return errors;
407
+ }
408
+ /**
409
+ * The limits as a graphql-js validation rule (`GraphQLEngine.rules`, RFC 0009), for GraphQL servers
410
+ * that parse and validate documents themselves: the same check as {@link checkLimits}, run once on
411
+ * the document node. `undefined` when every limit is disabled.
412
+ */
413
+ function limitsRule(limits) {
414
+ if (limits.maxDepth === void 0 && limits.maxAliases === void 0 && limits.maxDirectives === void 0) return;
415
+ const rule = (context) => ({ Document(node) {
416
+ const errors = checkLimits(node, limits);
417
+ if (errors !== void 0) for (const error of errors) context.reportError(error);
418
+ return false;
419
+ } });
420
+ Object.defineProperty(rule, "name", { value: "ZnightDocumentLimitsRule" });
421
+ return rule;
422
+ }
423
+ function countDirectives(directives, counter) {
424
+ if (directives !== void 0) counter.directives += directives.length;
425
+ }
426
+ function count(set, counter) {
427
+ for (const selection of set.selections) {
428
+ countDirectives(selection.directives, counter);
429
+ if (selection.kind === Kind.FIELD) {
430
+ if (selection.alias !== void 0) counter.aliases++;
431
+ if (selection.selectionSet !== void 0) count(selection.selectionSet, counter);
432
+ } else if (selection.kind === Kind.INLINE_FRAGMENT) count(selection.selectionSet, counter);
433
+ else counter.spreads++;
434
+ }
435
+ }
436
+ /**
437
+ * Aliases a selection set executes, fragment spreads expanded (`a: f { ...F } b: f { ...F }` counts
438
+ * the aliases of `F` twice). Fragment counts are memoised, so the walk stays linear in the document;
439
+ * counts saturate at `cap` (one more than the limit) and a fragment cycle counts 0 (validation
440
+ * rejects it).
441
+ */
442
+ function aliasesOf(set, fragments, memo, visiting, cap) {
443
+ let total = 0;
444
+ for (const selection of set.selections) {
445
+ if (selection.kind === Kind.FIELD) {
446
+ if (selection.alias !== void 0) total++;
447
+ if (selection.selectionSet !== void 0) total += aliasesOf(selection.selectionSet, fragments, memo, visiting, cap);
448
+ } else if (selection.kind === Kind.INLINE_FRAGMENT) total += aliasesOf(selection.selectionSet, fragments, memo, visiting, cap);
449
+ else {
450
+ const name = selection.name.value;
451
+ let known = memo.get(name);
452
+ if (known === void 0) {
453
+ const fragment = fragments.get(name);
454
+ if (fragment === void 0 || visiting.has(name)) continue;
455
+ visiting.add(name);
456
+ known = aliasesOf(fragment.selectionSet, fragments, memo, visiting, cap);
457
+ visiting.delete(name);
458
+ memo.set(name, known);
459
+ }
460
+ total += known;
461
+ }
462
+ if (total >= cap) return cap;
463
+ }
464
+ return total;
465
+ }
466
+ /**
467
+ * Field nesting of a selection set (`{ a { b } }` is 2). Introspection fields (`__schema`,
468
+ * `__type`) do not count: `MaxIntrospectionDepthRule` bounds them. Fragment depths are memoised; a
469
+ * fragment cycle counts 0 (validation rejects it).
470
+ */
471
+ function depthOf(set, fragments, depths, visiting) {
472
+ let max = 0;
473
+ for (const selection of set.selections) {
474
+ let depth = 0;
475
+ if (selection.kind === Kind.FIELD) {
476
+ if (selection.name.value.startsWith("__")) continue;
477
+ depth = selection.selectionSet === void 0 ? 1 : 1 + depthOf(selection.selectionSet, fragments, depths, visiting);
478
+ } else if (selection.kind === Kind.INLINE_FRAGMENT) depth = depthOf(selection.selectionSet, fragments, depths, visiting);
479
+ else {
480
+ const name = selection.name.value;
481
+ const known = depths.get(name);
482
+ if (known === void 0) {
483
+ const fragment = fragments.get(name);
484
+ if (fragment === void 0 || visiting.has(name)) continue;
485
+ visiting.add(name);
486
+ depth = depthOf(fragment.selectionSet, fragments, depths, visiting);
487
+ visiting.delete(name);
488
+ depths.set(name, depth);
489
+ } else depth = known;
490
+ }
491
+ if (depth > max) max = depth;
492
+ }
493
+ return max;
494
+ }
495
+ // src/tokens.ts
496
+ /**
497
+ * Tokens of the values `GraphQLPlugin` supplies to the container (declared in the package
498
+ * manifest). `GraphQLContext` is a request-scoped external: inject it in request-scoped providers
499
+ * or through `Lazy<GraphQLContext>`.
500
+ */
501
+ const GraphQLTokens = Object.freeze({
502
+ Context: "@znight/graphql#GraphQLContext",
503
+ PubSub: "@znight/graphql#PubSub",
504
+ Plugin: "@znight/graphql#GraphQLPlugin"
505
+ });
506
+ // src/transport/abort.ts
507
+ /** The stand-in passed as graphql-js `abortSignal` by the HTTP transport (one per request). */
508
+ var DeferredAbort = class {
509
+ aborted = false;
510
+ reason = void 0;
511
+ /** graphql-js registers one listener per execution; more are kept in {@link #others}. */
512
+ #listener = void 0;
513
+ #others = void 0;
514
+ throwIfAborted() {
515
+ if (this.aborted) throw this.reason;
516
+ }
517
+ addEventListener(type, listener) {
518
+ if (type !== "abort") return;
519
+ if (this.#listener === void 0) {
520
+ this.#listener = listener;
521
+ return;
522
+ }
523
+ this.#others ??= [];
524
+ this.#others.push(listener);
525
+ }
526
+ removeEventListener(type, listener) {
527
+ if (type !== "abort") return;
528
+ if (this.#listener === listener) {
529
+ this.#listener = void 0;
530
+ return;
531
+ }
532
+ const others = this.#others;
533
+ const index = others === void 0 ? -1 : others.indexOf(listener);
534
+ if (index !== -1) others?.splice(index, 1);
535
+ }
536
+ /**
537
+ * The execution is asynchronous: from now on, `signal` aborting aborts the execution. Reads the
538
+ * signal only when graphql-js still listens (a pending execution).
539
+ */
540
+ follow(read) {
541
+ if (this.#listener === void 0 && (this.#others === void 0 || this.#others.length === 0)) return;
542
+ const signal = read();
543
+ if (signal.aborted) {
544
+ this.#abort(signal.reason);
545
+ return;
546
+ }
547
+ signal.addEventListener("abort", () => this.#abort(signal.reason), { once: true });
548
+ }
549
+ /** This stand-in, typed as the `AbortSignal` graphql-js expects. */
550
+ asSignal() {
551
+ return this;
552
+ }
553
+ #abort(reason) {
554
+ if (this.aborted) return;
555
+ this.aborted = true;
556
+ this.reason = reason;
557
+ const listener = this.#listener;
558
+ const others = this.#others;
559
+ this.#listener = void 0;
560
+ this.#others = void 0;
561
+ listener?.();
562
+ if (others !== void 0) for (const other of others) other();
563
+ }
564
+ };
565
+ // src/execution.ts
566
+ /**
567
+ * The answer of {@link GraphQLExecutor.execute} to a subscription with
568
+ * {@link ExecuteOptions.streamSubscriptions}: start it with {@link GraphQLExecutor.subscribe}.
569
+ */
570
+ const SUBSCRIBE = Object.freeze({
571
+ status: 0,
572
+ result: Object.freeze({})
573
+ });
574
+ const OPERATION_HOOK = "graphql:operation";
575
+ const DIRECT = Object.freeze({});
576
+ const NO_ERRORS = Object.freeze([]);
577
+ const DONE$1 = Object.freeze({
578
+ done: true,
579
+ value: void 0
580
+ });
581
+ /** SHA-256 (hex) of a query text: `GraphQLOperationEvent.documentId`. */
582
+ function documentId(query) {
583
+ return createHash("sha256").update(query, "utf8").digest("hex");
584
+ }
585
+ /** A parsed and validated document, kept by the document cache. */
586
+ var CachedDocument = class {
587
+ query;
588
+ document;
589
+ /** The operation of a single-operation document. */
590
+ single;
591
+ /** Operations by name (documents with several operations). */
592
+ named;
593
+ #id;
594
+ constructor(query, document) {
595
+ this.query = query;
596
+ this.document = document;
597
+ let single;
598
+ let named;
599
+ let count = 0;
600
+ for (const definition of document.definitions) {
601
+ if (definition.kind !== Kind.OPERATION_DEFINITION) continue;
602
+ count++;
603
+ single = definition;
604
+ if (definition.name !== void 0) {
605
+ named ??= /* @__PURE__ */ new Map();
606
+ named.set(definition.name.value, definition);
607
+ }
608
+ }
609
+ this.single = count === 1 ? single : void 0;
610
+ this.named = named;
611
+ }
612
+ /** SHA-256 of the query, computed on first read (only the hook needs it). */
613
+ get id() {
614
+ this.#id ??= documentId(this.query);
615
+ return this.#id;
616
+ }
617
+ };
618
+ /** A bounded LRU of documents by exact query text. */
619
+ var DocumentCache = class {
620
+ #max;
621
+ #entries = /* @__PURE__ */ new Map();
622
+ #last;
623
+ constructor(max) {
624
+ this.#max = max;
625
+ }
626
+ get size() {
627
+ return this.#entries.size;
628
+ }
629
+ get(query) {
630
+ const entry = this.#entries.get(query);
631
+ if (entry !== void 0 && this.#last !== query) {
632
+ this.#entries.delete(query);
633
+ this.#entries.set(query, entry);
634
+ this.#last = query;
635
+ }
636
+ return entry;
637
+ }
638
+ set(query, entry) {
639
+ if (this.#max === 0) return;
640
+ if (this.#entries.size >= this.#max) {
641
+ const oldest = this.#entries.keys().next();
642
+ if (oldest.done !== true) this.#entries.delete(oldest.value);
643
+ }
644
+ this.#entries.set(query, entry);
645
+ this.#last = query;
646
+ }
647
+ clear() {
648
+ this.#entries.clear();
649
+ this.#last = void 0;
650
+ }
651
+ };
652
+ /** A request refused before execution. */
653
+ var Rejection = class {
654
+ status;
655
+ errors;
656
+ operationType;
657
+ operationName;
658
+ documentId;
659
+ headers;
660
+ constructor(status, errors, documentId, operation, headers) {
661
+ this.status = status;
662
+ this.errors = errors;
663
+ this.documentId = documentId;
664
+ this.operationType = operation?.operation;
665
+ this.operationName = operation?.name?.value;
666
+ this.headers = headers;
667
+ }
668
+ response() {
669
+ return {
670
+ status: this.status,
671
+ result: { errors: this.errors },
672
+ ...this.headers === void 0 ? {} : { headers: this.headers }
673
+ };
674
+ }
675
+ };
676
+ /** The GraphQL executor of a plugin. */
677
+ var GraphQLExecutor = class {
678
+ #schema;
679
+ #container;
680
+ #hooks;
681
+ #logger;
682
+ #formatter;
683
+ #runtime;
684
+ #rules;
685
+ #limits;
686
+ #maxTokens;
687
+ #hideSuggestions;
688
+ #cache;
689
+ #maxActive;
690
+ #subscriptions;
691
+ #streams = /* @__PURE__ */ new Set();
692
+ /**
693
+ * The streams the plugin ends in `onStop`: direct subscriptions and those of the engine. The SSE
694
+ * streams of the built-in transport are ended by `HttpPlugin`'s shutdown (with a `retry` hint).
695
+ */
696
+ #owned = /* @__PURE__ */ new Set();
697
+ /** The streams of the engine (drivers), a part of {@link #owned}: ended when `HttpPlugin` drains. */
698
+ #engineStreams = /* @__PURE__ */ new Set();
699
+ /** The application stops (`HttpPlugin` drains, or the plugin stops): the engine refuses new subscriptions (503). */
700
+ #closing = false;
701
+ /** Subscriptions counted against `maxActive` while their source starts (not yet in `#streams`). */
702
+ #starting = 0;
703
+ /** The engine's extra rules (`GraphQLEngine.rules`): the limits, introspection. Built on first use. */
704
+ #engineRules;
705
+ /** The document limits as a rule, `undefined` when every limit is off. */
706
+ #limitsRule;
707
+ /** Documents parsed by {@link parseDocument}: within their limits, not checked again by validation. */
708
+ #limited = /* @__PURE__ */ new WeakSet();
709
+ /** Operations of the documents the engine executed, by document (the server's or the cache's). */
710
+ #documents = /* @__PURE__ */ new WeakMap();
711
+ constructor(options) {
712
+ this.#schema = options.schema;
713
+ this.#container = options.container;
714
+ this.#hooks = options.hooks;
715
+ this.#logger = options.logger;
716
+ this.#formatter = options.formatter;
717
+ this.#runtime = options.runtime;
718
+ this.#rules = options.introspection ? specifiedRules : [...specifiedRules, NoSchemaIntrospectionCustomRule];
719
+ this.#limits = options.limits;
720
+ this.#maxTokens = options.limits.maxTokens;
721
+ this.#hideSuggestions = !options.introspection;
722
+ this.#cache = new DocumentCache(options.documentCache);
723
+ this.#maxActive = options.maxActiveSubscriptions;
724
+ this.#subscriptions = options.subscriptions !== false;
725
+ this.#limitsRule = limitsRule(options.limits);
726
+ }
727
+ /** The schema operations run against. */
728
+ get schema() {
729
+ return this.#schema;
730
+ }
731
+ /** The engine's own validation rules (`GraphQLEngine.rules`): the limits, and introspection when it is off. */
732
+ get engineRules() {
733
+ let rules = this.#engineRules;
734
+ if (rules === void 0) {
735
+ const list = [];
736
+ if (this.#limitsRule !== void 0) list.push(this.#limitsRule);
737
+ if (this.#hideSuggestions) list.push(NoSchemaIntrospectionCustomRule);
738
+ rules = Object.freeze(list);
739
+ this.#engineRules = rules;
740
+ }
741
+ return rules;
742
+ }
743
+ /** Active subscriptions (every transport). */
744
+ get activeSubscriptions() {
745
+ return this.#streams.size;
746
+ }
747
+ /** Documents in the cache. */
748
+ get cachedDocuments() {
749
+ return this.#cache.size;
750
+ }
751
+ /**
752
+ * Executes a query or a mutation. Resolves (or returns, when every resolver is synchronous) a
753
+ * response; rejects only when `options.signal` aborts the execution (with the signal's reason).
754
+ * A subscription is a 400: it needs {@link subscribe} ({@link SUBSCRIBE} with
755
+ * `streamSubscriptions`).
756
+ */
757
+ execute(request, options = DIRECT) {
758
+ const prepared = this.#prepare(request, options);
759
+ if (prepared instanceof Rejection) return this.#reject(prepared, request, options);
760
+ if (prepared.operation.operation === "subscription") {
761
+ if (options.streamSubscriptions === true) return SUBSCRIBE;
762
+ return this.#reject(new Rejection(400, [requestError("Subscriptions are delivered as a stream: send the request with accept: text/event-stream (GraphQL over SSE), or call GraphQLPlugin.subscribe()", GraphQLErrorCode.BadRequest)], prepared.entry.id, prepared.operation), request, options);
763
+ }
764
+ const scope = this.#container.currentScope();
765
+ const ctx = this.#context(prepared, request, options, scope);
766
+ const hooks = this.#hooks;
767
+ if (!hooks.hasWrappers(OPERATION_HOOK) && !hooks.hasListeners(OPERATION_HOOK)) return this.#inScope(scope, ctx, () => this.#run(prepared, request, options, ctx));
768
+ return this.#inScope(scope, ctx, () => this.#observed(ctx, prepared.entry.id, () => this.#run(prepared, request, options, ctx)));
769
+ }
770
+ /**
771
+ * Subscribes: a stream of results, one per source event, or a rejection (status 400, 405 or 503).
772
+ * A query or a mutation gives a stream of its single result. The stream ends when the source
773
+ * ends, when `options.signal` aborts, or with `return()`; a source failure ends it with a final
774
+ * error result (`SUBSCRIPTION_OVERFLOW` for a subscriber that fell behind `MemoryPubSub`).
775
+ */
776
+ async subscribe(request, options = DIRECT) {
777
+ const prepared = this.#prepare(request, options);
778
+ if (prepared instanceof Rejection) return this.#reject(prepared, request, options);
779
+ if (prepared.operation.operation !== "subscription") {
780
+ const response = await this.execute(request, options);
781
+ return response.status === 200 ? new SingleResultStream(response.result) : response;
782
+ }
783
+ if (this.#full()) return this.#reject(overflow(prepared), request, options);
784
+ const started = await this.#start(prepared, request, options, false);
785
+ return started instanceof Rejection ? started.response() : started;
786
+ }
787
+ /** Every slot of `subscriptions.maxActive` is taken (started streams and starting ones). */
788
+ #full() {
789
+ return this.#maxActive !== void 0 && this.#streams.size + this.#starting >= this.#maxActive;
790
+ }
791
+ /**
792
+ * Starts a subscription in the current scope (or in one of its own, open as long as the stream):
793
+ * the slot reserved, the context and the hook observer created, the source started. A rejection
794
+ * (variables) is observed and returned for the caller to answer.
795
+ */
796
+ async #start(prepared, request, options, engine) {
797
+ let reserved = this.#maxActive !== void 0;
798
+ if (reserved) this.#starting++;
799
+ const release = () => {
800
+ if (reserved) {
801
+ reserved = false;
802
+ this.#starting--;
803
+ }
804
+ };
805
+ const outer = this.#container.currentScope();
806
+ const ctx = this.#context(prepared, request, options, outer);
807
+ const ended = new AbortController();
808
+ ctx.endsWith(ended.signal);
809
+ let run;
810
+ let close;
811
+ if (outer === void 0) {
812
+ const opened = this.#container.openScope([[GraphQLTokens.Context, ctx]]);
813
+ run = (fn) => opened.run(fn);
814
+ close = () => {
815
+ ended.abort();
816
+ opened.close()?.catch((error) => this.#runtimeLogError("Failed to close the scope of a subscription", error));
817
+ };
818
+ } else {
819
+ outer.set(GraphQLTokens.Context, ctx);
820
+ run = (fn) => fn();
821
+ close = () => ended.abort();
822
+ }
823
+ const observer = this.#observer(ctx, prepared.entry.id);
824
+ try {
825
+ const stream = await run(() => this.#startSubscription(prepared, request, options, ctx, observer, run, close, release, engine));
826
+ release();
827
+ if (stream instanceof Rejection) {
828
+ close();
829
+ observer?.end("rejected", stream.errors);
830
+ }
831
+ return stream;
832
+ } catch (error) {
833
+ release();
834
+ close();
835
+ observer?.end("error", void 0);
836
+ throw error;
837
+ }
838
+ }
839
+ /**
840
+ * Ends every subscription the plugin owns (plugin `onStop`): direct ones and those started
841
+ * through the engine (drivers). Built-in SSE streams end with `HttpPlugin`'s shutdown.
842
+ */
843
+ async stopStreams() {
844
+ this.#closing = true;
845
+ await Promise.all([...this.#owned].map((stream) => stream.return()));
846
+ }
847
+ /**
848
+ * Ends the subscriptions of the engine (`HttpPlugin` drains, `GraphQLPlugin.onHttpDrain()`): a
849
+ * driver serves them as streamed responses `HttpPlugin` does not close. New ones are refused (503).
850
+ */
851
+ async stopEngineStreams() {
852
+ this.#closing = true;
853
+ await Promise.all([...this.#engineStreams].map((stream) => stream.return()));
854
+ }
855
+ /** Forgets every cached document. */
856
+ clear() {
857
+ this.#cache.clear();
858
+ }
859
+ /**
860
+ * `GraphQLEngine.parse`: the document of the cache for a valid text, otherwise the text parsed
861
+ * with `maxTokens` and checked against the depth, aliases and directives limits. Throws a
862
+ * `GraphQLError` (`GRAPHQL_PARSE_FAILED`, `QUERY_TOO_COMPLEX`), observed as a rejected operation.
863
+ */
864
+ parseDocument(source, driver) {
865
+ const text = typeof source === "string" ? source : source?.body;
866
+ if (typeof text === "string") {
867
+ const cached = this.#cache.get(text);
868
+ if (cached !== void 0) return cached.document;
869
+ }
870
+ let document;
871
+ try {
872
+ document = parse(source, this.#maxTokens === void 0 ? void 0 : { maxTokens: this.#maxTokens });
873
+ } catch (error) {
874
+ if (!(error instanceof GraphQLError)) throw error;
875
+ const failed = coded(error, GraphQLErrorCode.ParseFailed);
876
+ this.#rejectedDocument([failed], text, driver);
877
+ throw failed;
878
+ }
879
+ const limited = checkLimits(document, this.#limits);
880
+ if (limited !== void 0) {
881
+ this.#rejectedDocument(limited, text, driver);
882
+ throw limited[0];
883
+ }
884
+ this.#limited.add(document);
885
+ return document;
886
+ }
887
+ /**
888
+ * `GraphQLEngine.validate`: `rules` (default: graphql-js `specifiedRules`) plus the engine's own
889
+ * (the limits, unless {@link parseDocument} checked them, and introspection), with
890
+ * `hideSuggestions` when introspection is off. With the default rules on the engine's schema, a
891
+ * cached document is not validated again, and a valid one enters the document cache. Errors carry
892
+ * `GRAPHQL_VALIDATION_FAILED` (or the limit's code) and are observed as a rejected operation.
893
+ */
894
+ validateDocument(schema, document, rules, driver) {
895
+ const standard = rules === void 0 || isSpecifiedRules(rules);
896
+ const cacheable = standard && schema === this.#schema;
897
+ const text = document.loc?.source.body;
898
+ if (cacheable && text !== void 0 && this.#cache.get(text)?.document === document) return NO_ERRORS;
899
+ const limited = this.#limited.has(document);
900
+ let all;
901
+ if (standard) all = limited || this.#limitsRule === void 0 ? this.#rules : [this.#limitsRule, ...this.#rules];
902
+ else {
903
+ const merged = new Set(limited ? rules : [...this.engineRules, ...rules]);
904
+ for (const rule of this.engineRules) merged.add(rule);
905
+ if (limited && this.#limitsRule !== void 0) merged.delete(this.#limitsRule);
906
+ all = [...merged];
907
+ }
908
+ const errors = validate(schema, document, all, { hideSuggestions: this.#hideSuggestions });
909
+ if (errors.length === 0) {
910
+ if (cacheable && text !== void 0) {
911
+ const entry = new CachedDocument(text, document);
912
+ this.#cache.set(text, entry);
913
+ this.#documents.set(document, entry);
914
+ }
915
+ return NO_ERRORS;
916
+ }
917
+ const failed = errors.map((error) => coded(error, error.extensions["code"] === void 0 ? GraphQLErrorCode.ValidationFailed : void 0));
918
+ this.#rejectedDocument(failed, text, driver);
919
+ return failed;
920
+ }
921
+ /**
922
+ * `GraphQLEngine.execute`: a validated query or mutation of `args.document`, in the current
923
+ * request scope (or one of its own), with the operation's `GraphQLContext` as `contextValue`
924
+ * (`args.contextValue` becomes `serverContext`). The result is graphql-js's, its errors mapped
925
+ * and masked, as `GraphQLError` instances without `originalError`. Without `options.signal`, an
926
+ * execution that turns asynchronous follows `GraphQLContext.signal` (the HTTP request's).
927
+ */
928
+ executeDocument(args, options) {
929
+ const prepared = this.#prepareDocument(args);
930
+ if (prepared instanceof Rejection) return asEngineResult(this.#reject(prepared, requestOf(args), options));
931
+ if (prepared.operation.operation === "subscription") return asEngineResult(this.#reject(new Rejection(400, [requestError("Subscriptions are started with GraphQLEngine.subscribe()", GraphQLErrorCode.BadRequest)], prepared.entry.id, prepared.operation), requestOf(args), options));
932
+ const request = {
933
+ query: prepared.entry.query,
934
+ operationName: args.operationName,
935
+ variables: args.variableValues
936
+ };
937
+ let abort;
938
+ let effective = options;
939
+ if (options.signal === void 0) {
940
+ abort = new DeferredAbort();
941
+ effective = {
942
+ ...options,
943
+ abortSignal: abort.asSignal()
944
+ };
945
+ }
946
+ const scope = this.#container.currentScope();
947
+ const ctx = this.#context(prepared, request, effective, scope);
948
+ const run = () => this.#runEngine(prepared, request, effective, ctx, args.rootValue);
949
+ const hooks = this.#hooks;
950
+ const response = !hooks.hasWrappers(OPERATION_HOOK) && !hooks.hasListeners(OPERATION_HOOK) ? this.#inScope(scope, ctx, run) : this.#inScope(scope, ctx, () => this.#observed(ctx, prepared.entry.id, run));
951
+ if (!isPromise$1(response)) return response.result;
952
+ abort?.follow(() => ctx.signal);
953
+ return response.then(resultOf);
954
+ }
955
+ /**
956
+ * `GraphQLEngine.subscribe`: starts a validated subscription, a stream of graphql-js results (its
957
+ * loaders renewed per event), or a single error result (rejected, past `maxActive`, a source that
958
+ * could not start). A query or a mutation is executed: its single result. The stream ends with
959
+ * `return()`, `options.signal` (default: the HTTP request's), its source, or the plugin's stop.
960
+ */
961
+ async subscribeDocument(args, options) {
962
+ const prepared = this.#prepareDocument(args);
963
+ if (prepared instanceof Rejection) return asEngineResult(this.#reject(prepared, requestOf(args), options));
964
+ if (prepared.operation.operation !== "subscription") return this.executeDocument(args, options);
965
+ if (!this.#subscriptions) return asEngineResult(this.#reject(new Rejection(400, [requestError("Subscriptions are disabled on this server", GraphQLErrorCode.BadRequest)], prepared.entry.id, prepared.operation), requestOf(args), options));
966
+ if (this.#full() || this.#closing) return asEngineResult(this.#reject(unavailable(prepared, this.#closing), requestOf(args), options));
967
+ const request = {
968
+ query: prepared.entry.query,
969
+ operationName: args.operationName,
970
+ variables: args.variableValues
971
+ };
972
+ let effective = options;
973
+ if (options.signal === void 0) {
974
+ const http = this.#container.currentScope()?.get(HttpTokens.Context);
975
+ if (http !== void 0) effective = {
976
+ ...options,
977
+ signal: http.signal
978
+ };
979
+ }
980
+ const started = await this.#start(prepared, request, effective, true);
981
+ if (started instanceof Rejection) return { errors: started.errors.map(fromFormatted) };
982
+ return started;
983
+ }
984
+ /** The document of `args` and its operation, or a rejection (operation selection). */
985
+ #prepareDocument(args) {
986
+ if (args?.schema !== this.#schema) throw new ZnightError(ErrorCode.InvalidGraphQLSetup, "GraphQLEngine: the operation targets another schema than the engine's (ExecutionArgs.schema)", { hints: ["Give the GraphQL server engine.schema: its resolvers are the engine's pipelines"] });
987
+ const document = args.document;
988
+ let entry = this.#documents.get(document);
989
+ if (entry === void 0) {
990
+ const text = document.loc?.source.body;
991
+ const cached = text === void 0 ? void 0 : this.#cache.get(text);
992
+ entry = cached !== void 0 && cached.document === document ? cached : new CachedDocument(text ?? print(document), document);
993
+ this.#documents.set(document, entry);
994
+ }
995
+ const operationName = args.operationName;
996
+ let operation;
997
+ if (operationName === void 0 || operationName === null) {
998
+ operation = entry.single;
999
+ if (operation === void 0) return new Rejection(400, [requestError("Must provide operation name if query contains multiple operations.", GraphQLErrorCode.BadRequest)], entry.id);
1000
+ } else {
1001
+ operation = entry.named?.get(operationName);
1002
+ if (operation === void 0) return new Rejection(400, [requestError(`Unknown operation named "${operationName}".`, GraphQLErrorCode.BadRequest)], entry.id);
1003
+ }
1004
+ return {
1005
+ entry,
1006
+ operation
1007
+ };
1008
+ }
1009
+ #runEngine(prepared, request, options, ctx, rootValue) {
1010
+ let result;
1011
+ try {
1012
+ result = execute({
1013
+ schema: this.#schema,
1014
+ document: prepared.entry.document,
1015
+ rootValue,
1016
+ operationName: request.operationName,
1017
+ variableValues: request.variables,
1018
+ contextValue: ctx,
1019
+ abortSignal: options.abortSignal ?? options.signal,
1020
+ hideSuggestions: this.#hideSuggestions
1021
+ });
1022
+ } catch (error) {
1023
+ return Promise.reject(abortReason(error, options));
1024
+ }
1025
+ if (isPromise$1(result)) return result.then((settled) => this.#respondEngine(settled, ctx), (error) => Promise.reject(abortReason(error, options)));
1026
+ return this.#respondEngine(result, ctx);
1027
+ }
1028
+ /** {@link #respond} for the engine: the errors as `GraphQLError` instances. */
1029
+ #respondEngine(result, ctx) {
1030
+ const errors = result.errors;
1031
+ if (errors === void 0) return {
1032
+ status: 200,
1033
+ result
1034
+ };
1035
+ if (!("data" in result)) return {
1036
+ status: 400,
1037
+ result: { errors: errors.map((error) => coded(error, error.extensions["code"] === void 0 ? GraphQLErrorCode.BadUserInput : void 0)) }
1038
+ };
1039
+ return {
1040
+ status: 200,
1041
+ result: {
1042
+ data: result.data,
1043
+ errors: asGraphQLErrors(errors, this.#formatter.format(errors, ctx)),
1044
+ ...result.extensions === void 0 ? {} : { extensions: result.extensions }
1045
+ }
1046
+ };
1047
+ }
1048
+ /** A document rejected by {@link parseDocument} or {@link validateDocument}, for the hook. */
1049
+ #rejectedDocument(errors, text, driver) {
1050
+ const hooks = this.#hooks;
1051
+ const wrapped = hooks.hasWrappers(OPERATION_HOOK);
1052
+ if (!wrapped && !hooks.hasListeners(OPERATION_HOOK)) return;
1053
+ const event = {
1054
+ operationType: void 0,
1055
+ operationName: void 0,
1056
+ documentId: text === void 0 ? void 0 : documentId(text),
1057
+ transport: "http",
1058
+ driver,
1059
+ requestId: this.#scopedRequestId(),
1060
+ context: void 0,
1061
+ startTime: performance.now(),
1062
+ outcome: "rejected",
1063
+ errors: summaries(errors)
1064
+ };
1065
+ if (!wrapped) {
1066
+ hooks.emit(OPERATION_HOOK, event);
1067
+ return;
1068
+ }
1069
+ hooks.run(OPERATION_HOOK, event, () => Promise.resolve()).then(() => {
1070
+ if (hooks.hasListeners(OPERATION_HOOK)) hooks.emit(OPERATION_HOOK, event);
1071
+ }, (error) => this.#runtimeLogError("A \"graphql:operation\" hook wrapper failed", error));
1072
+ }
1073
+ /** Request checks, the document (cached, or parsed, limited and validated), the operation. */
1074
+ #prepare(request, options) {
1075
+ const query = request?.query;
1076
+ if (typeof query !== "string" || query === "") return badRequest(typeof query === "string" ? "The query is empty" : "Missing query (a string)");
1077
+ const operationName = request.operationName;
1078
+ if (operationName !== void 0 && operationName !== null && typeof operationName !== "string") return badRequest("operationName must be a string");
1079
+ const variables = request.variables;
1080
+ if (variables !== void 0 && variables !== null && (typeof variables !== "object" || Array.isArray(variables))) return badRequest("variables must be an object");
1081
+ const entry = this.#cache.get(query) ?? this.#load(query);
1082
+ if (entry instanceof Rejection) return entry;
1083
+ let operation;
1084
+ if (operationName === void 0 || operationName === null) {
1085
+ operation = entry.single;
1086
+ if (operation === void 0) return new Rejection(400, [requestError("Must provide operation name if query contains multiple operations.", GraphQLErrorCode.BadRequest)], entry.id);
1087
+ } else {
1088
+ operation = entry.named?.get(operationName);
1089
+ if (operation === void 0) return new Rejection(400, [requestError(`Unknown operation named "${operationName}".`, GraphQLErrorCode.BadRequest)], entry.id);
1090
+ }
1091
+ if (options.queriesOnly === true && operation.operation !== "query") return new Rejection(405, [requestError(`A ${operation.operation} cannot be sent with GET: use POST`, GraphQLErrorCode.BadRequest)], entry.id, operation, { allow: "POST" });
1092
+ return {
1093
+ entry,
1094
+ operation
1095
+ };
1096
+ }
1097
+ /** Parses, limits and validates a document; caches it when valid (rejected documents are not cached). */
1098
+ #load(query) {
1099
+ let document;
1100
+ try {
1101
+ document = parse(query, this.#maxTokens === void 0 ? void 0 : { maxTokens: this.#maxTokens });
1102
+ } catch (error) {
1103
+ if (!(error instanceof GraphQLError)) throw error;
1104
+ return new Rejection(400, [withCode(error, GraphQLErrorCode.ParseFailed)], documentId(query));
1105
+ }
1106
+ const limited = checkLimits(document, this.#limits);
1107
+ if (limited !== void 0) return new Rejection(400, limited.map((error) => withCode(error, void 0)), documentId(query));
1108
+ const errors = validate(this.#schema, document, this.#rules, { hideSuggestions: this.#hideSuggestions });
1109
+ if (errors.length > 0) return new Rejection(400, errors.map((error) => withCode(error, GraphQLErrorCode.ValidationFailed)), documentId(query));
1110
+ const entry = new CachedDocument(query, document);
1111
+ this.#cache.set(query, entry);
1112
+ return entry;
1113
+ }
1114
+ #context(prepared, request, options, scope) {
1115
+ return new OperationContext(this.#runtime, prepared.entry.document, prepared.operation, request, options, scope);
1116
+ }
1117
+ /** Runs `fn` in the current scope (the GraphQL context set in it) or in a new one, closed after. */
1118
+ #inScope(scope, ctx, fn) {
1119
+ if (scope !== void 0) {
1120
+ scope.set(GraphQLTokens.Context, ctx);
1121
+ return fn();
1122
+ }
1123
+ const opened = this.#container.openScope([[GraphQLTokens.Context, ctx]]);
1124
+ let result;
1125
+ try {
1126
+ result = opened.run(fn);
1127
+ } catch (error) {
1128
+ const closing = opened.close();
1129
+ if (closing === void 0) throw error;
1130
+ return closing.then(() => Promise.reject(error), () => Promise.reject(error));
1131
+ }
1132
+ if (isPromise$1(result)) return result.then((value) => {
1133
+ const closing = opened.close();
1134
+ return closing === void 0 ? value : closing.then(() => value);
1135
+ }, (error) => {
1136
+ const closing = opened.close();
1137
+ if (closing === void 0) throw error;
1138
+ return closing.then(() => Promise.reject(error), () => Promise.reject(error));
1139
+ });
1140
+ const closing = opened.close();
1141
+ return closing === void 0 ? result : closing.then(() => result);
1142
+ }
1143
+ #run(prepared, request, options, ctx) {
1144
+ let result;
1145
+ try {
1146
+ result = execute({
1147
+ schema: this.#schema,
1148
+ document: prepared.entry.document,
1149
+ operationName: request.operationName,
1150
+ variableValues: request.variables,
1151
+ contextValue: ctx,
1152
+ abortSignal: options.abortSignal ?? options.signal,
1153
+ hideSuggestions: this.#hideSuggestions
1154
+ });
1155
+ } catch (error) {
1156
+ return Promise.reject(abortReason(error, options));
1157
+ }
1158
+ if (isPromise$1(result)) return result.then((settled) => this.#respond(settled, ctx), (error) => Promise.reject(abortReason(error, options)));
1159
+ return this.#respond(result, ctx);
1160
+ }
1161
+ /** The response of an execution result: as is without errors, formatted errors otherwise. */
1162
+ #respond(result, ctx) {
1163
+ const errors = result.errors;
1164
+ if (errors === void 0) return {
1165
+ status: 200,
1166
+ result
1167
+ };
1168
+ if (!("data" in result)) return {
1169
+ status: 400,
1170
+ result: { errors: errors.map(variableError) }
1171
+ };
1172
+ return {
1173
+ status: 200,
1174
+ result: {
1175
+ data: result.data,
1176
+ errors: this.#formatter.format(errors, ctx),
1177
+ ...result.extensions === void 0 ? {} : { extensions: result.extensions }
1178
+ }
1179
+ };
1180
+ }
1181
+ /** Formats a subscription result: an event, a source that could not start or failed (field errors). */
1182
+ formatEvent(result, ctx) {
1183
+ const errors = result.errors;
1184
+ if (errors === void 0) return result;
1185
+ return {
1186
+ ..."data" in result ? { data: result.data } : {},
1187
+ errors: this.#formatter.format(errors, ctx)
1188
+ };
1189
+ }
1190
+ /** {@link formatEvent} for the engine: the errors formatted, then given back as `GraphQLError` instances. */
1191
+ engineEvent(result, ctx) {
1192
+ const errors = result.errors;
1193
+ if (errors === void 0) return result;
1194
+ return {
1195
+ ..."data" in result ? { data: result.data } : {},
1196
+ errors: asGraphQLErrors(errors, this.#formatter.format(errors, ctx))
1197
+ };
1198
+ }
1199
+ /**
1200
+ * Starts the source of a subscription. `engine`: the results are graphql-js results
1201
+ * ({@link engineEvent}) and a source that could not start is an error result instead of a stream
1202
+ * of one result.
1203
+ */
1204
+ #startSubscription(prepared, request, options, ctx, observer, run, close, release, engine) {
1205
+ const validated = validateSubscriptionArgs({
1206
+ schema: this.#schema,
1207
+ document: prepared.entry.document,
1208
+ operationName: request.operationName,
1209
+ variableValues: request.variables,
1210
+ contextValue: ctx,
1211
+ abortSignal: options.signal,
1212
+ hideSuggestions: this.#hideSuggestions
1213
+ });
1214
+ if (!("schema" in validated)) return new Rejection(400, validated.map(variableError), prepared.entry.id, prepared.operation);
1215
+ const start = (source) => {
1216
+ release();
1217
+ if (!isAsyncIterable(source)) {
1218
+ if (engine) {
1219
+ const result = this.engineEvent(source, ctx);
1220
+ close();
1221
+ observer?.end("error", result.errors);
1222
+ return result;
1223
+ }
1224
+ const result = this.formatEvent(source, ctx);
1225
+ close();
1226
+ observer?.end("error", result.errors);
1227
+ return new SingleResultStream(result);
1228
+ }
1229
+ const stream = new SubscriptionStream({
1230
+ executor: this,
1231
+ source: source[Symbol.asyncIterator](),
1232
+ validated,
1233
+ ctx,
1234
+ run,
1235
+ signal: options.signal,
1236
+ field: responseKey(prepared.operation),
1237
+ engine,
1238
+ onEnd: (outcome, errors) => {
1239
+ this.#streams.delete(stream);
1240
+ this.#owned.delete(stream);
1241
+ if (engine) this.#engineStreams.delete(stream);
1242
+ close();
1243
+ observer?.end(outcome, errors);
1244
+ }
1245
+ });
1246
+ this.#streams.add(stream);
1247
+ if (engine || ctx.transport === "direct") this.#owned.add(stream);
1248
+ if (engine) {
1249
+ this.#engineStreams.add(stream);
1250
+ if (this.#closing) stream.return();
1251
+ }
1252
+ return stream;
1253
+ };
1254
+ const source = createSourceEventStream(validated);
1255
+ return isPromise$1(source) ? source.then(start) : start(source);
1256
+ }
1257
+ /** A rejection as a response, observed by the hook (outcome `rejected`). */
1258
+ #reject(rejection, request, options) {
1259
+ const hooks = this.#hooks;
1260
+ const wrapped = hooks.hasWrappers(OPERATION_HOOK);
1261
+ if (!wrapped && !hooks.hasListeners(OPERATION_HOOK)) return rejection.response();
1262
+ const event = {
1263
+ operationType: rejection.operationType,
1264
+ operationName: rejection.operationName ?? (typeof request?.operationName === "string" ? request.operationName : void 0),
1265
+ documentId: rejection.documentId,
1266
+ transport: options.transport ?? "direct",
1267
+ driver: options.driver ?? "builtin",
1268
+ requestId: options.requestId ?? options.http?.requestId ?? this.#scopedRequestId(),
1269
+ context: void 0,
1270
+ startTime: performance.now(),
1271
+ outcome: "rejected",
1272
+ errors: summaries(rejection.errors)
1273
+ };
1274
+ if (!wrapped) {
1275
+ hooks.emit(OPERATION_HOOK, event);
1276
+ return rejection.response();
1277
+ }
1278
+ return hooks.run(OPERATION_HOOK, event, () => Promise.resolve(rejection.response())).then((response) => {
1279
+ if (hooks.hasListeners(OPERATION_HOOK)) hooks.emit(OPERATION_HOOK, event);
1280
+ return response;
1281
+ });
1282
+ }
1283
+ /** An executed query or mutation, observed by the hook. */
1284
+ #observed(ctx, id, run) {
1285
+ const hooks = this.#hooks;
1286
+ const event = operationEvent(ctx, id);
1287
+ const done = (response) => {
1288
+ event.outcome = outcomeOf(response);
1289
+ if (response.result.errors !== void 0) event.errors = summaries(response.result.errors);
1290
+ return response;
1291
+ };
1292
+ const failed = (error) => {
1293
+ event.outcome = "error";
1294
+ throw error;
1295
+ };
1296
+ const emit = () => {
1297
+ if (hooks.hasListeners(OPERATION_HOOK)) hooks.emit(OPERATION_HOOK, event);
1298
+ };
1299
+ if (hooks.hasWrappers(OPERATION_HOOK)) return hooks.run(OPERATION_HOOK, event, () => Promise.resolve(run()).then(done, failed)).then((response) => {
1300
+ emit();
1301
+ return response;
1302
+ }, (error) => {
1303
+ emit();
1304
+ throw error;
1305
+ });
1306
+ let response;
1307
+ try {
1308
+ response = run();
1309
+ } catch (error) {
1310
+ event.outcome = "error";
1311
+ emit();
1312
+ throw error;
1313
+ }
1314
+ if (!isPromise$1(response)) {
1315
+ done(response);
1316
+ emit();
1317
+ return response;
1318
+ }
1319
+ return response.then((settled) => {
1320
+ done(settled);
1321
+ emit();
1322
+ return settled;
1323
+ }, (error) => {
1324
+ event.outcome = "error";
1325
+ emit();
1326
+ throw error;
1327
+ });
1328
+ }
1329
+ /** The hook of a subscription, for its whole life (`end()` settles it), or `undefined` without wrappers nor listeners. */
1330
+ #observer(ctx, id) {
1331
+ const hooks = this.#hooks;
1332
+ const wrapped = hooks.hasWrappers(OPERATION_HOOK);
1333
+ if (!wrapped && !hooks.hasListeners(OPERATION_HOOK)) return void 0;
1334
+ const event = operationEvent(ctx, id);
1335
+ let resolve;
1336
+ if (wrapped) {
1337
+ const ended = new Promise((settle) => {
1338
+ resolve = settle;
1339
+ });
1340
+ hooks.run(OPERATION_HOOK, event, () => ended).catch((error) => this.#runtimeLogError("A \"graphql:operation\" hook wrapper failed", error));
1341
+ }
1342
+ let ended = false;
1343
+ return { end: (outcome, errors) => {
1344
+ if (ended) return;
1345
+ ended = true;
1346
+ event.outcome = outcome;
1347
+ if (errors !== void 0 && errors.length > 0) event.errors = summaries(errors);
1348
+ resolve?.();
1349
+ if (hooks.hasListeners(OPERATION_HOOK)) hooks.emit(OPERATION_HOOK, event);
1350
+ } };
1351
+ }
1352
+ /** The request id of the enclosing scope (a controller calling GraphQL), or a new one. */
1353
+ #scopedRequestId() {
1354
+ const id = this.#container.currentScope()?.get(HttpTokens.RequestId);
1355
+ return typeof id === "string" ? id : randomUUID();
1356
+ }
1357
+ #runtimeLogError(message, error) {
1358
+ this.#logger.error(`GraphQLPlugin: ${message}`, { err: error });
1359
+ }
1360
+ };
1361
+ /** The 503 of a subscription past `subscriptions.maxActive`. */
1362
+ function overflow(prepared) {
1363
+ return new Rejection(503, [requestError("Too many active subscriptions, retry later", GraphQLErrorCode.ServiceUnavailable)], prepared.entry.id, prepared.operation);
1364
+ }
1365
+ /** The 503 of an engine subscription past `subscriptions.maxActive`, or while the application stops. */
1366
+ function unavailable(prepared, closing) {
1367
+ if (!closing) return overflow(prepared);
1368
+ return new Rejection(503, [requestError("The server is shutting down, retry later", GraphQLErrorCode.ServiceUnavailable)], prepared.entry.id, prepared.operation);
1369
+ }
1370
+ /** Whether `rules` are graphql-js `specifiedRules` (the same list, or a copy of it). */
1371
+ function isSpecifiedRules(rules) {
1372
+ if (rules === specifiedRules) return true;
1373
+ if (rules.length !== specifiedRules.length) return false;
1374
+ for (let index = 0; index < rules.length; index++) if (rules[index] !== specifiedRules[index]) return false;
1375
+ return true;
1376
+ }
1377
+ /** The request of engine arguments, for the hook of a rejection (its operation name). */
1378
+ function requestOf(args) {
1379
+ return {
1380
+ query: "",
1381
+ operationName: args.operationName
1382
+ };
1383
+ }
1384
+ function resultOf(response) {
1385
+ return response.result;
1386
+ }
1387
+ /** `error` with `extensions.code` set to `code` (as is without code), without its original error. */
1388
+ function coded(error, code) {
1389
+ if (code === void 0) return error;
1390
+ return new GraphQLError(error.message, {
1391
+ nodes: error.nodes,
1392
+ source: error.source,
1393
+ positions: error.positions,
1394
+ path: error.path,
1395
+ extensions: {
1396
+ ...error.extensions,
1397
+ code
1398
+ }
1399
+ });
1400
+ }
1401
+ /**
1402
+ * The formatted errors of an execution as `GraphQLError` instances (RFC 0009 §4.2, rule 5): the
1403
+ * formatted message and extensions, the locations and path of the error they come from, no
1404
+ * `originalError` (servers such as Apollo Server wrap anything that is not an `Error`).
1405
+ */
1406
+ function asGraphQLErrors(originals, formatted) {
1407
+ const errors = new Array(formatted.length);
1408
+ for (let index = 0; index < formatted.length; index++) {
1409
+ const original = originals[index];
1410
+ const shown = formatted[index];
1411
+ errors[index] = new GraphQLError(shown.message, {
1412
+ nodes: original.nodes,
1413
+ source: original.source,
1414
+ positions: original.positions,
1415
+ path: original.path,
1416
+ extensions: shown.extensions
1417
+ });
1418
+ }
1419
+ return errors;
1420
+ }
1421
+ /** A request error (formatted, without location) as a `GraphQLError`. */
1422
+ function fromFormatted(error) {
1423
+ return new GraphQLError(error.message, {
1424
+ path: error.path,
1425
+ extensions: error.extensions
1426
+ });
1427
+ }
1428
+ /** A rejection answered to the engine: a result of `GraphQLError`s. */
1429
+ function asEngineResult(response) {
1430
+ const settle = (settled) => ({ errors: (settled.result.errors ?? []).map(fromFormatted) });
1431
+ return isPromise$1(response) ? response.then(settle) : settle(response);
1432
+ }
1433
+ function noop() {}
1434
+ function badRequest(message) {
1435
+ return new Rejection(400, [requestError(message, GraphQLErrorCode.BadRequest)], void 0);
1436
+ }
1437
+ function variableError(error) {
1438
+ return withCode(error, error.extensions["code"] === void 0 ? GraphQLErrorCode.BadUserInput : void 0);
1439
+ }
1440
+ /** graphql-js rejects with `AbortedGraphQLExecutionError`: callers get the reason of their signal. */
1441
+ function abortReason(error, options) {
1442
+ const signal = options.abortSignal ?? options.signal;
1443
+ if (error instanceof AbortedGraphQLExecutionError && signal?.aborted === true) return signal.reason;
1444
+ return error;
1445
+ }
1446
+ function operationEvent(ctx, id) {
1447
+ return {
1448
+ operationType: ctx.operationType,
1449
+ operationName: ctx.operationName,
1450
+ documentId: id,
1451
+ transport: ctx.transport,
1452
+ driver: ctx.driver,
1453
+ requestId: ctx.requestId,
1454
+ context: ctx,
1455
+ startTime: performance.now()
1456
+ };
1457
+ }
1458
+ function outcomeOf(response) {
1459
+ if (response.status !== 200) return "rejected";
1460
+ const result = response.result;
1461
+ if (result.errors === void 0 || result.errors.length === 0) return "success";
1462
+ return result.data === null || result.data === void 0 ? "error" : "partial";
1463
+ }
1464
+ function summaries(errors) {
1465
+ return errors.map((error) => {
1466
+ const code = error.extensions?.["code"];
1467
+ return {
1468
+ message: error.message,
1469
+ code: typeof code === "string" ? code : void 0,
1470
+ ...error.path === void 0 ? {} : { path: error.path }
1471
+ };
1472
+ });
1473
+ }
1474
+ /** The response key of the root field of a subscription (its single selection). */
1475
+ function responseKey(operation) {
1476
+ const selection = operation.selectionSet.selections[0];
1477
+ return selection?.kind === Kind.FIELD ? (selection.alias ?? selection.name).value : void 0;
1478
+ }
1479
+ function isPromise$1(value) {
1480
+ return typeof value?.then === "function";
1481
+ }
1482
+ function isAsyncIterable(value) {
1483
+ return value !== null && typeof value === "object" && typeof value[Symbol.asyncIterator] === "function";
1484
+ }
1485
+ /** A stream of one result (a query sent as a stream, a rejection for `plugin.subscribe()`). */
1486
+ function singleResult(result) {
1487
+ return new SingleResultStream(result);
1488
+ }
1489
+ /** The stream of a query or a mutation sent as a stream (SSE): its single result. */
1490
+ var SingleResultStream = class {
1491
+ #result;
1492
+ constructor(result) {
1493
+ this.#result = result;
1494
+ }
1495
+ next() {
1496
+ const result = this.#result;
1497
+ if (result === void 0) return Promise.resolve(DONE$1);
1498
+ this.#result = void 0;
1499
+ return Promise.resolve({
1500
+ done: false,
1501
+ value: result
1502
+ });
1503
+ }
1504
+ return() {
1505
+ this.#result = void 0;
1506
+ return Promise.resolve(DONE$1);
1507
+ }
1508
+ [Symbol.asyncIterator]() {
1509
+ return this;
1510
+ }
1511
+ };
1512
+ /**
1513
+ * The results of a subscription: each source event executed with `executeSubscriptionEvent`, a
1514
+ * fresh per-execution state (loaders) and the operation's `GraphQLContext`. `R` is
1515
+ * `FormattedExecutionResult` for the transports (`GraphQLResultStream`), `ExecutionResult` for the
1516
+ * engine (`EngineStream`).
1517
+ *
1518
+ * The source is started at once: its first `next()` is requested when the stream is created, so
1519
+ * that a resolver written as an async generator (`for await (const x of pubsub.subscribe(...))`)
1520
+ * has subscribed when `subscribe()` resolves, whatever the transport and however late its consumer
1521
+ * reads (an SSE body pulled on demand). Only that first event is read ahead; the next ones are read
1522
+ * when the consumer asks (backpressure).
1523
+ */
1524
+ var SubscriptionStream = class {
1525
+ #init;
1526
+ #onAbort;
1527
+ /** The first `next()` of the source, requested at creation and consumed by the first read. */
1528
+ #first;
1529
+ /** `next()` calls of the source still pending. */
1530
+ #reading = 0;
1531
+ #ended = false;
1532
+ #partial = false;
1533
+ constructor(init) {
1534
+ this.#init = init;
1535
+ const signal = init.signal;
1536
+ if (signal !== void 0) {
1537
+ this.#onAbort = () => {
1538
+ this.return();
1539
+ };
1540
+ signal.addEventListener("abort", this.#onAbort, { once: true });
1541
+ }
1542
+ const first = init.run(() => this.#read());
1543
+ first.catch(noop);
1544
+ this.#first = first;
1545
+ if (signal?.aborted === true) this.return();
1546
+ }
1547
+ next() {
1548
+ if (this.#ended) return Promise.resolve(DONE$1);
1549
+ return this.#init.run(() => this.#pull());
1550
+ }
1551
+ /** The next event of the source. */
1552
+ async #read() {
1553
+ this.#reading++;
1554
+ try {
1555
+ return await this.#init.source.next();
1556
+ } finally {
1557
+ this.#reading--;
1558
+ }
1559
+ }
1560
+ /** A result of the subscription, formatted for its consumer. */
1561
+ #format(result) {
1562
+ const init = this.#init;
1563
+ return init.engine ? init.executor.engineEvent(result, init.ctx) : init.executor.formatEvent(result, init.ctx);
1564
+ }
1565
+ async #pull() {
1566
+ const init = this.#init;
1567
+ let step;
1568
+ try {
1569
+ const first = this.#first;
1570
+ this.#first = void 0;
1571
+ step = await (first ?? this.#read());
1572
+ } catch (error) {
1573
+ if (this.#ended) return DONE$1;
1574
+ const result = this.#format({ errors: [new GraphQLError(errorMessage(error), {
1575
+ originalError: asError(error),
1576
+ path: init.field === void 0 ? void 0 : [init.field]
1577
+ })] });
1578
+ this.#end("error", result.errors);
1579
+ return {
1580
+ done: false,
1581
+ value: result
1582
+ };
1583
+ }
1584
+ if (step.done === true || this.#ended) {
1585
+ this.#end(this.#partial ? "partial" : "success", void 0);
1586
+ return DONE$1;
1587
+ }
1588
+ init.ctx.resetLoaders();
1589
+ const executed = executeSubscriptionEvent({
1590
+ ...init.validated,
1591
+ rootValue: step.value
1592
+ });
1593
+ const result = this.#format(isPromise$1(executed) ? await executed : executed);
1594
+ if (result.errors !== void 0) this.#partial = true;
1595
+ return {
1596
+ done: false,
1597
+ value: result
1598
+ };
1599
+ }
1600
+ async return() {
1601
+ if (this.#ended) return DONE$1;
1602
+ this.#first = void 0;
1603
+ this.#end(this.#partial ? "partial" : "success", void 0);
1604
+ const reading = this.#reading > 0;
1605
+ try {
1606
+ const returned = Promise.resolve(this.#init.source.return?.());
1607
+ if (reading) returned.catch(noop);
1608
+ else await returned;
1609
+ } catch {}
1610
+ return DONE$1;
1611
+ }
1612
+ throw() {
1613
+ return this.return();
1614
+ }
1615
+ [Symbol.asyncIterator]() {
1616
+ return this;
1617
+ }
1618
+ /** `await using`: ends the stream (an `AsyncGenerator` is disposable). */
1619
+ async [Symbol.asyncDispose]() {
1620
+ await this.return();
1621
+ }
1622
+ #end(outcome, errors) {
1623
+ if (this.#ended) return;
1624
+ this.#ended = true;
1625
+ if (this.#onAbort !== void 0) this.#init.signal?.removeEventListener("abort", this.#onAbort);
1626
+ this.#init.onEnd(outcome, errors);
1627
+ }
1628
+ };
1629
+ function asError(error) {
1630
+ return error instanceof Error ? error : new Error(String(error));
1631
+ }
1632
+ function errorMessage(error) {
1633
+ return error instanceof Error ? error.message : String(error);
1634
+ }
1635
+ // src/transport/accept.ts
1636
+ /**
1637
+ * Content negotiation of the GraphQL endpoint (RFC 0005 §8.3): which of the media types the
1638
+ * endpoint produces a request accepts best. Import-free.
1639
+ *
1640
+ * `application/json` is the only type matched by wildcards (`*\/*`, `application/*`): the
1641
+ * GraphQL-over-HTTP draft asks for `application/json` when the client accepts anything, and the
1642
+ * other types change the protocol (`application/graphql-response+json` statuses, an event stream,
1643
+ * the GraphiQL page), so they are chosen only when listed. Among types of equal quality,
1644
+ * `application/graphql-response+json` wins, then `application/json`, then HTML (a browser listing
1645
+ * `text/html` first gets GraphiQL). An event stream is chosen only when it is strictly preferred, or
1646
+ * the only acceptable type: a client listing JSON and `text/event-stream` alike (urql, for
1647
+ * incremental delivery) gets its queries and mutations as one JSON body, and its subscriptions as
1648
+ * a stream ({@link STREAMABLE}).
1649
+ */
1650
+ /** What a response is encoded as. */
1651
+ const Media = Object.freeze({
1652
+ EventStream: 0,
1653
+ GraphQLJson: 1,
1654
+ Json: 2,
1655
+ Html: 3,
1656
+ None: 4
1657
+ });
1658
+ /** The media type names, by {@link Media} value (`None` excluded). */
1659
+ const NAMES = [
1660
+ ["text", "event-stream"],
1661
+ ["application", "graphql-response+json"],
1662
+ ["application", "json"],
1663
+ ["text", "html"]
1664
+ ];
1665
+ /** The media ranges of an `accept` header (lower-case, without parameters other than `q`). */
1666
+ function parseRanges(header) {
1667
+ const ranges = [];
1668
+ for (const item of header.split(",")) {
1669
+ const parts = item.split(";");
1670
+ const media = parts[0].trim().toLowerCase();
1671
+ const slash = media.indexOf("/");
1672
+ if (slash <= 0 || slash === media.length - 1) continue;
1673
+ let q = 1;
1674
+ for (let index = 1; index < parts.length; index++) {
1675
+ const parameter = parts[index].trim();
1676
+ if (parameter.length < 2 || (parameter.charCodeAt(0) | 32) !== 113 || parameter[1] !== "=") continue;
1677
+ const value = Number(parameter.slice(2));
1678
+ if (value >= 0 && value <= 1) q = value;
1679
+ }
1680
+ ranges.push({
1681
+ type: media.slice(0, slash),
1682
+ subtype: media.slice(slash + 1),
1683
+ q
1684
+ });
1685
+ }
1686
+ return ranges;
1687
+ }
1688
+ /**
1689
+ * The quality of `type/subtype` in `ranges`: that of the most specific matching range (RFC 9110
1690
+ * §12.5.1), wildcards only when `wildcards` is set; 0 when nothing matches.
1691
+ */
1692
+ function quality(ranges, type, subtype, wildcards) {
1693
+ let best = -1;
1694
+ let specificity = -1;
1695
+ for (const range of ranges) {
1696
+ let level;
1697
+ if (range.type === type && range.subtype === subtype) level = 2;
1698
+ else if (!wildcards) continue;
1699
+ else if (range.type === type && range.subtype === "*") level = 1;
1700
+ else if (range.type === "*" && range.subtype === "*") level = 0;
1701
+ else continue;
1702
+ if (level > specificity || level === specificity && range.q > best) {
1703
+ specificity = level;
1704
+ best = range.q;
1705
+ }
1706
+ }
1707
+ return best < 0 ? 0 : best;
1708
+ }
1709
+ /**
1710
+ * Chooses the media type of a response among `produced` (a bit mask of `1 << Media.X`), per
1711
+ * `accept` (`undefined`: no header, i.e. `application/json`): a {@link MediaKind}, plus
1712
+ * {@link STREAMABLE} when a JSON type wins and an event stream is acceptable too.
1713
+ */
1714
+ function negotiate(accept, produced) {
1715
+ if (accept === void 0) return produced & 1 << Media.Json ? Media.Json : Media.None;
1716
+ const ranges = parseRanges(accept);
1717
+ if (ranges.length === 0) return produced & 1 << Media.Json ? Media.Json : Media.None;
1718
+ let chosen = Media.None;
1719
+ let best = 0;
1720
+ for (let media = Media.GraphQLJson; media < NAMES.length; media++) {
1721
+ if ((produced & 1 << media) === 0) continue;
1722
+ const [type, subtype] = NAMES[media];
1723
+ const q = quality(ranges, type, subtype, media === Media.Json);
1724
+ if (q > best) {
1725
+ best = q;
1726
+ chosen = media;
1727
+ }
1728
+ }
1729
+ if (produced & 1 << Media.EventStream) {
1730
+ const q = quality(ranges, "text", "event-stream", false);
1731
+ if (q > best) return Media.EventStream;
1732
+ if (q > 0 && (chosen === Media.GraphQLJson || chosen === Media.Json)) chosen |= 8;
1733
+ }
1734
+ return chosen;
1735
+ }
1736
+ const CACHE_MAX = 64;
1737
+ /**
1738
+ * {@link negotiate} for one route, memoised by header value: a server sees a handful of `accept`
1739
+ * values, so a request costs a map lookup. Bounded, since clients choose the values: cleared when
1740
+ * full.
1741
+ */
1742
+ var Negotiator = class {
1743
+ #produced;
1744
+ #cache = /* @__PURE__ */ new Map();
1745
+ /** The answer without `accept` header. */
1746
+ #absent;
1747
+ /** `produced`: a bit mask of `1 << Media.X`. */
1748
+ constructor(produced) {
1749
+ this.#produced = produced;
1750
+ this.#absent = negotiate(void 0, produced);
1751
+ }
1752
+ /** The {@link negotiate} answer: a {@link MediaKind}, possibly with {@link STREAMABLE}. */
1753
+ choose(accept) {
1754
+ if (accept === void 0) return this.#absent;
1755
+ let media = this.#cache.get(accept);
1756
+ if (media === void 0) {
1757
+ media = negotiate(accept, this.#produced);
1758
+ if (this.#cache.size === CACHE_MAX) this.#cache.clear();
1759
+ this.#cache.set(accept, media);
1760
+ }
1761
+ return media;
1762
+ }
1763
+ };
1764
+ // src/transport/graphiql.ts
1765
+ /**
1766
+ * The GraphiQL page (RFC 0005 §8.3, §8.9): served on `GET path` with `accept: text/html` when the
1767
+ * `graphiql` option is on (default: `NODE_ENV === 'development'`). React and GraphiQL load from a
1768
+ * pinned CDN with Subresource Integrity; the inline script is fixed at boot and allowed by hash in
1769
+ * the page's own `content-security-policy`, which overrides the strict API default of RFC 0001 on
1770
+ * this response only.
1771
+ */
1772
+ /** jsDelivr. */
1773
+ const GRAPHIQL_CDN = "https://cdn.jsdelivr.net";
1774
+ /**
1775
+ * The assets of the page: GraphiQL 3 (the last major with a UMD build) on React 18. Bumped by hand,
1776
+ * with their SRI hashes (`sha384` of the files on the CDN).
1777
+ */
1778
+ const GRAPHIQL_ASSETS = Object.freeze([
1779
+ {
1780
+ url: `${GRAPHIQL_CDN}/npm/graphiql@3.9.0/graphiql.min.css`,
1781
+ integrity: "sha384-QMux00XgRtwRLSYIY3kw2rj1ovk5AuuliAchk+HSQbqdbGFnz9GYuqIlOqxhwCE2",
1782
+ kind: "style"
1783
+ },
1784
+ {
1785
+ url: `${GRAPHIQL_CDN}/npm/react@18.3.1/umd/react.production.min.js`,
1786
+ integrity: "sha384-DGyLxAyjq0f9SPpVevD6IgztCFlnMF6oW/XQGmfe+IsZ8TqEiDrcHkMLKI6fiB/Z",
1787
+ kind: "script"
1788
+ },
1789
+ {
1790
+ url: `${GRAPHIQL_CDN}/npm/react-dom@18.3.1/umd/react-dom.production.min.js`,
1791
+ integrity: "sha384-gTGxhz21lVGYNMcdJOyq01Edg0jhn/c22nsx0kyqP0TxaV5WVdsSH1fSDUf5YJj1",
1792
+ kind: "script"
1793
+ },
1794
+ {
1795
+ url: `${GRAPHIQL_CDN}/npm/graphiql@3.9.0/graphiql.min.js`,
1796
+ integrity: "sha384-8NGfVj4CVlqHajlZj+bPJT4thxPMHMJYn7DWK2CvtopLd02E7qsPHazniBYvVjOO",
1797
+ kind: "script"
1798
+ }
1799
+ ]);
1800
+ /** `<`, `>` and `&` escaped: JSON safe inside a `<script>` element. */
1801
+ function scriptJson(value) {
1802
+ return JSON.stringify(value).replace(/</g, "\\u003c").replace(/>/g, "\\u003e").replace(/&/g, "\\u0026");
1803
+ }
1804
+ function escapeHtml(text) {
1805
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
1806
+ }
1807
+ /** The GraphiQL page of the endpoint `endpoint` (an absolute path, prefix included) and its CSP. */
1808
+ function renderGraphiQL(endpoint) {
1809
+ const tags = GRAPHIQL_ASSETS.map((asset) => asset.kind === "style" ? `<link rel="stylesheet" href="${escapeHtml(asset.url)}" integrity="${asset.integrity}" crossorigin="anonymous">` : `<script src="${escapeHtml(asset.url)}" integrity="${asset.integrity}" crossorigin="anonymous"><\/script>`);
1810
+ const inline = [`const fetcher = GraphiQL.createFetcher({ url: ${scriptJson(endpoint)} })`, `ReactDOM.createRoot(document.getElementById('graphiql')).render(React.createElement(GraphiQL, { fetcher }))`].join("\n");
1811
+ const hash = `'sha256-${createHash("sha256").update(inline).digest("base64")}'`;
1812
+ return {
1813
+ html: [
1814
+ "<!doctype html>",
1815
+ "<html lang=\"en\">",
1816
+ "<head>",
1817
+ "<meta charset=\"utf-8\">",
1818
+ "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">",
1819
+ "<title>GraphiQL</title>",
1820
+ "<style>body { margin: 0; } #graphiql { height: 100dvh; }</style>",
1821
+ ...tags.filter((tag) => tag.startsWith("<link")),
1822
+ "</head>",
1823
+ "<body>",
1824
+ "<div id=\"graphiql\">Loading GraphiQL…</div>",
1825
+ ...tags.filter((tag) => tag.startsWith("<script")),
1826
+ `<script>${inline}<\/script>`,
1827
+ "</body>",
1828
+ "</html>",
1829
+ ""
1830
+ ].join("\n"),
1831
+ csp: [
1832
+ "default-src 'none'",
1833
+ `script-src ${GRAPHIQL_CDN} ${hash}`,
1834
+ `style-src 'unsafe-inline' ${GRAPHIQL_CDN}`,
1835
+ `font-src data: ${GRAPHIQL_CDN}`,
1836
+ "img-src 'self' data:",
1837
+ "connect-src 'self'",
1838
+ "base-uri 'none'",
1839
+ "form-action 'none'",
1840
+ "frame-ancestors 'self'"
1841
+ ].join("; ")
1842
+ };
1843
+ }
1844
+ // src/transport/sse.ts
1845
+ /** The last event of a stream that ended normally. */
1846
+ const COMPLETE = Object.freeze({
1847
+ event: "complete",
1848
+ data: ""
1849
+ });
1850
+ const DONE = Object.freeze({
1851
+ done: true,
1852
+ value: void 0
1853
+ });
1854
+ /**
1855
+ * The event source of an operation: one `next` event per result, then `complete` when the
1856
+ * operation ended by itself. A stream ended by `ctx.signal` (client gone, shutdown, `maxDuration`)
1857
+ * gets no `complete`: GraphQL-over-SSE clients then treat it as a lost connection and retry.
1858
+ */
1859
+ var ResultEvents = class {
1860
+ #results;
1861
+ #signal;
1862
+ #done = false;
1863
+ constructor(results, signal) {
1864
+ this.#results = results;
1865
+ this.#signal = signal;
1866
+ }
1867
+ async next() {
1868
+ if (this.#done) return DONE;
1869
+ const step = await this.#results.next();
1870
+ if (step.done !== true) return {
1871
+ done: false,
1872
+ value: {
1873
+ event: "next",
1874
+ data: step.value
1875
+ }
1876
+ };
1877
+ this.#done = true;
1878
+ return this.#signal.aborted ? DONE : {
1879
+ done: false,
1880
+ value: COMPLETE
1881
+ };
1882
+ }
1883
+ async return() {
1884
+ this.#done = true;
1885
+ await this.#results.return();
1886
+ return DONE;
1887
+ }
1888
+ [Symbol.asyncIterator]() {
1889
+ return this;
1890
+ }
1891
+ };
1892
+ // src/transport/http.ts
1893
+ const JSON_TYPE = "application/json; charset=utf-8";
1894
+ const GRAPHQL_JSON_TYPE = "application/graphql-response+json; charset=utf-8";
1895
+ /** Response options of `application/graphql-response+json` replies, by status. */
1896
+ const GRAPHQL_JSON_200 = Object.freeze({
1897
+ status: 200,
1898
+ headers: Object.freeze({ "content-type": GRAPHQL_JSON_TYPE })
1899
+ });
1900
+ const GRAPHQL_JSON_400 = Object.freeze({
1901
+ status: 400,
1902
+ headers: Object.freeze({ "content-type": GRAPHQL_JSON_TYPE })
1903
+ });
1904
+ /** `application/json` is what the serialiser gives an object body: only the status is set. */
1905
+ const JSON_200 = Object.freeze({ status: 200 });
1906
+ const UTF8 = new TextDecoder("utf-8", { fatal: true });
1907
+ /** A request error before execution: `{ errors: [{ message, extensions: { code } }] }`. */
1908
+ function errorBody(message, code = GraphQLErrorCode.BadRequest) {
1909
+ return { errors: [{
1910
+ message,
1911
+ extensions: { code }
1912
+ }] };
1913
+ }
1914
+ /** A reply of `status` carrying a GraphQL error body in the negotiated JSON type (`application/json` before negotiation). */
1915
+ function errorReply(media, status, body, headers) {
1916
+ const type = media === Media.GraphQLJson ? GRAPHQL_JSON_TYPE : JSON_TYPE;
1917
+ return new HttpResponse(body, {
1918
+ status,
1919
+ headers: {
1920
+ ...headers,
1921
+ "content-type": type
1922
+ }
1923
+ });
1924
+ }
1925
+ const NOT_ACCEPTABLE = errorReply(Media.Json, 406, errorBody("None of the accepted media types can be produced: accept application/graphql-response+json, application/json or text/event-stream"));
1926
+ const GET_NOT_ALLOWED = errorReply(Media.Json, 405, errorBody("GraphQL operations are sent with POST on this endpoint"), Object.freeze({ allow: "POST" }));
1927
+ /**
1928
+ * Whether a `POST` body is JSON (`application/json`, UTF-8): the only body the endpoint takes (and
1929
+ * the Yoga driver), so that a cross-site request, which cannot send it without a CORS preflight,
1930
+ * never executes an operation. `content-type` values seen most often are compared before parsing.
1931
+ */
1932
+ function isJsonRequest(contentType) {
1933
+ if (contentType === "application/json" || contentType === "application/json; charset=utf-8") return true;
1934
+ const media = parseMediaType(contentType);
1935
+ if (media === void 0 || media.type !== "application/json") return false;
1936
+ return media.charset === void 0 || media.charset === "utf-8" || media.charset === "utf8";
1937
+ }
1938
+ /** The 415 of a `POST` body that is not JSON, in the negotiated JSON type. */
1939
+ function notJson(media) {
1940
+ return errorReply(media, 415, errorBody("Send GraphQL requests as application/json (content-type: application/json)"));
1941
+ }
1942
+ /** Why the parameters of a request are invalid, `undefined` when they are valid. */
1943
+ function invalidParams(params) {
1944
+ const query = params["query"];
1945
+ if (typeof query !== "string") return query === void 0 || query === null ? "Missing query: send { query, variables?, operationName?, extensions? }" : "The query must be a string";
1946
+ const operationName = params["operationName"];
1947
+ if (operationName !== void 0 && operationName !== null && typeof operationName !== "string") return "operationName must be a string";
1948
+ if (!isMapOrNull(params["variables"])) return "variables must be an object";
1949
+ if (!isMapOrNull(params["extensions"])) return "extensions must be an object";
1950
+ }
1951
+ /** The parameters of a GraphQL `GET` request. */
1952
+ const GET_PARAMS = [
1953
+ "query",
1954
+ "operationName",
1955
+ "variables",
1956
+ "extensions"
1957
+ ];
1958
+ /** A query string component decoded like `URLSearchParams` (`+` is a space); `undefined` when malformed. */
1959
+ function decodeComponent(text) {
1960
+ if (text.indexOf("%") === -1 && text.indexOf("+") === -1) return text;
1961
+ try {
1962
+ return decodeURIComponent(text.replaceAll("+", " "));
1963
+ } catch {
1964
+ return;
1965
+ }
1966
+ }
1967
+ /**
1968
+ * The GraphQL parameters of a request target, read directly: `ctx.query` builds a record of every
1969
+ * parameter through `URLSearchParams` (about 0.35 µs of a `GET` on Node.js, 0.15 µs on Bun, see
1970
+ * `test/bench/http.bench.ts`). A repeated parameter
1971
+ * becomes an array, like `ctx.query`. `undefined` when a component is malformed percent-encoding:
1972
+ * the caller falls back to `ctx.query` (which keeps such sequences as they are).
1973
+ */
1974
+ function readGetParams(url) {
1975
+ const params = {};
1976
+ const mark = url.indexOf("?");
1977
+ if (mark === -1) return params;
1978
+ const length = url.length;
1979
+ let start = mark + 1;
1980
+ while (start < length) {
1981
+ let end = url.indexOf("&", start);
1982
+ if (end === -1) end = length;
1983
+ if (end > start) {
1984
+ let equals = url.indexOf("=", start);
1985
+ if (equals === -1 || equals > end) equals = end;
1986
+ const name = decodeComponent(url.slice(start, equals));
1987
+ if (name === void 0) return void 0;
1988
+ if (name === "query" || name === "operationName" || name === "variables" || name === "extensions") {
1989
+ const value = equals === end ? "" : decodeComponent(url.slice(equals + 1, end));
1990
+ if (value === void 0) return void 0;
1991
+ const existing = params[name];
1992
+ if (existing === void 0) params[name] = value;
1993
+ else params[name] = typeof existing === "string" ? [existing, value] : [...existing, value];
1994
+ }
1995
+ }
1996
+ start = end + 1;
1997
+ }
1998
+ return params;
1999
+ }
2000
+ function isMapOrNull(value) {
2001
+ return value === void 0 || value === null || typeof value === "object" && !Array.isArray(value);
2002
+ }
2003
+ function isPromise(value) {
2004
+ return typeof value?.then === "function";
2005
+ }
2006
+ /** The handlers of the GraphQL endpoint. */
2007
+ var HttpTransport = class {
2008
+ #resolveExecutor;
2009
+ #executor;
2010
+ #getQueries;
2011
+ #sse;
2012
+ #post;
2013
+ #get;
2014
+ /** The GraphiQL page, `undefined` when disabled. */
2015
+ #graphiql;
2016
+ constructor(options) {
2017
+ this.#resolveExecutor = options.executor;
2018
+ this.#getQueries = options.get;
2019
+ this.#sse = options.sse;
2020
+ const json = 1 << Media.GraphQLJson | 1 << Media.Json | (options.sse === false ? 0 : 1 << Media.EventStream);
2021
+ this.#post = new Negotiator(json);
2022
+ this.#get = new Negotiator(options.graphiql ? json | 1 << Media.Html : json);
2023
+ if (options.graphiql) {
2024
+ const page = renderGraphiQL(options.endpoint);
2025
+ this.#graphiql = new HttpResponse(new TextEncoder().encode(page.html), {
2026
+ status: 200,
2027
+ headers: {
2028
+ "content-type": "text/html; charset=utf-8",
2029
+ "content-security-policy": page.csp,
2030
+ "cache-control": "no-store"
2031
+ }
2032
+ });
2033
+ }
2034
+ }
2035
+ /** `POST path`. */
2036
+ post = (ctx) => {
2037
+ const headers = ctx.headers;
2038
+ const negotiated = this.#post.choose(headers["accept"]);
2039
+ const media = negotiated & 7;
2040
+ if (media === Media.None) return NOT_ACCEPTABLE;
2041
+ if (!isJsonRequest(headers["content-type"])) return notJson(media);
2042
+ const encoding = headers["content-encoding"];
2043
+ if (encoding !== void 0 && encoding.trim().toLowerCase() !== "identity" && encoding.trim() !== "") return errorReply(media, 415, errorBody(`Unsupported content encoding "${encoding}"`));
2044
+ return ctx.rawBody().then((bytes) => this.#posted(ctx, bytes, media, negotiated >= 8), (error) => transportError(error, media));
2045
+ };
2046
+ /** `GET path`. */
2047
+ get = (ctx) => {
2048
+ const media = this.#get.choose(ctx.headers["accept"]) & 7;
2049
+ if (media === Media.Html) return this.#graphiql;
2050
+ if (media === Media.None) return NOT_ACCEPTABLE;
2051
+ if (!this.#getQueries) return GET_NOT_ALLOWED;
2052
+ const params = readGetParams(ctx.url) ?? ctx.query;
2053
+ const request = {};
2054
+ for (const name of GET_PARAMS) {
2055
+ const value = params[name];
2056
+ if (value === void 0) continue;
2057
+ if (typeof value !== "string") return errorReply(media, 400, errorBody(`Repeated ${name} parameter`));
2058
+ if (name === "variables" || name === "extensions") try {
2059
+ request[name] = JSON.parse(value);
2060
+ } catch {
2061
+ return errorReply(media, 400, errorBody(`The ${name} parameter must be JSON`));
2062
+ }
2063
+ else request[name] = value;
2064
+ }
2065
+ const invalid = invalidParams(request);
2066
+ if (invalid !== void 0) return errorReply(media, 400, errorBody(invalid));
2067
+ if (media === Media.EventStream) return this.#stream(ctx, request, true);
2068
+ return this.#execute(ctx, request, media, true, false);
2069
+ };
2070
+ /** A `POST` body read: parsed, checked, executed or streamed. */
2071
+ #posted(ctx, bytes, media, streamable) {
2072
+ if (bytes.byteLength === 0) return errorReply(media, 400, errorBody("Missing request body: send { query, variables?, operationName? }"));
2073
+ let body;
2074
+ try {
2075
+ body = JSON.parse(UTF8.decode(bytes));
2076
+ } catch {
2077
+ return errorReply(media, 400, errorBody("Malformed JSON body"));
2078
+ }
2079
+ if (body === null || typeof body !== "object") return errorReply(media, 400, errorBody("The body must be a JSON object: { query, variables?, operationName? }"));
2080
+ if (Array.isArray(body)) return errorReply(media, 400, errorBody("Operation batching is not supported: send one operation per request"));
2081
+ const invalid = invalidParams(body);
2082
+ if (invalid !== void 0) return errorReply(media, 400, errorBody(invalid));
2083
+ if (media === Media.EventStream) return this.#stream(ctx, body, false);
2084
+ return this.#execute(ctx, body, media, false, streamable);
2085
+ }
2086
+ /**
2087
+ * Executes a query or a mutation; synchronous while the execution is. A subscription is streamed
2088
+ * when the client accepts an event stream (`streamable`), a 400 otherwise.
2089
+ */
2090
+ #execute(ctx, request, media, get, streamable) {
2091
+ const executor = this.#executor ?? this.#bind();
2092
+ const abort = new DeferredAbort();
2093
+ const options = {
2094
+ transport: "http",
2095
+ http: ctx,
2096
+ abortSignal: abort.asSignal(),
2097
+ queriesOnly: get,
2098
+ streamSubscriptions: streamable
2099
+ };
2100
+ const response = executor.execute(request, options);
2101
+ if (response === SUBSCRIBE) return this.#stream(ctx, request, get);
2102
+ if (!isPromise(response)) return reply(response, media, get);
2103
+ abort.follow(() => ctx.signal);
2104
+ return response.then((settled) => reply(settled, media, get), (error) => {
2105
+ if (!abort.aborted || error !== abort.reason) throw error;
2106
+ return errorReply(media, 503, errorBody("The request was aborted", GraphQLErrorCode.ServiceUnavailable));
2107
+ });
2108
+ }
2109
+ /** GraphQL over SSE: any operation, its results as `next` events. */
2110
+ #stream(ctx, request, get) {
2111
+ const executor = this.#executor ?? this.#bind();
2112
+ const signal = ctx.signal;
2113
+ const options = {
2114
+ transport: "sse",
2115
+ http: ctx,
2116
+ signal,
2117
+ queriesOnly: get
2118
+ };
2119
+ return executor.subscribe(request, options).then((outcome) => {
2120
+ if ("status" in outcome) return errorReply(Media.Json, outcome.status, outcome.result, outcome.headers);
2121
+ return HttpResponse.sse(new ResultEvents(outcome, signal), this.#sse);
2122
+ }, (error) => {
2123
+ if (!signal.aborted || error !== signal.reason) throw error;
2124
+ return errorReply(Media.Json, 503, errorBody("The request was aborted", GraphQLErrorCode.ServiceUnavailable));
2125
+ });
2126
+ }
2127
+ #bind() {
2128
+ this.#executor = this.#resolveExecutor();
2129
+ return this.#executor;
2130
+ }
2131
+ };
2132
+ /**
2133
+ * The HTTP reply of an executor response. `application/json` answers 200 to every well-formed
2134
+ * request (request errors included); `application/graphql-response+json` keeps the status.
2135
+ */
2136
+ function reply(response, media, get) {
2137
+ const status = response.status;
2138
+ const result = response.result;
2139
+ if (media === Media.Json) {
2140
+ if (status === 200 || status === 400) return get ? result : new HttpResponse(result, JSON_200);
2141
+ return errorReply(media, status, result, response.headers);
2142
+ }
2143
+ if (response.headers === void 0) {
2144
+ if (status === 200) return new HttpResponse(result, GRAPHQL_JSON_200);
2145
+ if (status === 400) return new HttpResponse(result, GRAPHQL_JSON_400);
2146
+ }
2147
+ return errorReply(media, status, result, response.headers);
2148
+ }
2149
+ /** A failure reading the body (413 past the body limit, 400 for an aborted body) with a GraphQL error body. */
2150
+ function transportError(error, media) {
2151
+ if (!isHttpException(error)) throw error;
2152
+ return errorReply(media, error.status, errorBody(error.message));
2153
+ }
2154
+ /** Checks the SSE options once (`HttpResponse.sse()` validates them): `undefined` when they are valid. */
2155
+ function checkSseOptions(options) {
2156
+ try {
2157
+ HttpResponse.sse([], options);
2158
+ return;
2159
+ } catch (error) {
2160
+ return error instanceof Error ? error.message : String(error);
2161
+ }
2162
+ }
2163
+ export { Media as a, singleResult as c, resolveIn as d, GraphQLErrorCode as f, notJson as i, GraphQLTokens as l, checkSseOptions as n, negotiate as o, GraphQLException as p, isJsonRequest as r, GraphQLExecutor as s, HttpTransport as t, ErrorFormatter as u };
2164
+
2165
+ //# sourceMappingURL=http-BWcayUtH.mjs.map