@fluojs/graphql 1.0.4 → 2.0.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 +153 -17
- package/README.md +158 -17
- package/dist/dataloader/dataloader.d.ts.map +1 -1
- package/dist/dataloader/dataloader.js +2 -3
- package/dist/decorators.d.ts +53 -0
- package/dist/decorators.d.ts.map +1 -1
- package/dist/decorators.js +123 -1
- package/dist/discovery.d.ts.map +1 -1
- package/dist/discovery.js +13 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/instance-of-patch.d.ts +21 -0
- package/dist/instance-of-patch.d.ts.map +1 -0
- package/dist/instance-of-patch.js +126 -0
- package/dist/metadata.d.ts +21 -1
- package/dist/metadata.d.ts.map +1 -1
- package/dist/metadata.js +68 -7
- package/dist/module.d.ts +14 -10
- package/dist/module.d.ts.map +1 -1
- package/dist/module.js +69 -14
- package/dist/node/graphql-websocket-transport.d.ts +53 -0
- package/dist/node/graphql-websocket-transport.d.ts.map +1 -0
- package/dist/node/graphql-websocket-transport.js +219 -0
- package/dist/pipeline/input-pipeline.js +1 -1
- package/dist/schema/object-field-resolvers.d.ts +18 -0
- package/dist/schema/object-field-resolvers.d.ts.map +1 -0
- package/dist/schema/object-field-resolvers.js +86 -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 +10 -12
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +191 -303
- package/dist/transport/transport.d.ts.map +1 -1
- package/dist/transport/transport.js +1 -1
- package/dist/types.d.ts +40 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +19 -1
- package/package.json +11 -10
package/dist/service.js
CHANGED
|
@@ -4,27 +4,35 @@ 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 {
|
|
7
|
+
import { createRequire } from 'node:module';
|
|
8
8
|
import { Inject } from '@fluojs/core';
|
|
9
|
+
import { Controller, Get, Post } from '@fluojs/http';
|
|
9
10
|
import { APPLICATION_LOGGER, COMPILED_MODULES, HTTP_APPLICATION_ADAPTER, RUNTIME_CONTAINER } from '@fluojs/runtime/internal';
|
|
10
11
|
import { discoverResolverDescriptors } from './discovery.js';
|
|
11
12
|
import { createGraphqlValidationPlugin, resolveGraphqlRequestLimits } from './guardrails.js';
|
|
13
|
+
import { installGraphqlInstanceOfPatch } from './instance-of-patch.js';
|
|
12
14
|
import { GRAPHQL_INTERNAL_MODULE_OPTIONS_TOKEN } from './internal-tokens.js';
|
|
13
15
|
import { createCodeFirstSchema, resolveSchema } from './schema/schema.js';
|
|
14
16
|
import { isGraphqlPath, toFetchRequest, writeFetchResponse } from './transport/transport.js';
|
|
15
17
|
import { GRAPHQL_OPERATION_CONTAINER } from './types.js';
|
|
16
18
|
const GRAPHQL_CONTEXT_OVERRIDE = Symbol('fluo.graphql.context.override');
|
|
19
|
+
const runtimeRequire = createRequire(import.meta.url);
|
|
17
20
|
const DEFAULT_GRAPHQL_WEBSOCKET_LIMITS = {
|
|
18
21
|
maxConnections: 100,
|
|
19
22
|
maxOperationsPerConnection: 25,
|
|
20
23
|
maxPayloadBytes: 64 * 1024
|
|
21
24
|
};
|
|
22
|
-
function
|
|
23
|
-
if (
|
|
24
|
-
|
|
25
|
+
function collectCleanupError(errors, error) {
|
|
26
|
+
if (error instanceof AggregateError) {
|
|
27
|
+
for (const nested of error.errors) {
|
|
28
|
+
collectCleanupError(errors, nested);
|
|
29
|
+
}
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
if (errors.includes(error)) {
|
|
33
|
+
return;
|
|
25
34
|
}
|
|
26
|
-
|
|
27
|
-
return typeof server.on === 'function' && typeof server.off === 'function';
|
|
35
|
+
errors.push(error);
|
|
28
36
|
}
|
|
29
37
|
function buildFrameworkRequestFromFetchRequest(request) {
|
|
30
38
|
const requestUrl = new URL(request.url);
|
|
@@ -40,45 +48,10 @@ function buildFrameworkRequestFromFetchRequest(request) {
|
|
|
40
48
|
url: requestUrl.pathname + requestUrl.search
|
|
41
49
|
};
|
|
42
50
|
}
|
|
43
|
-
|
|
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
|
-
let graphqlInstanceOfPatchRefCount = 0;
|
|
75
|
-
let restoreGraphqlInstanceOfPatch;
|
|
76
|
-
const allowedCrossRealmGraphqlObjects = new WeakSet();
|
|
77
|
-
|
|
51
|
+
let _GraphqlEndpointContr;
|
|
78
52
|
/**
|
|
79
53
|
* Declares the HTTP endpoints that receive GraphQL GET and POST requests.
|
|
80
54
|
*/
|
|
81
|
-
let _GraphqlEndpointContr;
|
|
82
55
|
class GraphqlEndpointController {
|
|
83
56
|
static {
|
|
84
57
|
({
|
|
@@ -100,22 +73,7 @@ class GraphqlEndpointController {
|
|
|
100
73
|
}
|
|
101
74
|
}
|
|
102
75
|
export { _GraphqlEndpointContr as GraphqlEndpointController };
|
|
103
|
-
function
|
|
104
|
-
const className = constructor.prototype?.[Symbol.toStringTag];
|
|
105
|
-
if (typeof className !== 'string' || !className.startsWith('GraphQL')) {
|
|
106
|
-
return undefined;
|
|
107
|
-
}
|
|
108
|
-
if (typeof value !== 'object' || value === null) {
|
|
109
|
-
return undefined;
|
|
110
|
-
}
|
|
111
|
-
if (Symbol.toStringTag in value) {
|
|
112
|
-
const valueClassName = value[Symbol.toStringTag];
|
|
113
|
-
return valueClassName === className ? className : undefined;
|
|
114
|
-
}
|
|
115
|
-
const valueClassName = value.constructor?.name;
|
|
116
|
-
return valueClassName === className ? className : undefined;
|
|
117
|
-
}
|
|
118
|
-
function markAllowedCrossRealmGraphqlObjects(value, visited = new WeakSet()) {
|
|
76
|
+
function markAllowedCrossRealmGraphqlObjects(value, allowedObjects, visited = new WeakSet()) {
|
|
119
77
|
if (typeof value !== 'object' || value === null) {
|
|
120
78
|
return;
|
|
121
79
|
}
|
|
@@ -124,73 +82,26 @@ function markAllowedCrossRealmGraphqlObjects(value, visited = new WeakSet()) {
|
|
|
124
82
|
}
|
|
125
83
|
visited.add(value);
|
|
126
84
|
const tag = value[Symbol.toStringTag];
|
|
127
|
-
|
|
128
|
-
|
|
85
|
+
const constructorName = value.constructor?.name;
|
|
86
|
+
if (typeof tag === 'string' && tag.startsWith('GraphQL') || typeof constructorName === 'string' && constructorName.startsWith('GraphQL')) {
|
|
87
|
+
allowedObjects.add(value);
|
|
129
88
|
}
|
|
130
89
|
if (Array.isArray(value)) {
|
|
131
90
|
for (const item of value) {
|
|
132
|
-
markAllowedCrossRealmGraphqlObjects(item, visited);
|
|
91
|
+
markAllowedCrossRealmGraphqlObjects(item, allowedObjects, visited);
|
|
133
92
|
}
|
|
134
93
|
return;
|
|
135
94
|
}
|
|
136
95
|
for (const nestedValue of Object.values(value)) {
|
|
137
|
-
markAllowedCrossRealmGraphqlObjects(nestedValue, visited);
|
|
96
|
+
markAllowedCrossRealmGraphqlObjects(nestedValue, allowedObjects, visited);
|
|
138
97
|
}
|
|
139
98
|
}
|
|
140
|
-
function isAllowedCrossRealmGraphqlObject(value, constructor) {
|
|
141
|
-
if (typeof value !== 'object' || value === null) {
|
|
142
|
-
return false;
|
|
143
|
-
}
|
|
144
|
-
return allowedCrossRealmGraphqlObjects.has(value) && getCrossRealmGraphqlTag(value, constructor) !== undefined;
|
|
145
|
-
}
|
|
146
|
-
function installGraphqlInstanceOfPatch(instanceOfModule) {
|
|
147
|
-
if (restoreGraphqlInstanceOfPatch) {
|
|
148
|
-
graphqlInstanceOfPatchRefCount += 1;
|
|
149
|
-
return releaseGraphqlInstanceOfPatch;
|
|
150
|
-
}
|
|
151
|
-
const patchedFrom = instanceOfModule.instanceOf;
|
|
152
|
-
const patchedInstanceOf = (value, constructor) => {
|
|
153
|
-
try {
|
|
154
|
-
if (patchedFrom(value, constructor)) {
|
|
155
|
-
return true;
|
|
156
|
-
}
|
|
157
|
-
} catch (error) {
|
|
158
|
-
if (isAllowedCrossRealmGraphqlObject(value, constructor)) {
|
|
159
|
-
return true;
|
|
160
|
-
}
|
|
161
|
-
throw error;
|
|
162
|
-
}
|
|
163
|
-
return isAllowedCrossRealmGraphqlObject(value, constructor);
|
|
164
|
-
};
|
|
165
|
-
instanceOfModule.instanceOf = patchedInstanceOf;
|
|
166
|
-
graphqlInstanceOfPatchRefCount = 1;
|
|
167
|
-
restoreGraphqlInstanceOfPatch = () => {
|
|
168
|
-
if (instanceOfModule.instanceOf !== patchedInstanceOf) {
|
|
169
|
-
return;
|
|
170
|
-
}
|
|
171
|
-
instanceOfModule.instanceOf = patchedFrom;
|
|
172
|
-
};
|
|
173
|
-
return releaseGraphqlInstanceOfPatch;
|
|
174
|
-
}
|
|
175
|
-
function releaseGraphqlInstanceOfPatch() {
|
|
176
|
-
if (graphqlInstanceOfPatchRefCount === 0) {
|
|
177
|
-
return;
|
|
178
|
-
}
|
|
179
|
-
graphqlInstanceOfPatchRefCount -= 1;
|
|
180
|
-
if (graphqlInstanceOfPatchRefCount > 0) {
|
|
181
|
-
return;
|
|
182
|
-
}
|
|
183
|
-
restoreGraphqlInstanceOfPatch?.();
|
|
184
|
-
restoreGraphqlInstanceOfPatch = undefined;
|
|
185
|
-
}
|
|
186
99
|
async function loadGraphqlDeps() {
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
const
|
|
191
|
-
const
|
|
192
|
-
const yogaMod = runtimeRequire('graphql-yoga');
|
|
193
|
-
const instanceOfModule = runtimeRequire('graphql/jsutils/instanceOf.js');
|
|
100
|
+
const graphqlSpecifier = 'graphql';
|
|
101
|
+
const yogaSpecifier = 'graphql-yoga';
|
|
102
|
+
const instanceOfSpecifier = 'graphql/jsutils/instanceOf.js';
|
|
103
|
+
const [graphqlMod, yogaMod] = await Promise.all([import(/* @vite-ignore */graphqlSpecifier), import(/* @vite-ignore */yogaSpecifier)]);
|
|
104
|
+
const instanceOfModule = runtimeRequire(instanceOfSpecifier);
|
|
194
105
|
return {
|
|
195
106
|
GraphQLError: graphqlMod.GraphQLError,
|
|
196
107
|
GraphQLBoolean: graphqlMod.GraphQLBoolean,
|
|
@@ -198,11 +109,13 @@ async function loadGraphqlDeps() {
|
|
|
198
109
|
GraphQLID: graphqlMod.GraphQLID,
|
|
199
110
|
GraphQLInt: graphqlMod.GraphQLInt,
|
|
200
111
|
GraphQLList: graphqlMod.GraphQLList,
|
|
112
|
+
GraphQLNonNull: graphqlMod.GraphQLNonNull,
|
|
201
113
|
GraphQLObjectType: graphqlMod.GraphQLObjectType,
|
|
202
114
|
GraphQLSchema: graphqlMod.GraphQLSchema,
|
|
203
115
|
GraphQLString: graphqlMod.GraphQLString,
|
|
204
116
|
GraphQLUnionType: graphqlMod.GraphQLUnionType,
|
|
205
117
|
buildSchema: graphqlMod.buildSchema,
|
|
118
|
+
createGraphQLError: yogaMod.createGraphQLError,
|
|
206
119
|
createYoga: yogaMod.createYoga,
|
|
207
120
|
execute: graphqlMod.execute,
|
|
208
121
|
instanceOfModule,
|
|
@@ -218,62 +131,60 @@ class GraphqlLifecycleService {
|
|
|
218
131
|
static {
|
|
219
132
|
[_GraphqlLifecycleServ, _initClass2] = _applyDecs(this, [Inject(RUNTIME_CONTAINER, COMPILED_MODULES, APPLICATION_LOGGER, HTTP_APPLICATION_ADAPTER, GRAPHQL_INTERNAL_MODULE_OPTIONS_TOKEN)], []).c;
|
|
220
133
|
}
|
|
134
|
+
allowedCrossRealmGraphqlObjects;
|
|
221
135
|
graphQLErrorConstructor;
|
|
222
|
-
|
|
223
|
-
operationContainers = new WeakMap();
|
|
136
|
+
operationContainers = new Map();
|
|
224
137
|
requestContexts = new WeakMap();
|
|
138
|
+
failedWebsocketOperationContainers = new Map();
|
|
139
|
+
websocketContainerDisposals = new Map();
|
|
140
|
+
websocketContainersAttemptedInCurrentCleanup;
|
|
225
141
|
websocketOperationContainers = new Map();
|
|
226
|
-
|
|
227
|
-
websocketServer;
|
|
228
|
-
websocketUpgradeListener;
|
|
229
|
-
websocketUpgradeServer;
|
|
142
|
+
websocketTransport;
|
|
230
143
|
executeGraphqlOperation;
|
|
231
144
|
releaseGraphqlInstanceOfPatch;
|
|
232
145
|
subscribeGraphqlOperation;
|
|
233
146
|
yoga;
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
147
|
+
async handle(context, next) {
|
|
148
|
+
if (!isGraphqlPath(context.request.path)) {
|
|
149
|
+
await next();
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
const yoga = this.yoga;
|
|
153
|
+
if (!yoga) {
|
|
154
|
+
this.logger.error('GraphQL middleware was invoked before GraphQL Yoga initialization.', undefined, 'GraphqlLifecycleService');
|
|
155
|
+
context.response.setStatus(500);
|
|
156
|
+
await context.response.send({
|
|
157
|
+
errors: [{
|
|
158
|
+
message: 'GraphQL server not initialized.'
|
|
159
|
+
}]
|
|
160
|
+
});
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
try {
|
|
164
|
+
const fetchRequest = toFetchRequest(context.request);
|
|
165
|
+
this.requestContexts.set(fetchRequest, {
|
|
166
|
+
principal: context.requestContext.principal,
|
|
167
|
+
request: context.request
|
|
168
|
+
});
|
|
169
|
+
try {
|
|
170
|
+
const fetchResponse = await yoga.fetch(fetchRequest);
|
|
171
|
+
await writeFetchResponse(fetchResponse, context.response);
|
|
172
|
+
} finally {
|
|
173
|
+
this.requestContexts.delete(fetchRequest);
|
|
174
|
+
await this.disposeOperationContainer(fetchRequest);
|
|
239
175
|
}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
176
|
+
} catch (error) {
|
|
177
|
+
this.logger.error('Failed to process GraphQL request.', error, 'GraphqlLifecycleService');
|
|
178
|
+
if (!context.response.committed) {
|
|
243
179
|
context.response.setStatus(500);
|
|
244
180
|
await context.response.send({
|
|
245
181
|
errors: [{
|
|
246
|
-
message: '
|
|
182
|
+
message: 'Internal server error.'
|
|
247
183
|
}]
|
|
248
184
|
});
|
|
249
|
-
return;
|
|
250
|
-
}
|
|
251
|
-
try {
|
|
252
|
-
const fetchRequest = toFetchRequest(context.request);
|
|
253
|
-
this.requestContexts.set(fetchRequest, {
|
|
254
|
-
principal: context.requestContext.principal,
|
|
255
|
-
request: context.request
|
|
256
|
-
});
|
|
257
|
-
try {
|
|
258
|
-
const fetchResponse = await yoga.fetch(fetchRequest);
|
|
259
|
-
await writeFetchResponse(fetchResponse, context.response);
|
|
260
|
-
} finally {
|
|
261
|
-
this.requestContexts.delete(fetchRequest);
|
|
262
|
-
await this.disposeOperationContainer(fetchRequest);
|
|
263
|
-
}
|
|
264
|
-
} catch (error) {
|
|
265
|
-
this.logger.error('Failed to process GraphQL request.', error, 'GraphqlLifecycleService');
|
|
266
|
-
if (!context.response.committed) {
|
|
267
|
-
context.response.setStatus(500);
|
|
268
|
-
await context.response.send({
|
|
269
|
-
errors: [{
|
|
270
|
-
message: 'Internal server error.'
|
|
271
|
-
}]
|
|
272
|
-
});
|
|
273
|
-
}
|
|
274
185
|
}
|
|
275
186
|
}
|
|
276
|
-
}
|
|
187
|
+
}
|
|
277
188
|
constructor(runtimeContainer, compiledModules, logger, adapter, options) {
|
|
278
189
|
this.runtimeContainer = runtimeContainer;
|
|
279
190
|
this.compiledModules = compiledModules;
|
|
@@ -282,7 +193,7 @@ class GraphqlLifecycleService {
|
|
|
282
193
|
this.options = options;
|
|
283
194
|
}
|
|
284
195
|
async onApplicationBootstrap() {
|
|
285
|
-
if (this.
|
|
196
|
+
if (this.yoga) {
|
|
286
197
|
return;
|
|
287
198
|
}
|
|
288
199
|
try {
|
|
@@ -306,8 +217,6 @@ class GraphqlLifecycleService {
|
|
|
306
217
|
schema
|
|
307
218
|
});
|
|
308
219
|
await this.registerWebSocketTransport();
|
|
309
|
-
this.registerMiddleware();
|
|
310
|
-
this.middlewareRegistered = true;
|
|
311
220
|
} catch (error) {
|
|
312
221
|
try {
|
|
313
222
|
await this.resetGraphqlRuntimeState();
|
|
@@ -321,13 +230,28 @@ class GraphqlLifecycleService {
|
|
|
321
230
|
await this.resetGraphqlRuntimeState();
|
|
322
231
|
}
|
|
323
232
|
async resetGraphqlRuntimeState() {
|
|
324
|
-
|
|
325
|
-
this.
|
|
326
|
-
|
|
233
|
+
const cleanupErrors = [];
|
|
234
|
+
this.websocketContainersAttemptedInCurrentCleanup = new Set();
|
|
235
|
+
try {
|
|
236
|
+
await this.unregisterWebSocketTransport();
|
|
237
|
+
} catch (error) {
|
|
238
|
+
collectCleanupError(cleanupErrors, error);
|
|
239
|
+
} finally {
|
|
240
|
+
this.websocketContainersAttemptedInCurrentCleanup = undefined;
|
|
241
|
+
}
|
|
242
|
+
try {
|
|
243
|
+
await this.disposeAllOperationContainers();
|
|
244
|
+
} catch (error) {
|
|
245
|
+
collectCleanupError(cleanupErrors, error);
|
|
246
|
+
}
|
|
247
|
+
if (cleanupErrors.length > 0) {
|
|
248
|
+
throw new AggregateError(cleanupErrors, 'Failed to clean up GraphQL runtime resources.');
|
|
249
|
+
}
|
|
327
250
|
this.executeGraphqlOperation = undefined;
|
|
328
251
|
this.graphQLErrorConstructor = undefined;
|
|
329
252
|
this.releaseGraphqlInstanceOfPatch?.();
|
|
330
253
|
this.releaseGraphqlInstanceOfPatch = undefined;
|
|
254
|
+
this.allowedCrossRealmGraphqlObjects = undefined;
|
|
331
255
|
this.subscribeGraphqlOperation = undefined;
|
|
332
256
|
this.yoga = undefined;
|
|
333
257
|
}
|
|
@@ -349,43 +273,18 @@ class GraphqlLifecycleService {
|
|
|
349
273
|
};
|
|
350
274
|
}
|
|
351
275
|
resolveSchema(deps) {
|
|
352
|
-
this.
|
|
353
|
-
|
|
276
|
+
const allowedObjects = this.allowedCrossRealmGraphqlObjects ?? new WeakSet();
|
|
277
|
+
this.allowedCrossRealmGraphqlObjects = allowedObjects;
|
|
278
|
+
this.releaseGraphqlInstanceOfPatch ??= installGraphqlInstanceOfPatch(deps.instanceOfModule, allowedObjects);
|
|
279
|
+
const markAllowedObject = value => markAllowedCrossRealmGraphqlObjects(value, allowedObjects);
|
|
280
|
+
return resolveSchema(deps, this.options.schema, () => this.createCodeFirstSchema(deps, markAllowedObject), markAllowedObject);
|
|
354
281
|
}
|
|
355
|
-
createCodeFirstSchema(deps) {
|
|
356
|
-
return createCodeFirstSchema(deps, this.runtimeContainer, this.discoverResolverDescriptors(),
|
|
282
|
+
createCodeFirstSchema(deps, markAllowedObject) {
|
|
283
|
+
return createCodeFirstSchema(deps, this.runtimeContainer, this.discoverResolverDescriptors(), markAllowedObject);
|
|
357
284
|
}
|
|
358
285
|
discoverResolverDescriptors() {
|
|
359
286
|
return discoverResolverDescriptors(this.compiledModules, this.options);
|
|
360
287
|
}
|
|
361
|
-
registerMiddleware() {
|
|
362
|
-
for (const compiledModule of this.compiledModules) {
|
|
363
|
-
if (!compiledModule.providerTokens.has(_GraphqlLifecycleServ)) {
|
|
364
|
-
continue;
|
|
365
|
-
}
|
|
366
|
-
const middleware = compiledModule.definition.middleware ?? [];
|
|
367
|
-
if (!middleware.includes(this.middleware)) {
|
|
368
|
-
compiledModule.definition.middleware = [...middleware, this.middleware];
|
|
369
|
-
continue;
|
|
370
|
-
}
|
|
371
|
-
compiledModule.definition.middleware = [...middleware];
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
unregisterMiddleware() {
|
|
375
|
-
for (const compiledModule of this.compiledModules) {
|
|
376
|
-
if (!compiledModule.providerTokens.has(_GraphqlLifecycleServ)) {
|
|
377
|
-
continue;
|
|
378
|
-
}
|
|
379
|
-
const middleware = compiledModule.definition.middleware ?? [];
|
|
380
|
-
const remaining = [];
|
|
381
|
-
for (const entry of middleware) {
|
|
382
|
-
if (entry !== this.middleware) {
|
|
383
|
-
remaining.push(entry);
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
compiledModule.definition.middleware = remaining;
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
288
|
buildGraphqlContext(request, requestContextOverride, operationContainerOverride) {
|
|
390
289
|
const storedContext = this.requestContexts.get(request);
|
|
391
290
|
const requestContext = {
|
|
@@ -406,126 +305,81 @@ class GraphqlLifecycleService {
|
|
|
406
305
|
};
|
|
407
306
|
}
|
|
408
307
|
async registerWebSocketTransport() {
|
|
409
|
-
if (!this.isWebSocketTransportEnabled() || this.yoga === undefined || this.
|
|
308
|
+
if (!this.isWebSocketTransportEnabled() || this.yoga === undefined || this.websocketTransport !== undefined) {
|
|
410
309
|
return;
|
|
411
310
|
}
|
|
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,
|
|
311
|
+
const {
|
|
312
|
+
createNodeGraphqlWebSocketTransport
|
|
313
|
+
} = await import('./node/graphql-websocket-transport.js');
|
|
314
|
+
this.websocketTransport = await createNodeGraphqlWebSocketTransport({
|
|
315
|
+
adapter: this.adapter,
|
|
316
|
+
connectionInitWaitTimeoutMs: this.options.subscriptions?.websocket?.connectionInitWaitTimeoutMs,
|
|
441
317
|
execute: args => {
|
|
442
318
|
if (!this.executeGraphqlOperation) {
|
|
443
319
|
throw new Error('GraphQL execute function not initialized.');
|
|
444
320
|
}
|
|
445
321
|
return this.executeGraphqlOperation(args);
|
|
446
322
|
},
|
|
447
|
-
|
|
448
|
-
|
|
323
|
+
keepAliveMs: this.options.subscriptions?.websocket?.keepAliveMs,
|
|
324
|
+
limits: this.resolveWebSocketLimits(),
|
|
325
|
+
onComplete: async (socketKey, operationId) => {
|
|
326
|
+
await this.disposeWebSocketOperationContainer(socketKey, operationId);
|
|
449
327
|
},
|
|
450
|
-
onDisconnect:
|
|
451
|
-
|
|
452
|
-
},
|
|
453
|
-
onSubscribe: async (context, message) => this.handleWebSocketSubscribe(context, message.id, message.payload),
|
|
328
|
+
onDisconnect: socketKey => this.disposeAllWebSocketOperationContainers(socketKey),
|
|
329
|
+
onSubscribe: request => this.handleWebSocketSubscribe(request),
|
|
454
330
|
subscribe: args => {
|
|
455
331
|
if (!this.subscribeGraphqlOperation) {
|
|
456
332
|
throw new Error('GraphQL subscribe function not initialized.');
|
|
457
333
|
}
|
|
458
334
|
return this.subscribeGraphqlOperation(args);
|
|
459
335
|
}
|
|
460
|
-
}
|
|
461
|
-
upgradeServer.on('upgrade', upgradeListener);
|
|
462
|
-
this.websocketDisposable = websocketDisposable;
|
|
463
|
-
this.websocketServer = websocketServer;
|
|
464
|
-
this.websocketUpgradeListener = upgradeListener;
|
|
465
|
-
this.websocketUpgradeServer = upgradeServer;
|
|
336
|
+
});
|
|
466
337
|
}
|
|
467
338
|
async unregisterWebSocketTransport() {
|
|
468
|
-
|
|
469
|
-
|
|
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) {
|
|
339
|
+
const cleanupErrors = [];
|
|
340
|
+
if (this.websocketTransport) {
|
|
479
341
|
try {
|
|
480
|
-
await this.
|
|
342
|
+
await this.websocketTransport.dispose();
|
|
481
343
|
} catch (error) {
|
|
482
344
|
this.logger.error('Failed to dispose GraphQL websocket transport.', error, 'GraphqlLifecycleService');
|
|
345
|
+
collectCleanupError(cleanupErrors, error);
|
|
346
|
+
}
|
|
347
|
+
if (cleanupErrors.length === 0) {
|
|
348
|
+
this.websocketTransport = undefined;
|
|
483
349
|
}
|
|
484
350
|
}
|
|
485
|
-
this.
|
|
486
|
-
|
|
351
|
+
const socketKeys = new Set([...this.websocketOperationContainers.keys(), ...this.failedWebsocketOperationContainers.keys()]);
|
|
352
|
+
for (const socketKey of socketKeys) {
|
|
487
353
|
try {
|
|
488
|
-
await
|
|
354
|
+
await this.disposeAllWebSocketOperationContainers(socketKey);
|
|
489
355
|
} catch (error) {
|
|
490
|
-
|
|
356
|
+
collectCleanupError(cleanupErrors, error);
|
|
491
357
|
}
|
|
492
358
|
}
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
await this.disposeAllWebSocketOperationContainers(socketKey);
|
|
359
|
+
if (cleanupErrors.length > 0) {
|
|
360
|
+
throw new AggregateError(cleanupErrors, 'Failed to dispose GraphQL websocket resources.');
|
|
496
361
|
}
|
|
497
362
|
}
|
|
498
363
|
isWebSocketTransportEnabled() {
|
|
499
364
|
return this.options.subscriptions?.websocket?.enabled === true;
|
|
500
365
|
}
|
|
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) {
|
|
366
|
+
async handleWebSocketSubscribe(request) {
|
|
512
367
|
const yoga = this.yoga;
|
|
513
368
|
if (!yoga) {
|
|
514
369
|
throw new Error('GraphQL server not initialized.');
|
|
515
370
|
}
|
|
516
|
-
const websocketLimitError = this.createWebSocketOperationLimitError(
|
|
371
|
+
const websocketLimitError = this.createWebSocketOperationLimitError(request.socket, request.operationId);
|
|
517
372
|
if (websocketLimitError) {
|
|
518
373
|
return [websocketLimitError];
|
|
519
374
|
}
|
|
520
|
-
const
|
|
521
|
-
const
|
|
522
|
-
const operationContainer = this.getOrCreateWebSocketOperationContainer(context.extra.socket, operationId);
|
|
523
|
-
const graphqlContext = this.buildGraphqlContext(fetchRequest, {
|
|
524
|
-
connectionParams: isConnectionParamsRecord(context.connectionParams) ? context.connectionParams : undefined,
|
|
525
|
-
request: frameworkRequest,
|
|
526
|
-
socket: context.extra.socket
|
|
527
|
-
}, operationContainer);
|
|
375
|
+
const fetchRequest = toFetchRequest(request.request);
|
|
376
|
+
const operationContainer = this.getOrCreateWebSocketOperationContainer(request.socket, request.operationId);
|
|
528
377
|
try {
|
|
378
|
+
const graphqlContext = this.buildGraphqlContext(fetchRequest, {
|
|
379
|
+
connectionParams: request.connectionParams,
|
|
380
|
+
request: request.request,
|
|
381
|
+
socket: request.socket
|
|
382
|
+
}, operationContainer);
|
|
529
383
|
const {
|
|
530
384
|
contextFactory,
|
|
531
385
|
parse,
|
|
@@ -535,21 +389,21 @@ class GraphqlLifecycleService {
|
|
|
535
389
|
request: fetchRequest,
|
|
536
390
|
[GRAPHQL_CONTEXT_OVERRIDE]: graphqlContext
|
|
537
391
|
});
|
|
538
|
-
const document = parse(payload.query);
|
|
392
|
+
const document = parse(request.payload.query);
|
|
539
393
|
const validationErrors = validate(schema, document);
|
|
540
394
|
if (validationErrors.length > 0) {
|
|
541
|
-
await this.disposeWebSocketOperationContainer(
|
|
395
|
+
await this.disposeWebSocketOperationContainer(request.socket, request.operationId);
|
|
542
396
|
return validationErrors;
|
|
543
397
|
}
|
|
544
398
|
return {
|
|
545
399
|
contextValue: await contextFactory(),
|
|
546
400
|
document,
|
|
547
|
-
operationName: payload.operationName ?? undefined,
|
|
401
|
+
operationName: request.payload.operationName ?? undefined,
|
|
548
402
|
schema,
|
|
549
|
-
variableValues: payload.variables
|
|
403
|
+
variableValues: request.payload.variables
|
|
550
404
|
};
|
|
551
405
|
} catch (error) {
|
|
552
|
-
await this.disposeWebSocketOperationContainer(
|
|
406
|
+
await this.disposeWebSocketOperationContainer(request.socket, request.operationId);
|
|
553
407
|
throw error;
|
|
554
408
|
}
|
|
555
409
|
}
|
|
@@ -568,15 +422,6 @@ class GraphqlLifecycleService {
|
|
|
568
422
|
}
|
|
569
423
|
return new GraphQLError(`GraphQL websocket active operation count exceeds the configured limit of ${String(limits.maxOperationsPerConnection)}.`);
|
|
570
424
|
}
|
|
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
425
|
getOrCreateWebSocketOperationContainer(socketKey, operationId) {
|
|
581
426
|
const existingSocketContainers = this.websocketOperationContainers.get(socketKey);
|
|
582
427
|
if (existingSocketContainers?.has(operationId)) {
|
|
@@ -598,25 +443,55 @@ class GraphqlLifecycleService {
|
|
|
598
443
|
if (socketContainers && socketContainers.size === 0) {
|
|
599
444
|
this.websocketOperationContainers.delete(socketKey);
|
|
600
445
|
}
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
446
|
+
return await this.disposeWebSocketContainerOnce(socketKey, operationContainer);
|
|
447
|
+
}
|
|
448
|
+
async disposeWebSocketContainerOnce(socketKey, operationContainer) {
|
|
449
|
+
const inFlight = this.websocketContainerDisposals.get(operationContainer);
|
|
450
|
+
if (inFlight) {
|
|
451
|
+
return await inFlight;
|
|
605
452
|
}
|
|
453
|
+
const attempted = this.websocketContainersAttemptedInCurrentCleanup;
|
|
454
|
+
if (attempted?.has(operationContainer)) {
|
|
455
|
+
return undefined;
|
|
456
|
+
}
|
|
457
|
+
attempted?.add(operationContainer);
|
|
458
|
+
const disposal = operationContainer.dispose().then(() => {
|
|
459
|
+
this.failedWebsocketOperationContainers.get(socketKey)?.delete(operationContainer);
|
|
460
|
+
return undefined;
|
|
461
|
+
}, error => {
|
|
462
|
+
const failedContainers = this.failedWebsocketOperationContainers.get(socketKey) ?? new Set();
|
|
463
|
+
failedContainers.add(operationContainer);
|
|
464
|
+
this.failedWebsocketOperationContainers.set(socketKey, failedContainers);
|
|
465
|
+
this.logger.error('Failed to dispose GraphQL websocket operation container.', error, 'GraphqlLifecycleService');
|
|
466
|
+
return error;
|
|
467
|
+
}).finally(() => {
|
|
468
|
+
this.websocketContainerDisposals.delete(operationContainer);
|
|
469
|
+
});
|
|
470
|
+
this.websocketContainerDisposals.set(operationContainer, disposal);
|
|
471
|
+
return await disposal;
|
|
606
472
|
}
|
|
607
473
|
async disposeAllWebSocketOperationContainers(socketKey) {
|
|
608
|
-
const
|
|
609
|
-
|
|
610
|
-
|
|
474
|
+
const cleanupErrors = [];
|
|
475
|
+
const failedContainers = this.failedWebsocketOperationContainers.get(socketKey);
|
|
476
|
+
for (const operationContainer of [...(failedContainers ?? [])]) {
|
|
477
|
+
const cleanupError = await this.disposeWebSocketContainerOnce(socketKey, operationContainer);
|
|
478
|
+
if (cleanupError !== undefined) {
|
|
479
|
+
collectCleanupError(cleanupErrors, cleanupError);
|
|
480
|
+
}
|
|
611
481
|
}
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
482
|
+
if (failedContainers?.size === 0) {
|
|
483
|
+
this.failedWebsocketOperationContainers.delete(socketKey);
|
|
484
|
+
}
|
|
485
|
+
const socketContainers = this.websocketOperationContainers.get(socketKey);
|
|
486
|
+
for (const operationId of [...(socketContainers?.keys() ?? [])]) {
|
|
487
|
+
const cleanupError = await this.disposeWebSocketOperationContainer(socketKey, operationId);
|
|
488
|
+
if (cleanupError !== undefined) {
|
|
489
|
+
collectCleanupError(cleanupErrors, cleanupError);
|
|
618
490
|
}
|
|
619
491
|
}
|
|
492
|
+
if (cleanupErrors.length > 0) {
|
|
493
|
+
throw new AggregateError(cleanupErrors, 'Failed to dispose GraphQL websocket operation containers.');
|
|
494
|
+
}
|
|
620
495
|
}
|
|
621
496
|
getOrCreateOperationContainer(request) {
|
|
622
497
|
const existing = this.operationContainers.get(request);
|
|
@@ -632,11 +507,24 @@ class GraphqlLifecycleService {
|
|
|
632
507
|
if (!operationContainer) {
|
|
633
508
|
return;
|
|
634
509
|
}
|
|
635
|
-
this.operationContainers.delete(request);
|
|
636
510
|
try {
|
|
637
511
|
await operationContainer.dispose();
|
|
638
512
|
} catch (error) {
|
|
639
513
|
this.logger.error('Failed to dispose GraphQL operation container.', error, 'GraphqlLifecycleService');
|
|
514
|
+
return error;
|
|
515
|
+
}
|
|
516
|
+
this.operationContainers.delete(request);
|
|
517
|
+
}
|
|
518
|
+
async disposeAllOperationContainers() {
|
|
519
|
+
const cleanupErrors = [];
|
|
520
|
+
for (const request of [...this.operationContainers.keys()]) {
|
|
521
|
+
const cleanupError = await this.disposeOperationContainer(request);
|
|
522
|
+
if (cleanupError !== undefined) {
|
|
523
|
+
collectCleanupError(cleanupErrors, cleanupError);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
if (cleanupErrors.length > 0) {
|
|
527
|
+
throw new AggregateError(cleanupErrors, 'Failed to dispose GraphQL operation containers.');
|
|
640
528
|
}
|
|
641
529
|
}
|
|
642
530
|
static {
|