@leaven-graphql/nestjs 0.1.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -14,358 +14,219 @@ var __legacyMetadataTS = (k, v) => {
14
14
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function")
15
15
  return Reflect.metadata(k, v);
16
16
  };
17
- var __require = import.meta.require;
18
17
 
19
18
  // src/module.ts
20
19
  import {
21
20
  Module,
22
- Global
21
+ Global,
22
+ Inject as Inject5,
23
+ Optional
23
24
  } from "@nestjs/common";
24
25
 
25
- // src/driver.ts
26
- import { Injectable } from "@nestjs/common";
27
- import { LeavenExecutor } from "@leaven-graphql/core";
28
- class LeavenDriver {
29
- executor = null;
30
- schema = null;
31
- options;
32
- constructor(options) {
33
- this.options = options;
26
+ // src/types.ts
27
+ var LEAVEN_MODULE_OPTIONS = "LEAVEN_MODULE_OPTIONS";
28
+ var LEAVEN_DRIVER = "LEAVEN_DRIVER";
29
+ var LEAVEN_PUBSUB = "LEAVEN_PUBSUB";
30
+
31
+ // src/module.ts
32
+ import { createPubSub } from "@leaven-graphql/ws";
33
+
34
+ // src/guards.ts
35
+ import {
36
+ Injectable,
37
+ UnauthorizedException,
38
+ ForbiddenException,
39
+ SetMetadata,
40
+ Inject
41
+ } from "@nestjs/common";
42
+ import { Reflector } from "@nestjs/core";
43
+
44
+ // src/execution-context.ts
45
+ var GQL_RESOLVER_ARGS = {
46
+ ROOT: 0,
47
+ ARGS: 1,
48
+ CONTEXT: 2,
49
+ INFO: 3
50
+ };
51
+
52
+ class GqlExecutionContext {
53
+ host;
54
+ args;
55
+ constructor(host) {
56
+ this.host = host;
57
+ this.args = host.getArgs();
34
58
  }
35
- async onModuleInit() {
36
- if (this.options.schema) {
37
- this.schema = this.options.schema;
38
- this.initializeExecutor();
39
- }
59
+ static create(context) {
60
+ return new GqlExecutionContext(context);
40
61
  }
41
- async onModuleDestroy() {
42
- if (this.executor) {
43
- this.executor.clearCaches();
44
- this.executor = null;
45
- }
62
+ getContext() {
63
+ return this.args[GQL_RESOLVER_ARGS.CONTEXT];
46
64
  }
47
- setSchema(schema) {
48
- this.schema = schema;
49
- this.initializeExecutor();
65
+ getRoot() {
66
+ return this.args[GQL_RESOLVER_ARGS.ROOT];
50
67
  }
51
- getSchema() {
52
- return this.schema;
68
+ getArgs() {
69
+ return this.args[GQL_RESOLVER_ARGS.ARGS];
53
70
  }
54
- getExecutor() {
55
- return this.executor;
71
+ getInfo() {
72
+ return this.args[GQL_RESOLVER_ARGS.INFO];
56
73
  }
57
- initializeExecutor() {
58
- if (!this.schema) {
59
- throw new Error("Schema must be set before initializing executor");
60
- }
61
- const config = {
62
- schema: this.schema,
63
- cache: this.options.cache,
64
- metrics: this.options.metrics,
65
- maxComplexity: this.options.maxComplexity
66
- };
67
- this.executor = new LeavenExecutor(config);
74
+ getArg(name) {
75
+ const args = this.getArgs();
76
+ return args[name];
68
77
  }
69
- async execute(query, variables, context, operationName) {
70
- if (!this.executor) {
71
- throw new Error("Executor not initialized. Set schema first.");
72
- }
73
- const queryString = typeof query === "string" ? query : this.documentToString(query);
74
- try {
75
- const result = await this.executor.execute({
76
- query: queryString,
77
- variables,
78
- operationName
79
- }, context);
80
- const response = result.response;
81
- let formattedErrors = response.errors;
82
- if (formattedErrors && this.options.formatError) {
83
- formattedErrors = formattedErrors.map(this.options.formatError);
84
- }
85
- let extensions = response.extensions;
86
- if (this.options.metrics && result.metrics) {
87
- extensions = {
88
- ...extensions,
89
- metrics: result.metrics
90
- };
91
- }
92
- return {
93
- data: response.data,
94
- errors: formattedErrors,
95
- extensions
96
- };
97
- } catch (error) {
98
- const message = error instanceof Error ? error.message : "Execution failed";
99
- const formattedError = {
100
- message,
101
- extensions: {
102
- code: "INTERNAL_SERVER_ERROR",
103
- ...this.options.includeStacktraceInErrorResponses && error instanceof Error ? { stacktrace: error.stack?.split(`
104
- `) } : {}
105
- }
106
- };
107
- return {
108
- errors: [
109
- this.options.formatError ? this.options.formatError(formattedError) : formattedError
110
- ]
111
- };
112
- }
78
+ getContextProperty(name) {
79
+ const ctx = this.getContext();
80
+ return ctx[name];
113
81
  }
114
- async handleRequest(request, response) {
115
- const body = await this.parseRequestBody(request);
116
- const context = await this.createContext(request, response);
117
- return this.execute(body.query, body.variables, context, body.operationName);
82
+ getHost() {
83
+ return this.host;
118
84
  }
119
- async parseRequestBody(request) {
120
- const contentType = request.headers.get("content-type") ?? "";
121
- if (contentType.includes("application/json")) {
122
- const body = await request.json();
123
- return {
124
- query: body.query,
125
- variables: body.variables,
126
- operationName: body.operationName
127
- };
128
- }
129
- const url = new URL(request.url);
130
- const query = url.searchParams.get("query");
131
- const variables = url.searchParams.get("variables");
132
- const operationName = url.searchParams.get("operationName");
133
- if (!query) {
134
- throw new Error("Query is required");
85
+ getType() {
86
+ return this.host.getType?.() ?? "unknown";
87
+ }
88
+ isGraphQL() {
89
+ return this.getType() === "graphql";
90
+ }
91
+ getHandler() {
92
+ return this.host.getHandler?.();
93
+ }
94
+ getClass() {
95
+ return this.host.getClass?.();
96
+ }
97
+ getFieldName() {
98
+ const info = this.getInfo();
99
+ return info?.fieldName;
100
+ }
101
+ getParentTypeName() {
102
+ const info = this.getInfo();
103
+ return info?.parentType?.name;
104
+ }
105
+ getReturnTypeName() {
106
+ const info = this.getInfo();
107
+ return info?.returnType?.toString();
108
+ }
109
+ getOperationName() {
110
+ const info = this.getInfo();
111
+ return info?.operation?.name?.value;
112
+ }
113
+ getOperationType() {
114
+ const info = this.getInfo();
115
+ return info?.operation?.operation;
116
+ }
117
+ getVariables() {
118
+ const info = this.getInfo();
119
+ return info?.variableValues ?? {};
120
+ }
121
+ getPath() {
122
+ return this.getFullPath().filter((segment) => typeof segment === "string");
123
+ }
124
+ getFullPath() {
125
+ const info = this.getInfo();
126
+ if (!info?.path)
127
+ return [];
128
+ const path = [];
129
+ let current = info.path;
130
+ while (current) {
131
+ path.unshift(current.key);
132
+ current = current.prev;
135
133
  }
136
- return {
137
- query,
138
- variables: variables ? JSON.parse(variables) : undefined,
139
- operationName: operationName ?? undefined
140
- };
134
+ return path;
141
135
  }
142
- async createContext(request, response) {
143
- const baseContext = {
144
- req: request,
145
- res: response
136
+ getSelectedFields() {
137
+ const info = this.getInfo();
138
+ if (!info?.fieldNodes?.length)
139
+ return [];
140
+ const fields = new Set;
141
+ const visitedFragments = new Set;
142
+ const collect = (selectionSet) => {
143
+ if (!selectionSet?.selections)
144
+ return;
145
+ for (const selection of selectionSet.selections) {
146
+ if (selection.kind === "Field") {
147
+ fields.add(selection.name.value);
148
+ } else if (selection.kind === "InlineFragment") {
149
+ collect(selection.selectionSet);
150
+ } else if (selection.kind === "FragmentSpread") {
151
+ const fragmentName = selection.name.value;
152
+ if (visitedFragments.has(fragmentName))
153
+ continue;
154
+ visitedFragments.add(fragmentName);
155
+ collect(info.fragments?.[fragmentName]?.selectionSet);
156
+ }
157
+ }
146
158
  };
147
- if (this.options.context) {
148
- const customContext = await this.options.context(request, response);
149
- return { ...baseContext, ...customContext };
159
+ for (const fieldNode of info.fieldNodes) {
160
+ collect(fieldNode?.selectionSet);
150
161
  }
151
- return baseContext;
162
+ return [...fields];
152
163
  }
153
- documentToString(document) {
154
- const { print } = __require("graphql");
155
- return print(document);
164
+ }
165
+ var executionContextCache = new WeakMap;
166
+ function getCachedGqlExecutionContext(context) {
167
+ let gqlContext = executionContextCache.get(context);
168
+ if (!gqlContext) {
169
+ gqlContext = GqlExecutionContext.create(context);
170
+ executionContextCache.set(context, gqlContext);
156
171
  }
157
- isPlaygroundEnabled() {
158
- if (this.options.playground !== undefined) {
159
- return this.options.playground;
160
- }
161
- return true;
172
+ return gqlContext;
173
+ }
174
+ function getGqlContext(context) {
175
+ return getCachedGqlExecutionContext(context).getContext();
176
+ }
177
+ function getGqlArgs(context) {
178
+ return getCachedGqlExecutionContext(context).getArgs();
179
+ }
180
+ function getGqlInfo(context) {
181
+ return getCachedGqlExecutionContext(context).getInfo();
182
+ }
183
+ function getGqlRoot(context) {
184
+ return getCachedGqlExecutionContext(context).getRoot();
185
+ }
186
+
187
+ // src/guards.ts
188
+ var IS_PUBLIC_KEY = "leaven:isPublic";
189
+ var ROLES_KEY = "leaven:roles";
190
+ var PERMISSIONS_KEY = "leaven:permissions";
191
+ var LEAVEN_MAX_COMPLEXITY = "LEAVEN_MAX_COMPLEXITY";
192
+ var LEAVEN_MAX_DEPTH = "LEAVEN_MAX_DEPTH";
193
+ var Public = () => SetMetadata(IS_PUBLIC_KEY, true);
194
+ var Roles = (...roles) => SetMetadata(ROLES_KEY, roles);
195
+ var Permissions = (...permissions) => SetMetadata(PERMISSIONS_KEY, permissions);
196
+
197
+ class AuthGuard {
198
+ reflector;
199
+ constructor(reflector) {
200
+ this.reflector = reflector;
162
201
  }
163
- isIntrospectionEnabled() {
164
- if (this.options.introspection !== undefined) {
165
- return this.options.introspection;
202
+ canActivate(context) {
203
+ const isPublic = this.reflector.getAllAndOverride(IS_PUBLIC_KEY, [
204
+ context.getHandler(),
205
+ context.getClass()
206
+ ]);
207
+ if (isPublic) {
208
+ return true;
209
+ }
210
+ const gqlContext = this.getGqlContext(context);
211
+ const user = this.extractUser(gqlContext);
212
+ if (!user) {
213
+ throw new UnauthorizedException("Authentication required");
166
214
  }
167
215
  return true;
168
216
  }
169
- getPath() {
170
- return this.options.path ?? "/graphql";
171
- }
172
- getCacheStats() {
173
- return this.executor?.getCacheStats() ?? null;
217
+ getGqlContext(context) {
218
+ return getGqlContext(context);
174
219
  }
175
- clearCaches() {
176
- this.executor?.clearCaches();
220
+ extractUser(context) {
221
+ return context.user ?? context.req?.user ?? null;
177
222
  }
178
223
  }
179
- LeavenDriver = __legacyDecorateClassTS([
224
+ AuthGuard = __legacyDecorateClassTS([
180
225
  Injectable(),
181
226
  __legacyMetadataTS("design:paramtypes", [
182
- typeof LeavenModuleOptions === "undefined" ? Object : LeavenModuleOptions
227
+ typeof Reflector === "undefined" ? Object : Reflector
183
228
  ])
184
- ], LeavenDriver);
185
-
186
- // src/module.ts
187
- var LEAVEN_MODULE_OPTIONS = "LEAVEN_MODULE_OPTIONS";
188
- var LEAVEN_DRIVER = "LEAVEN_DRIVER";
189
-
190
- class LeavenModule {
191
- static forRoot(options = {}) {
192
- const optionsProvider = {
193
- provide: LEAVEN_MODULE_OPTIONS,
194
- useValue: options
195
- };
196
- const driverProvider = {
197
- provide: LEAVEN_DRIVER,
198
- useFactory: (opts) => new LeavenDriver(opts),
199
- inject: [LEAVEN_MODULE_OPTIONS]
200
- };
201
- return {
202
- module: LeavenModule,
203
- providers: [optionsProvider, driverProvider],
204
- exports: [LEAVEN_MODULE_OPTIONS, LEAVEN_DRIVER]
205
- };
206
- }
207
- static forRootAsync(options) {
208
- const asyncProviders = this.createAsyncProviders(options);
209
- const driverProvider = {
210
- provide: LEAVEN_DRIVER,
211
- useFactory: (opts) => new LeavenDriver(opts),
212
- inject: [LEAVEN_MODULE_OPTIONS]
213
- };
214
- return {
215
- module: LeavenModule,
216
- imports: options.imports ?? [],
217
- providers: [...asyncProviders, driverProvider],
218
- exports: [LEAVEN_MODULE_OPTIONS, LEAVEN_DRIVER]
219
- };
220
- }
221
- static createAsyncProviders(options) {
222
- if (options.useExisting || options.useFactory) {
223
- return [this.createAsyncOptionsProvider(options)];
224
- }
225
- const useClass = options.useClass;
226
- return [
227
- this.createAsyncOptionsProvider(options),
228
- {
229
- provide: useClass,
230
- useClass
231
- }
232
- ];
233
- }
234
- static createAsyncOptionsProvider(options) {
235
- if (options.useFactory) {
236
- return {
237
- provide: LEAVEN_MODULE_OPTIONS,
238
- useFactory: options.useFactory,
239
- inject: options.inject ?? []
240
- };
241
- }
242
- const inject = [
243
- options.useClass ?? options.useExisting
244
- ];
245
- return {
246
- provide: LEAVEN_MODULE_OPTIONS,
247
- useFactory: async (optionsFactory) => optionsFactory.createLeavenOptions(),
248
- inject
249
- };
250
- }
251
- }
252
- LeavenModule = __legacyDecorateClassTS([
253
- Global(),
254
- Module({})
255
- ], LeavenModule);
256
- // src/decorators.ts
257
- import {
258
- createParamDecorator,
259
- SetMetadata,
260
- applyDecorators
261
- } from "@nestjs/common";
262
- var COMPLEXITY_KEY = "leaven:complexity";
263
- var DEPRECATED_KEY = "leaven:deprecated";
264
- var DESCRIPTION_KEY = "leaven:description";
265
- var CACHE_KEY = "leaven:cache";
266
- var SUBSCRIPTION_FILTER_KEY = "leaven:subscription:filter";
267
- var Context = createParamDecorator((data, ctx) => {
268
- const gqlContext = getGqlContext(ctx);
269
- if (data) {
270
- return gqlContext[data];
271
- }
272
- return gqlContext;
273
- });
274
- var Info = createParamDecorator((_data, ctx) => {
275
- const args = ctx.getArgs();
276
- return args[3];
277
- });
278
- var Root = createParamDecorator((_data, ctx) => {
279
- const args = ctx.getArgs();
280
- return args[0];
281
- });
282
- var Parent = Root;
283
- var Args = createParamDecorator((data, ctx) => {
284
- const args = ctx.getArgs();
285
- const gqlArgs = args[1];
286
- if (data) {
287
- return gqlArgs?.[data];
288
- }
289
- return gqlArgs;
290
- });
291
- function Complexity(value) {
292
- return SetMetadata(COMPLEXITY_KEY, value);
293
- }
294
- function Deprecated(reason) {
295
- return SetMetadata(DEPRECATED_KEY, reason);
296
- }
297
- function Description(text) {
298
- return SetMetadata(DESCRIPTION_KEY, text);
299
- }
300
- function CacheHint(options) {
301
- return SetMetadata(CACHE_KEY, options);
302
- }
303
- function SubscriptionFilter(filter) {
304
- return SetMetadata(SUBSCRIPTION_FILTER_KEY, filter);
305
- }
306
- function Decorators(...decorators) {
307
- return applyDecorators(...decorators);
308
- }
309
- function getGqlContext(ctx) {
310
- const args = ctx.getArgs();
311
- return args[2];
312
- }
313
- function createContextDecorator(key) {
314
- return () => createParamDecorator((_data, ctx) => {
315
- const gqlContext = getGqlContext(ctx);
316
- return gqlContext[key];
317
- })();
318
- }
319
- // src/guards.ts
320
- import {
321
- Injectable as Injectable2,
322
- UnauthorizedException,
323
- ForbiddenException,
324
- SetMetadata as SetMetadata2,
325
- Inject
326
- } from "@nestjs/common";
327
- import { Reflector } from "@nestjs/core";
328
- var IS_PUBLIC_KEY = "leaven:isPublic";
329
- var ROLES_KEY = "leaven:roles";
330
- var PERMISSIONS_KEY = "leaven:permissions";
331
- var Public = () => SetMetadata2(IS_PUBLIC_KEY, true);
332
- var Roles = (...roles) => SetMetadata2(ROLES_KEY, roles);
333
- var Permissions = (...permissions) => SetMetadata2(PERMISSIONS_KEY, permissions);
334
-
335
- class AuthGuard {
336
- reflector;
337
- constructor(reflector) {
338
- this.reflector = reflector;
339
- }
340
- canActivate(context) {
341
- const isPublic = this.reflector.getAllAndOverride(IS_PUBLIC_KEY, [
342
- context.getHandler(),
343
- context.getClass()
344
- ]);
345
- if (isPublic) {
346
- return true;
347
- }
348
- const gqlContext = this.getGqlContext(context);
349
- const user = this.extractUser(gqlContext);
350
- if (!user) {
351
- throw new UnauthorizedException("Authentication required");
352
- }
353
- return true;
354
- }
355
- getGqlContext(context) {
356
- const args = context.getArgs();
357
- return args[2];
358
- }
359
- extractUser(context) {
360
- return context.user ?? context.req?.user ?? null;
361
- }
362
- }
363
- AuthGuard = __legacyDecorateClassTS([
364
- Injectable2(),
365
- __legacyMetadataTS("design:paramtypes", [
366
- typeof Reflector === "undefined" ? Object : Reflector
367
- ])
368
- ], AuthGuard);
229
+ ], AuthGuard);
369
230
 
370
231
  class RolesGuard {
371
232
  reflector;
@@ -393,8 +254,7 @@ class RolesGuard {
393
254
  return true;
394
255
  }
395
256
  getGqlContext(context) {
396
- const args = context.getArgs();
397
- return args[2];
257
+ return getGqlContext(context);
398
258
  }
399
259
  extractUser(context) {
400
260
  return context.user ?? context.req?.user ?? null;
@@ -412,7 +272,7 @@ class RolesGuard {
412
272
  }
413
273
  }
414
274
  RolesGuard = __legacyDecorateClassTS([
415
- Injectable2(),
275
+ Injectable(),
416
276
  __legacyMetadataTS("design:paramtypes", [
417
277
  typeof Reflector === "undefined" ? Object : Reflector
418
278
  ])
@@ -441,8 +301,7 @@ class PermissionsGuard {
441
301
  return true;
442
302
  }
443
303
  getGqlContext(context) {
444
- const args = context.getArgs();
445
- return args[2];
304
+ return getGqlContext(context);
446
305
  }
447
306
  extractUser(context) {
448
307
  return context.user ?? context.req?.user ?? null;
@@ -460,7 +319,7 @@ class PermissionsGuard {
460
319
  }
461
320
  }
462
321
  PermissionsGuard = __legacyDecorateClassTS([
463
- Injectable2(),
322
+ Injectable(),
464
323
  __legacyMetadataTS("design:paramtypes", [
465
324
  typeof Reflector === "undefined" ? Object : Reflector
466
325
  ])
@@ -472,21 +331,23 @@ class ComplexityGuard {
472
331
  this.maxComplexity = maxComplexity;
473
332
  }
474
333
  canActivate(context) {
475
- const gqlContext = this.getGqlContext(context);
476
- const currentComplexity = gqlContext._queryComplexity;
477
- if (currentComplexity !== undefined && currentComplexity > this.maxComplexity) {
334
+ if (this.maxComplexity === Number.POSITIVE_INFINITY) {
335
+ return true;
336
+ }
337
+ const gqlContext = getGqlContext(context);
338
+ const currentComplexity = gqlContext?._queryComplexity;
339
+ if (typeof currentComplexity !== "number" || Number.isNaN(currentComplexity)) {
340
+ throw new ForbiddenException("Query complexity could not be determined; rejecting the request");
341
+ }
342
+ if (currentComplexity > this.maxComplexity) {
478
343
  throw new ForbiddenException(`Query complexity ${currentComplexity} exceeds maximum allowed ${this.maxComplexity}`);
479
344
  }
480
345
  return true;
481
346
  }
482
- getGqlContext(context) {
483
- const args = context.getArgs();
484
- return args[2];
485
- }
486
347
  }
487
348
  ComplexityGuard = __legacyDecorateClassTS([
488
- Injectable2(),
489
- __legacyDecorateParamTS(0, Inject("LEAVEN_MAX_COMPLEXITY")),
349
+ Injectable(),
350
+ __legacyDecorateParamTS(0, Inject(LEAVEN_MAX_COMPLEXITY)),
490
351
  __legacyMetadataTS("design:paramtypes", [
491
352
  Number
492
353
  ])
@@ -498,188 +359,305 @@ class DepthGuard {
498
359
  this.maxDepth = maxDepth;
499
360
  }
500
361
  canActivate(context) {
501
- const gqlContext = this.getGqlContext(context);
502
- const currentDepth = gqlContext._queryDepth;
503
- if (currentDepth !== undefined && currentDepth > this.maxDepth) {
362
+ if (this.maxDepth === Number.POSITIVE_INFINITY) {
363
+ return true;
364
+ }
365
+ const gqlContext = getGqlContext(context);
366
+ const currentDepth = gqlContext?._queryDepth;
367
+ if (typeof currentDepth !== "number" || Number.isNaN(currentDepth)) {
368
+ throw new ForbiddenException("Query depth could not be determined; rejecting the request");
369
+ }
370
+ if (currentDepth > this.maxDepth) {
504
371
  throw new ForbiddenException(`Query depth ${currentDepth} exceeds maximum allowed ${this.maxDepth}`);
505
372
  }
506
373
  return true;
507
374
  }
508
- getGqlContext(context) {
509
- const args = context.getArgs();
510
- return args[2];
511
- }
512
375
  }
513
376
  DepthGuard = __legacyDecorateClassTS([
514
- Injectable2(),
515
- __legacyDecorateParamTS(0, Inject("LEAVEN_MAX_DEPTH")),
377
+ Injectable(),
378
+ __legacyDecorateParamTS(0, Inject(LEAVEN_MAX_DEPTH)),
516
379
  __legacyMetadataTS("design:paramtypes", [
517
380
  Number
518
381
  ])
519
382
  ], DepthGuard);
520
- // src/interceptors.ts
383
+
384
+ // src/driver.ts
385
+ import { Injectable as Injectable2 } from "@nestjs/common";
521
386
  import {
522
- Injectable as Injectable3
523
- } from "@nestjs/common";
524
- import { tap, catchError, throwError } from "rxjs";
525
- import { Reflector as Reflector2 } from "@nestjs/core";
526
- class LoggingInterceptor {
527
- intercept(context, next) {
528
- const className = context.getClass().name;
529
- const methodName = context.getHandler().name;
530
- const start = performance.now();
531
- return next.handle().pipe(tap(() => {
532
- const duration = (performance.now() - start).toFixed(2);
533
- console.log(`[GraphQL] ${className}.${methodName} - ${duration}ms`);
534
- }));
387
+ GraphQLError,
388
+ parse,
389
+ print
390
+ } from "graphql";
391
+ import {
392
+ LeavenExecutor,
393
+ calculateQueryDepth,
394
+ compileQuery
395
+ } from "@leaven-graphql/core";
396
+ import { ValidationError, formatError, isLeavenError } from "@leaven-graphql/errors";
397
+ class LeavenDriver {
398
+ executor = null;
399
+ schema = null;
400
+ options;
401
+ static MAX_ANALYSIS_ENTRIES = 1000;
402
+ analysisCache = new Map;
403
+ constructor(options) {
404
+ this.options = options;
535
405
  }
536
- }
537
- LoggingInterceptor = __legacyDecorateClassTS([
538
- Injectable3()
539
- ], LoggingInterceptor);
540
-
541
- class ErrorFormattingInterceptor {
542
- intercept(_context, next) {
543
- return next.handle().pipe(catchError((error) => {
544
- const formattedError = this.formatError(error);
545
- return throwError(() => formattedError);
546
- }));
406
+ async onModuleInit() {
407
+ if (this.options.schema) {
408
+ this.schema = this.options.schema;
409
+ this.initializeExecutor();
410
+ }
547
411
  }
548
- formatError(error) {
549
- if (error instanceof Error) {
550
- if (false) {}
551
- return error;
412
+ async onModuleDestroy() {
413
+ if (this.executor) {
414
+ await this.executor.clearCaches();
415
+ this.executor = null;
552
416
  }
553
- return new Error(String(error));
417
+ this.analysisCache.clear();
554
418
  }
555
- }
556
- ErrorFormattingInterceptor = __legacyDecorateClassTS([
557
- Injectable3()
558
- ], ErrorFormattingInterceptor);
559
-
560
- class ComplexityInterceptor {
561
- reflector;
562
- constructor(reflector) {
563
- this.reflector = reflector;
419
+ setSchema(schema) {
420
+ this.schema = schema;
421
+ this.initializeExecutor();
564
422
  }
565
- intercept(context, next) {
566
- const complexity = this.reflector.get(COMPLEXITY_KEY, context.getHandler());
567
- if (complexity !== undefined) {
568
- const args = context.getArgs();
569
- const gqlContext = args[2];
570
- if (gqlContext) {
571
- gqlContext._fieldComplexity = complexity;
572
- }
573
- }
574
- return next.handle();
423
+ getSchema() {
424
+ return this.schema;
575
425
  }
576
- }
577
- ComplexityInterceptor = __legacyDecorateClassTS([
578
- Injectable3(),
579
- __legacyMetadataTS("design:paramtypes", [
580
- typeof Reflector2 === "undefined" ? Object : Reflector2
581
- ])
582
- ], ComplexityInterceptor);
583
-
584
- class CachingInterceptor {
585
- reflector;
586
- constructor(reflector) {
587
- this.reflector = reflector;
426
+ getExecutor() {
427
+ return this.executor;
588
428
  }
589
- intercept(context, next) {
590
- const cacheHint = this.reflector.get(CACHE_KEY, context.getHandler());
591
- return next.handle().pipe(tap(() => {
592
- if (cacheHint) {
593
- const args = context.getArgs();
594
- const gqlInfo = args[3];
595
- if (gqlInfo?.cacheControl) {
596
- gqlInfo.cacheControl.setCacheHint({
597
- maxAge: cacheHint.maxAge,
598
- scope: cacheHint.scope
599
- });
600
- }
601
- }
602
- }));
429
+ initializeExecutor() {
430
+ if (!this.schema) {
431
+ throw new Error("Schema must be set before initializing executor");
432
+ }
433
+ const config = {
434
+ schema: this.schema,
435
+ cache: this.options.cache,
436
+ metrics: this.options.metrics,
437
+ maxComplexity: this.options.maxComplexity,
438
+ maxDepth: this.options.maxDepth,
439
+ introspection: this.isIntrospectionEnabled()
440
+ };
441
+ this.analysisCache.clear();
442
+ this.executor = new LeavenExecutor(config);
603
443
  }
604
- }
605
- CachingInterceptor = __legacyDecorateClassTS([
606
- Injectable3(),
607
- __legacyMetadataTS("design:paramtypes", [
608
- typeof Reflector2 === "undefined" ? Object : Reflector2
609
- ])
610
- ], CachingInterceptor);
611
-
612
- class MetricsInterceptor {
613
- metrics = new Map;
614
- intercept(context, next) {
615
- const className = context.getClass().name;
616
- const methodName = context.getHandler().name;
617
- const key = `${className}.${methodName}`;
618
- const start = performance.now();
619
- return next.handle().pipe(tap(() => {
620
- const duration = performance.now() - start;
621
- this.recordSuccess(key, duration);
622
- }), catchError((error) => {
623
- const duration = performance.now() - start;
624
- this.recordError(key, duration);
625
- return throwError(() => error);
626
- }));
444
+ async execute(query, variables, context, operationName) {
445
+ if (!this.executor) {
446
+ throw new Error("Executor not initialized. Set schema first.");
447
+ }
448
+ const queryString = typeof query === "string" ? query : print(query);
449
+ if (context) {
450
+ this.attachQueryAnalysis(query, queryString, context, operationName);
451
+ }
452
+ try {
453
+ const result = await this.executor.execute({
454
+ query: queryString,
455
+ variables,
456
+ operationName
457
+ }, context);
458
+ const response = result.response;
459
+ let formattedErrors = response.errors;
460
+ if (formattedErrors && this.options.formatError) {
461
+ const formatErrorFn = this.options.formatError;
462
+ formattedErrors = formattedErrors.map((error) => formatErrorFn(error));
463
+ }
464
+ let extensions = response.extensions;
465
+ if (this.options.metrics && result.metrics) {
466
+ extensions = {
467
+ ...extensions,
468
+ metrics: result.metrics
469
+ };
470
+ }
471
+ return {
472
+ data: response.data,
473
+ errors: formattedErrors,
474
+ extensions
475
+ };
476
+ } catch (error) {
477
+ return { errors: [this.formatThrownError(error)] };
478
+ }
627
479
  }
628
- recordSuccess(key, duration) {
629
- const metrics = this.getOrCreateMetrics(key);
630
- metrics.totalCount++;
631
- metrics.successCount++;
632
- metrics.totalDuration += duration;
633
- metrics.lastDuration = duration;
634
- metrics.minDuration = Math.min(metrics.minDuration, duration);
635
- metrics.maxDuration = Math.max(metrics.maxDuration, duration);
480
+ formatThrownError(error) {
481
+ const isProduction = false;
482
+ const includeStack = this.options.includeStacktraceInErrorResponses ?? false;
483
+ const formattedError = formatError(error, {
484
+ maskErrors: !isLeavenError(error) && isProduction && !includeStack,
485
+ includeStackTrace: includeStack
486
+ });
487
+ return this.options.formatError ? this.options.formatError(formattedError) : formattedError;
636
488
  }
637
- recordError(key, duration) {
638
- const metrics = this.getOrCreateMetrics(key);
639
- metrics.totalCount++;
640
- metrics.errorCount++;
641
- metrics.totalDuration += duration;
642
- metrics.lastDuration = duration;
489
+ async subscribe(query, variables, context, operationName) {
490
+ if (!this.executor) {
491
+ throw new Error("Executor not initialized. Set schema first.");
492
+ }
493
+ const queryString = typeof query === "string" ? query : print(query);
494
+ if (context) {
495
+ this.attachQueryAnalysis(query, queryString, context, operationName);
496
+ }
497
+ try {
498
+ const result = await this.executor.subscribe({ query: queryString, variables, operationName }, context);
499
+ if (!(Symbol.asyncIterator in result)) {
500
+ let errors = result.errors;
501
+ if (errors && this.options.formatError) {
502
+ const formatErrorFn = this.options.formatError;
503
+ errors = errors.map((error) => formatErrorFn(error));
504
+ }
505
+ return { errors };
506
+ }
507
+ return result;
508
+ } catch (error) {
509
+ return { errors: [this.formatThrownError(error)] };
510
+ }
643
511
  }
644
- getOrCreateMetrics(key) {
645
- let metrics = this.metrics.get(key);
646
- if (!metrics) {
647
- metrics = {
648
- totalCount: 0,
649
- successCount: 0,
650
- errorCount: 0,
651
- totalDuration: 0,
652
- lastDuration: 0,
653
- minDuration: Infinity,
654
- maxDuration: 0
655
- };
656
- this.metrics.set(key, metrics);
512
+ async handleRequest(request, response) {
513
+ const body = await this.parseRequestBody(request);
514
+ const context = await this.createContext(request, response);
515
+ return this.execute(body.query, body.variables, context, body.operationName);
516
+ }
517
+ async parseRequestBody(request) {
518
+ const contentType = request.headers.get("content-type") ?? "";
519
+ if (request.method === "POST") {
520
+ if (contentType.includes("application/json")) {
521
+ const body = await request.json();
522
+ return {
523
+ query: body.query,
524
+ variables: body.variables,
525
+ operationName: body.operationName
526
+ };
527
+ }
528
+ if (contentType.includes("application/graphql")) {
529
+ const query = await request.text();
530
+ if (!query) {
531
+ throw new ValidationError("Query is required");
532
+ }
533
+ return { query };
534
+ }
535
+ throw new ValidationError(`Unsupported content type "${contentType || "(none)"}": use application/json or application/graphql`);
657
536
  }
658
- return metrics;
537
+ const url = new URL(request.url);
538
+ const query = url.searchParams.get("query");
539
+ const variables = url.searchParams.get("variables");
540
+ const operationName = url.searchParams.get("operationName");
541
+ if (!query) {
542
+ throw new ValidationError("Query is required");
543
+ }
544
+ let parsedVariables;
545
+ if (variables) {
546
+ try {
547
+ parsedVariables = JSON.parse(variables);
548
+ } catch {
549
+ throw new ValidationError('The "variables" query parameter contains malformed JSON');
550
+ }
551
+ }
552
+ return {
553
+ query,
554
+ variables: parsedVariables,
555
+ operationName: operationName ?? undefined
556
+ };
659
557
  }
660
- getMetrics() {
661
- return new Map(this.metrics);
558
+ async createContext(request, response) {
559
+ const baseContext = {
560
+ req: request,
561
+ res: response
562
+ };
563
+ if (this.options.context) {
564
+ const customContext = await this.options.context(request, response);
565
+ return { ...baseContext, ...customContext };
566
+ }
567
+ return baseContext;
662
568
  }
663
- getSummary() {
664
- return Array.from(this.metrics.entries()).map(([operation, metrics]) => ({
665
- operation,
666
- ...metrics,
667
- averageDuration: metrics.totalCount > 0 ? metrics.totalDuration / metrics.totalCount : 0,
668
- errorRate: metrics.totalCount > 0 ? metrics.errorCount / metrics.totalCount : 0
669
- }));
569
+ attachQueryAnalysis(query, queryString, context, operationName) {
570
+ if (this.options.maxComplexity === undefined && this.options.maxDepth === undefined) {
571
+ return;
572
+ }
573
+ const target = context;
574
+ const cacheKey = `${queryString}\x00${operationName ?? ""}`;
575
+ const memoized = this.analysisCache.get(cacheKey);
576
+ if (memoized) {
577
+ this.analysisCache.delete(cacheKey);
578
+ this.analysisCache.set(cacheKey, memoized);
579
+ target._queryDepth = memoized.depth;
580
+ target._queryComplexity = memoized.complexity;
581
+ return;
582
+ }
583
+ let document;
584
+ try {
585
+ document = typeof query === "string" ? parse(query) : query;
586
+ } catch (error) {
587
+ if (error instanceof GraphQLError) {
588
+ return;
589
+ }
590
+ this.recordAnalysisFailure(target, error);
591
+ return;
592
+ }
593
+ try {
594
+ const depth = calculateQueryDepth(document);
595
+ if (!this.schema) {
596
+ throw new Error("Schema is not set; query complexity cannot be computed");
597
+ }
598
+ const complexity = compileQuery(this.schema, document, operationName, {
599
+ calculateComplexity: true
600
+ }).complexity;
601
+ target._queryDepth = depth;
602
+ target._queryComplexity = complexity;
603
+ this.memoizeAnalysis(cacheKey, { depth, complexity });
604
+ } catch (error) {
605
+ this.recordAnalysisFailure(target, error);
606
+ }
670
607
  }
671
- reset() {
672
- this.metrics.clear();
608
+ memoizeAnalysis(key, analysis) {
609
+ if (this.analysisCache.size >= LeavenDriver.MAX_ANALYSIS_ENTRIES) {
610
+ const oldest = this.analysisCache.keys().next().value;
611
+ if (oldest !== undefined) {
612
+ this.analysisCache.delete(oldest);
613
+ }
614
+ }
615
+ this.analysisCache.set(key, analysis);
616
+ }
617
+ recordAnalysisFailure(target, error) {
618
+ console.error("[Leaven] Query analysis failed; failing closed:", error);
619
+ target._queryDepth = Number.POSITIVE_INFINITY;
620
+ target._queryComplexity = Number.POSITIVE_INFINITY;
621
+ }
622
+ isPlaygroundEnabled() {
623
+ if (this.options.playground !== undefined) {
624
+ return this.options.playground;
625
+ }
626
+ return true;
627
+ }
628
+ isIntrospectionEnabled() {
629
+ if (this.options.introspection !== undefined) {
630
+ return this.options.introspection;
631
+ }
632
+ return true;
633
+ }
634
+ getPath() {
635
+ return this.options.path ?? "/graphql";
636
+ }
637
+ getCacheStats() {
638
+ return this.executor?.getCacheStats() ?? null;
639
+ }
640
+ async clearCaches() {
641
+ this.analysisCache.clear();
642
+ await this.executor?.clearCaches();
673
643
  }
674
644
  }
675
- MetricsInterceptor = __legacyDecorateClassTS([
676
- Injectable3()
677
- ], MetricsInterceptor);
645
+ LeavenDriver = __legacyDecorateClassTS([
646
+ Injectable2(),
647
+ __legacyMetadataTS("design:paramtypes", [
648
+ typeof LeavenModuleOptions === "undefined" ? Object : LeavenModuleOptions
649
+ ])
650
+ ], LeavenDriver);
651
+
678
652
  // src/middleware.ts
679
653
  import {
680
- Injectable as Injectable4,
654
+ Injectable as Injectable3,
681
655
  Inject as Inject2
682
656
  } from "@nestjs/common";
657
+ import { formatError as formatError2, isLeavenError as isLeavenError2 } from "@leaven-graphql/errors";
658
+ import { renderGraphiQL } from "@leaven-graphql/playground";
659
+ var VALID_HOST = /^[a-z0-9.-]+(:\d+)?$/i;
660
+
683
661
  class GraphQLMiddleware {
684
662
  driver;
685
663
  options;
@@ -701,12 +679,16 @@ class GraphQLMiddleware {
701
679
  return;
702
680
  }
703
681
  if (req.method === "OPTIONS") {
704
- this.handleCorsPreFlight(res);
682
+ if (this.resolveCorsOptions() === null) {
683
+ return next();
684
+ }
685
+ this.handleCorsPreFlight(req, res);
705
686
  return;
706
687
  }
707
688
  next();
708
689
  }
709
690
  async handleGraphQLRequest(req, res) {
691
+ this.applyCorsHeaders(req, res);
710
692
  try {
711
693
  const { query, variables, operationName } = req.body ?? {};
712
694
  if (!query) {
@@ -718,74 +700,127 @@ class GraphQLMiddleware {
718
700
  const context = {
719
701
  req,
720
702
  res,
721
- ...this.options.context ? await this.options.context(req, res) : {}
703
+ ...this.options.context ? await this.options.context(this.toFetchRequest(req), res) : {}
722
704
  };
723
705
  const result = await this.driver.execute(query, variables, context, operationName);
724
- const status = result.errors?.length ? 200 : 200;
725
- res.status(status).json(result);
706
+ res.status(200).json(result);
726
707
  } catch (error) {
727
- const message = error instanceof Error ? error.message : "Internal server error";
728
- res.status(500).json({
729
- errors: [{ message }]
708
+ const isProduction = false;
709
+ const includeStack = this.options.includeStacktraceInErrorResponses ?? false;
710
+ const formattedError = formatError2(error, {
711
+ maskErrors: !isLeavenError2(error) && isProduction && !includeStack,
712
+ includeStackTrace: includeStack
713
+ });
714
+ res.status(isLeavenError2(error) ? error.statusCode : 500).json({
715
+ errors: [formattedError]
730
716
  });
731
717
  }
732
718
  }
733
719
  servePlayground(res) {
734
- const path = this.driver.getPath();
735
- const html = this.getPlaygroundHtml(path);
720
+ const html = renderGraphiQL({ endpoint: this.driver.getPath() });
736
721
  res.setHeader("Content-Type", "text/html");
737
722
  res.send(html);
738
723
  }
739
- handleCorsPreFlight(res) {
724
+ handleCorsPreFlight(req, res) {
725
+ this.applyCorsHeaders(req, res);
726
+ res.status(204).end();
727
+ }
728
+ resolveCorsOptions() {
740
729
  const corsOptions = this.options.cors;
741
- if (corsOptions === false) {
742
- res.status(204).end();
730
+ if (corsOptions === undefined || corsOptions === false) {
731
+ return null;
732
+ }
733
+ return corsOptions === true ? {} : corsOptions;
734
+ }
735
+ applyCorsHeaders(req, res) {
736
+ const options = this.resolveCorsOptions();
737
+ if (options === null) {
743
738
  return;
744
739
  }
745
- const options = corsOptions === true ? {} : corsOptions ?? {};
746
- res.setHeader("Access-Control-Allow-Origin", Array.isArray(options.origin) ? options.origin.join(", ") : typeof options.origin === "string" ? options.origin : "*");
740
+ const requestOrigin = typeof req.headers.origin === "string" ? req.headers.origin : undefined;
741
+ const allowOrigin = this.resolveAllowedOrigin(options, requestOrigin);
742
+ if (allowOrigin.value !== undefined) {
743
+ res.setHeader("Access-Control-Allow-Origin", allowOrigin.value);
744
+ }
745
+ if (allowOrigin.varies) {
746
+ res.setHeader("Vary", "Origin");
747
+ }
747
748
  res.setHeader("Access-Control-Allow-Methods", Array.isArray(options.methods) ? options.methods.join(", ") : options.methods ?? "GET, POST, OPTIONS");
748
749
  res.setHeader("Access-Control-Allow-Headers", Array.isArray(options.allowedHeaders) ? options.allowedHeaders.join(", ") : options.allowedHeaders ?? "Content-Type, Authorization");
750
+ if (options.exposedHeaders) {
751
+ res.setHeader("Access-Control-Expose-Headers", Array.isArray(options.exposedHeaders) ? options.exposedHeaders.join(", ") : options.exposedHeaders);
752
+ }
749
753
  if (options.credentials) {
750
754
  res.setHeader("Access-Control-Allow-Credentials", "true");
751
755
  }
752
756
  if (options.maxAge) {
753
757
  res.setHeader("Access-Control-Max-Age", String(options.maxAge));
754
758
  }
755
- res.status(204).end();
756
759
  }
757
- getPlaygroundHtml(endpoint) {
758
- return `
759
- <!DOCTYPE html>
760
- <html lang="en">
761
- <head>
762
- <meta charset="UTF-8">
763
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
764
- <title>Leaven GraphQL Playground</title>
765
- <link rel="stylesheet" href="https://unpkg.com/graphiql@3.0.9/graphiql.min.css" />
766
- <style>
767
- body { margin: 0; height: 100vh; }
768
- #graphiql { height: 100vh; }
769
- </style>
770
- </head>
771
- <body>
772
- <div id="graphiql">Loading...</div>
773
- <script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
774
- <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
775
- <script crossorigin src="https://unpkg.com/graphiql@3.0.9/graphiql.min.js"></script>
776
- <script>
777
- const fetcher = GraphiQL.createFetcher({ url: '${endpoint}' });
778
- ReactDOM.createRoot(document.getElementById('graphiql')).render(
779
- React.createElement(GraphiQL, { fetcher })
780
- );
781
- </script>
782
- </body>
783
- </html>
784
- `.trim();
760
+ resolveAllowedOrigin(options, requestOrigin) {
761
+ const { origin, credentials } = options;
762
+ if (origin === false) {
763
+ return { varies: false };
764
+ }
765
+ if (Array.isArray(origin)) {
766
+ return requestOrigin !== undefined && origin.includes(requestOrigin) ? { value: requestOrigin, varies: true } : { varies: true };
767
+ }
768
+ if (typeof origin === "string" && origin !== "*") {
769
+ return { value: origin, varies: false };
770
+ }
771
+ if (credentials) {
772
+ return requestOrigin !== undefined ? { value: requestOrigin, varies: true } : { varies: true };
773
+ }
774
+ if (origin === true) {
775
+ return requestOrigin !== undefined ? { value: requestOrigin, varies: true } : { value: "*", varies: true };
776
+ }
777
+ return { value: "*", varies: false };
778
+ }
779
+ toFetchRequest(req) {
780
+ const headers = new Headers;
781
+ for (const [name, value] of Object.entries(req.headers)) {
782
+ if (value === undefined) {
783
+ continue;
784
+ }
785
+ if (Array.isArray(value)) {
786
+ for (const entry of value) {
787
+ headers.append(name, entry);
788
+ }
789
+ } else {
790
+ headers.set(name, value);
791
+ }
792
+ }
793
+ const path = req.originalUrl ?? req.url ?? req.path;
794
+ const url = `${this.resolveOrigin(req)}${path}`;
795
+ const init = { method: req.method, headers };
796
+ if (req.body !== undefined && req.method !== "GET" && req.method !== "HEAD") {
797
+ if (!headers.has("content-type")) {
798
+ headers.set("content-type", "application/json");
799
+ }
800
+ init.body = JSON.stringify(req.body);
801
+ }
802
+ try {
803
+ return new globalThis.Request(url, init);
804
+ } catch {
805
+ return new globalThis.Request(`${this.canonicalOrigin(req)}${this.driver.getPath()}`, init);
806
+ }
807
+ }
808
+ resolveOrigin(req) {
809
+ if (this.options.publicUrl) {
810
+ return this.canonicalOrigin(req);
811
+ }
812
+ const host = req.headers.host;
813
+ if (typeof host === "string" && VALID_HOST.test(host)) {
814
+ return `${req.secure ? "https" : "http"}://${host}`;
815
+ }
816
+ return this.canonicalOrigin(req);
817
+ }
818
+ canonicalOrigin(req) {
819
+ return this.options.publicUrl?.replace(/\/+$/, "") ?? (req.secure ? "https://localhost" : "http://localhost");
785
820
  }
786
821
  }
787
822
  GraphQLMiddleware = __legacyDecorateClassTS([
788
- Injectable4(),
823
+ Injectable3(),
789
824
  __legacyDecorateParamTS(0, Inject2(LEAVEN_DRIVER)),
790
825
  __legacyDecorateParamTS(1, Inject2(LEAVEN_MODULE_OPTIONS)),
791
826
  __legacyMetadataTS("design:paramtypes", [
@@ -796,111 +831,112 @@ GraphQLMiddleware = __legacyDecorateClassTS([
796
831
  function createGraphQLMiddleware() {
797
832
  return GraphQLMiddleware;
798
833
  }
799
- // src/execution-context.ts
800
- class GqlExecutionContext {
801
- host;
802
- args;
803
- constructor(host) {
804
- this.host = host;
805
- this.args = host.getArgs();
806
- }
807
- static create(context) {
808
- return new GqlExecutionContext(context);
834
+
835
+ // src/schema-builder.ts
836
+ import { Injectable as Injectable4, Inject as Inject3 } from "@nestjs/common";
837
+ import {
838
+ buildSchema as buildSchemaFromSDL,
839
+ lexicographicSortSchema,
840
+ parse as parse2,
841
+ print as print2,
842
+ printSchema,
843
+ visit
844
+ } from "graphql";
845
+ import { makeExecutableSchema } from "@graphql-tools/schema";
846
+
847
+ // src/decorators.ts
848
+ import {
849
+ createParamDecorator,
850
+ SetMetadata as SetMetadata2,
851
+ applyDecorators
852
+ } from "@nestjs/common";
853
+ var COMPLEXITY_KEY = "leaven:complexity";
854
+ var DEPRECATED_KEY = "leaven:deprecated";
855
+ var DESCRIPTION_KEY = "leaven:description";
856
+ var CACHE_KEY = "leaven:cache";
857
+ var SUBSCRIPTION_FILTER_KEY = "leaven:subscription:filter";
858
+ function contextExtractor(data, ctx) {
859
+ const gqlContext = getGqlContext(ctx);
860
+ if (data) {
861
+ return gqlContext[data];
809
862
  }
810
- getContext() {
811
- return this.args[2];
863
+ return gqlContext;
864
+ }
865
+ var Context = createParamDecorator(contextExtractor);
866
+ function infoExtractor(_data, ctx) {
867
+ return ctx.getArgs()[GQL_RESOLVER_ARGS.INFO];
868
+ }
869
+ var Info = createParamDecorator(infoExtractor);
870
+ function rootExtractor(_data, ctx) {
871
+ return ctx.getArgs()[GQL_RESOLVER_ARGS.ROOT];
872
+ }
873
+ var Root = createParamDecorator(rootExtractor);
874
+ var Parent = Root;
875
+ function argsExtractor(data, ctx) {
876
+ const gqlArgs = ctx.getArgs()[GQL_RESOLVER_ARGS.ARGS];
877
+ if (data) {
878
+ return gqlArgs?.[data];
812
879
  }
813
- getRoot() {
814
- return this.args[0];
815
- }
816
- getArgs() {
817
- return this.args[1];
818
- }
819
- getInfo() {
820
- return this.args[3];
821
- }
822
- getArg(name) {
823
- const args = this.getArgs();
824
- return args[name];
825
- }
826
- getContextProperty(name) {
827
- const ctx = this.getContext();
828
- return ctx[name];
829
- }
830
- getHost() {
831
- return this.host;
832
- }
833
- getType() {
834
- return this.host.getType?.() ?? "unknown";
835
- }
836
- isGraphQL() {
837
- return this.getType() === "graphql";
838
- }
839
- getHandler() {
840
- return this.host.getHandler?.();
841
- }
842
- getClass() {
843
- return this.host.getClass?.();
844
- }
845
- getFieldName() {
846
- const info = this.getInfo();
847
- return info?.fieldName;
848
- }
849
- getParentTypeName() {
850
- const info = this.getInfo();
851
- return info?.parentType?.name;
852
- }
853
- getReturnTypeName() {
854
- const info = this.getInfo();
855
- return info?.returnType?.toString();
856
- }
857
- getOperationName() {
858
- const info = this.getInfo();
859
- return info?.operation?.name?.value;
860
- }
861
- getOperationType() {
862
- const info = this.getInfo();
863
- return info?.operation?.operation;
864
- }
865
- getVariables() {
866
- const info = this.getInfo();
867
- return info?.variableValues ?? {};
868
- }
869
- getPath() {
870
- const info = this.getInfo();
871
- if (!info?.path)
872
- return [];
873
- const path = [];
874
- let current = info.path;
875
- while (current) {
876
- if (typeof current.key === "string") {
877
- path.unshift(current.key);
878
- }
879
- current = current.prev;
880
- }
881
- return path;
882
- }
883
- getSelectedFields() {
884
- const info = this.getInfo();
885
- if (!info?.fieldNodes?.[0]?.selectionSet?.selections)
886
- return [];
887
- return info.fieldNodes[0].selectionSet.selections.filter((selection) => selection.kind === "Field").map((field) => field.name.value);
880
+ return gqlArgs;
881
+ }
882
+ var Args = createParamDecorator(argsExtractor);
883
+ function Complexity(value) {
884
+ return SetMetadata2(COMPLEXITY_KEY, value);
885
+ }
886
+ function Deprecated(reason) {
887
+ return SetMetadata2(DEPRECATED_KEY, reason);
888
+ }
889
+ function Description(text) {
890
+ return SetMetadata2(DESCRIPTION_KEY, text);
891
+ }
892
+ function CacheHint(options) {
893
+ return SetMetadata2(CACHE_KEY, options);
894
+ }
895
+ async function* filterAsyncIterator(source, predicate) {
896
+ for await (const payload of source) {
897
+ let accepted;
898
+ try {
899
+ accepted = await predicate(payload);
900
+ } catch (error) {
901
+ console.error("Subscription filter threw; dropping event:", error);
902
+ continue;
903
+ }
904
+ if (accepted) {
905
+ yield payload;
906
+ }
888
907
  }
889
908
  }
890
- function getGqlContext2(context) {
891
- return GqlExecutionContext.create(context).getContext();
909
+ function wrapWithFilter(descriptor, filter) {
910
+ const original = descriptor.value;
911
+ if (typeof original !== "function") {
912
+ return;
913
+ }
914
+ descriptor.value = function(...args) {
915
+ const source = original.apply(this, args);
916
+ const variables = args[GQL_RESOLVER_ARGS.ARGS];
917
+ const context = args[GQL_RESOLVER_ARGS.CONTEXT];
918
+ const predicate = (payload) => filter(payload, variables, context);
919
+ return source instanceof Promise ? source.then((resolved) => filterAsyncIterator(resolved, predicate)) : filterAsyncIterator(source, predicate);
920
+ };
892
921
  }
893
- function getGqlArgs(context) {
894
- return GqlExecutionContext.create(context).getArgs();
922
+ function SubscriptionFilter(filter) {
923
+ return (target, propertyKey, descriptor) => {
924
+ wrapWithFilter(descriptor, filter);
925
+ return SetMetadata2(SUBSCRIPTION_FILTER_KEY, filter)(target, propertyKey, descriptor);
926
+ };
895
927
  }
896
- function getGqlInfo(context) {
897
- return GqlExecutionContext.create(context).getInfo();
928
+ function Decorators(...decorators) {
929
+ return applyDecorators(...decorators);
898
930
  }
899
- function getGqlRoot(context) {
900
- return GqlExecutionContext.create(context).getRoot();
931
+ function createContextDecorator(key) {
932
+ const decorator = createParamDecorator((_data, ctx) => {
933
+ const gqlContext = getGqlContext(ctx);
934
+ return gqlContext[key];
935
+ });
936
+ return () => decorator();
901
937
  }
938
+
902
939
  // src/schema-builder.ts
903
- import { Injectable as Injectable5, Inject as Inject3 } from "@nestjs/common";
904
940
  class SchemaBuilderService {
905
941
  options;
906
942
  driver;
@@ -916,72 +952,54 @@ class SchemaBuilderService {
916
952
  this.driver.setSchema(schema);
917
953
  }
918
954
  }
919
- async buildSchema() {
920
- if (this.options.schema) {
921
- return this.options.schema;
955
+ resolveSchemaSource() {
956
+ const { schema, typeDefs, resolvers, autoSchemaFile, buildSchemaOptions } = this.options;
957
+ if (schema) {
958
+ return { type: "provided", schema };
922
959
  }
923
- if (this.options.typeDefs) {
924
- return this.buildFromTypeDefs();
960
+ if (typeDefs) {
961
+ return { type: "typeDefs", typeDefs, resolvers };
925
962
  }
926
- if (this.options.autoSchemaFile) {
927
- return null;
963
+ if (autoSchemaFile) {
964
+ return { type: "autoSchema", options: buildSchemaOptions ?? {} };
928
965
  }
929
966
  return null;
930
967
  }
931
- buildFromTypeDefs() {
932
- const { typeDefs, resolvers } = this.options;
933
- if (!typeDefs || !resolvers) {
968
+ async buildSchema() {
969
+ const source = this.resolveSchemaSource();
970
+ if (!source) {
934
971
  return null;
935
972
  }
936
- try {
937
- const { buildSchema } = __require("graphql");
938
- const { addResolversToSchema, makeExecutableSchema } = (()=>{throw new Error("Cannot require module "+"@graphql-tools/schema");})();
939
- if (makeExecutableSchema) {
940
- return makeExecutableSchema({
941
- typeDefs,
942
- resolvers
943
- });
944
- }
945
- const schema = buildSchema(typeof typeDefs === "string" ? typeDefs : this.mergeTypeDefs(typeDefs));
946
- if (addResolversToSchema) {
947
- return addResolversToSchema({
948
- schema,
949
- resolvers: Array.isArray(resolvers) ? this.mergeResolvers(resolvers) : resolvers
950
- });
951
- }
952
- return schema;
953
- } catch (error) {
954
- console.warn("Failed to build schema from typeDefs. Install @graphql-tools/schema for full support.", error);
955
- return null;
973
+ switch (source.type) {
974
+ case "provided":
975
+ return source.schema;
976
+ case "typeDefs":
977
+ return this.buildFromTypeDefs(source.typeDefs, source.resolvers);
978
+ case "autoSchema":
979
+ throw new Error("Failed to build the GraphQL schema: 'autoSchemaFile' was configured, but code-first " + "schema generation is not implemented. Supply a pre-built executable schema via the " + "'schema' option, or SDL via 'typeDefs' (optionally with 'resolvers').");
980
+ }
981
+ }
982
+ async buildFromTypeDefs(typeDefs, resolvers) {
983
+ const sdl = this.mergeTypeDefs(typeDefs);
984
+ if (!resolvers) {
985
+ return buildSchemaFromSDL(sdl);
956
986
  }
987
+ const schema = makeExecutableSchema({
988
+ typeDefs: sdl,
989
+ resolvers
990
+ });
991
+ applyResolverFieldMetadata(schema, resolvers);
992
+ return schema;
957
993
  }
958
994
  mergeTypeDefs(typeDefs) {
959
995
  if (typeof typeDefs === "string") {
960
996
  return typeDefs;
961
997
  }
962
998
  if (Array.isArray(typeDefs)) {
963
- return typeDefs.map((td) => {
964
- if (typeof td === "string") {
965
- return td;
966
- }
967
- const { print: print2 } = __require("graphql");
968
- return print2(td);
969
- }).join(`
999
+ return typeDefs.map((td) => typeof td === "string" ? td : print2(td)).join(`
970
1000
  `);
971
1001
  }
972
- const { print } = __require("graphql");
973
- return print(typeDefs);
974
- }
975
- mergeResolvers(resolvers) {
976
- return resolvers.reduce((merged, resolver) => {
977
- for (const [typeName, fields] of Object.entries(resolver)) {
978
- merged[typeName] = {
979
- ...merged[typeName] || {},
980
- ...fields
981
- };
982
- }
983
- return merged;
984
- }, {});
1002
+ return print2(typeDefs);
985
1003
  }
986
1004
  getSchema() {
987
1005
  return this.builtSchema;
@@ -995,7 +1013,7 @@ class SchemaBuilderService {
995
1013
  }
996
1014
  }
997
1015
  SchemaBuilderService = __legacyDecorateClassTS([
998
- Injectable5(),
1016
+ Injectable4(),
999
1017
  __legacyDecorateParamTS(0, Inject3(LEAVEN_MODULE_OPTIONS)),
1000
1018
  __legacyDecorateParamTS(1, Inject3(LEAVEN_DRIVER)),
1001
1019
  __legacyMetadataTS("design:paramtypes", [
@@ -1003,15 +1021,71 @@ SchemaBuilderService = __legacyDecorateClassTS([
1003
1021
  typeof LeavenDriver === "undefined" ? Object : LeavenDriver
1004
1022
  ])
1005
1023
  ], SchemaBuilderService);
1024
+ function collectResolverFunctions(container) {
1025
+ const functions = new Map;
1026
+ let current = container;
1027
+ while (current && current !== Object.prototype) {
1028
+ for (const key of Object.getOwnPropertyNames(current)) {
1029
+ if (key === "constructor" || functions.has(key))
1030
+ continue;
1031
+ const descriptor = Object.getOwnPropertyDescriptor(current, key);
1032
+ if (descriptor && typeof descriptor.value === "function") {
1033
+ functions.set(key, descriptor.value);
1034
+ }
1035
+ }
1036
+ current = Object.getPrototypeOf(current);
1037
+ }
1038
+ return functions;
1039
+ }
1040
+ function applyResolverFieldMetadata(schema, resolvers) {
1041
+ if (typeof Reflect.getMetadata !== "function") {
1042
+ return;
1043
+ }
1044
+ const maps = Array.isArray(resolvers) ? resolvers : [resolvers];
1045
+ for (const map of maps) {
1046
+ for (const [typeName, container] of Object.entries(map ?? {})) {
1047
+ if (!container || typeof container !== "object")
1048
+ continue;
1049
+ const type = schema.getType(typeName);
1050
+ if (!type || typeof type.getFields !== "function") {
1051
+ continue;
1052
+ }
1053
+ const fields = type.getFields();
1054
+ for (const [fieldName, fn] of collectResolverFunctions(container)) {
1055
+ const field = fields[fieldName];
1056
+ if (!field)
1057
+ continue;
1058
+ const description = Reflect.getMetadata(DESCRIPTION_KEY, fn);
1059
+ if (typeof description === "string") {
1060
+ field.description = description;
1061
+ }
1062
+ const deprecationReason = Reflect.getMetadata(DEPRECATED_KEY, fn);
1063
+ if (typeof deprecationReason === "string") {
1064
+ field.deprecationReason = deprecationReason;
1065
+ }
1066
+ }
1067
+ }
1068
+ }
1069
+ }
1070
+ function stripDescriptions(sdl) {
1071
+ const strippedAst = visit(parse2(sdl), {
1072
+ enter(node) {
1073
+ if ("description" in node && node.description) {
1074
+ const { description: _description, ...rest } = node;
1075
+ return rest;
1076
+ }
1077
+ return;
1078
+ }
1079
+ });
1080
+ return print2(strippedAst);
1081
+ }
1006
1082
  async function generateSchemaFile(schema, options) {
1007
- const { printSchema, lexicographicSortSchema } = __require("graphql");
1008
- const { writeFile } = __require("fs/promises");
1009
- let schemaToWrite = schema;
1010
- if (options.sortSchema) {
1011
- schemaToWrite = lexicographicSortSchema(schema);
1012
- }
1013
- const sdl = printSchema(schemaToWrite);
1014
- await writeFile(options.path, sdl, "utf-8");
1083
+ const schemaToWrite = options.sortSchema ? lexicographicSortSchema(schema) : schema;
1084
+ let sdl = printSchema(schemaToWrite);
1085
+ if (options.includeDescriptions === false) {
1086
+ sdl = stripDescriptions(sdl);
1087
+ }
1088
+ await Bun.write(options.path, sdl);
1015
1089
  }
1016
1090
  var resolverMetadataStorage = [];
1017
1091
  function getResolverMetadata() {
@@ -1023,8 +1097,17 @@ function registerResolverMetadata(metadata) {
1023
1097
  function clearResolverMetadata() {
1024
1098
  resolverMetadataStorage.length = 0;
1025
1099
  }
1100
+
1026
1101
  // src/subscriptions.ts
1027
- import { Injectable as Injectable6, Inject as Inject4 } from "@nestjs/common";
1102
+ import {
1103
+ Injectable as Injectable5,
1104
+ Inject as Inject4,
1105
+ SetMetadata as SetMetadata3
1106
+ } from "@nestjs/common";
1107
+ import { parse as parse3, getOperationAST } from "graphql";
1108
+ var SOCKET_OPEN = 1;
1109
+ var ITERATOR_TEARDOWN_TIMEOUT_MS = 1000;
1110
+
1028
1111
  class SubscriptionManager {
1029
1112
  options;
1030
1113
  driver;
@@ -1037,20 +1120,55 @@ class SubscriptionManager {
1037
1120
  }
1038
1121
  async onModuleInit() {}
1039
1122
  async onModuleDestroy() {
1040
- for (const [socket] of this.connections) {
1123
+ const sockets = [...this.connections.keys()];
1124
+ await Promise.all(sockets.map((socket) => this.handleClose(socket)));
1125
+ for (const socket of sockets) {
1041
1126
  socket.close(1000, "Server shutdown");
1042
1127
  }
1043
1128
  this.connections.clear();
1044
1129
  }
1045
1130
  normalizeConfig() {
1131
+ const userConfig = this.options.subscriptions ?? {};
1132
+ return {
1133
+ ...userConfig,
1134
+ path: userConfig.path ?? this.options.path ?? "/graphql",
1135
+ keepAlive: userConfig.keepAlive ?? 12000,
1136
+ connectionInitWaitTimeout: userConfig.connectionInitWaitTimeout ?? 3000,
1137
+ maxSubscriptionsPerConnection: userConfig.maxSubscriptionsPerConnection ?? 100
1138
+ };
1139
+ }
1140
+ getPath() {
1141
+ return this.config.path ?? "/graphql";
1142
+ }
1143
+ getWebSocketConfig() {
1046
1144
  return {
1047
- path: this.options.path ?? "/graphql",
1048
- keepAlive: 12000,
1049
- connectionInitWaitTimeout: 3000,
1050
- maxSubscriptionsPerConnection: 100
1145
+ open: (socket) => {
1146
+ this.handleOpen(socket);
1147
+ },
1148
+ message: (socket, message) => {
1149
+ this.handleMessage(socket, message).catch((error) => console.error("WebSocket message handling failed:", error));
1150
+ },
1151
+ close: (socket) => {
1152
+ this.handleClose(socket).catch((error) => console.error("WebSocket close handling failed:", error));
1153
+ }
1051
1154
  };
1052
1155
  }
1156
+ handleOpen(socket, request) {
1157
+ this.openConnection(socket, request);
1158
+ }
1053
1159
  async handleConnection(socket, request) {
1160
+ this.openConnection(socket, request);
1161
+ socket.addEventListener("message", (event) => {
1162
+ this.handleMessage(socket, event.data).catch((error) => console.error("WebSocket message handling failed:", error));
1163
+ });
1164
+ socket.addEventListener("close", () => {
1165
+ this.handleClose(socket).catch((e) => console.error("WebSocket close handling failed:", e));
1166
+ });
1167
+ socket.addEventListener("error", (error) => {
1168
+ this.handleError(socket, error);
1169
+ });
1170
+ }
1171
+ openConnection(socket, request) {
1054
1172
  const state = {
1055
1173
  socket,
1056
1174
  request,
@@ -1058,30 +1176,36 @@ class SubscriptionManager {
1058
1176
  initialized: false,
1059
1177
  connectionParams: undefined,
1060
1178
  context: undefined,
1061
- keepAliveInterval: undefined
1179
+ keepAliveInterval: undefined,
1180
+ initTimeout: undefined
1062
1181
  };
1063
1182
  this.connections.set(socket, state);
1064
- socket.addEventListener("message", (event) => {
1065
- this.handleMessage(socket, event.data);
1066
- });
1067
- socket.addEventListener("close", () => {
1068
- this.handleClose(socket);
1069
- });
1070
- socket.addEventListener("error", (error) => {
1071
- this.handleError(socket, error);
1072
- });
1073
- setTimeout(() => {
1183
+ state.initTimeout = setTimeout(() => {
1184
+ state.initTimeout = undefined;
1074
1185
  if (!state.initialized) {
1075
1186
  socket.close(4408, "Connection initialization timeout");
1076
1187
  }
1077
1188
  }, this.config.connectionInitWaitTimeout);
1189
+ return state;
1078
1190
  }
1079
1191
  async handleMessage(socket, data) {
1080
1192
  const state = this.connections.get(socket);
1081
1193
  if (!state)
1082
1194
  return;
1195
+ let message;
1196
+ try {
1197
+ const parsed = JSON.parse(typeof data === "string" ? data : new TextDecoder().decode(data));
1198
+ if (parsed === null || typeof parsed !== "object" || typeof parsed.type !== "string") {
1199
+ socket.close(4400, "Bad Request");
1200
+ return;
1201
+ }
1202
+ message = parsed;
1203
+ } catch (error) {
1204
+ console.error("Error parsing WebSocket message:", error);
1205
+ socket.close(4400, "Bad Request");
1206
+ return;
1207
+ }
1083
1208
  try {
1084
- const message = JSON.parse(data);
1085
1209
  switch (message.type) {
1086
1210
  case "connection_init":
1087
1211
  await this.handleConnectionInit(socket, state, message);
@@ -1098,10 +1222,18 @@ class SubscriptionManager {
1098
1222
  await this.handleComplete(socket, state, message);
1099
1223
  break;
1100
1224
  default:
1225
+ socket.close(4400, "Bad Request");
1101
1226
  break;
1102
1227
  }
1103
1228
  } catch (error) {
1104
1229
  console.error("Error handling WebSocket message:", error);
1230
+ const type = message.type;
1231
+ const id = message.id;
1232
+ if ((type === "subscribe" || type === "complete") && typeof id === "string") {
1233
+ this.sendError(socket, id, "Internal server error");
1234
+ } else {
1235
+ socket.close(1011, "Internal server error");
1236
+ }
1105
1237
  }
1106
1238
  }
1107
1239
  async handleConnectionInit(socket, state, message) {
@@ -1124,6 +1256,10 @@ class SubscriptionManager {
1124
1256
  }
1125
1257
  state.initialized = true;
1126
1258
  state.context = ctx;
1259
+ if (state.initTimeout) {
1260
+ clearTimeout(state.initTimeout);
1261
+ state.initTimeout = undefined;
1262
+ }
1127
1263
  this.send(socket, { type: "connection_ack" });
1128
1264
  if (this.config.keepAlive) {
1129
1265
  state.keepAliveInterval = setInterval(() => {
@@ -1136,50 +1272,150 @@ class SubscriptionManager {
1136
1272
  socket.close(4401, "Unauthorized");
1137
1273
  return;
1138
1274
  }
1275
+ if (typeof message.id !== "string" || typeof message.payload?.query !== "string") {
1276
+ socket.close(4400, "Bad Request");
1277
+ return;
1278
+ }
1279
+ if (state.subscriptions.has(message.id)) {
1280
+ socket.close(4409, `Subscriber already exists: ${message.id}`);
1281
+ return;
1282
+ }
1139
1283
  if (state.subscriptions.size >= (this.config.maxSubscriptionsPerConnection ?? 100)) {
1140
1284
  this.sendError(socket, message.id, "Too many subscriptions");
1141
1285
  return;
1142
1286
  }
1143
- if (this.config.onSubscribe && state.context) {
1144
- await this.config.onSubscribe(state.context, message);
1287
+ const streaming = this.isSubscriptionOperation(message.payload.query, message.payload.operationName);
1288
+ let registration;
1289
+ if (streaming) {
1290
+ registration = { clientId: message.id };
1291
+ state.subscriptions.set(message.id, registration);
1145
1292
  }
1146
1293
  try {
1294
+ if (this.config.onSubscribe && state.context) {
1295
+ await this.config.onSubscribe(state.context, message);
1296
+ }
1147
1297
  const schema = this.driver.getSchema();
1148
1298
  if (!schema) {
1299
+ if (registration && state.subscriptions.get(message.id) === registration) {
1300
+ state.subscriptions.delete(message.id);
1301
+ }
1149
1302
  this.sendError(socket, message.id, "Schema not available");
1150
1303
  return;
1151
1304
  }
1152
1305
  let context = {
1153
1306
  req: state.request,
1154
1307
  res: {},
1155
- ...state.connectionParams
1308
+ connectionParams: state.connectionParams
1156
1309
  };
1157
1310
  if (this.config.context && state.context) {
1158
1311
  context = await this.config.context(state.context);
1159
1312
  }
1160
- const result = await this.driver.execute(message.payload.query, message.payload.variables, context, message.payload.operationName);
1161
- if (result.data || result.errors) {
1162
- this.send(socket, {
1163
- id: message.id,
1164
- type: "next",
1165
- payload: result
1166
- });
1167
- this.send(socket, { id: message.id, type: "complete" });
1313
+ let args = {
1314
+ schema,
1315
+ document: message.payload.query,
1316
+ contextValue: context,
1317
+ variableValues: message.payload.variables,
1318
+ operationName: message.payload.operationName
1319
+ };
1320
+ if (this.config.onOperation && state.context) {
1321
+ args = await this.config.onOperation(state.context, message, args);
1322
+ }
1323
+ if (registration) {
1324
+ await this.startStream(socket, state, message.id, args, registration);
1168
1325
  return;
1169
1326
  }
1170
- state.subscriptions.set(message.id, {
1171
- message,
1172
- context
1327
+ const result = await this.driver.execute(args.document, args.variableValues, args.contextValue, args.operationName);
1328
+ this.send(socket, {
1329
+ id: message.id,
1330
+ type: "next",
1331
+ payload: result
1173
1332
  });
1333
+ this.send(socket, { id: message.id, type: "complete" });
1174
1334
  } catch (error) {
1335
+ if (registration && state.subscriptions.get(message.id) === registration) {
1336
+ state.subscriptions.delete(message.id);
1337
+ }
1175
1338
  const errorMessage = error instanceof Error ? error.message : "Subscription failed";
1176
1339
  this.sendError(socket, message.id, errorMessage);
1177
1340
  }
1178
1341
  }
1342
+ async startStream(socket, state, id, args, registration) {
1343
+ const result = await this.driver.subscribe(args.document, args.variableValues, args.contextValue, args.operationName);
1344
+ if (!(Symbol.asyncIterator in result)) {
1345
+ if (state.subscriptions.get(id) === registration) {
1346
+ state.subscriptions.delete(id);
1347
+ }
1348
+ const errors = result.errors ?? [{ message: "Subscription failed" }];
1349
+ this.send(socket, { id, type: "error", payload: [...errors] });
1350
+ return;
1351
+ }
1352
+ const iterator = result;
1353
+ if (state.subscriptions.get(id) !== registration) {
1354
+ await this.closeIterator(iterator);
1355
+ return;
1356
+ }
1357
+ registration.iterator = iterator;
1358
+ this.pumpStream(socket, state, id, registration, iterator).catch((error) => {
1359
+ console.error(`Subscription pump failed for operation ${id}:`, error);
1360
+ state.subscriptions.delete(id);
1361
+ });
1362
+ }
1363
+ async pumpStream(socket, state, id, registration, iterator) {
1364
+ const owns = () => state.subscriptions.get(id) === registration;
1365
+ try {
1366
+ for await (const payload of iterator) {
1367
+ if (!owns()) {
1368
+ return;
1369
+ }
1370
+ this.send(socket, { id, type: "next", payload });
1371
+ }
1372
+ if (owns()) {
1373
+ this.send(socket, { id, type: "complete" });
1374
+ }
1375
+ } catch (error) {
1376
+ if (owns()) {
1377
+ const message = error instanceof Error ? error.message : "Subscription failed";
1378
+ this.sendError(socket, id, message);
1379
+ }
1380
+ } finally {
1381
+ if (owns()) {
1382
+ state.subscriptions.delete(id);
1383
+ }
1384
+ }
1385
+ }
1386
+ async closeIterator(iterator) {
1387
+ if (!iterator?.return) {
1388
+ return;
1389
+ }
1390
+ const teardown = Promise.resolve().then(() => iterator.return?.()).then(() => "settled").catch((error) => {
1391
+ console.error("Subscription iterator teardown failed:", error);
1392
+ return "settled";
1393
+ });
1394
+ let timer;
1395
+ const deadline = new Promise((resolve) => {
1396
+ timer = setTimeout(() => resolve("timeout"), ITERATOR_TEARDOWN_TIMEOUT_MS);
1397
+ });
1398
+ try {
1399
+ if (await Promise.race([teardown, deadline]) === "timeout") {
1400
+ console.error(`Subscription iterator did not finish teardown within ${ITERATOR_TEARDOWN_TIMEOUT_MS}ms; ` + "abandoning it. The resolver is most likely an async generator suspended " + "awaiting an event that never arrives, which cancellation cannot resume.");
1401
+ }
1402
+ } finally {
1403
+ clearTimeout(timer);
1404
+ }
1405
+ }
1406
+ isSubscriptionOperation(query, operationName) {
1407
+ try {
1408
+ const document = parse3(query);
1409
+ return getOperationAST(document, operationName)?.operation === "subscription";
1410
+ } catch {
1411
+ return false;
1412
+ }
1413
+ }
1179
1414
  async handleComplete(socket, state, message) {
1180
1415
  const subscription = state.subscriptions.get(message.id);
1181
1416
  if (subscription) {
1182
1417
  state.subscriptions.delete(message.id);
1418
+ await this.closeIterator(subscription.iterator);
1183
1419
  if (this.config.onComplete && state.context) {
1184
1420
  await this.config.onComplete(state.context, message);
1185
1421
  }
@@ -1191,21 +1427,32 @@ class SubscriptionManager {
1191
1427
  return;
1192
1428
  if (state.keepAliveInterval) {
1193
1429
  clearInterval(state.keepAliveInterval);
1430
+ state.keepAliveInterval = undefined;
1194
1431
  }
1195
- if (this.config.onDisconnect && state.context) {
1196
- await this.config.onDisconnect(state.context);
1432
+ if (state.initTimeout) {
1433
+ clearTimeout(state.initTimeout);
1434
+ state.initTimeout = undefined;
1197
1435
  }
1436
+ const subscriptions = [...state.subscriptions.values()];
1198
1437
  state.subscriptions.clear();
1199
1438
  this.connections.delete(ws);
1439
+ await Promise.all(subscriptions.map((s) => this.closeIterator(s.iterator)));
1440
+ if (this.config.onDisconnect && state.context) {
1441
+ try {
1442
+ await this.config.onDisconnect(state.context);
1443
+ } catch (error) {
1444
+ console.error("onDisconnect hook failed:", error);
1445
+ }
1446
+ }
1200
1447
  }
1201
1448
  handleError(ws, _error) {
1202
1449
  const state = this.connections.get(ws);
1203
1450
  if (state) {
1204
- this.handleClose(ws);
1451
+ this.handleClose(ws).catch((e) => console.error("WebSocket close handling failed:", e));
1205
1452
  }
1206
1453
  }
1207
1454
  send(socket, message) {
1208
- if (socket.readyState === WebSocket.OPEN) {
1455
+ if (socket.readyState === SOCKET_OPEN) {
1209
1456
  socket.send(JSON.stringify(message));
1210
1457
  }
1211
1458
  }
@@ -1216,18 +1463,36 @@ class SubscriptionManager {
1216
1463
  payload: [{ message }]
1217
1464
  });
1218
1465
  }
1219
- publish(id, payload) {
1466
+ registerSubscription(socket, clientId, topic, context) {
1467
+ const state = this.connections.get(socket);
1468
+ if (!state || !state.initialized) {
1469
+ return false;
1470
+ }
1471
+ if (state.subscriptions.has(clientId)) {
1472
+ return false;
1473
+ }
1474
+ if (state.subscriptions.size >= (this.config.maxSubscriptionsPerConnection ?? 100)) {
1475
+ return false;
1476
+ }
1477
+ state.subscriptions.set(clientId, { clientId, topic, context });
1478
+ return true;
1479
+ }
1480
+ publishToTopic(topic, payload) {
1220
1481
  for (const [socket, state] of this.connections) {
1221
- const subscription = state.subscriptions.get(id);
1222
- if (subscription) {
1223
- this.send(socket, {
1224
- id,
1225
- type: "next",
1226
- payload: { data: payload }
1227
- });
1482
+ for (const subscription of state.subscriptions.values()) {
1483
+ if (subscription.topic === topic) {
1484
+ this.send(socket, {
1485
+ id: subscription.clientId,
1486
+ type: "next",
1487
+ payload: { data: payload }
1488
+ });
1489
+ }
1228
1490
  }
1229
1491
  }
1230
1492
  }
1493
+ publish(topic, payload) {
1494
+ this.publishToTopic(topic, payload);
1495
+ }
1231
1496
  getConnectionCount() {
1232
1497
  return this.connections.size;
1233
1498
  }
@@ -1240,7 +1505,7 @@ class SubscriptionManager {
1240
1505
  }
1241
1506
  }
1242
1507
  SubscriptionManager = __legacyDecorateClassTS([
1243
- Injectable6(),
1508
+ Injectable5(),
1244
1509
  __legacyDecorateParamTS(0, Inject4(LEAVEN_MODULE_OPTIONS)),
1245
1510
  __legacyDecorateParamTS(1, Inject4(LEAVEN_DRIVER)),
1246
1511
  __legacyMetadataTS("design:paramtypes", [
@@ -1249,73 +1514,593 @@ SubscriptionManager = __legacyDecorateClassTS([
1249
1514
  ])
1250
1515
  ], SubscriptionManager);
1251
1516
  function Subscription(returns, options) {
1252
- return (_target, _propertyKey, descriptor) => {
1253
- if (descriptor.value) {
1517
+ return (target, propertyKey, descriptor) => {
1518
+ const filter = options?.filter;
1519
+ if (filter) {
1520
+ wrapWithFilter(descriptor, filter);
1521
+ SetMetadata3(SUBSCRIPTION_FILTER_KEY, filter)(target, propertyKey, descriptor);
1522
+ }
1523
+ if (descriptor.value && typeof Reflect.defineMetadata === "function") {
1254
1524
  Reflect.defineMetadata("leaven:subscription", { returns, options }, descriptor.value);
1255
1525
  }
1256
1526
  return descriptor;
1257
1527
  };
1258
1528
  }
1259
1529
  function InjectPubSub() {
1260
- return (_target, propertyKey, parameterIndex) => {
1261
- if (_target && propertyKey !== undefined) {
1262
- const existingParams = Reflect.getMetadata("leaven:inject:pubsub", _target, propertyKey) || [];
1263
- existingParams.push(parameterIndex);
1264
- Reflect.defineMetadata("leaven:inject:pubsub", existingParams, _target, propertyKey);
1265
- }
1266
- };
1530
+ return Inject4(LEAVEN_PUBSUB);
1267
1531
  }
1268
- export {
1269
- registerResolverMetadata,
1270
- getResolverMetadata,
1271
- getGqlRoot,
1272
- getGqlInfo,
1273
- getGqlContext2 as getGqlContext,
1274
- getGqlArgs,
1275
- generateSchemaFile,
1276
- createGraphQLMiddleware,
1277
- createContextDecorator,
1278
- clearResolverMetadata,
1279
- SubscriptionManager,
1280
- SubscriptionFilter,
1281
- Subscription,
1282
- SchemaBuilderService,
1283
- SUBSCRIPTION_FILTER_KEY,
1284
- Root,
1285
- RolesGuard,
1286
- Roles,
1287
- ROLES_KEY,
1288
- Public,
1289
- PermissionsGuard,
1290
- Permissions,
1291
- Parent,
1292
- PERMISSIONS_KEY,
1293
- MetricsInterceptor,
1294
- LoggingInterceptor,
1295
- LeavenModule,
1296
- LeavenDriver,
1297
- LEAVEN_MODULE_OPTIONS,
1298
- LEAVEN_DRIVER,
1299
- InjectPubSub,
1300
- Info,
1301
- IS_PUBLIC_KEY,
1302
- GraphQLMiddleware,
1303
- GqlExecutionContext,
1304
- ErrorFormattingInterceptor,
1305
- Description,
1306
- DepthGuard,
1307
- Deprecated,
1308
- Decorators,
1309
- DESCRIPTION_KEY,
1310
- DEPRECATED_KEY,
1311
- Context,
1312
- ComplexityInterceptor,
1313
- ComplexityGuard,
1314
- Complexity,
1315
- CachingInterceptor,
1316
- CacheHint,
1317
- COMPLEXITY_KEY,
1318
- CACHE_KEY,
1319
- AuthGuard,
1320
- Args
1532
+
1533
+ // src/module.ts
1534
+ class LeavenModule {
1535
+ options;
1536
+ constructor(options) {
1537
+ this.options = options;
1538
+ }
1539
+ configure(consumer) {
1540
+ if (!this.options) {
1541
+ return;
1542
+ }
1543
+ consumer.apply(GraphQLMiddleware).forRoutes(this.options.path ?? "/graphql");
1544
+ }
1545
+ static forRoot(options = {}) {
1546
+ const optionsProvider = {
1547
+ provide: LEAVEN_MODULE_OPTIONS,
1548
+ useValue: options
1549
+ };
1550
+ const driverProvider = {
1551
+ provide: LEAVEN_DRIVER,
1552
+ useFactory: (opts) => new LeavenDriver(opts),
1553
+ inject: [LEAVEN_MODULE_OPTIONS]
1554
+ };
1555
+ return {
1556
+ module: LeavenModule,
1557
+ providers: [
1558
+ optionsProvider,
1559
+ driverProvider,
1560
+ ...LeavenModule.createLimitProviders(),
1561
+ LeavenModule.createPubSubProvider(),
1562
+ SchemaBuilderService,
1563
+ SubscriptionManager,
1564
+ GraphQLMiddleware
1565
+ ],
1566
+ exports: [
1567
+ LEAVEN_MODULE_OPTIONS,
1568
+ LEAVEN_DRIVER,
1569
+ LEAVEN_MAX_COMPLEXITY,
1570
+ LEAVEN_MAX_DEPTH,
1571
+ LEAVEN_PUBSUB,
1572
+ SchemaBuilderService,
1573
+ SubscriptionManager
1574
+ ]
1575
+ };
1576
+ }
1577
+ static createLimitProviders() {
1578
+ return [
1579
+ {
1580
+ provide: LEAVEN_MAX_COMPLEXITY,
1581
+ useFactory: (opts) => opts.maxComplexity ?? Infinity,
1582
+ inject: [LEAVEN_MODULE_OPTIONS]
1583
+ },
1584
+ {
1585
+ provide: LEAVEN_MAX_DEPTH,
1586
+ useFactory: (opts) => opts.maxDepth ?? Infinity,
1587
+ inject: [LEAVEN_MODULE_OPTIONS]
1588
+ }
1589
+ ];
1590
+ }
1591
+ static createPubSubProvider() {
1592
+ return {
1593
+ provide: LEAVEN_PUBSUB,
1594
+ useFactory: (opts) => opts.pubSub ?? createPubSub(),
1595
+ inject: [LEAVEN_MODULE_OPTIONS]
1596
+ };
1597
+ }
1598
+ static forRootAsync(options) {
1599
+ const asyncProviders = this.createAsyncProviders(options);
1600
+ const driverProvider = {
1601
+ provide: LEAVEN_DRIVER,
1602
+ useFactory: (opts) => new LeavenDriver(opts),
1603
+ inject: [LEAVEN_MODULE_OPTIONS]
1604
+ };
1605
+ return {
1606
+ module: LeavenModule,
1607
+ imports: options.imports ?? [],
1608
+ providers: [
1609
+ ...asyncProviders,
1610
+ driverProvider,
1611
+ ...LeavenModule.createLimitProviders(),
1612
+ LeavenModule.createPubSubProvider(),
1613
+ SchemaBuilderService,
1614
+ SubscriptionManager,
1615
+ GraphQLMiddleware
1616
+ ],
1617
+ exports: [
1618
+ LEAVEN_MODULE_OPTIONS,
1619
+ LEAVEN_DRIVER,
1620
+ LEAVEN_MAX_COMPLEXITY,
1621
+ LEAVEN_MAX_DEPTH,
1622
+ LEAVEN_PUBSUB,
1623
+ SchemaBuilderService,
1624
+ SubscriptionManager
1625
+ ]
1626
+ };
1627
+ }
1628
+ static createAsyncProviders(options) {
1629
+ if (!options.useFactory && !options.useClass && !options.useExisting) {
1630
+ throw new Error("LeavenModule.forRootAsync requires one of useFactory, useClass, or useExisting");
1631
+ }
1632
+ if (options.useExisting || options.useFactory) {
1633
+ return [this.createAsyncOptionsProvider(options)];
1634
+ }
1635
+ const useClass = options.useClass;
1636
+ return [
1637
+ this.createAsyncOptionsProvider(options),
1638
+ {
1639
+ provide: useClass,
1640
+ useClass
1641
+ }
1642
+ ];
1643
+ }
1644
+ static createAsyncOptionsProvider(options) {
1645
+ if (options.useFactory) {
1646
+ return {
1647
+ provide: LEAVEN_MODULE_OPTIONS,
1648
+ useFactory: options.useFactory,
1649
+ inject: options.inject ?? []
1650
+ };
1651
+ }
1652
+ const inject = [
1653
+ options.useClass ?? options.useExisting
1654
+ ];
1655
+ return {
1656
+ provide: LEAVEN_MODULE_OPTIONS,
1657
+ useFactory: async (optionsFactory) => optionsFactory.createLeavenOptions(),
1658
+ inject
1659
+ };
1660
+ }
1661
+ }
1662
+ LeavenModule = __legacyDecorateClassTS([
1663
+ Global(),
1664
+ Module({}),
1665
+ __legacyDecorateParamTS(0, Optional()),
1666
+ __legacyDecorateParamTS(0, Inject5(LEAVEN_MODULE_OPTIONS)),
1667
+ __legacyMetadataTS("design:paramtypes", [
1668
+ typeof LeavenModuleOptions === "undefined" ? Object : LeavenModuleOptions
1669
+ ])
1670
+ ], LeavenModule);
1671
+ // src/graphql-driver.ts
1672
+ import { HttpException } from "@nestjs/common";
1673
+ import { AbstractGraphQLDriver } from "@nestjs/graphql";
1674
+ import { LeavenExecutor as LeavenExecutor2 } from "@leaven-graphql/core";
1675
+ import {
1676
+ ERROR_CODES,
1677
+ ErrorCode,
1678
+ formatError as formatError3,
1679
+ getErrorCode,
1680
+ isLeavenError as isLeavenError3
1681
+ } from "@leaven-graphql/errors";
1682
+ import { renderGraphiQL as renderGraphiQL2 } from "@leaven-graphql/playground";
1683
+ var HTTP_STATUS_TO_ERROR_CODE = {
1684
+ 400: ErrorCode.BAD_REQUEST,
1685
+ 401: ErrorCode.UNAUTHENTICATED,
1686
+ 403: ErrorCode.FORBIDDEN,
1687
+ 404: ErrorCode.NOT_FOUND,
1688
+ 409: ErrorCode.ALREADY_EXISTS,
1689
+ 413: ErrorCode.PAYLOAD_TOO_LARGE,
1690
+ 429: ErrorCode.RATE_LIMITED
1691
+ };
1692
+
1693
+ class LeavenGraphQLDriver extends AbstractGraphQLDriver {
1694
+ executor = null;
1695
+ options = null;
1696
+ async start(options) {
1697
+ if (!options.schema) {
1698
+ throw new Error("LeavenGraphQLDriver: no schema was provided to start(). This is normally supplied by GraphQLModule.");
1699
+ }
1700
+ this.options = options;
1701
+ this.executor = new LeavenExecutor2({
1702
+ schema: options.schema,
1703
+ cache: options.cache,
1704
+ metrics: options.metrics,
1705
+ maxComplexity: options.maxComplexity,
1706
+ maxDepth: options.maxDepth,
1707
+ introspection: options.introspection ?? true,
1708
+ formatExecutionError: LeavenGraphQLDriver.formatExecutionError
1709
+ });
1710
+ const path = this.getNormalizedPath(options);
1711
+ const httpAdapter = this.httpAdapterHost.httpAdapter;
1712
+ httpAdapter.post(path, (req, res) => this.handleOperation(req, res));
1713
+ if (options.playground) {
1714
+ httpAdapter.get(path, (_req, res) => {
1715
+ this.servePlayground(res, path);
1716
+ });
1717
+ }
1718
+ }
1719
+ async stop() {
1720
+ if (this.executor) {
1721
+ await this.executor.clearCaches();
1722
+ this.executor = null;
1723
+ }
1724
+ }
1725
+ getExecutor() {
1726
+ return this.executor;
1727
+ }
1728
+ async handleOperation(req, res) {
1729
+ if (!this.executor) {
1730
+ res.status(503).json({
1731
+ errors: [
1732
+ {
1733
+ message: "GraphQL server is not running",
1734
+ extensions: { code: ErrorCode.INTERNAL_ERROR }
1735
+ }
1736
+ ]
1737
+ });
1738
+ return;
1739
+ }
1740
+ let body;
1741
+ try {
1742
+ body = await LeavenGraphQLDriver.readBody(req);
1743
+ } catch {
1744
+ this.respond(res, {
1745
+ errors: [
1746
+ {
1747
+ message: "Malformed request body: expected JSON",
1748
+ extensions: { code: ErrorCode.BAD_REQUEST }
1749
+ }
1750
+ ]
1751
+ });
1752
+ return;
1753
+ }
1754
+ if (!body.query) {
1755
+ this.respond(res, {
1756
+ errors: [
1757
+ {
1758
+ message: "Query is required",
1759
+ extensions: { code: ErrorCode.BAD_REQUEST }
1760
+ }
1761
+ ]
1762
+ });
1763
+ return;
1764
+ }
1765
+ const context = await this.buildContext(req, res);
1766
+ try {
1767
+ const result = await this.executor.execute({
1768
+ query: body.query,
1769
+ variables: body.variables,
1770
+ operationName: body.operationName
1771
+ }, context);
1772
+ const extensions = this.options?.metrics && result.metrics ? { ...result.response.extensions, metrics: result.metrics } : result.response.extensions;
1773
+ this.respond(res, {
1774
+ data: result.response.data,
1775
+ errors: result.response.errors,
1776
+ extensions
1777
+ });
1778
+ } catch (error) {
1779
+ this.respond(res, { errors: [this.formatThrownError(error)] });
1780
+ }
1781
+ }
1782
+ servePlayground(res, path) {
1783
+ const html = renderGraphiQL2({
1784
+ endpoint: path,
1785
+ subscriptionEndpoint: this.options?.subscriptionEndpoint
1786
+ });
1787
+ res.setHeader("Content-Type", "text/html; charset=utf-8");
1788
+ res.send(html);
1789
+ }
1790
+ async buildContext(req, res) {
1791
+ const base = { req, res };
1792
+ const factory = this.options?.context;
1793
+ if (typeof factory === "function") {
1794
+ const produced = await factory(base);
1795
+ return { ...base, ...produced };
1796
+ }
1797
+ if (factory && typeof factory === "object") {
1798
+ return { ...base, ...factory };
1799
+ }
1800
+ return base;
1801
+ }
1802
+ respond(res, response) {
1803
+ const errors = response.errors?.map((error) => this.finalizeError(error));
1804
+ const hasErrors = errors !== undefined && errors.length > 0;
1805
+ const totalFailure = response.data === undefined || response.data === null;
1806
+ let status = 200;
1807
+ if (hasErrors && totalFailure) {
1808
+ const code = errors[0]?.extensions?.code;
1809
+ const known = typeof code === "string" ? getErrorCode(code) : null;
1810
+ status = known ? ERROR_CODES[known].status : 500;
1811
+ }
1812
+ const body = {};
1813
+ if (response.data !== undefined) {
1814
+ body.data = response.data;
1815
+ }
1816
+ if (hasErrors) {
1817
+ body.errors = errors;
1818
+ }
1819
+ if (response.extensions !== undefined) {
1820
+ body.extensions = response.extensions;
1821
+ }
1822
+ res.status(status).json(body);
1823
+ }
1824
+ finalizeError(error) {
1825
+ const rawCode = error.extensions?.code;
1826
+ const code = typeof rawCode === "string" ? getErrorCode(rawCode) : null;
1827
+ const resolved = code ?? ErrorCode.INTERNAL_ERROR;
1828
+ const includeStack = this.options?.includeStacktraceInErrorResponses ?? false;
1829
+ const mask = resolved === ErrorCode.INTERNAL_ERROR && false;
1830
+ const finalized = mask ? {
1831
+ message: "An unexpected error occurred",
1832
+ extensions: { code: ErrorCode.INTERNAL_ERROR }
1833
+ } : {
1834
+ ...error,
1835
+ extensions: { ...error.extensions, code: resolved }
1836
+ };
1837
+ return this.options?.formatError ? this.options.formatError(finalized) : finalized;
1838
+ }
1839
+ formatThrownError(error) {
1840
+ const includeStack = this.options?.includeStacktraceInErrorResponses ?? false;
1841
+ const formatted = formatError3(error, {
1842
+ maskErrors: !isLeavenError3(error) && false,
1843
+ includeStackTrace: includeStack
1844
+ });
1845
+ return this.options?.formatError ? this.options.formatError(formatted) : formatted;
1846
+ }
1847
+ static async readBody(req) {
1848
+ const raw = req.body;
1849
+ if (typeof raw === "string") {
1850
+ return JSON.parse(raw);
1851
+ }
1852
+ if (raw !== null && typeof raw === "object" && !(raw instanceof ArrayBuffer)) {
1853
+ return raw;
1854
+ }
1855
+ const json = req.raw?.json;
1856
+ if (json) {
1857
+ return await json.call(req.raw);
1858
+ }
1859
+ return {};
1860
+ }
1861
+ static formatExecutionError(error) {
1862
+ const formatted = error.toJSON();
1863
+ const original = error.originalError;
1864
+ if (!(original instanceof HttpException)) {
1865
+ return formatted;
1866
+ }
1867
+ const status = original.getStatus();
1868
+ const code = HTTP_STATUS_TO_ERROR_CODE[status] ?? (status >= 400 && status < 500 ? ErrorCode.BAD_REQUEST : ErrorCode.INTERNAL_ERROR);
1869
+ return {
1870
+ ...formatted,
1871
+ extensions: { ...formatted.extensions, code, statusCode: status }
1872
+ };
1873
+ }
1874
+ }
1875
+ // src/interceptors.ts
1876
+ import {
1877
+ Injectable as Injectable6
1878
+ } from "@nestjs/common";
1879
+ import { tap, catchError, throwError } from "rxjs";
1880
+ import { Reflector as Reflector2 } from "@nestjs/core";
1881
+ import { GraphQLError as GraphQLError2 } from "graphql";
1882
+ import { errorToGraphQL, maskError } from "@leaven-graphql/errors";
1883
+ class LoggingInterceptor {
1884
+ intercept(context, next) {
1885
+ const className = context.getClass().name;
1886
+ const methodName = context.getHandler().name;
1887
+ const start = performance.now();
1888
+ return next.handle().pipe(tap(() => {
1889
+ const duration = (performance.now() - start).toFixed(2);
1890
+ console.log(`[GraphQL] ${className}.${methodName} - ${duration}ms`);
1891
+ }), catchError((error) => {
1892
+ const duration = (performance.now() - start).toFixed(2);
1893
+ console.error(`[GraphQL] ${className}.${methodName} - ${duration}ms - failed:`, error);
1894
+ return throwError(() => error);
1895
+ }));
1896
+ }
1897
+ }
1898
+ LoggingInterceptor = __legacyDecorateClassTS([
1899
+ Injectable6()
1900
+ ], LoggingInterceptor);
1901
+
1902
+ class ErrorFormattingInterceptor {
1903
+ intercept(_context, next) {
1904
+ return next.handle().pipe(catchError((error) => {
1905
+ const formattedError = this.formatError(error);
1906
+ return throwError(() => formattedError);
1907
+ }));
1908
+ }
1909
+ formatError(error) {
1910
+ const isProduction = false;
1911
+ const source = errorToGraphQL(error);
1912
+ const formatted = maskError(source, {
1913
+ maskErrors: isProduction,
1914
+ includeStackTrace: !isProduction
1915
+ });
1916
+ const keepLocations = formatted.locations !== undefined;
1917
+ return new GraphQLError2(formatted.message, {
1918
+ nodes: keepLocations ? source.nodes : undefined,
1919
+ source: keepLocations ? source.source : undefined,
1920
+ positions: keepLocations ? source.positions : undefined,
1921
+ path: formatted.path,
1922
+ extensions: formatted.extensions
1923
+ });
1924
+ }
1925
+ }
1926
+ ErrorFormattingInterceptor = __legacyDecorateClassTS([
1927
+ Injectable6()
1928
+ ], ErrorFormattingInterceptor);
1929
+
1930
+ class ComplexityInterceptor {
1931
+ reflector;
1932
+ constructor(reflector) {
1933
+ this.reflector = reflector;
1934
+ }
1935
+ intercept(context, next) {
1936
+ const complexity = this.reflector.get(COMPLEXITY_KEY, context.getHandler());
1937
+ if (complexity !== undefined) {
1938
+ const gqlContext = getGqlContext(context);
1939
+ if (gqlContext) {
1940
+ const current = typeof gqlContext._resolvedFieldComplexity === "number" ? gqlContext._resolvedFieldComplexity : 0;
1941
+ gqlContext._resolvedFieldComplexity = current + complexity;
1942
+ }
1943
+ }
1944
+ return next.handle();
1945
+ }
1946
+ }
1947
+ ComplexityInterceptor = __legacyDecorateClassTS([
1948
+ Injectable6(),
1949
+ __legacyMetadataTS("design:paramtypes", [
1950
+ typeof Reflector2 === "undefined" ? Object : Reflector2
1951
+ ])
1952
+ ], ComplexityInterceptor);
1953
+
1954
+ class CachingInterceptor {
1955
+ reflector;
1956
+ constructor(reflector) {
1957
+ this.reflector = reflector;
1958
+ }
1959
+ intercept(context, next) {
1960
+ const cacheHint = this.reflector.get(CACHE_KEY, context.getHandler());
1961
+ return next.handle().pipe(tap(() => {
1962
+ if (cacheHint) {
1963
+ const gqlInfo = getGqlInfo(context);
1964
+ if (gqlInfo?.cacheControl) {
1965
+ gqlInfo.cacheControl.setCacheHint({
1966
+ maxAge: cacheHint.maxAge,
1967
+ scope: cacheHint.scope
1968
+ });
1969
+ }
1970
+ }
1971
+ }));
1972
+ }
1973
+ }
1974
+ CachingInterceptor = __legacyDecorateClassTS([
1975
+ Injectable6(),
1976
+ __legacyMetadataTS("design:paramtypes", [
1977
+ typeof Reflector2 === "undefined" ? Object : Reflector2
1978
+ ])
1979
+ ], CachingInterceptor);
1980
+
1981
+ class MetricsInterceptor {
1982
+ metrics = new Map;
1983
+ intercept(context, next) {
1984
+ const className = context.getClass().name;
1985
+ const methodName = context.getHandler().name;
1986
+ const key = `${className}.${methodName}`;
1987
+ const start = performance.now();
1988
+ return next.handle().pipe(tap(() => {
1989
+ const duration = performance.now() - start;
1990
+ this.recordSuccess(key, duration);
1991
+ }), catchError((error) => {
1992
+ const duration = performance.now() - start;
1993
+ this.recordError(key, duration);
1994
+ return throwError(() => error);
1995
+ }));
1996
+ }
1997
+ recordSuccess(key, duration) {
1998
+ const metrics = this.getOrCreateMetrics(key);
1999
+ metrics.totalCount++;
2000
+ metrics.successCount++;
2001
+ metrics.totalDuration += duration;
2002
+ metrics.lastDuration = duration;
2003
+ metrics.minDuration = Math.min(metrics.minDuration, duration);
2004
+ metrics.maxDuration = Math.max(metrics.maxDuration, duration);
2005
+ }
2006
+ recordError(key, duration) {
2007
+ const metrics = this.getOrCreateMetrics(key);
2008
+ metrics.totalCount++;
2009
+ metrics.errorCount++;
2010
+ metrics.totalDuration += duration;
2011
+ metrics.lastDuration = duration;
2012
+ metrics.minDuration = Math.min(metrics.minDuration, duration);
2013
+ metrics.maxDuration = Math.max(metrics.maxDuration, duration);
2014
+ }
2015
+ getOrCreateMetrics(key) {
2016
+ let metrics = this.metrics.get(key);
2017
+ if (!metrics) {
2018
+ metrics = {
2019
+ totalCount: 0,
2020
+ successCount: 0,
2021
+ errorCount: 0,
2022
+ totalDuration: 0,
2023
+ lastDuration: 0,
2024
+ minDuration: Infinity,
2025
+ maxDuration: 0
2026
+ };
2027
+ this.metrics.set(key, metrics);
2028
+ }
2029
+ return metrics;
2030
+ }
2031
+ getMetrics() {
2032
+ return new Map(this.metrics);
2033
+ }
2034
+ getSummary() {
2035
+ return Array.from(this.metrics.entries()).map(([operation, metrics]) => ({
2036
+ operation,
2037
+ ...metrics,
2038
+ averageDuration: metrics.totalCount > 0 ? metrics.totalDuration / metrics.totalCount : 0,
2039
+ errorRate: metrics.totalCount > 0 ? metrics.errorCount / metrics.totalCount : 0
2040
+ }));
2041
+ }
2042
+ reset() {
2043
+ this.metrics.clear();
2044
+ }
2045
+ }
2046
+ MetricsInterceptor = __legacyDecorateClassTS([
2047
+ Injectable6()
2048
+ ], MetricsInterceptor);
2049
+ export {
2050
+ Args,
2051
+ AuthGuard,
2052
+ CACHE_KEY,
2053
+ COMPLEXITY_KEY,
2054
+ CacheHint,
2055
+ CachingInterceptor,
2056
+ Complexity,
2057
+ ComplexityGuard,
2058
+ ComplexityInterceptor,
2059
+ Context,
2060
+ DEPRECATED_KEY,
2061
+ DESCRIPTION_KEY,
2062
+ Decorators,
2063
+ Deprecated,
2064
+ DepthGuard,
2065
+ Description,
2066
+ ErrorFormattingInterceptor,
2067
+ GqlExecutionContext,
2068
+ GraphQLMiddleware,
2069
+ IS_PUBLIC_KEY,
2070
+ Info,
2071
+ InjectPubSub,
2072
+ LEAVEN_DRIVER,
2073
+ LEAVEN_MAX_COMPLEXITY,
2074
+ LEAVEN_MAX_DEPTH,
2075
+ LEAVEN_MODULE_OPTIONS,
2076
+ LEAVEN_PUBSUB,
2077
+ LeavenDriver,
2078
+ LeavenGraphQLDriver,
2079
+ LeavenModule,
2080
+ LoggingInterceptor,
2081
+ MetricsInterceptor,
2082
+ PERMISSIONS_KEY,
2083
+ Parent,
2084
+ Permissions,
2085
+ PermissionsGuard,
2086
+ Public,
2087
+ ROLES_KEY,
2088
+ Roles,
2089
+ RolesGuard,
2090
+ Root,
2091
+ SUBSCRIPTION_FILTER_KEY,
2092
+ SchemaBuilderService,
2093
+ Subscription,
2094
+ SubscriptionFilter,
2095
+ SubscriptionManager,
2096
+ clearResolverMetadata,
2097
+ createContextDecorator,
2098
+ createGraphQLMiddleware,
2099
+ generateSchemaFile,
2100
+ getGqlArgs,
2101
+ getGqlContext,
2102
+ getGqlInfo,
2103
+ getGqlRoot,
2104
+ getResolverMetadata,
2105
+ registerResolverMetadata
1321
2106
  };