@fluojs/graphql 1.0.4 → 1.1.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/README.ko.md +95 -7
- package/README.md +95 -7
- package/dist/decorators.d.ts +39 -0
- package/dist/decorators.d.ts.map +1 -1
- package/dist/decorators.js +102 -1
- package/dist/discovery.d.ts.map +1 -1
- package/dist/discovery.js +7 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/metadata.d.ts +21 -1
- package/dist/metadata.d.ts.map +1 -1
- package/dist/metadata.js +68 -7
- package/dist/node/graphql-websocket-transport.d.ts +36 -0
- package/dist/node/graphql-websocket-transport.d.ts.map +1 -0
- package/dist/node/graphql-websocket-transport.js +156 -0
- package/dist/schema/object-field-resolvers.d.ts +17 -0
- package/dist/schema/object-field-resolvers.d.ts.map +1 -0
- package/dist/schema/object-field-resolvers.js +78 -0
- package/dist/schema/schema.d.ts +5 -1
- package/dist/schema/schema.d.ts.map +1 -1
- package/dist/schema/schema.js +75 -39
- package/dist/service.d.ts +2 -7
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +43 -151
- package/dist/types.d.ts +16 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +9 -1
- package/package.json +7 -7
package/dist/schema/schema.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { DtoValidationError } from '@fluojs/validation';
|
|
2
2
|
import { createGraphqlInput, resolveArgType, resolveOutputType } from '../pipeline/input-pipeline.js';
|
|
3
3
|
import { GRAPHQL_OPERATION_CONTAINER, isGraphqlListTypeRef } from '../types.js';
|
|
4
|
+
import { ObjectFieldResolverRegistry } from './object-field-resolvers.js';
|
|
4
5
|
function isAsyncIterable(value) {
|
|
5
6
|
return typeof value === 'object' && value !== null && Symbol.asyncIterator in value;
|
|
6
7
|
}
|
|
@@ -14,7 +15,6 @@ function scalarByName(deps, scalar) {
|
|
|
14
15
|
return deps.GraphQLBoolean;
|
|
15
16
|
case 'id':
|
|
16
17
|
return deps.GraphQLID;
|
|
17
|
-
case 'string':
|
|
18
18
|
default:
|
|
19
19
|
return deps.GraphQLString;
|
|
20
20
|
}
|
|
@@ -35,43 +35,60 @@ function builtinScalarByGraphqlName(deps, scalarName) {
|
|
|
35
35
|
return undefined;
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
|
-
function normalizeFieldOutputType(deps, type) {
|
|
38
|
+
function normalizeFieldOutputType(deps, outputTypeCache, type, transformObjectFields) {
|
|
39
|
+
if (isListOutputType(type)) {
|
|
40
|
+
return new deps.GraphQLList(normalizeFieldOutputType(deps, outputTypeCache, type.ofType, transformObjectFields));
|
|
41
|
+
}
|
|
42
|
+
if (isNonNullOutputType(type)) {
|
|
43
|
+
return new deps.GraphQLNonNull(normalizeFieldOutputType(deps, outputTypeCache, type.ofType, transformObjectFields));
|
|
44
|
+
}
|
|
39
45
|
const maybeScalarName = type.name;
|
|
40
46
|
if (typeof maybeScalarName === 'string') {
|
|
41
|
-
|
|
47
|
+
const builtinScalar = builtinScalarByGraphqlName(deps, maybeScalarName);
|
|
48
|
+
if (builtinScalar) {
|
|
49
|
+
return builtinScalar;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (isUnionOutputType(type)) {
|
|
53
|
+
return normalizeUnionOutputType(deps, outputTypeCache, type, transformObjectFields);
|
|
54
|
+
}
|
|
55
|
+
if (isObjectOutputType(type)) {
|
|
56
|
+
return normalizeObjectOutputType(deps, outputTypeCache, type, transformObjectFields);
|
|
42
57
|
}
|
|
43
58
|
return type;
|
|
44
59
|
}
|
|
45
|
-
function normalizeObjectOutputType(deps, outputTypeCache, outputType) {
|
|
60
|
+
function normalizeObjectOutputType(deps, outputTypeCache, outputType, transformObjectFields) {
|
|
46
61
|
const outputTypeName = outputType.name;
|
|
47
62
|
const cached = outputTypeCache.get(outputTypeName);
|
|
48
63
|
if (cached) {
|
|
49
64
|
return cached;
|
|
50
65
|
}
|
|
51
66
|
const config = outputType.toConfig();
|
|
52
|
-
const clonedFields = Object.fromEntries(Object.entries(config.fields).map(([fieldName, fieldConfig]) => {
|
|
53
|
-
const field = fieldConfig;
|
|
54
|
-
return [fieldName, {
|
|
55
|
-
...field,
|
|
56
|
-
type: normalizeFieldOutputType(deps, field.type)
|
|
57
|
-
}];
|
|
58
|
-
}));
|
|
59
67
|
const normalized = new deps.GraphQLObjectType({
|
|
60
68
|
...config,
|
|
61
|
-
fields:
|
|
69
|
+
fields: () => {
|
|
70
|
+
const clonedFields = Object.fromEntries(Object.entries(config.fields).map(([fieldName, fieldConfig]) => {
|
|
71
|
+
const field = fieldConfig;
|
|
72
|
+
return [fieldName, {
|
|
73
|
+
...field,
|
|
74
|
+
type: normalizeFieldOutputType(deps, outputTypeCache, field.type, transformObjectFields)
|
|
75
|
+
}];
|
|
76
|
+
}));
|
|
77
|
+
return transformObjectFields(outputTypeName, clonedFields);
|
|
78
|
+
}
|
|
62
79
|
});
|
|
63
80
|
outputTypeCache.set(outputTypeName, normalized);
|
|
81
|
+
normalized.getFields();
|
|
64
82
|
return normalized;
|
|
65
83
|
}
|
|
66
|
-
function normalizeUnionOutputType(deps, outputTypeCache, outputType) {
|
|
84
|
+
function normalizeUnionOutputType(deps, outputTypeCache, outputType, transformObjectFields) {
|
|
67
85
|
const outputTypeName = outputType.name;
|
|
68
86
|
const cached = outputTypeCache.get(outputTypeName);
|
|
69
87
|
if (cached) {
|
|
70
88
|
return cached;
|
|
71
89
|
}
|
|
72
90
|
const config = outputType.toConfig();
|
|
73
|
-
const
|
|
74
|
-
const normalizedTypeByName = new Set(normalizedTypes.map(itemType => itemType.name).filter(name => typeof name === 'string'));
|
|
91
|
+
const normalizedTypeByName = new Set(config.types.map(itemType => itemType.name));
|
|
75
92
|
const normalized = new deps.GraphQLUnionType({
|
|
76
93
|
...config,
|
|
77
94
|
resolveType: async (...args) => {
|
|
@@ -88,36 +105,46 @@ function normalizeUnionOutputType(deps, outputTypeCache, outputType) {
|
|
|
88
105
|
}
|
|
89
106
|
return undefined;
|
|
90
107
|
},
|
|
91
|
-
types:
|
|
108
|
+
types: () => config.types.map(itemType => normalizeObjectOutputType(deps, outputTypeCache, itemType, transformObjectFields))
|
|
92
109
|
});
|
|
93
110
|
outputTypeCache.set(outputTypeName, normalized);
|
|
111
|
+
normalized.getTypes();
|
|
94
112
|
return normalized;
|
|
95
113
|
}
|
|
96
114
|
function isUnionOutputType(value) {
|
|
97
115
|
return typeof value === 'object' && typeof value.getTypes === 'function';
|
|
98
116
|
}
|
|
117
|
+
function isObjectOutputType(value) {
|
|
118
|
+
return value[Symbol.toStringTag] === 'GraphQLObjectType';
|
|
119
|
+
}
|
|
120
|
+
function isListOutputType(value) {
|
|
121
|
+
return value[Symbol.toStringTag] === 'GraphQLList';
|
|
122
|
+
}
|
|
123
|
+
function isNonNullOutputType(value) {
|
|
124
|
+
return value[Symbol.toStringTag] === 'GraphQLNonNull';
|
|
125
|
+
}
|
|
99
126
|
function resolveArgGraphqlType(deps, argType) {
|
|
100
127
|
if (isGraphqlListTypeRef(argType)) {
|
|
101
128
|
return new deps.GraphQLList(scalarByName(deps, argType.ofType));
|
|
102
129
|
}
|
|
103
130
|
return scalarByName(deps, argType);
|
|
104
131
|
}
|
|
105
|
-
function resolveNamedRootOutputType(deps, outputTypeCache, markAllowedCrossRealmGraphqlObjects, outputRef) {
|
|
132
|
+
function resolveNamedRootOutputType(deps, outputTypeCache, markAllowedCrossRealmGraphqlObjects, outputRef, transformObjectFields) {
|
|
106
133
|
if (typeof outputRef === 'string') {
|
|
107
134
|
return scalarByName(deps, outputRef);
|
|
108
135
|
}
|
|
109
136
|
markAllowedCrossRealmGraphqlObjects(outputRef);
|
|
110
137
|
if (isUnionOutputType(outputRef)) {
|
|
111
|
-
return normalizeUnionOutputType(deps, outputTypeCache, outputRef);
|
|
138
|
+
return normalizeUnionOutputType(deps, outputTypeCache, outputRef, transformObjectFields);
|
|
112
139
|
}
|
|
113
|
-
return normalizeObjectOutputType(deps, outputTypeCache, outputRef);
|
|
140
|
+
return normalizeObjectOutputType(deps, outputTypeCache, outputRef, transformObjectFields);
|
|
114
141
|
}
|
|
115
|
-
function resolveRootOutputType(deps, outputTypeCache, markAllowedCrossRealmGraphqlObjects, outputRef) {
|
|
142
|
+
function resolveRootOutputType(deps, outputTypeCache, markAllowedCrossRealmGraphqlObjects, outputRef, transformObjectFields) {
|
|
116
143
|
if (isGraphqlListTypeRef(outputRef)) {
|
|
117
|
-
const listItemType = resolveNamedRootOutputType(deps, outputTypeCache, markAllowedCrossRealmGraphqlObjects, outputRef.ofType);
|
|
144
|
+
const listItemType = resolveNamedRootOutputType(deps, outputTypeCache, markAllowedCrossRealmGraphqlObjects, outputRef.ofType, transformObjectFields);
|
|
118
145
|
return new deps.GraphQLList(listItemType);
|
|
119
146
|
}
|
|
120
|
-
return resolveNamedRootOutputType(deps, outputTypeCache, markAllowedCrossRealmGraphqlObjects, outputRef);
|
|
147
|
+
return resolveNamedRootOutputType(deps, outputTypeCache, markAllowedCrossRealmGraphqlObjects, outputRef, transformObjectFields);
|
|
121
148
|
}
|
|
122
149
|
function createFieldArgs(deps, handler) {
|
|
123
150
|
return Object.fromEntries(handler.argFields.map(argField => [argField.argName, {
|
|
@@ -147,7 +174,7 @@ function createOperationField(descriptor, handler, args, outputType, invokeResol
|
|
|
147
174
|
type: outputType
|
|
148
175
|
};
|
|
149
176
|
}
|
|
150
|
-
function pickFieldsByType(deps, descriptors, handlerType, markAllowedCrossRealmGraphqlObjects, outputTypeCache, invokeResolver) {
|
|
177
|
+
function pickFieldsByType(deps, descriptors, handlerType, markAllowedCrossRealmGraphqlObjects, outputTypeCache, transformObjectFields, invokeResolver) {
|
|
151
178
|
const fields = {};
|
|
152
179
|
for (const descriptor of descriptors) {
|
|
153
180
|
for (const handler of descriptor.handlers) {
|
|
@@ -156,7 +183,7 @@ function pickFieldsByType(deps, descriptors, handlerType, markAllowedCrossRealmG
|
|
|
156
183
|
}
|
|
157
184
|
const args = createFieldArgs(deps, handler);
|
|
158
185
|
const outputRef = resolveOutputType(handler);
|
|
159
|
-
const outputType = resolveRootOutputType(deps, outputTypeCache, markAllowedCrossRealmGraphqlObjects, outputRef);
|
|
186
|
+
const outputType = resolveRootOutputType(deps, outputTypeCache, markAllowedCrossRealmGraphqlObjects, outputRef, transformObjectFields);
|
|
160
187
|
if (Object.hasOwn(fields, handler.fieldName)) {
|
|
161
188
|
throw new Error(`GraphQL schema conflict: field "${handler.fieldName}" on ${handlerType} type is registered more than once. ` + `Found duplicate in resolver "${descriptor.targetName}". Each field name must be unique across all resolvers.`);
|
|
162
189
|
}
|
|
@@ -175,20 +202,22 @@ function isGraphQLSchemaLike(value) {
|
|
|
175
202
|
}
|
|
176
203
|
return typeof value.getQueryType === 'function' && typeof value.getTypeMap === 'function';
|
|
177
204
|
}
|
|
178
|
-
function toGraphqlValidationError(deps, error) {
|
|
179
|
-
|
|
205
|
+
function toGraphqlValidationError(deps, error, markAllowedCrossRealmGraphqlObjects) {
|
|
206
|
+
const graphqlError = deps.createGraphQLError('Validation failed.', {
|
|
180
207
|
extensions: {
|
|
181
208
|
code: 'BAD_USER_INPUT',
|
|
182
209
|
issues: error.issues
|
|
183
210
|
}
|
|
184
211
|
});
|
|
212
|
+
markAllowedCrossRealmGraphqlObjects(graphqlError);
|
|
213
|
+
return graphqlError;
|
|
185
214
|
}
|
|
186
|
-
async function createResolverInput(deps, handler, args) {
|
|
215
|
+
async function createResolverInput(deps, handler, args, markAllowedCrossRealmGraphqlObjects) {
|
|
187
216
|
try {
|
|
188
217
|
return await createGraphqlInput(handler.inputClass, args, handler.argFields);
|
|
189
218
|
} catch (error) {
|
|
190
219
|
if (error instanceof DtoValidationError) {
|
|
191
|
-
throw toGraphqlValidationError(deps, error);
|
|
220
|
+
throw toGraphqlValidationError(deps, error, markAllowedCrossRealmGraphqlObjects);
|
|
192
221
|
}
|
|
193
222
|
throw error;
|
|
194
223
|
}
|
|
@@ -200,21 +229,21 @@ function resolveResolverMethod(instance, descriptor, handler) {
|
|
|
200
229
|
}
|
|
201
230
|
return value;
|
|
202
231
|
}
|
|
203
|
-
function createResolverInvoker(deps, runtimeContainer) {
|
|
204
|
-
return async (descriptor, handler, args, contextValue) => {
|
|
232
|
+
function createResolverInvoker(deps, runtimeContainer, markAllowedCrossRealmGraphqlObjects, objectFieldResolvers) {
|
|
233
|
+
return async (descriptor, handler, args, contextValue, source) => {
|
|
205
234
|
if (descriptor.scope === 'singleton') {
|
|
206
235
|
const instance = await runtimeContainer.resolve(descriptor.token);
|
|
207
236
|
const resolverMethod = resolveResolverMethod(instance, descriptor, handler);
|
|
208
|
-
const
|
|
209
|
-
return resolverMethod.call(instance,
|
|
237
|
+
const methodArguments = handler.type === 'field' ? objectFieldResolvers.createMethodArguments(handler, source, contextValue) : [await createResolverInput(deps, handler, args, markAllowedCrossRealmGraphqlObjects), contextValue];
|
|
238
|
+
return resolverMethod.call(instance, ...methodArguments);
|
|
210
239
|
}
|
|
211
240
|
const operationContainer = contextValue[GRAPHQL_OPERATION_CONTAINER] ?? runtimeContainer.createRequestScope();
|
|
212
241
|
const disposeOperationContainer = contextValue[GRAPHQL_OPERATION_CONTAINER] === undefined;
|
|
213
242
|
try {
|
|
214
243
|
const instance = await operationContainer.resolve(descriptor.token);
|
|
215
244
|
const resolverMethod = resolveResolverMethod(instance, descriptor, handler);
|
|
216
|
-
const
|
|
217
|
-
return await resolverMethod.call(instance,
|
|
245
|
+
const methodArguments = handler.type === 'field' ? objectFieldResolvers.createMethodArguments(handler, source, contextValue) : [await createResolverInput(deps, handler, args, markAllowedCrossRealmGraphqlObjects), contextValue];
|
|
246
|
+
return await resolverMethod.call(instance, ...methodArguments);
|
|
218
247
|
} finally {
|
|
219
248
|
if (disposeOperationContainer) {
|
|
220
249
|
await operationContainer.dispose();
|
|
@@ -282,17 +311,24 @@ export function createCodeFirstSchema(deps, runtimeContainer, resolverDescriptor
|
|
|
282
311
|
if (resolverDescriptors.length === 0) {
|
|
283
312
|
throw new Error('GraphQL module requires either schema or at least one resolver decorated with @Resolver().');
|
|
284
313
|
}
|
|
285
|
-
const
|
|
314
|
+
const objectFieldResolvers = new ObjectFieldResolverRegistry(resolverDescriptors);
|
|
315
|
+
const invokeResolver = createResolverInvoker(deps, runtimeContainer, markAllowedCrossRealmGraphqlObjects, objectFieldResolvers);
|
|
286
316
|
const outputTypeCache = new Map();
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
317
|
+
function attachObjectFieldResolvers(typeName, fields) {
|
|
318
|
+
return objectFieldResolvers.attach(typeName, fields, outputType => resolveRootOutputType(deps, outputTypeCache, markAllowedCrossRealmGraphqlObjects, outputType, attachObjectFieldResolvers), (descriptor, handler, source, contextValue) => invokeResolver(descriptor, handler, {}, contextValue, source));
|
|
319
|
+
}
|
|
320
|
+
const queryFields = pickFieldsByType(deps, resolverDescriptors, 'query', markAllowedCrossRealmGraphqlObjects, outputTypeCache, attachObjectFieldResolvers, invokeResolver);
|
|
321
|
+
const mutationFields = pickFieldsByType(deps, resolverDescriptors, 'mutation', markAllowedCrossRealmGraphqlObjects, outputTypeCache, attachObjectFieldResolvers, invokeResolver);
|
|
322
|
+
const subscriptionFields = pickFieldsByType(deps, resolverDescriptors, 'subscription', markAllowedCrossRealmGraphqlObjects, outputTypeCache, attachObjectFieldResolvers, invokeResolver);
|
|
323
|
+
objectFieldResolvers.assertAllTargetsAttached();
|
|
290
324
|
const queryType = createQueryRootType(deps, queryFields);
|
|
291
325
|
const mutationType = createOptionalRootType(deps, 'Mutation', mutationFields);
|
|
292
326
|
const subscriptionType = createOptionalRootType(deps, 'Subscription', subscriptionFields);
|
|
293
|
-
|
|
327
|
+
const schema = new deps.GraphQLSchema({
|
|
294
328
|
mutation: mutationType,
|
|
295
329
|
query: queryType,
|
|
296
330
|
subscription: subscriptionType
|
|
297
331
|
});
|
|
332
|
+
markAllowedCrossRealmGraphqlObjects(schema);
|
|
333
|
+
return schema;
|
|
298
334
|
}
|
package/dist/service.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { type HttpApplicationAdapter } from '@fluojs/http';
|
|
2
1
|
import type { Container } from '@fluojs/di';
|
|
2
|
+
import { type HttpApplicationAdapter } from '@fluojs/http';
|
|
3
3
|
import type { ApplicationLogger, CompiledModule, OnApplicationBootstrap, OnApplicationShutdown } from '@fluojs/runtime';
|
|
4
4
|
import type { GraphqlModuleOptions } from './types.js';
|
|
5
5
|
/**
|
|
@@ -23,10 +23,7 @@ export declare class GraphqlLifecycleService implements OnApplicationBootstrap,
|
|
|
23
23
|
private readonly operationContainers;
|
|
24
24
|
private readonly requestContexts;
|
|
25
25
|
private readonly websocketOperationContainers;
|
|
26
|
-
private
|
|
27
|
-
private websocketServer;
|
|
28
|
-
private websocketUpgradeListener;
|
|
29
|
-
private websocketUpgradeServer;
|
|
26
|
+
private websocketTransport;
|
|
30
27
|
private executeGraphqlOperation;
|
|
31
28
|
private releaseGraphqlInstanceOfPatch;
|
|
32
29
|
private subscribeGraphqlOperation;
|
|
@@ -48,10 +45,8 @@ export declare class GraphqlLifecycleService implements OnApplicationBootstrap,
|
|
|
48
45
|
private registerWebSocketTransport;
|
|
49
46
|
private unregisterWebSocketTransport;
|
|
50
47
|
private isWebSocketTransportEnabled;
|
|
51
|
-
private resolveUpgradeServer;
|
|
52
48
|
private handleWebSocketSubscribe;
|
|
53
49
|
private createWebSocketOperationLimitError;
|
|
54
|
-
private rejectWebSocketUpgrade;
|
|
55
50
|
private getOrCreateWebSocketOperationContainer;
|
|
56
51
|
private disposeWebSocketOperationContainer;
|
|
57
52
|
private disposeAllWebSocketOperationContainers;
|
package/dist/service.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,EAA0C,KAAK,sBAAsB,EAA4D,MAAM,cAAc,CAAC;AAC7J,OAAO,KAAK,EAAE,iBAAiB,EAAE,cAAc,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AA0BxH,OAAO,KAAK,EAEV,oBAAoB,EAGrB,MAAM,YAAY,CAAC;AA2EpB;;GAEG;AACH,qBACa,yBAAyB;IAEpC,SAAS,IAAI,SAAS;IAKtB,UAAU,IAAI,SAAS;CAGxB;AA4JD;;GAEG;AACH,qBACa,uBAAwB,YAAW,sBAAsB,EAAE,qBAAqB;IA0DzF,OAAO,CAAC,QAAQ,CAAC,gBAAgB;IACjC,OAAO,CAAC,QAAQ,CAAC,eAAe;IAChC,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,OAAO;IA7D1B,OAAO,CAAC,uBAAuB,CAAsC;IACrE,OAAO,CAAC,oBAAoB,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAqC;IACzE,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAiD;IACjF,OAAO,CAAC,QAAQ,CAAC,4BAA4B,CAA6C;IAC1F,OAAO,CAAC,kBAAkB,CAA4C;IACtE,OAAO,CAAC,uBAAuB,CAAoC;IACnE,OAAO,CAAC,6BAA6B,CAA2B;IAChE,OAAO,CAAC,yBAAyB,CAAsC;IACvE,OAAO,CAAC,IAAI,CAAuB;IAEnC,OAAO,CAAC,QAAQ,CAAC,UAAU,CA2CzB;gBAGiB,gBAAgB,EAAE,SAAS,EAC3B,eAAe,EAAE,SAAS,cAAc,EAAE,EAC1C,MAAM,EAAE,iBAAiB,EACzB,OAAO,EAAE,sBAAsB,EAC/B,OAAO,EAAE,oBAAoB;IAG1C,sBAAsB,IAAI,OAAO,CAAC,IAAI,CAAC;IAgDvC,qBAAqB,IAAI,OAAO,CAAC,IAAI,CAAC;YAI9B,wBAAwB;IAYtC,OAAO,CAAC,sBAAsB;IAI9B,OAAO,CAAC,2BAA2B;IAInC,OAAO,CAAC,sBAAsB;IAe9B,OAAO,CAAC,aAAa;IAMrB,OAAO,CAAC,qBAAqB;IAI7B,OAAO,CAAC,2BAA2B;IAInC,OAAO,CAAC,kBAAkB;IAiB1B,OAAO,CAAC,oBAAoB;IAmB5B,OAAO,CAAC,mBAAmB;YAyBb,0BAA0B;YAgC1B,4BAA4B;IAgB1C,OAAO,CAAC,2BAA2B;YAIrB,wBAAwB;IAmDtC,OAAO,CAAC,kCAAkC;IAwB1C,OAAO,CAAC,sCAAsC;YAchC,kCAAkC;YAqBlC,sCAAsC;IAkBpD,OAAO,CAAC,6BAA6B;YAYvB,yBAAyB;CAexC"}
|
package/dist/service.js
CHANGED
|
@@ -4,8 +4,8 @@ function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol"
|
|
|
4
4
|
function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
|
|
5
5
|
function _setFunctionName(e, t, n) { "symbol" == typeof t && (t = (t = t.description) ? "[" + t + "]" : ""); try { Object.defineProperty(e, "name", { configurable: !0, value: n ? n + " " + t : t }); } catch (e) {} return e; }
|
|
6
6
|
function _checkInRHS(e) { if (Object(e) !== e) throw TypeError("right-hand side of 'in' should be an object, got " + (null !== e ? typeof e : "null")); return e; }
|
|
7
|
-
import { Controller, Get, Post } from '@fluojs/http';
|
|
8
7
|
import { Inject } from '@fluojs/core';
|
|
8
|
+
import { Controller, Get, Post } from '@fluojs/http';
|
|
9
9
|
import { APPLICATION_LOGGER, COMPILED_MODULES, HTTP_APPLICATION_ADAPTER, RUNTIME_CONTAINER } from '@fluojs/runtime/internal';
|
|
10
10
|
import { discoverResolverDescriptors } from './discovery.js';
|
|
11
11
|
import { createGraphqlValidationPlugin, resolveGraphqlRequestLimits } from './guardrails.js';
|
|
@@ -19,13 +19,6 @@ const DEFAULT_GRAPHQL_WEBSOCKET_LIMITS = {
|
|
|
19
19
|
maxOperationsPerConnection: 25,
|
|
20
20
|
maxPayloadBytes: 64 * 1024
|
|
21
21
|
};
|
|
22
|
-
function hasNodeUpgradeServer(value) {
|
|
23
|
-
if (typeof value !== 'object' || value === null) {
|
|
24
|
-
return false;
|
|
25
|
-
}
|
|
26
|
-
const server = value;
|
|
27
|
-
return typeof server.on === 'function' && typeof server.off === 'function';
|
|
28
|
-
}
|
|
29
22
|
function buildFrameworkRequestFromFetchRequest(request) {
|
|
30
23
|
const requestUrl = new URL(request.url);
|
|
31
24
|
return {
|
|
@@ -40,37 +33,6 @@ function buildFrameworkRequestFromFetchRequest(request) {
|
|
|
40
33
|
url: requestUrl.pathname + requestUrl.search
|
|
41
34
|
};
|
|
42
35
|
}
|
|
43
|
-
function buildFrameworkRequestFromIncomingMessage(request) {
|
|
44
|
-
const requestUrl = new URL(request.url ?? '/graphql', 'http://localhost');
|
|
45
|
-
return {
|
|
46
|
-
cookies: {},
|
|
47
|
-
headers: request.headers,
|
|
48
|
-
method: request.method ?? 'GET',
|
|
49
|
-
params: {},
|
|
50
|
-
path: requestUrl.pathname,
|
|
51
|
-
query: Object.fromEntries(requestUrl.searchParams.entries()),
|
|
52
|
-
raw: request,
|
|
53
|
-
url: requestUrl.pathname + requestUrl.search
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
function isConnectionParamsRecord(value) {
|
|
57
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
58
|
-
}
|
|
59
|
-
function closeWebSocketServer(server) {
|
|
60
|
-
return new Promise((resolve, reject) => {
|
|
61
|
-
server.close(error => {
|
|
62
|
-
if (error?.message === 'The server is not running') {
|
|
63
|
-
resolve();
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
if (error) {
|
|
67
|
-
reject(error);
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
resolve();
|
|
71
|
-
});
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
36
|
let graphqlInstanceOfPatchRefCount = 0;
|
|
75
37
|
let restoreGraphqlInstanceOfPatch;
|
|
76
38
|
const allowedCrossRealmGraphqlObjects = new WeakSet();
|
|
@@ -101,16 +63,17 @@ class GraphqlEndpointController {
|
|
|
101
63
|
}
|
|
102
64
|
export { _GraphqlEndpointContr as GraphqlEndpointController };
|
|
103
65
|
function getCrossRealmGraphqlTag(value, constructor) {
|
|
104
|
-
const
|
|
66
|
+
const prototypeTag = constructor.prototype?.[Symbol.toStringTag];
|
|
67
|
+
const className = typeof prototypeTag === 'string' ? prototypeTag : constructor.name;
|
|
105
68
|
if (typeof className !== 'string' || !className.startsWith('GraphQL')) {
|
|
106
69
|
return undefined;
|
|
107
70
|
}
|
|
108
71
|
if (typeof value !== 'object' || value === null) {
|
|
109
72
|
return undefined;
|
|
110
73
|
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
return
|
|
74
|
+
const valueTag = value[Symbol.toStringTag];
|
|
75
|
+
if (typeof valueTag === 'string') {
|
|
76
|
+
return valueTag === className ? className : undefined;
|
|
114
77
|
}
|
|
115
78
|
const valueClassName = value.constructor?.name;
|
|
116
79
|
return valueClassName === className ? className : undefined;
|
|
@@ -124,7 +87,8 @@ function markAllowedCrossRealmGraphqlObjects(value, visited = new WeakSet()) {
|
|
|
124
87
|
}
|
|
125
88
|
visited.add(value);
|
|
126
89
|
const tag = value[Symbol.toStringTag];
|
|
127
|
-
|
|
90
|
+
const constructorName = value.constructor?.name;
|
|
91
|
+
if (typeof tag === 'string' && tag.startsWith('GraphQL') || typeof constructorName === 'string' && constructorName.startsWith('GraphQL')) {
|
|
128
92
|
allowedCrossRealmGraphqlObjects.add(value);
|
|
129
93
|
}
|
|
130
94
|
if (Array.isArray(value)) {
|
|
@@ -184,13 +148,10 @@ function releaseGraphqlInstanceOfPatch() {
|
|
|
184
148
|
restoreGraphqlInstanceOfPatch = undefined;
|
|
185
149
|
}
|
|
186
150
|
async function loadGraphqlDeps() {
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
const
|
|
191
|
-
const graphqlMod = runtimeRequire('graphql');
|
|
192
|
-
const yogaMod = runtimeRequire('graphql-yoga');
|
|
193
|
-
const instanceOfModule = runtimeRequire('graphql/jsutils/instanceOf.js');
|
|
151
|
+
const graphqlSpecifier = 'graphql';
|
|
152
|
+
const yogaSpecifier = 'graphql-yoga';
|
|
153
|
+
const instanceOfSpecifier = 'graphql/jsutils/instanceOf.js';
|
|
154
|
+
const [graphqlMod, yogaMod, instanceOfModule] = await Promise.all([import(/* @vite-ignore */graphqlSpecifier), import(/* @vite-ignore */yogaSpecifier), import(/* @vite-ignore */instanceOfSpecifier)]);
|
|
194
155
|
return {
|
|
195
156
|
GraphQLError: graphqlMod.GraphQLError,
|
|
196
157
|
GraphQLBoolean: graphqlMod.GraphQLBoolean,
|
|
@@ -198,11 +159,13 @@ async function loadGraphqlDeps() {
|
|
|
198
159
|
GraphQLID: graphqlMod.GraphQLID,
|
|
199
160
|
GraphQLInt: graphqlMod.GraphQLInt,
|
|
200
161
|
GraphQLList: graphqlMod.GraphQLList,
|
|
162
|
+
GraphQLNonNull: graphqlMod.GraphQLNonNull,
|
|
201
163
|
GraphQLObjectType: graphqlMod.GraphQLObjectType,
|
|
202
164
|
GraphQLSchema: graphqlMod.GraphQLSchema,
|
|
203
165
|
GraphQLString: graphqlMod.GraphQLString,
|
|
204
166
|
GraphQLUnionType: graphqlMod.GraphQLUnionType,
|
|
205
167
|
buildSchema: graphqlMod.buildSchema,
|
|
168
|
+
createGraphQLError: yogaMod.createGraphQLError,
|
|
206
169
|
createYoga: yogaMod.createYoga,
|
|
207
170
|
execute: graphqlMod.execute,
|
|
208
171
|
instanceOfModule,
|
|
@@ -223,10 +186,7 @@ class GraphqlLifecycleService {
|
|
|
223
186
|
operationContainers = new WeakMap();
|
|
224
187
|
requestContexts = new WeakMap();
|
|
225
188
|
websocketOperationContainers = new Map();
|
|
226
|
-
|
|
227
|
-
websocketServer;
|
|
228
|
-
websocketUpgradeListener;
|
|
229
|
-
websocketUpgradeServer;
|
|
189
|
+
websocketTransport;
|
|
230
190
|
executeGraphqlOperation;
|
|
231
191
|
releaseGraphqlInstanceOfPatch;
|
|
232
192
|
subscribeGraphqlOperation;
|
|
@@ -406,91 +366,43 @@ class GraphqlLifecycleService {
|
|
|
406
366
|
};
|
|
407
367
|
}
|
|
408
368
|
async registerWebSocketTransport() {
|
|
409
|
-
if (!this.isWebSocketTransportEnabled() || this.yoga === undefined || this.
|
|
369
|
+
if (!this.isWebSocketTransportEnabled() || this.yoga === undefined || this.websocketTransport !== undefined) {
|
|
410
370
|
return;
|
|
411
371
|
}
|
|
412
|
-
const
|
|
413
|
-
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
}] = await Promise.all([import('graphql-ws'), import('graphql-ws/lib/use/ws'), import('ws')]);
|
|
419
|
-
const upgradeServer = this.resolveUpgradeServer();
|
|
420
|
-
const websocketLimits = this.resolveWebSocketLimits();
|
|
421
|
-
const websocketServer = new WebSocketServer({
|
|
422
|
-
handleProtocols: protocols => handleProtocols(protocols),
|
|
423
|
-
maxPayload: websocketLimits?.maxPayloadBytes ?? 0,
|
|
424
|
-
noServer: true
|
|
425
|
-
});
|
|
426
|
-
const upgradeListener = (request, socket, head) => {
|
|
427
|
-
const targetPath = new URL(request.url ?? '/', 'http://localhost').pathname;
|
|
428
|
-
if (!isGraphqlPath(targetPath)) {
|
|
429
|
-
return;
|
|
430
|
-
}
|
|
431
|
-
if (websocketLimits && websocketServer.clients.size >= websocketLimits.maxConnections) {
|
|
432
|
-
this.rejectWebSocketUpgrade(socket, 503, 'GraphQL websocket connection count exceeds the configured limit.');
|
|
433
|
-
return;
|
|
434
|
-
}
|
|
435
|
-
websocketServer.handleUpgrade(request, socket, head, websocket => {
|
|
436
|
-
websocketServer.emit('connection', websocket, request);
|
|
437
|
-
});
|
|
438
|
-
};
|
|
439
|
-
const websocketDisposable = useServer({
|
|
440
|
-
connectionInitWaitTimeout: this.options.subscriptions?.websocket?.connectionInitWaitTimeoutMs,
|
|
372
|
+
const {
|
|
373
|
+
createNodeGraphqlWebSocketTransport
|
|
374
|
+
} = await import('./node/graphql-websocket-transport.js');
|
|
375
|
+
this.websocketTransport = await createNodeGraphqlWebSocketTransport({
|
|
376
|
+
adapter: this.adapter,
|
|
377
|
+
connectionInitWaitTimeoutMs: this.options.subscriptions?.websocket?.connectionInitWaitTimeoutMs,
|
|
441
378
|
execute: args => {
|
|
442
379
|
if (!this.executeGraphqlOperation) {
|
|
443
380
|
throw new Error('GraphQL execute function not initialized.');
|
|
444
381
|
}
|
|
445
382
|
return this.executeGraphqlOperation(args);
|
|
446
383
|
},
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
onDisconnect:
|
|
451
|
-
|
|
452
|
-
},
|
|
453
|
-
onSubscribe: async (context, message) => this.handleWebSocketSubscribe(context, message.id, message.payload),
|
|
384
|
+
keepAliveMs: this.options.subscriptions?.websocket?.keepAliveMs,
|
|
385
|
+
limits: this.resolveWebSocketLimits(),
|
|
386
|
+
onComplete: (socketKey, operationId) => this.disposeWebSocketOperationContainer(socketKey, operationId),
|
|
387
|
+
onDisconnect: socketKey => this.disposeAllWebSocketOperationContainers(socketKey),
|
|
388
|
+
onSubscribe: request => this.handleWebSocketSubscribe(request),
|
|
454
389
|
subscribe: args => {
|
|
455
390
|
if (!this.subscribeGraphqlOperation) {
|
|
456
391
|
throw new Error('GraphQL subscribe function not initialized.');
|
|
457
392
|
}
|
|
458
393
|
return this.subscribeGraphqlOperation(args);
|
|
459
394
|
}
|
|
460
|
-
}
|
|
461
|
-
upgradeServer.on('upgrade', upgradeListener);
|
|
462
|
-
this.websocketDisposable = websocketDisposable;
|
|
463
|
-
this.websocketServer = websocketServer;
|
|
464
|
-
this.websocketUpgradeListener = upgradeListener;
|
|
465
|
-
this.websocketUpgradeServer = upgradeServer;
|
|
395
|
+
});
|
|
466
396
|
}
|
|
467
397
|
async unregisterWebSocketTransport() {
|
|
468
|
-
if (this.
|
|
469
|
-
this.websocketUpgradeServer.off('upgrade', this.websocketUpgradeListener);
|
|
470
|
-
}
|
|
471
|
-
this.websocketUpgradeListener = undefined;
|
|
472
|
-
this.websocketUpgradeServer = undefined;
|
|
473
|
-
if (this.websocketServer) {
|
|
474
|
-
for (const client of this.websocketServer.clients) {
|
|
475
|
-
client.terminate();
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
if (this.websocketDisposable) {
|
|
398
|
+
if (this.websocketTransport) {
|
|
479
399
|
try {
|
|
480
|
-
await this.
|
|
400
|
+
await this.websocketTransport.dispose();
|
|
481
401
|
} catch (error) {
|
|
482
402
|
this.logger.error('Failed to dispose GraphQL websocket transport.', error, 'GraphqlLifecycleService');
|
|
483
403
|
}
|
|
484
404
|
}
|
|
485
|
-
this.
|
|
486
|
-
if (this.websocketServer) {
|
|
487
|
-
try {
|
|
488
|
-
await closeWebSocketServer(this.websocketServer);
|
|
489
|
-
} catch (error) {
|
|
490
|
-
this.logger.error('Failed to close GraphQL websocket server.', error, 'GraphqlLifecycleService');
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
this.websocketServer = undefined;
|
|
405
|
+
this.websocketTransport = undefined;
|
|
494
406
|
for (const socketKey of this.websocketOperationContainers.keys()) {
|
|
495
407
|
await this.disposeAllWebSocketOperationContainers(socketKey);
|
|
496
408
|
}
|
|
@@ -498,32 +410,21 @@ class GraphqlLifecycleService {
|
|
|
498
410
|
isWebSocketTransportEnabled() {
|
|
499
411
|
return this.options.subscriptions?.websocket?.enabled === true;
|
|
500
412
|
}
|
|
501
|
-
|
|
502
|
-
if (typeof this.adapter.getServer !== 'function') {
|
|
503
|
-
throw new Error('GraphQL websocket subscriptions require an HTTP adapter with getServer(). Use the Node HTTP adapter or provide a compatible adapter implementation.');
|
|
504
|
-
}
|
|
505
|
-
const server = this.adapter.getServer();
|
|
506
|
-
if (!hasNodeUpgradeServer(server)) {
|
|
507
|
-
throw new Error('GraphQL websocket subscriptions require adapter.getServer() to return a Node HTTP/S server that supports upgrade listeners.');
|
|
508
|
-
}
|
|
509
|
-
return server;
|
|
510
|
-
}
|
|
511
|
-
async handleWebSocketSubscribe(context, operationId, payload) {
|
|
413
|
+
async handleWebSocketSubscribe(request) {
|
|
512
414
|
const yoga = this.yoga;
|
|
513
415
|
if (!yoga) {
|
|
514
416
|
throw new Error('GraphQL server not initialized.');
|
|
515
417
|
}
|
|
516
|
-
const websocketLimitError = this.createWebSocketOperationLimitError(
|
|
418
|
+
const websocketLimitError = this.createWebSocketOperationLimitError(request.socket, request.operationId);
|
|
517
419
|
if (websocketLimitError) {
|
|
518
420
|
return [websocketLimitError];
|
|
519
421
|
}
|
|
520
|
-
const
|
|
521
|
-
const
|
|
522
|
-
const operationContainer = this.getOrCreateWebSocketOperationContainer(context.extra.socket, operationId);
|
|
422
|
+
const fetchRequest = toFetchRequest(request.request);
|
|
423
|
+
const operationContainer = this.getOrCreateWebSocketOperationContainer(request.socket, request.operationId);
|
|
523
424
|
const graphqlContext = this.buildGraphqlContext(fetchRequest, {
|
|
524
|
-
connectionParams:
|
|
525
|
-
request:
|
|
526
|
-
socket:
|
|
425
|
+
connectionParams: request.connectionParams,
|
|
426
|
+
request: request.request,
|
|
427
|
+
socket: request.socket
|
|
527
428
|
}, operationContainer);
|
|
528
429
|
try {
|
|
529
430
|
const {
|
|
@@ -535,21 +436,21 @@ class GraphqlLifecycleService {
|
|
|
535
436
|
request: fetchRequest,
|
|
536
437
|
[GRAPHQL_CONTEXT_OVERRIDE]: graphqlContext
|
|
537
438
|
});
|
|
538
|
-
const document = parse(payload.query);
|
|
439
|
+
const document = parse(request.payload.query);
|
|
539
440
|
const validationErrors = validate(schema, document);
|
|
540
441
|
if (validationErrors.length > 0) {
|
|
541
|
-
await this.disposeWebSocketOperationContainer(
|
|
442
|
+
await this.disposeWebSocketOperationContainer(request.socket, request.operationId);
|
|
542
443
|
return validationErrors;
|
|
543
444
|
}
|
|
544
445
|
return {
|
|
545
446
|
contextValue: await contextFactory(),
|
|
546
447
|
document,
|
|
547
|
-
operationName: payload.operationName ?? undefined,
|
|
448
|
+
operationName: request.payload.operationName ?? undefined,
|
|
548
449
|
schema,
|
|
549
|
-
variableValues: payload.variables
|
|
450
|
+
variableValues: request.payload.variables
|
|
550
451
|
};
|
|
551
452
|
} catch (error) {
|
|
552
|
-
await this.disposeWebSocketOperationContainer(
|
|
453
|
+
await this.disposeWebSocketOperationContainer(request.socket, request.operationId);
|
|
553
454
|
throw error;
|
|
554
455
|
}
|
|
555
456
|
}
|
|
@@ -568,15 +469,6 @@ class GraphqlLifecycleService {
|
|
|
568
469
|
}
|
|
569
470
|
return new GraphQLError(`GraphQL websocket active operation count exceeds the configured limit of ${String(limits.maxOperationsPerConnection)}.`);
|
|
570
471
|
}
|
|
571
|
-
rejectWebSocketUpgrade(socket, statusCode, message) {
|
|
572
|
-
if (!socket.writable) {
|
|
573
|
-
socket.destroy();
|
|
574
|
-
return;
|
|
575
|
-
}
|
|
576
|
-
const body = `${message}\n`;
|
|
577
|
-
socket.write([`HTTP/1.1 ${String(statusCode)} ${statusCode === 503 ? 'Service Unavailable' : 'Bad Request'}`, 'Connection: close', 'Content-Type: text/plain; charset=utf-8', `Content-Length: ${String(new TextEncoder().encode(body).byteLength)}`, '', body].join('\r\n'));
|
|
578
|
-
socket.destroy();
|
|
579
|
-
}
|
|
580
472
|
getOrCreateWebSocketOperationContainer(socketKey, operationId) {
|
|
581
473
|
const existingSocketContainers = this.websocketOperationContainers.get(socketKey);
|
|
582
474
|
if (existingSocketContainers?.has(operationId)) {
|
package/dist/types.d.ts
CHANGED
|
@@ -99,9 +99,20 @@ export interface ResolverMetadata {
|
|
|
99
99
|
typeName: string;
|
|
100
100
|
}
|
|
101
101
|
/**
|
|
102
|
-
* Supported GraphQL operation handler categories.
|
|
102
|
+
* Supported GraphQL root-operation and object-field handler categories.
|
|
103
103
|
*/
|
|
104
|
-
export type ResolverHandlerType = 'query' | 'mutation' | 'subscription';
|
|
104
|
+
export type ResolverHandlerType = 'query' | 'mutation' | 'subscription' | 'field';
|
|
105
|
+
/**
|
|
106
|
+
* Explicit source kinds that can be bound to object field-resolver parameters.
|
|
107
|
+
*/
|
|
108
|
+
export type FieldResolverParameterKind = 'parent' | 'context';
|
|
109
|
+
/**
|
|
110
|
+
* Describes one positional parameter binding for an object field resolver.
|
|
111
|
+
*/
|
|
112
|
+
export interface FieldResolverParameterBindingMetadata {
|
|
113
|
+
index: number;
|
|
114
|
+
kind: FieldResolverParameterKind;
|
|
115
|
+
}
|
|
105
116
|
/**
|
|
106
117
|
* Scalar names supported by the code-first schema helpers.
|
|
107
118
|
*/
|
|
@@ -157,6 +168,7 @@ export interface ResolverHandlerMetadata {
|
|
|
157
168
|
inputClass?: Function;
|
|
158
169
|
argTypes?: Record<string, GraphqlArgType>;
|
|
159
170
|
outputType?: GraphqlRootOutputType;
|
|
171
|
+
nullable?: boolean;
|
|
160
172
|
}
|
|
161
173
|
/**
|
|
162
174
|
* Describes how one method parameter maps to a named GraphQL argument.
|
|
@@ -177,6 +189,8 @@ export interface ResolverHandlerDescriptor {
|
|
|
177
189
|
argFields: ArgFieldMetadata[];
|
|
178
190
|
argTypes?: Record<string, GraphqlArgType>;
|
|
179
191
|
outputType?: GraphqlRootOutputType;
|
|
192
|
+
nullable?: boolean;
|
|
193
|
+
parameterBindings: FieldResolverParameterBindingMetadata[];
|
|
180
194
|
}
|
|
181
195
|
/**
|
|
182
196
|
* Fully discovered resolver descriptor used by the GraphQL runtime.
|