@martel/calyx 1.8.0 → 1.10.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.
Files changed (41) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +71 -27
  3. package/benchmarks/graphql-benchmark.ts +81 -0
  4. package/benchmarks/index.ts +32 -0
  5. package/benchmarks/openapi-benchmark.ts +168 -0
  6. package/benchmarks/serialization-benchmark.ts +52 -0
  7. package/benchmarks/techniques-benchmark.ts +84 -0
  8. package/benchmarks/validation-benchmark.ts +74 -0
  9. package/bun.lock +11 -0
  10. package/package.json +7 -6
  11. package/src/cli/index.ts +19 -3
  12. package/src/compression/compression.middleware.ts +7 -0
  13. package/src/cookies/cookies.ts +69 -0
  14. package/src/database/mongoose.module.ts +250 -0
  15. package/src/database/typeorm.module.ts +276 -0
  16. package/src/file-upload/file-upload.interceptor.ts +93 -0
  17. package/src/file-upload/index.ts +1 -0
  18. package/src/graphql/decorators.ts +70 -0
  19. package/src/graphql/graphql.module.ts +401 -57
  20. package/src/http/application.ts +434 -74
  21. package/src/http-client/http-client.module.ts +124 -0
  22. package/src/http-client/index.ts +1 -0
  23. package/src/index.ts +14 -0
  24. package/src/logger/index.ts +1 -0
  25. package/src/logger/logger.service.ts +118 -0
  26. package/src/mvc/index.ts +1 -0
  27. package/src/mvc/mvc.ts +22 -0
  28. package/src/openapi/decorators.ts +154 -0
  29. package/src/openapi/swagger.module.ts +172 -20
  30. package/src/queue/queue.module.ts +174 -0
  31. package/src/session/index.ts +1 -0
  32. package/src/session/session.middleware.ts +82 -0
  33. package/src/sse/index.ts +1 -0
  34. package/src/sse/sse.ts +18 -0
  35. package/src/streaming/index.ts +1 -0
  36. package/src/streaming/streamable-file.ts +32 -0
  37. package/src/validation/pipe.ts +79 -10
  38. package/src/versioning/versioning.ts +46 -0
  39. package/tests/graphql.test.ts +245 -6
  40. package/tests/openapi.test.ts +78 -11
  41. package/tests/techniques.test.ts +471 -0
@@ -3,6 +3,10 @@ import { Module } from '../core/decorators.ts';
3
3
  import {
4
4
  GraphQLSchema,
5
5
  GraphQLObjectType,
6
+ GraphQLInputObjectType,
7
+ GraphQLScalarType,
8
+ GraphQLInterfaceType,
9
+ GraphQLUnionType,
6
10
  GraphQLString,
7
11
  GraphQLInt,
8
12
  GraphQLFloat,
@@ -24,6 +28,66 @@ export class GraphQLModule {
24
28
  }
25
29
 
26
30
  const typeMap = new Map<any, any>();
31
+ const inputTypeMap = new Map<any, any>();
32
+
33
+ function findModuleClass(resolverClass: any): any {
34
+ for (const [moduleClass, record] of container.getModules().entries()) {
35
+ if (record.providers.has(resolverClass) || record.controllers.has(resolverClass)) {
36
+ return moduleClass;
37
+ }
38
+ }
39
+ return null;
40
+ }
41
+
42
+ function compileLifecycleItems(moduleClass: any, items: any[]): any[] {
43
+ return items.map((item) => {
44
+ if (typeof item === 'function') {
45
+ const isClass =
46
+ item.prototype &&
47
+ (typeof item.prototype.canActivate === 'function' ||
48
+ typeof item.prototype.intercept === 'function' ||
49
+ typeof item.prototype.transform === 'function' ||
50
+ typeof item.prototype.catch === 'function');
51
+ if (!isClass) {
52
+ return { isReqScoped: false, token: item, instance: item };
53
+ }
54
+ let instance: any;
55
+ try {
56
+ instance = container.resolveTokenInModuleContext(moduleClass, item);
57
+ } catch {
58
+ instance = new item();
59
+ }
60
+ return { isReqScoped: false, token: item, instance };
61
+ }
62
+ return { isReqScoped: false, token: item.constructor, instance: item };
63
+ });
64
+ }
65
+
66
+ function buildFieldsConfig(typeClass: any, isInput: boolean): any {
67
+ const fieldsMetadata: { propertyKey: string; typeFunc?: any; options?: any }[] =
68
+ Reflect.getMetadata('calyx:fields', typeClass) || [];
69
+
70
+ const fieldsConfig: any = {};
71
+ for (const field of fieldsMetadata) {
72
+ let returnTypeClass = field.typeFunc ? field.typeFunc(null) : undefined;
73
+ if (!returnTypeClass) {
74
+ returnTypeClass = Reflect.getMetadata('design:type', typeClass.prototype, field.propertyKey);
75
+ }
76
+ if (!returnTypeClass) {
77
+ returnTypeClass = String;
78
+ }
79
+
80
+ let gqlType = isInput ? getGraphQLInputType(returnTypeClass) : getGraphQLType(returnTypeClass);
81
+ if (Array.isArray(returnTypeClass)) {
82
+ gqlType = new GraphQLList(isInput ? getGraphQLInputType(returnTypeClass[0]) : getGraphQLType(returnTypeClass[0]));
83
+ }
84
+ if (!field.options?.nullable) {
85
+ gqlType = new GraphQLNonNull(gqlType);
86
+ }
87
+ fieldsConfig[field.propertyKey] = { type: gqlType };
88
+ }
89
+ return fieldsConfig;
90
+ }
27
91
 
28
92
  function getGraphQLType(typeClass: any): any {
29
93
  if (typeClass === String) return GraphQLString;
@@ -31,34 +95,49 @@ export class GraphQLModule {
31
95
  if (typeClass === Boolean) return GraphQLBoolean;
32
96
  if (typeMap.has(typeClass)) return typeMap.get(typeClass);
33
97
 
98
+ // Custom Scalar
99
+ if (typeof typeClass === 'function' && Reflect.hasMetadata('calyx:scalar', typeClass)) {
100
+ const scalarMeta = Reflect.getMetadata('calyx:scalar', typeClass);
101
+ const inst = container.get(typeClass) || new typeClass();
102
+ const gqlScalar = new GraphQLScalarType({
103
+ name: scalarMeta.name,
104
+ description: Reflect.getMetadata('calyx:description', typeClass),
105
+ serialize: inst.serialize ? inst.serialize.bind(inst) : (val: any) => val,
106
+ parseValue: inst.parseValue ? inst.parseValue.bind(inst) : (val: any) => val,
107
+ parseLiteral: inst.parseLiteral ? inst.parseLiteral.bind(inst) : undefined,
108
+ });
109
+ typeMap.set(typeClass, gqlScalar);
110
+ return gqlScalar;
111
+ }
112
+
113
+ // Interface
114
+ if (typeof typeClass === 'function' && Reflect.hasMetadata('calyx:interface_type', typeClass)) {
115
+ const gqlInterfaceType = new GraphQLInterfaceType({
116
+ name: typeClass.name,
117
+ description: Reflect.getMetadata('calyx:description', typeClass),
118
+ fields: () => buildFieldsConfig(typeClass, false),
119
+ });
120
+ typeMap.set(typeClass, gqlInterfaceType);
121
+ return gqlInterfaceType;
122
+ }
123
+
124
+ // Union
125
+ if (typeClass && (typeClass.__isUnion || (typeof typeClass === 'function' && Reflect.hasMetadata('calyx:union_type', typeClass)))) {
126
+ const unionMeta = typeClass.__isUnion ? typeClass : Reflect.getMetadata('calyx:union_type', typeClass);
127
+ const gqlUnionType = new GraphQLUnionType({
128
+ name: unionMeta.name,
129
+ types: () => unionMeta.types().map((t: any) => getGraphQLType(t)),
130
+ resolveType: unionMeta.resolveType,
131
+ });
132
+ typeMap.set(typeClass, gqlUnionType);
133
+ return gqlUnionType;
134
+ }
135
+
136
+ // ObjectType
34
137
  if (typeof typeClass === 'function' && Reflect.hasMetadata('calyx:object_type', typeClass)) {
35
138
  const gqlObjectType = new GraphQLObjectType({
36
139
  name: typeClass.name,
37
- fields: () => {
38
- const fieldsMetadata: { propertyKey: string; typeFunc?: any; options?: any }[] =
39
- Reflect.getMetadata('calyx:fields', typeClass) || [];
40
-
41
- const fieldsConfig: any = {};
42
- for (const field of fieldsMetadata) {
43
- let returnTypeClass = field.typeFunc ? field.typeFunc(null) : undefined;
44
- if (!returnTypeClass) {
45
- returnTypeClass = Reflect.getMetadata('design:type', typeClass.prototype, field.propertyKey);
46
- }
47
- if (!returnTypeClass) {
48
- returnTypeClass = String;
49
- }
50
-
51
- let gqlType = getGraphQLType(returnTypeClass);
52
- if (Array.isArray(returnTypeClass)) {
53
- gqlType = new GraphQLList(getGraphQLType(returnTypeClass[0]));
54
- }
55
- if (!field.options?.nullable) {
56
- gqlType = new GraphQLNonNull(gqlType);
57
- }
58
- fieldsConfig[field.propertyKey] = { type: gqlType };
59
- }
60
- return fieldsConfig;
61
- },
140
+ fields: () => buildFieldsConfig(typeClass, false),
62
141
  });
63
142
 
64
143
  typeMap.set(typeClass, gqlObjectType);
@@ -68,48 +147,216 @@ export class GraphQLModule {
68
147
  return GraphQLString;
69
148
  }
70
149
 
150
+ function getGraphQLInputType(typeClass: any): any {
151
+ if (typeClass === String) return GraphQLString;
152
+ if (typeClass === Number) return GraphQLFloat;
153
+ if (typeClass === Boolean) return GraphQLBoolean;
154
+ if (inputTypeMap.has(typeClass)) return inputTypeMap.get(typeClass);
155
+
156
+ if (typeof typeClass === 'function' && Reflect.hasMetadata('calyx:input_type', typeClass)) {
157
+ const gqlInputObjectType = new GraphQLInputObjectType({
158
+ name: typeClass.name,
159
+ fields: () => buildFieldsConfig(typeClass, true),
160
+ });
161
+
162
+ inputTypeMap.set(typeClass, gqlInputObjectType);
163
+ return gqlInputObjectType;
164
+ }
165
+
166
+ return GraphQLString;
167
+ }
168
+
71
169
  const queryFields: any = {};
72
170
  const mutationFields: any = {};
171
+ const subscriptionFields: any = {};
73
172
 
74
173
  for (const resolverInstance of resolverInstances) {
75
174
  const resolverClass = resolverInstance.constructor;
175
+ const moduleClass = findModuleClass(resolverClass);
76
176
 
77
- // Queries
78
- const queries: { propertyKey: string | symbol; typeFunc?: any; options?: any }[] =
79
- Reflect.getMetadata('calyx:queries', resolverClass) || [];
80
- for (const query of queries) {
81
- const returnTypeClass = query.typeFunc ? query.typeFunc(null) : String;
177
+ // Compile helper for queries, mutations, subscriptions
178
+ const compileField = (fieldMeta: { propertyKey: string | symbol; typeFunc?: any; options?: any }, list: any) => {
179
+ const returnTypeClass = fieldMeta.typeFunc ? fieldMeta.typeFunc(null) : String;
82
180
  let gqlType = getGraphQLType(returnTypeClass);
83
181
  if (Array.isArray(returnTypeClass)) {
84
182
  gqlType = new GraphQLList(getGraphQLType(returnTypeClass[0]));
85
183
  }
86
184
 
87
185
  const argsMetadata: { parameterIndex: number; name: string }[] =
88
- Reflect.getMetadata('calyx:args', resolverInstance, query.propertyKey) || [];
186
+ Reflect.getMetadata('calyx:args', resolverInstance, fieldMeta.propertyKey) || [];
89
187
  const argsConfig: any = {};
90
188
 
91
- const paramTypes = Reflect.getMetadata('design:paramtypes', resolverInstance, query.propertyKey) || [];
189
+ const paramTypes = Reflect.getMetadata('design:paramtypes', resolverInstance, fieldMeta.propertyKey) || [];
92
190
  for (const arg of argsMetadata) {
93
191
  const paramType = paramTypes[arg.parameterIndex] || String;
94
- argsConfig[arg.name] = { type: getGraphQLType(paramType) };
192
+ if (!arg.name && typeof paramType === 'function' && Reflect.hasMetadata('calyx:args_type', paramType)) {
193
+ const fieldsMetadata = Reflect.getMetadata('calyx:fields', paramType) || [];
194
+ for (const f of fieldsMetadata) {
195
+ let fType = f.typeFunc ? f.typeFunc(null) : undefined;
196
+ if (!fType) {
197
+ fType = Reflect.getMetadata('design:type', paramType.prototype, f.propertyKey) || String;
198
+ }
199
+ let gqlFType = getGraphQLInputType(fType);
200
+ if (Array.isArray(fType)) {
201
+ gqlFType = new GraphQLList(getGraphQLInputType(fType[0]));
202
+ }
203
+ if (!f.options?.nullable) {
204
+ gqlFType = new GraphQLNonNull(gqlFType);
205
+ }
206
+ argsConfig[f.propertyKey] = { type: gqlFType };
207
+ }
208
+ } else {
209
+ const argName = arg.name || 'args';
210
+ argsConfig[argName] = { type: getGraphQLInputType(paramType) };
211
+ }
95
212
  }
96
213
 
97
- const queryName = query.options?.name || String(query.propertyKey);
214
+ // Enhancers metadata resolution
215
+ const classGuards = Reflect.getMetadata('calyx:guards', resolverClass) || [];
216
+ const methodGuards = Reflect.getMetadata('calyx:guards', resolverClass.prototype, fieldMeta.propertyKey) || [];
217
+ const compiledGuards = compileLifecycleItems(moduleClass, [...classGuards, ...methodGuards]);
98
218
 
99
- queryFields[queryName] = {
100
- type: gqlType,
101
- args: argsConfig,
102
- resolve: async (parent: any, args: any, context: any) => {
103
- const params: any[] = [];
104
- for (const arg of argsMetadata) {
105
- params[arg.parameterIndex] = args[arg.name];
219
+ const classInterceptors = Reflect.getMetadata('calyx:interceptors', resolverClass) || [];
220
+ const methodInterceptors = Reflect.getMetadata('calyx:interceptors', resolverClass.prototype, fieldMeta.propertyKey) || [];
221
+ const compiledInterceptors = compileLifecycleItems(moduleClass, [...classInterceptors, ...methodInterceptors]);
222
+
223
+ const classFilters = Reflect.getMetadata('calyx:filters', resolverClass) || [];
224
+ const methodFilters = Reflect.getMetadata('calyx:filters', resolverClass.prototype, fieldMeta.propertyKey) || [];
225
+ const compiledFilters = compileLifecycleItems(moduleClass, [...classFilters, ...methodFilters]);
226
+
227
+ const classPipes = Reflect.getMetadata('calyx:pipes', resolverClass) || [];
228
+ const methodPipes = Reflect.getMetadata('calyx:pipes', resolverClass.prototype, fieldMeta.propertyKey) || [];
229
+ const compiledPipes = compileLifecycleItems(moduleClass, [...classPipes, ...methodPipes]);
230
+
231
+ const fieldName = fieldMeta.options?.name || String(fieldMeta.propertyKey);
232
+
233
+ const resolveFn = async (parent: any, args: any, context: any, info: any) => {
234
+ const req = context?.req;
235
+ const { CalyxExecutionContext } = await import('../lifecycle/context.ts');
236
+ const execContext = new CalyxExecutionContext(req, null, resolverClass, resolverInstance[fieldMeta.propertyKey]);
237
+ (execContext as any).type = 'graphql';
238
+ (execContext as any).data = args;
239
+
240
+ const gqlArgs = [parent, args, context, info];
241
+ execContext.getArgs = () => gqlArgs as any;
242
+ execContext.getArgByIndex = (index: number) => gqlArgs[index];
243
+
244
+ try {
245
+ // 1. Guards
246
+ for (const guard of compiledGuards) {
247
+ const canActivate = guard.instance.canActivate(execContext);
248
+ const resolved = canActivate instanceof Promise ? await canActivate : canActivate;
249
+ if (!resolved) {
250
+ const { HttpException } = await import('../http/exceptions.ts');
251
+ throw new HttpException('Forbidden resource', 403);
252
+ }
253
+ }
254
+
255
+ // 2. Pipes
256
+ let processedArgs = args;
257
+ for (const pipe of compiledPipes) {
258
+ const transformed = pipe.instance.transform(processedArgs, {
259
+ type: 'custom',
260
+ metatype: undefined,
261
+ data: undefined,
262
+ });
263
+ processedArgs = transformed instanceof Promise ? await transformed : transformed;
264
+ }
265
+
266
+ const executeHandler = async () => {
267
+ const params: any[] = [];
268
+ for (const arg of argsMetadata) {
269
+ const paramType = paramTypes[arg.parameterIndex] || String;
270
+ if (!arg.name && typeof paramType === 'function' && Reflect.hasMetadata('calyx:args_type', paramType)) {
271
+ const argInst = new paramType();
272
+ const fieldsMetadata = Reflect.getMetadata('calyx:fields', paramType) || [];
273
+ for (const f of fieldsMetadata) {
274
+ argInst[f.propertyKey] = processedArgs[f.propertyKey];
275
+ }
276
+ params[arg.parameterIndex] = argInst;
277
+ } else {
278
+ const argName = arg.name || 'args';
279
+ params[arg.parameterIndex] = processedArgs[argName];
280
+ }
281
+ }
282
+ const parentParams = Reflect.getMetadata('calyx:parent', resolverInstance, fieldMeta.propertyKey) || [];
283
+ for (const idx of parentParams) {
284
+ params[idx] = parent;
285
+ }
286
+ return resolverInstance[fieldMeta.propertyKey](...params);
287
+ };
288
+
289
+ // 3. Interceptors
290
+ if (compiledInterceptors.length > 0) {
291
+ let interceptorIndex = 0;
292
+ const nextHandler = async (): Promise<any> => {
293
+ if (interceptorIndex < compiledInterceptors.length) {
294
+ const interceptor = compiledInterceptors[interceptorIndex++];
295
+ return interceptor.instance.intercept(execContext, { handle: () => nextHandler() });
296
+ }
297
+ return executeHandler();
298
+ };
299
+ return await nextHandler();
300
+ } else {
301
+ return await executeHandler();
106
302
  }
107
- const parentParams = Reflect.getMetadata('calyx:parent', resolverInstance, query.propertyKey) || [];
108
- for (const idx of parentParams) {
109
- params[idx] = parent;
303
+ } catch (err: any) {
304
+ // 4. Exception Filters
305
+ if (compiledFilters.length > 0) {
306
+ for (const filter of compiledFilters) {
307
+ const catchException = Reflect.getMetadata('calyx:catch', filter.token) || [];
308
+ if (catchException.length === 0 || catchException.some((exc: any) => err instanceof exc)) {
309
+ const filterResult = filter.instance.catch(err, execContext);
310
+ if (filterResult !== undefined) {
311
+ return filterResult;
312
+ }
313
+ }
314
+ }
110
315
  }
111
- return resolverInstance[query.propertyKey](...params);
112
- },
316
+ throw err;
317
+ }
318
+ };
319
+
320
+ list[fieldName] = {
321
+ type: gqlType,
322
+ args: argsConfig,
323
+ resolve: resolveFn,
324
+ };
325
+
326
+ return { argsMetadata, paramTypes };
327
+ };
328
+
329
+ // Queries
330
+ const queries: { propertyKey: string | symbol; typeFunc?: any; options?: any }[] =
331
+ Reflect.getMetadata('calyx:queries', resolverClass) || [];
332
+ for (const query of queries) {
333
+ compileField(query, queryFields);
334
+ }
335
+
336
+ // Mutations
337
+ const mutations: { propertyKey: string | symbol; typeFunc?: any; options?: any }[] =
338
+ Reflect.getMetadata('calyx:mutations', resolverClass) || [];
339
+ for (const mutation of mutations) {
340
+ compileField(mutation, mutationFields);
341
+ }
342
+
343
+ // Subscriptions
344
+ const subscriptions: { propertyKey: string | symbol; typeFunc?: any; options?: any }[] =
345
+ Reflect.getMetadata('calyx:subscriptions', resolverClass) || [];
346
+ for (const sub of subscriptions) {
347
+ const { argsMetadata, paramTypes } = compileField(sub, subscriptionFields);
348
+ const subName = sub.options?.name || String(sub.propertyKey);
349
+
350
+ const originalResolve = subscriptionFields[subName].resolve;
351
+ subscriptionFields[subName].subscribe = originalResolve;
352
+ subscriptionFields[subName].resolve = (payload: any) => {
353
+ if (sub.options?.resolve) {
354
+ return sub.options.resolve(payload);
355
+ }
356
+ if (payload && typeof payload === 'object' && subName in payload) {
357
+ return payload[subName];
358
+ }
359
+ return payload;
113
360
  };
114
361
  }
115
362
 
@@ -125,18 +372,107 @@ export class GraphQLModule {
125
372
  for (const fieldRes of fieldResolvers) {
126
373
  const fieldName = fieldRes.options?.name || String(fieldRes.propertyKey);
127
374
  if (targetFields[fieldName]) {
128
- targetFields[fieldName].resolve = async (parent: any, args: any, context: any) => {
129
- const argsMetadata: { parameterIndex: number; name: string }[] =
130
- Reflect.getMetadata('calyx:args', resolverInstance, fieldRes.propertyKey) || [];
131
- const params: any[] = [];
132
- for (const arg of argsMetadata) {
133
- params[arg.parameterIndex] = args[arg.name];
134
- }
135
- const parentParams = Reflect.getMetadata('calyx:parent', resolverInstance, fieldRes.propertyKey) || [];
136
- for (const idx of parentParams) {
137
- params[idx] = parent;
375
+ // Resolve field enhancers resolution
376
+ const classGuards = Reflect.getMetadata('calyx:guards', resolverClass) || [];
377
+ const methodGuards = Reflect.getMetadata('calyx:guards', resolverClass.prototype, fieldRes.propertyKey) || [];
378
+ const compiledGuards = compileLifecycleItems(moduleClass, [...classGuards, ...methodGuards]);
379
+
380
+ const classInterceptors = Reflect.getMetadata('calyx:interceptors', resolverClass) || [];
381
+ const methodInterceptors = Reflect.getMetadata('calyx:interceptors', resolverClass.prototype, fieldRes.propertyKey) || [];
382
+ const compiledInterceptors = compileLifecycleItems(moduleClass, [...classInterceptors, ...methodInterceptors]);
383
+
384
+ const classFilters = Reflect.getMetadata('calyx:filters', resolverClass) || [];
385
+ const methodFilters = Reflect.getMetadata('calyx:filters', resolverClass.prototype, fieldRes.propertyKey) || [];
386
+ const compiledFilters = compileLifecycleItems(moduleClass, [...classFilters, ...methodFilters]);
387
+
388
+ const classPipes = Reflect.getMetadata('calyx:pipes', resolverClass) || [];
389
+ const methodPipes = Reflect.getMetadata('calyx:pipes', resolverClass.prototype, fieldRes.propertyKey) || [];
390
+ const compiledPipes = compileLifecycleItems(moduleClass, [...classPipes, ...methodPipes]);
391
+
392
+ targetFields[fieldName].resolve = async (parent: any, args: any, context: any, info: any) => {
393
+ const req = context?.req;
394
+ const { CalyxExecutionContext } = await import('../lifecycle/context.ts');
395
+ const execContext = new CalyxExecutionContext(req, null, resolverClass, resolverInstance[fieldRes.propertyKey]);
396
+ (execContext as any).type = 'graphql';
397
+ (execContext as any).data = args;
398
+
399
+ const gqlArgs = [parent, args, context, info];
400
+ execContext.getArgs = () => gqlArgs as any;
401
+ execContext.getArgByIndex = (index: number) => gqlArgs[index];
402
+
403
+ try {
404
+ for (const guard of compiledGuards) {
405
+ const canActivate = guard.instance.canActivate(execContext);
406
+ const resolved = canActivate instanceof Promise ? await canActivate : canActivate;
407
+ if (!resolved) {
408
+ const { HttpException } = await import('../http/exceptions.ts');
409
+ throw new HttpException('Forbidden resource', 403);
410
+ }
411
+ }
412
+
413
+ let processedArgs = args;
414
+ for (const pipe of compiledPipes) {
415
+ const transformed = pipe.instance.transform(processedArgs, {
416
+ type: 'custom',
417
+ metatype: undefined,
418
+ data: undefined,
419
+ });
420
+ processedArgs = transformed instanceof Promise ? await transformed : transformed;
421
+ }
422
+
423
+ const executeHandler = async () => {
424
+ const argsMetadata: { parameterIndex: number; name: string }[] =
425
+ Reflect.getMetadata('calyx:args', resolverInstance, fieldRes.propertyKey) || [];
426
+ const params: any[] = [];
427
+ const paramTypes = Reflect.getMetadata('design:paramtypes', resolverInstance, fieldRes.propertyKey) || [];
428
+ for (const arg of argsMetadata) {
429
+ const paramType = paramTypes[arg.parameterIndex] || String;
430
+ if (!arg.name && typeof paramType === 'function' && Reflect.hasMetadata('calyx:args_type', paramType)) {
431
+ const argInst = new paramType();
432
+ const fieldsMetadata = Reflect.getMetadata('calyx:fields', paramType) || [];
433
+ for (const f of fieldsMetadata) {
434
+ argInst[f.propertyKey] = processedArgs[f.propertyKey];
435
+ }
436
+ params[arg.parameterIndex] = argInst;
437
+ } else {
438
+ const argName = arg.name || 'args';
439
+ params[arg.parameterIndex] = processedArgs[argName];
440
+ }
441
+ }
442
+ const parentParams = Reflect.getMetadata('calyx:parent', resolverInstance, fieldRes.propertyKey) || [];
443
+ for (const idx of parentParams) {
444
+ params[idx] = parent;
445
+ }
446
+ return resolverInstance[fieldRes.propertyKey](...params);
447
+ };
448
+
449
+ if (compiledInterceptors.length > 0) {
450
+ let interceptorIndex = 0;
451
+ const nextHandler = async (): Promise<any> => {
452
+ if (interceptorIndex < compiledInterceptors.length) {
453
+ const interceptor = compiledInterceptors[interceptorIndex++];
454
+ return interceptor.instance.intercept(execContext, { handle: () => nextHandler() });
455
+ }
456
+ return executeHandler();
457
+ };
458
+ return await nextHandler();
459
+ } else {
460
+ return await executeHandler();
461
+ }
462
+ } catch (err: any) {
463
+ if (compiledFilters.length > 0) {
464
+ for (const filter of compiledFilters) {
465
+ const catchException = Reflect.getMetadata('calyx:catch', filter.token) || [];
466
+ if (catchException.length === 0 || catchException.some((exc: any) => err instanceof exc)) {
467
+ const filterResult = filter.instance.catch(err, execContext);
468
+ if (filterResult !== undefined) {
469
+ return filterResult;
470
+ }
471
+ }
472
+ }
473
+ }
474
+ throw err;
138
475
  }
139
- return resolverInstance[fieldRes.propertyKey](...params);
140
476
  };
141
477
  }
142
478
  }
@@ -161,6 +497,14 @@ export class GraphQLModule {
161
497
  }),
162
498
  }
163
499
  : {}),
500
+ ...(Object.keys(subscriptionFields).length > 0
501
+ ? {
502
+ subscription: new GraphQLObjectType({
503
+ name: 'Subscription',
504
+ fields: subscriptionFields,
505
+ }),
506
+ }
507
+ : {}),
164
508
  });
165
509
  }
166
510
  }