@leaven-graphql/nestjs 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -14,323 +14,185 @@ 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");
135
- }
136
- return {
137
- query,
138
- variables: variables ? JSON.parse(variables) : undefined,
139
- operationName: operationName ?? undefined
140
- };
85
+ getType() {
86
+ return this.host.getType?.() ?? "unknown";
141
87
  }
142
- async createContext(request, response) {
143
- const baseContext = {
144
- req: request,
145
- res: response
146
- };
147
- if (this.options.context) {
148
- const customContext = await this.options.context(request, response);
149
- return { ...baseContext, ...customContext };
150
- }
151
- return baseContext;
88
+ isGraphQL() {
89
+ return this.getType() === "graphql";
152
90
  }
153
- documentToString(document) {
154
- const { print } = __require("graphql");
155
- return print(document);
91
+ getHandler() {
92
+ return this.host.getHandler?.();
156
93
  }
157
- isPlaygroundEnabled() {
158
- if (this.options.playground !== undefined) {
159
- return this.options.playground;
160
- }
161
- return true;
94
+ getClass() {
95
+ return this.host.getClass?.();
162
96
  }
163
- isIntrospectionEnabled() {
164
- if (this.options.introspection !== undefined) {
165
- return this.options.introspection;
166
- }
167
- return true;
97
+ getFieldName() {
98
+ const info = this.getInfo();
99
+ return info?.fieldName;
168
100
  }
169
- getPath() {
170
- return this.options.path ?? "/graphql";
101
+ getParentTypeName() {
102
+ const info = this.getInfo();
103
+ return info?.parentType?.name;
171
104
  }
172
- getCacheStats() {
173
- return this.executor?.getCacheStats() ?? null;
105
+ getReturnTypeName() {
106
+ const info = this.getInfo();
107
+ return info?.returnType?.toString();
174
108
  }
175
- clearCaches() {
176
- this.executor?.clearCaches();
109
+ getOperationName() {
110
+ const info = this.getInfo();
111
+ return info?.operation?.name?.value;
177
112
  }
178
- }
179
- LeavenDriver = __legacyDecorateClassTS([
180
- Injectable(),
181
- __legacyMetadataTS("design:paramtypes", [
182
- typeof LeavenModuleOptions === "undefined" ? Object : LeavenModuleOptions
183
- ])
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
- };
113
+ getOperationType() {
114
+ const info = this.getInfo();
115
+ return info?.operation?.operation;
206
116
  }
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
- };
117
+ getVariables() {
118
+ const info = this.getInfo();
119
+ return info?.variableValues ?? {};
220
120
  }
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
- ];
121
+ getPath() {
122
+ return this.getFullPath().filter((segment) => typeof segment === "string");
233
123
  }
234
- static createAsyncOptionsProvider(options) {
235
- if (options.useFactory) {
236
- return {
237
- provide: LEAVEN_MODULE_OPTIONS,
238
- useFactory: options.useFactory,
239
- inject: options.inject ?? []
240
- };
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;
241
133
  }
242
- const inject = [
243
- options.useClass ?? options.useExisting
244
- ];
245
- return {
246
- provide: LEAVEN_MODULE_OPTIONS,
247
- useFactory: async (optionsFactory) => optionsFactory.createLeavenOptions(),
248
- inject
134
+ return path;
135
+ }
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
+ }
249
158
  };
159
+ for (const fieldNode of info.fieldNodes) {
160
+ collect(fieldNode?.selectionSet);
161
+ }
162
+ return [...fields];
250
163
  }
251
164
  }
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];
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);
271
171
  }
272
172
  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
173
  }
303
- function SubscriptionFilter(filter) {
304
- return SetMetadata(SUBSCRIPTION_FILTER_KEY, filter);
174
+ function getGqlContext(context) {
175
+ return getCachedGqlExecutionContext(context).getContext();
305
176
  }
306
- function Decorators(...decorators) {
307
- return applyDecorators(...decorators);
177
+ function getGqlArgs(context) {
178
+ return getCachedGqlExecutionContext(context).getArgs();
308
179
  }
309
- function getGqlContext(ctx) {
310
- const args = ctx.getArgs();
311
- return args[2];
180
+ function getGqlInfo(context) {
181
+ return getCachedGqlExecutionContext(context).getInfo();
312
182
  }
313
- function createContextDecorator(key) {
314
- return () => createParamDecorator((_data, ctx) => {
315
- const gqlContext = getGqlContext(ctx);
316
- return gqlContext[key];
317
- })();
183
+ function getGqlRoot(context) {
184
+ return getCachedGqlExecutionContext(context).getRoot();
318
185
  }
186
+
319
187
  // 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
188
  var IS_PUBLIC_KEY = "leaven:isPublic";
329
189
  var ROLES_KEY = "leaven:roles";
330
190
  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);
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);
334
196
 
335
197
  class AuthGuard {
336
198
  reflector;
@@ -353,15 +215,14 @@ class AuthGuard {
353
215
  return true;
354
216
  }
355
217
  getGqlContext(context) {
356
- const args = context.getArgs();
357
- return args[2];
218
+ return getGqlContext(context);
358
219
  }
359
220
  extractUser(context) {
360
221
  return context.user ?? context.req?.user ?? null;
361
222
  }
362
223
  }
363
224
  AuthGuard = __legacyDecorateClassTS([
364
- Injectable2(),
225
+ Injectable(),
365
226
  __legacyMetadataTS("design:paramtypes", [
366
227
  typeof Reflector === "undefined" ? Object : Reflector
367
228
  ])
@@ -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;
423
+ getSchema() {
424
+ return this.schema;
425
+ }
426
+ getExecutor() {
427
+ return this.executor;
428
+ }
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);
443
+ }
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
+ };
572
470
  }
471
+ return {
472
+ data: response.data,
473
+ errors: formattedErrors,
474
+ extensions
475
+ };
476
+ } catch (error) {
477
+ return { errors: [this.formatThrownError(error)] };
573
478
  }
574
- return next.handle();
575
479
  }
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;
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;
588
488
  }
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
- });
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));
600
504
  }
505
+ return { errors };
601
506
  }
602
- }));
507
+ return result;
508
+ } catch (error) {
509
+ return { errors: [this.formatThrownError(error)] };
510
+ }
603
511
  }
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
- }));
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);
627
516
  }
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);
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 query2 = await request.text();
530
+ if (!query2) {
531
+ throw new ValidationError("Query is required");
532
+ }
533
+ return { query: query2 };
534
+ }
535
+ throw new ValidationError(`Unsupported content type "${contentType || "(none)"}": use application/json or application/graphql`);
536
+ }
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
+ };
636
557
  }
637
- recordError(key, duration) {
638
- const metrics = this.getOrCreateMetrics(key);
639
- metrics.totalCount++;
640
- metrics.errorCount++;
641
- metrics.totalDuration += duration;
642
- metrics.lastDuration = duration;
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;
643
568
  }
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);
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
+ }
607
+ }
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;
657
631
  }
658
- return metrics;
632
+ return true;
659
633
  }
660
- getMetrics() {
661
- return new Map(this.metrics);
634
+ getPath() {
635
+ return this.options.path ?? "/graphql";
662
636
  }
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
- }));
637
+ getCacheStats() {
638
+ return this.executor?.getCacheStats() ?? null;
670
639
  }
671
- reset() {
672
- this.metrics.clear();
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);
809
- }
810
- getContext() {
811
- return this.args[2];
812
- }
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;
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];
864
862
  }
865
- getVariables() {
866
- const info = this.getInfo();
867
- return info?.variableValues ?? {};
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];
868
879
  }
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
+ 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;
880
906
  }
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);
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').");
956
980
  }
957
981
  }
982
+ async buildFromTypeDefs(typeDefs, resolvers) {
983
+ const sdl = this.mergeTypeDefs(typeDefs);
984
+ if (!resolvers) {
985
+ return buildSchemaFromSDL(sdl);
986
+ }
987
+ const schema = makeExecutableSchema({
988
+ typeDefs: sdl,
989
+ resolvers
990
+ });
991
+ applyResolverFieldMetadata(schema, resolvers);
992
+ return schema;
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
- 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(() => {
1182
+ this.connections.set(socket, state);
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) {
@@ -1122,155 +1254,804 @@ class SubscriptionManager {
1122
1254
  return;
1123
1255
  }
1124
1256
  }
1125
- state.initialized = true;
1126
- state.context = ctx;
1127
- this.send(socket, { type: "connection_ack" });
1128
- if (this.config.keepAlive) {
1129
- state.keepAliveInterval = setInterval(() => {
1130
- this.send(socket, { type: "ping" });
1131
- }, this.config.keepAlive);
1257
+ state.initialized = true;
1258
+ state.context = ctx;
1259
+ if (state.initTimeout) {
1260
+ clearTimeout(state.initTimeout);
1261
+ state.initTimeout = undefined;
1262
+ }
1263
+ this.send(socket, { type: "connection_ack" });
1264
+ if (this.config.keepAlive) {
1265
+ state.keepAliveInterval = setInterval(() => {
1266
+ this.send(socket, { type: "ping" });
1267
+ }, this.config.keepAlive);
1268
+ }
1269
+ }
1270
+ async handleSubscribe(socket, state, message) {
1271
+ if (!state.initialized) {
1272
+ socket.close(4401, "Unauthorized");
1273
+ return;
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
+ }
1283
+ if (state.subscriptions.size >= (this.config.maxSubscriptionsPerConnection ?? 100)) {
1284
+ this.sendError(socket, message.id, "Too many subscriptions");
1285
+ return;
1286
+ }
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);
1292
+ }
1293
+ try {
1294
+ if (this.config.onSubscribe && state.context) {
1295
+ await this.config.onSubscribe(state.context, message);
1296
+ }
1297
+ const schema = this.driver.getSchema();
1298
+ if (!schema) {
1299
+ if (registration && state.subscriptions.get(message.id) === registration) {
1300
+ state.subscriptions.delete(message.id);
1301
+ }
1302
+ this.sendError(socket, message.id, "Schema not available");
1303
+ return;
1304
+ }
1305
+ let context = {
1306
+ req: state.request,
1307
+ res: {},
1308
+ connectionParams: state.connectionParams
1309
+ };
1310
+ if (this.config.context && state.context) {
1311
+ context = await this.config.context(state.context);
1312
+ }
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);
1325
+ return;
1326
+ }
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
1332
+ });
1333
+ this.send(socket, { id: message.id, type: "complete" });
1334
+ } catch (error) {
1335
+ if (registration && state.subscriptions.get(message.id) === registration) {
1336
+ state.subscriptions.delete(message.id);
1337
+ }
1338
+ const errorMessage = error instanceof Error ? error.message : "Subscription failed";
1339
+ this.sendError(socket, message.id, errorMessage);
1340
+ }
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
+ }
1414
+ async handleComplete(socket, state, message) {
1415
+ const subscription = state.subscriptions.get(message.id);
1416
+ if (subscription) {
1417
+ state.subscriptions.delete(message.id);
1418
+ await this.closeIterator(subscription.iterator);
1419
+ if (this.config.onComplete && state.context) {
1420
+ await this.config.onComplete(state.context, message);
1421
+ }
1422
+ }
1423
+ }
1424
+ async handleClose(ws) {
1425
+ const state = this.connections.get(ws);
1426
+ if (!state)
1427
+ return;
1428
+ if (state.keepAliveInterval) {
1429
+ clearInterval(state.keepAliveInterval);
1430
+ state.keepAliveInterval = undefined;
1431
+ }
1432
+ if (state.initTimeout) {
1433
+ clearTimeout(state.initTimeout);
1434
+ state.initTimeout = undefined;
1435
+ }
1436
+ const subscriptions = [...state.subscriptions.values()];
1437
+ state.subscriptions.clear();
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
+ }
1447
+ }
1448
+ handleError(ws, _error) {
1449
+ const state = this.connections.get(ws);
1450
+ if (state) {
1451
+ this.handleClose(ws).catch((e) => console.error("WebSocket close handling failed:", e));
1452
+ }
1453
+ }
1454
+ send(socket, message) {
1455
+ if (socket.readyState === SOCKET_OPEN) {
1456
+ socket.send(JSON.stringify(message));
1457
+ }
1458
+ }
1459
+ sendError(socket, id, message) {
1460
+ this.send(socket, {
1461
+ id,
1462
+ type: "error",
1463
+ payload: [{ message }]
1464
+ });
1465
+ }
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) {
1481
+ for (const [socket, state] of this.connections) {
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
+ }
1490
+ }
1491
+ }
1492
+ }
1493
+ publish(topic, payload) {
1494
+ this.publishToTopic(topic, payload);
1495
+ }
1496
+ getConnectionCount() {
1497
+ return this.connections.size;
1498
+ }
1499
+ getSubscriptionCount() {
1500
+ let count = 0;
1501
+ for (const [_, state] of this.connections) {
1502
+ count += state.subscriptions.size;
1503
+ }
1504
+ return count;
1505
+ }
1506
+ }
1507
+ SubscriptionManager = __legacyDecorateClassTS([
1508
+ Injectable5(),
1509
+ __legacyDecorateParamTS(0, Inject4(LEAVEN_MODULE_OPTIONS)),
1510
+ __legacyDecorateParamTS(1, Inject4(LEAVEN_DRIVER)),
1511
+ __legacyMetadataTS("design:paramtypes", [
1512
+ typeof LeavenModuleOptions === "undefined" ? Object : LeavenModuleOptions,
1513
+ typeof LeavenDriver === "undefined" ? Object : LeavenDriver
1514
+ ])
1515
+ ], SubscriptionManager);
1516
+ function Subscription(returns, options) {
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") {
1524
+ Reflect.defineMetadata("leaven:subscription", { returns, options }, descriptor.value);
1525
+ }
1526
+ return descriptor;
1527
+ };
1528
+ }
1529
+ function InjectPubSub() {
1530
+ return Inject4(LEAVEN_PUBSUB);
1531
+ }
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
+ });
1132
1717
  }
1133
1718
  }
1134
- async handleSubscribe(socket, state, message) {
1135
- if (!state.initialized) {
1136
- socket.close(4401, "Unauthorized");
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
+ });
1137
1738
  return;
1138
1739
  }
1139
- if (state.subscriptions.size >= (this.config.maxSubscriptionsPerConnection ?? 100)) {
1140
- this.sendError(socket, message.id, "Too many subscriptions");
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
+ });
1141
1752
  return;
1142
1753
  }
1143
- if (this.config.onSubscribe && state.context) {
1144
- await this.config.onSubscribe(state.context, message);
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;
1145
1764
  }
1765
+ const context = await this.buildContext(req, res);
1146
1766
  try {
1147
- const schema = this.driver.getSchema();
1148
- if (!schema) {
1149
- this.sendError(socket, message.id, "Schema not available");
1150
- return;
1151
- }
1152
- let context = {
1153
- req: state.request,
1154
- res: {},
1155
- ...state.connectionParams
1156
- };
1157
- if (this.config.context && state.context) {
1158
- context = await this.config.context(state.context);
1159
- }
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" });
1168
- return;
1169
- }
1170
- state.subscriptions.set(message.id, {
1171
- message,
1172
- context
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
1173
1777
  });
1174
1778
  } catch (error) {
1175
- const errorMessage = error instanceof Error ? error.message : "Subscription failed";
1176
- this.sendError(socket, message.id, errorMessage);
1779
+ this.respond(res, { errors: [this.formatThrownError(error)] });
1177
1780
  }
1178
1781
  }
1179
- async handleComplete(socket, state, message) {
1180
- const subscription = state.subscriptions.get(message.id);
1181
- if (subscription) {
1182
- state.subscriptions.delete(message.id);
1183
- if (this.config.onComplete && state.context) {
1184
- await this.config.onComplete(state.context, message);
1185
- }
1186
- }
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);
1187
1789
  }
1188
- async handleClose(ws) {
1189
- const state = this.connections.get(ws);
1190
- if (!state)
1191
- return;
1192
- if (state.keepAliveInterval) {
1193
- clearInterval(state.keepAliveInterval);
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);
1194
1851
  }
1195
- if (this.config.onDisconnect && state.context) {
1196
- await this.config.onDisconnect(state.context);
1852
+ if (raw !== null && typeof raw === "object" && !(raw instanceof ArrayBuffer)) {
1853
+ return raw;
1197
1854
  }
1198
- state.subscriptions.clear();
1199
- this.connections.delete(ws);
1200
- }
1201
- handleError(ws, _error) {
1202
- const state = this.connections.get(ws);
1203
- if (state) {
1204
- this.handleClose(ws);
1855
+ const json = req.raw?.json;
1856
+ if (json) {
1857
+ return await json.call(req.raw);
1205
1858
  }
1859
+ return {};
1206
1860
  }
1207
- send(socket, message) {
1208
- if (socket.readyState === WebSocket.OPEN) {
1209
- socket.send(JSON.stringify(message));
1861
+ static formatExecutionError(error) {
1862
+ const formatted = error.toJSON();
1863
+ const original = error.originalError;
1864
+ if (!(original instanceof HttpException)) {
1865
+ return formatted;
1210
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
+ };
1211
1873
  }
1212
- sendError(socket, id, message) {
1213
- this.send(socket, {
1214
- id,
1215
- type: "error",
1216
- payload: [{ message }]
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
1217
1923
  });
1218
1924
  }
1219
- publish(id, payload) {
1220
- 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
- });
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;
1228
1942
  }
1229
1943
  }
1944
+ return next.handle();
1230
1945
  }
1231
- getConnectionCount() {
1232
- return this.connections.size;
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;
1233
1958
  }
1234
- getSubscriptionCount() {
1235
- let count = 0;
1236
- for (const [_, state] of this.connections) {
1237
- count += state.subscriptions.size;
1238
- }
1239
- return count;
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
+ }));
1240
1972
  }
1241
1973
  }
1242
- SubscriptionManager = __legacyDecorateClassTS([
1974
+ CachingInterceptor = __legacyDecorateClassTS([
1243
1975
  Injectable6(),
1244
- __legacyDecorateParamTS(0, Inject4(LEAVEN_MODULE_OPTIONS)),
1245
- __legacyDecorateParamTS(1, Inject4(LEAVEN_DRIVER)),
1246
1976
  __legacyMetadataTS("design:paramtypes", [
1247
- typeof LeavenModuleOptions === "undefined" ? Object : LeavenModuleOptions,
1248
- typeof LeavenDriver === "undefined" ? Object : LeavenDriver
1977
+ typeof Reflector2 === "undefined" ? Object : Reflector2
1249
1978
  ])
1250
- ], SubscriptionManager);
1251
- function Subscription(returns, options) {
1252
- return (_target, _propertyKey, descriptor) => {
1253
- if (descriptor.value) {
1254
- Reflect.defineMetadata("leaven:subscription", { returns, options }, descriptor.value);
1255
- }
1256
- return descriptor;
1257
- };
1258
- }
1259
- 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);
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);
1265
2028
  }
1266
- };
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
+ }
1267
2045
  }
2046
+ MetricsInterceptor = __legacyDecorateClassTS([
2047
+ Injectable6()
2048
+ ], MetricsInterceptor);
1268
2049
  export {
1269
2050
  registerResolverMetadata,
1270
2051
  getResolverMetadata,
1271
2052
  getGqlRoot,
1272
2053
  getGqlInfo,
1273
- getGqlContext2 as getGqlContext,
2054
+ getGqlContext,
1274
2055
  getGqlArgs,
1275
2056
  generateSchemaFile,
1276
2057
  createGraphQLMiddleware,
@@ -1293,8 +2074,12 @@ export {
1293
2074
  MetricsInterceptor,
1294
2075
  LoggingInterceptor,
1295
2076
  LeavenModule,
2077
+ LeavenGraphQLDriver,
1296
2078
  LeavenDriver,
2079
+ LEAVEN_PUBSUB,
1297
2080
  LEAVEN_MODULE_OPTIONS,
2081
+ LEAVEN_MAX_DEPTH,
2082
+ LEAVEN_MAX_COMPLEXITY,
1298
2083
  LEAVEN_DRIVER,
1299
2084
  InjectPubSub,
1300
2085
  Info,