@martel/calyx 1.8.0 → 1.9.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/CHANGELOG.md +8 -0
- package/README.md +71 -27
- package/benchmarks/graphql-benchmark.ts +81 -0
- package/benchmarks/index.ts +32 -0
- package/benchmarks/openapi-benchmark.ts +168 -0
- package/benchmarks/serialization-benchmark.ts +52 -0
- package/benchmarks/techniques-benchmark.ts +84 -0
- package/benchmarks/validation-benchmark.ts +74 -0
- package/bun.lock +11 -0
- package/package.json +7 -6
- package/src/cli/index.ts +19 -3
- package/src/compression/compression.middleware.ts +7 -0
- package/src/cookies/cookies.ts +69 -0
- package/src/database/mongoose.module.ts +250 -0
- package/src/database/typeorm.module.ts +276 -0
- package/src/file-upload/file-upload.interceptor.ts +93 -0
- package/src/file-upload/index.ts +1 -0
- package/src/graphql/decorators.ts +70 -0
- package/src/graphql/graphql.module.ts +197 -47
- package/src/http/application.ts +330 -70
- package/src/http-client/http-client.module.ts +124 -0
- package/src/http-client/index.ts +1 -0
- package/src/index.ts +14 -0
- package/src/logger/index.ts +1 -0
- package/src/logger/logger.service.ts +118 -0
- package/src/mvc/index.ts +1 -0
- package/src/mvc/mvc.ts +22 -0
- package/src/openapi/decorators.ts +154 -0
- package/src/openapi/swagger.module.ts +172 -20
- package/src/queue/queue.module.ts +174 -0
- package/src/session/index.ts +1 -0
- package/src/session/session.middleware.ts +82 -0
- package/src/sse/index.ts +1 -0
- package/src/sse/sse.ts +18 -0
- package/src/streaming/index.ts +1 -0
- package/src/streaming/streamable-file.ts +32 -0
- package/src/validation/pipe.ts +79 -10
- package/src/versioning/versioning.ts +46 -0
- package/tests/graphql.test.ts +68 -4
- package/tests/openapi.test.ts +78 -11
- package/tests/techniques.test.ts +471 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { NestInterceptor, ExecutionContext, CallHandler } from '../lifecycle/interfaces.ts';
|
|
2
|
+
import { createParamDecorator } from '../http/decorators.ts';
|
|
3
|
+
import { Injectable } from '../core/decorators.ts';
|
|
4
|
+
|
|
5
|
+
export interface MulterFile {
|
|
6
|
+
fieldname: string;
|
|
7
|
+
originalname: string;
|
|
8
|
+
encoding: string;
|
|
9
|
+
mimetype: string;
|
|
10
|
+
size: number;
|
|
11
|
+
buffer: Buffer;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
@Injectable()
|
|
15
|
+
export class FileInterceptor implements NestInterceptor {
|
|
16
|
+
constructor(private readonly fieldName: string) {}
|
|
17
|
+
|
|
18
|
+
async intercept(context: ExecutionContext, next: CallHandler) {
|
|
19
|
+
const ctx = context.switchToHttp();
|
|
20
|
+
const req = ctx.getRequest();
|
|
21
|
+
const contentType = req.headers.get('content-type') || '';
|
|
22
|
+
|
|
23
|
+
if (contentType.includes('multipart/form-data')) {
|
|
24
|
+
try {
|
|
25
|
+
const formData = await req.clone().formData();
|
|
26
|
+
const file = formData.get(this.fieldName);
|
|
27
|
+
if (file && typeof file !== 'string') {
|
|
28
|
+
const buffer = Buffer.from(await file.arrayBuffer());
|
|
29
|
+
(req as any).file = {
|
|
30
|
+
fieldname: this.fieldName,
|
|
31
|
+
originalname: file.name,
|
|
32
|
+
encoding: '7bit',
|
|
33
|
+
mimetype: file.type,
|
|
34
|
+
size: file.size,
|
|
35
|
+
buffer,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
} catch (err) {
|
|
39
|
+
console.error('FileInterceptor parsing error:', err);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return next.handle();
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
@Injectable()
|
|
48
|
+
export class FilesInterceptor implements NestInterceptor {
|
|
49
|
+
constructor(private readonly fieldName: string) {}
|
|
50
|
+
|
|
51
|
+
async intercept(context: ExecutionContext, next: CallHandler) {
|
|
52
|
+
const ctx = context.switchToHttp();
|
|
53
|
+
const req = ctx.getRequest();
|
|
54
|
+
const contentType = req.headers.get('content-type') || '';
|
|
55
|
+
|
|
56
|
+
if (contentType.includes('multipart/form-data')) {
|
|
57
|
+
try {
|
|
58
|
+
const formData = await req.clone().formData();
|
|
59
|
+
const files = formData.getAll(this.fieldName);
|
|
60
|
+
const multerFiles: MulterFile[] = [];
|
|
61
|
+
|
|
62
|
+
for (const file of files) {
|
|
63
|
+
if (file && typeof file !== 'string') {
|
|
64
|
+
const buffer = Buffer.from(await file.arrayBuffer());
|
|
65
|
+
multerFiles.push({
|
|
66
|
+
fieldname: this.fieldName,
|
|
67
|
+
originalname: file.name,
|
|
68
|
+
encoding: '7bit',
|
|
69
|
+
mimetype: file.type,
|
|
70
|
+
size: file.size,
|
|
71
|
+
buffer,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
(req as any).files = multerFiles;
|
|
76
|
+
} catch {
|
|
77
|
+
// ignore
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return next.handle();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export const UploadedFile = createParamDecorator((data, ctx) => {
|
|
86
|
+
const req = ctx.switchToHttp().getRequest();
|
|
87
|
+
return (req as any).file;
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
export const UploadedFiles = createParamDecorator((data, ctx) => {
|
|
91
|
+
const req = ctx.switchToHttp().getRequest();
|
|
92
|
+
return (req as any).files;
|
|
93
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './file-upload.interceptor.ts';
|
|
@@ -60,3 +60,73 @@ export function Field(typeFunc?: (returns: any) => any, options?: { nullable?: b
|
|
|
60
60
|
Reflect.defineMetadata('calyx:fields', fields, target.constructor);
|
|
61
61
|
};
|
|
62
62
|
}
|
|
63
|
+
|
|
64
|
+
export function InputType(options?: { description?: string }): ClassDecorator {
|
|
65
|
+
return (target) => {
|
|
66
|
+
Reflect.defineMetadata('calyx:input_type', true, target);
|
|
67
|
+
if (options?.description) {
|
|
68
|
+
Reflect.defineMetadata('calyx:description', options.description, target);
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function ArgsType(): ClassDecorator {
|
|
74
|
+
return (target) => {
|
|
75
|
+
Reflect.defineMetadata('calyx:args_type', true, target);
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function InterfaceType(options?: { description?: string }): ClassDecorator {
|
|
80
|
+
return (target) => {
|
|
81
|
+
Reflect.defineMetadata('calyx:interface_type', true, target);
|
|
82
|
+
if (options?.description) {
|
|
83
|
+
Reflect.defineMetadata('calyx:description', options.description, target);
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function UnionType(options: { name: string; types: () => any[] }): ClassDecorator {
|
|
89
|
+
return (target) => {
|
|
90
|
+
Reflect.defineMetadata('calyx:union_type', options, target);
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function createUnionType(options: { name: string; types: () => any[]; resolveType?: any }) {
|
|
95
|
+
return {
|
|
96
|
+
name: options.name,
|
|
97
|
+
types: options.types,
|
|
98
|
+
resolveType: options.resolveType,
|
|
99
|
+
__isUnion: true,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function Subscription(typeFunc?: (returns: any) => any, options?: { name?: string; filter?: any; resolve?: any }): MethodDecorator {
|
|
104
|
+
return (target, propertyKey) => {
|
|
105
|
+
const subscriptions = Reflect.getOwnMetadata('calyx:subscriptions', target.constructor) || [];
|
|
106
|
+
subscriptions.push({ propertyKey, typeFunc, options });
|
|
107
|
+
Reflect.defineMetadata('calyx:subscriptions', subscriptions, target.constructor);
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function Scalar(name: string, typeFunc?: any): ClassDecorator {
|
|
112
|
+
return (target) => {
|
|
113
|
+
Reflect.defineMetadata('calyx:scalar', { name, typeFunc }, target);
|
|
114
|
+
Reflect.defineMetadata(METADATA_KEYS.INJECTABLE, true, target);
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function Directive(sdl: string): ClassDecorator & MethodDecorator & PropertyDecorator {
|
|
119
|
+
return (target: any, propertyKey?: string | symbol) => {
|
|
120
|
+
const key = 'calyx:directives';
|
|
121
|
+
if (propertyKey) {
|
|
122
|
+
const existing = Reflect.getOwnMetadata(key, target, propertyKey) || [];
|
|
123
|
+
existing.push(sdl);
|
|
124
|
+
Reflect.defineMetadata(key, existing, target, propertyKey);
|
|
125
|
+
} else {
|
|
126
|
+
const existing = Reflect.getOwnMetadata(key, target) || [];
|
|
127
|
+
existing.push(sdl);
|
|
128
|
+
Reflect.defineMetadata(key, existing, target);
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
@@ -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,33 @@ 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 buildFieldsConfig(typeClass: any, isInput: boolean): any {
|
|
34
|
+
const fieldsMetadata: { propertyKey: string; typeFunc?: any; options?: any }[] =
|
|
35
|
+
Reflect.getMetadata('calyx:fields', typeClass) || [];
|
|
36
|
+
|
|
37
|
+
const fieldsConfig: any = {};
|
|
38
|
+
for (const field of fieldsMetadata) {
|
|
39
|
+
let returnTypeClass = field.typeFunc ? field.typeFunc(null) : undefined;
|
|
40
|
+
if (!returnTypeClass) {
|
|
41
|
+
returnTypeClass = Reflect.getMetadata('design:type', typeClass.prototype, field.propertyKey);
|
|
42
|
+
}
|
|
43
|
+
if (!returnTypeClass) {
|
|
44
|
+
returnTypeClass = String;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
let gqlType = isInput ? getGraphQLInputType(returnTypeClass) : getGraphQLType(returnTypeClass);
|
|
48
|
+
if (Array.isArray(returnTypeClass)) {
|
|
49
|
+
gqlType = new GraphQLList(isInput ? getGraphQLInputType(returnTypeClass[0]) : getGraphQLType(returnTypeClass[0]));
|
|
50
|
+
}
|
|
51
|
+
if (!field.options?.nullable) {
|
|
52
|
+
gqlType = new GraphQLNonNull(gqlType);
|
|
53
|
+
}
|
|
54
|
+
fieldsConfig[field.propertyKey] = { type: gqlType };
|
|
55
|
+
}
|
|
56
|
+
return fieldsConfig;
|
|
57
|
+
}
|
|
27
58
|
|
|
28
59
|
function getGraphQLType(typeClass: any): any {
|
|
29
60
|
if (typeClass === String) return GraphQLString;
|
|
@@ -31,34 +62,49 @@ export class GraphQLModule {
|
|
|
31
62
|
if (typeClass === Boolean) return GraphQLBoolean;
|
|
32
63
|
if (typeMap.has(typeClass)) return typeMap.get(typeClass);
|
|
33
64
|
|
|
65
|
+
// Custom Scalar
|
|
66
|
+
if (typeof typeClass === 'function' && Reflect.hasMetadata('calyx:scalar', typeClass)) {
|
|
67
|
+
const scalarMeta = Reflect.getMetadata('calyx:scalar', typeClass);
|
|
68
|
+
const inst = container.get(typeClass) || new typeClass();
|
|
69
|
+
const gqlScalar = new GraphQLScalarType({
|
|
70
|
+
name: scalarMeta.name,
|
|
71
|
+
description: Reflect.getMetadata('calyx:description', typeClass),
|
|
72
|
+
serialize: inst.serialize ? inst.serialize.bind(inst) : (val: any) => val,
|
|
73
|
+
parseValue: inst.parseValue ? inst.parseValue.bind(inst) : (val: any) => val,
|
|
74
|
+
parseLiteral: inst.parseLiteral ? inst.parseLiteral.bind(inst) : undefined,
|
|
75
|
+
});
|
|
76
|
+
typeMap.set(typeClass, gqlScalar);
|
|
77
|
+
return gqlScalar;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Interface
|
|
81
|
+
if (typeof typeClass === 'function' && Reflect.hasMetadata('calyx:interface_type', typeClass)) {
|
|
82
|
+
const gqlInterfaceType = new GraphQLInterfaceType({
|
|
83
|
+
name: typeClass.name,
|
|
84
|
+
description: Reflect.getMetadata('calyx:description', typeClass),
|
|
85
|
+
fields: () => buildFieldsConfig(typeClass, false),
|
|
86
|
+
});
|
|
87
|
+
typeMap.set(typeClass, gqlInterfaceType);
|
|
88
|
+
return gqlInterfaceType;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Union
|
|
92
|
+
if (typeClass && (typeClass.__isUnion || (typeof typeClass === 'function' && Reflect.hasMetadata('calyx:union_type', typeClass)))) {
|
|
93
|
+
const unionMeta = typeClass.__isUnion ? typeClass : Reflect.getMetadata('calyx:union_type', typeClass);
|
|
94
|
+
const gqlUnionType = new GraphQLUnionType({
|
|
95
|
+
name: unionMeta.name,
|
|
96
|
+
types: () => unionMeta.types().map((t: any) => getGraphQLType(t)),
|
|
97
|
+
resolveType: unionMeta.resolveType,
|
|
98
|
+
});
|
|
99
|
+
typeMap.set(typeClass, gqlUnionType);
|
|
100
|
+
return gqlUnionType;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ObjectType
|
|
34
104
|
if (typeof typeClass === 'function' && Reflect.hasMetadata('calyx:object_type', typeClass)) {
|
|
35
105
|
const gqlObjectType = new GraphQLObjectType({
|
|
36
106
|
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
|
-
},
|
|
107
|
+
fields: () => buildFieldsConfig(typeClass, false),
|
|
62
108
|
});
|
|
63
109
|
|
|
64
110
|
typeMap.set(typeClass, gqlObjectType);
|
|
@@ -68,48 +114,132 @@ export class GraphQLModule {
|
|
|
68
114
|
return GraphQLString;
|
|
69
115
|
}
|
|
70
116
|
|
|
117
|
+
function getGraphQLInputType(typeClass: any): any {
|
|
118
|
+
if (typeClass === String) return GraphQLString;
|
|
119
|
+
if (typeClass === Number) return GraphQLFloat;
|
|
120
|
+
if (typeClass === Boolean) return GraphQLBoolean;
|
|
121
|
+
if (inputTypeMap.has(typeClass)) return inputTypeMap.get(typeClass);
|
|
122
|
+
|
|
123
|
+
if (typeof typeClass === 'function' && Reflect.hasMetadata('calyx:input_type', typeClass)) {
|
|
124
|
+
const gqlInputObjectType = new GraphQLInputObjectType({
|
|
125
|
+
name: typeClass.name,
|
|
126
|
+
fields: () => buildFieldsConfig(typeClass, true),
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
inputTypeMap.set(typeClass, gqlInputObjectType);
|
|
130
|
+
return gqlInputObjectType;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return GraphQLString;
|
|
134
|
+
}
|
|
135
|
+
|
|
71
136
|
const queryFields: any = {};
|
|
72
137
|
const mutationFields: any = {};
|
|
138
|
+
const subscriptionFields: any = {};
|
|
73
139
|
|
|
74
140
|
for (const resolverInstance of resolverInstances) {
|
|
75
141
|
const resolverClass = resolverInstance.constructor;
|
|
76
142
|
|
|
77
|
-
//
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
for (const query of queries) {
|
|
81
|
-
const returnTypeClass = query.typeFunc ? query.typeFunc(null) : String;
|
|
143
|
+
// Compile helper for queries, mutations, subscriptions
|
|
144
|
+
const compileField = (fieldMeta: { propertyKey: string | symbol; typeFunc?: any; options?: any }, list: any) => {
|
|
145
|
+
const returnTypeClass = fieldMeta.typeFunc ? fieldMeta.typeFunc(null) : String;
|
|
82
146
|
let gqlType = getGraphQLType(returnTypeClass);
|
|
83
147
|
if (Array.isArray(returnTypeClass)) {
|
|
84
148
|
gqlType = new GraphQLList(getGraphQLType(returnTypeClass[0]));
|
|
85
149
|
}
|
|
86
150
|
|
|
87
151
|
const argsMetadata: { parameterIndex: number; name: string }[] =
|
|
88
|
-
Reflect.getMetadata('calyx:args', resolverInstance,
|
|
152
|
+
Reflect.getMetadata('calyx:args', resolverInstance, fieldMeta.propertyKey) || [];
|
|
89
153
|
const argsConfig: any = {};
|
|
90
154
|
|
|
91
|
-
const paramTypes = Reflect.getMetadata('design:paramtypes', resolverInstance,
|
|
155
|
+
const paramTypes = Reflect.getMetadata('design:paramtypes', resolverInstance, fieldMeta.propertyKey) || [];
|
|
92
156
|
for (const arg of argsMetadata) {
|
|
93
157
|
const paramType = paramTypes[arg.parameterIndex] || String;
|
|
94
|
-
|
|
158
|
+
if (!arg.name && typeof paramType === 'function' && Reflect.hasMetadata('calyx:args_type', paramType)) {
|
|
159
|
+
const fieldsMetadata = Reflect.getMetadata('calyx:fields', paramType) || [];
|
|
160
|
+
for (const f of fieldsMetadata) {
|
|
161
|
+
let fType = f.typeFunc ? f.typeFunc(null) : undefined;
|
|
162
|
+
if (!fType) {
|
|
163
|
+
fType = Reflect.getMetadata('design:type', paramType.prototype, f.propertyKey) || String;
|
|
164
|
+
}
|
|
165
|
+
let gqlFType = getGraphQLInputType(fType);
|
|
166
|
+
if (Array.isArray(fType)) {
|
|
167
|
+
gqlFType = new GraphQLList(getGraphQLInputType(fType[0]));
|
|
168
|
+
}
|
|
169
|
+
if (!f.options?.nullable) {
|
|
170
|
+
gqlFType = new GraphQLNonNull(gqlFType);
|
|
171
|
+
}
|
|
172
|
+
argsConfig[f.propertyKey] = { type: gqlFType };
|
|
173
|
+
}
|
|
174
|
+
} else {
|
|
175
|
+
const argName = arg.name || 'args';
|
|
176
|
+
argsConfig[argName] = { type: getGraphQLInputType(paramType) };
|
|
177
|
+
}
|
|
95
178
|
}
|
|
96
179
|
|
|
97
|
-
const
|
|
180
|
+
const fieldName = fieldMeta.options?.name || String(fieldMeta.propertyKey);
|
|
98
181
|
|
|
99
|
-
|
|
182
|
+
const resolveFn = async (parent: any, args: any, context: any) => {
|
|
183
|
+
const params: any[] = [];
|
|
184
|
+
for (const arg of argsMetadata) {
|
|
185
|
+
const paramType = paramTypes[arg.parameterIndex] || String;
|
|
186
|
+
if (!arg.name && typeof paramType === 'function' && Reflect.hasMetadata('calyx:args_type', paramType)) {
|
|
187
|
+
const argInst = new paramType();
|
|
188
|
+
const fieldsMetadata = Reflect.getMetadata('calyx:fields', paramType) || [];
|
|
189
|
+
for (const f of fieldsMetadata) {
|
|
190
|
+
argInst[f.propertyKey] = args[f.propertyKey];
|
|
191
|
+
}
|
|
192
|
+
params[arg.parameterIndex] = argInst;
|
|
193
|
+
} else {
|
|
194
|
+
const argName = arg.name || 'args';
|
|
195
|
+
params[arg.parameterIndex] = args[argName];
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
const parentParams = Reflect.getMetadata('calyx:parent', resolverInstance, fieldMeta.propertyKey) || [];
|
|
199
|
+
for (const idx of parentParams) {
|
|
200
|
+
params[idx] = parent;
|
|
201
|
+
}
|
|
202
|
+
return resolverInstance[fieldMeta.propertyKey](...params);
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
list[fieldName] = {
|
|
100
206
|
type: gqlType,
|
|
101
207
|
args: argsConfig,
|
|
102
|
-
resolve:
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
208
|
+
resolve: resolveFn,
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
return { argsMetadata, paramTypes };
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
// Queries
|
|
215
|
+
const queries: { propertyKey: string | symbol; typeFunc?: any; options?: any }[] =
|
|
216
|
+
Reflect.getMetadata('calyx:queries', resolverClass) || [];
|
|
217
|
+
for (const query of queries) {
|
|
218
|
+
compileField(query, queryFields);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Mutations
|
|
222
|
+
const mutations: { propertyKey: string | symbol; typeFunc?: any; options?: any }[] =
|
|
223
|
+
Reflect.getMetadata('calyx:mutations', resolverClass) || [];
|
|
224
|
+
for (const mutation of mutations) {
|
|
225
|
+
compileField(mutation, mutationFields);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Subscriptions
|
|
229
|
+
const subscriptions: { propertyKey: string | symbol; typeFunc?: any; options?: any }[] =
|
|
230
|
+
Reflect.getMetadata('calyx:subscriptions', resolverClass) || [];
|
|
231
|
+
for (const sub of subscriptions) {
|
|
232
|
+
const { argsMetadata, paramTypes } = compileField(sub, subscriptionFields);
|
|
233
|
+
const subName = sub.options?.name || String(sub.propertyKey);
|
|
234
|
+
|
|
235
|
+
// Wrap with standard GraphQL subscribe / resolve
|
|
236
|
+
const originalResolve = subscriptionFields[subName].resolve;
|
|
237
|
+
subscriptionFields[subName].subscribe = originalResolve;
|
|
238
|
+
subscriptionFields[subName].resolve = (payload: any) => {
|
|
239
|
+
if (sub.options?.resolve) {
|
|
240
|
+
return sub.options.resolve(payload);
|
|
241
|
+
}
|
|
242
|
+
return payload;
|
|
113
243
|
};
|
|
114
244
|
}
|
|
115
245
|
|
|
@@ -129,8 +259,20 @@ export class GraphQLModule {
|
|
|
129
259
|
const argsMetadata: { parameterIndex: number; name: string }[] =
|
|
130
260
|
Reflect.getMetadata('calyx:args', resolverInstance, fieldRes.propertyKey) || [];
|
|
131
261
|
const params: any[] = [];
|
|
262
|
+
const paramTypes = Reflect.getMetadata('design:paramtypes', resolverInstance, fieldRes.propertyKey) || [];
|
|
132
263
|
for (const arg of argsMetadata) {
|
|
133
|
-
|
|
264
|
+
const paramType = paramTypes[arg.parameterIndex] || String;
|
|
265
|
+
if (!arg.name && typeof paramType === 'function' && Reflect.hasMetadata('calyx:args_type', paramType)) {
|
|
266
|
+
const argInst = new paramType();
|
|
267
|
+
const fieldsMetadata = Reflect.getMetadata('calyx:fields', paramType) || [];
|
|
268
|
+
for (const f of fieldsMetadata) {
|
|
269
|
+
argInst[f.propertyKey] = args[f.propertyKey];
|
|
270
|
+
}
|
|
271
|
+
params[arg.parameterIndex] = argInst;
|
|
272
|
+
} else {
|
|
273
|
+
const argName = arg.name || 'args';
|
|
274
|
+
params[arg.parameterIndex] = args[argName];
|
|
275
|
+
}
|
|
134
276
|
}
|
|
135
277
|
const parentParams = Reflect.getMetadata('calyx:parent', resolverInstance, fieldRes.propertyKey) || [];
|
|
136
278
|
for (const idx of parentParams) {
|
|
@@ -161,6 +303,14 @@ export class GraphQLModule {
|
|
|
161
303
|
}),
|
|
162
304
|
}
|
|
163
305
|
: {}),
|
|
306
|
+
...(Object.keys(subscriptionFields).length > 0
|
|
307
|
+
? {
|
|
308
|
+
subscription: new GraphQLObjectType({
|
|
309
|
+
name: 'Subscription',
|
|
310
|
+
fields: subscriptionFields,
|
|
311
|
+
}),
|
|
312
|
+
}
|
|
313
|
+
: {}),
|
|
164
314
|
});
|
|
165
315
|
}
|
|
166
316
|
}
|