@fluojs/graphql 1.0.0-beta.1
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/LICENSE +21 -0
- package/README.ko.md +158 -0
- package/README.md +158 -0
- package/dist/dataloader/dataloader.d.ts +127 -0
- package/dist/dataloader/dataloader.d.ts.map +1 -0
- package/dist/dataloader/dataloader.js +151 -0
- package/dist/dataloader.d.ts +2 -0
- package/dist/dataloader.d.ts.map +1 -0
- package/dist/dataloader.js +1 -0
- package/dist/decorators.d.ts +21 -0
- package/dist/decorators.d.ts.map +1 -0
- package/dist/decorators.js +111 -0
- package/dist/discovery.d.ts +4 -0
- package/dist/discovery.d.ts.map +1 -0
- package/dist/discovery.js +142 -0
- package/dist/guardrails.d.ts +35 -0
- package/dist/guardrails.d.ts.map +1 -0
- package/dist/guardrails.js +162 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/internal-tokens.d.ts +7 -0
- package/dist/internal-tokens.d.ts.map +1 -0
- package/dist/internal-tokens.js +4 -0
- package/dist/metadata.d.ts +20 -0
- package/dist/metadata.d.ts.map +1 -0
- package/dist/metadata.js +120 -0
- package/dist/module.d.ts +22 -0
- package/dist/module.d.ts.map +1 -0
- package/dist/module.js +33 -0
- package/dist/pipeline/input-pipeline.d.ts +34 -0
- package/dist/pipeline/input-pipeline.d.ts.map +1 -0
- package/dist/pipeline/input-pipeline.js +129 -0
- package/dist/schema/schema.d.ts +20 -0
- package/dist/schema/schema.d.ts.map +1 -0
- package/dist/schema/schema.js +278 -0
- package/dist/service.d.ts +58 -0
- package/dist/service.d.ts.map +1 -0
- package/dist/service.js +602 -0
- package/dist/transport/transport.d.ts +5 -0
- package/dist/transport/transport.d.ts.map +1 -0
- package/dist/transport/transport.js +119 -0
- package/dist/types.d.ts +221 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +132 -0
- package/package.json +61 -0
package/dist/service.js
ADDED
|
@@ -0,0 +1,602 @@
|
|
|
1
|
+
let _initProto, _initClass, _initClass2;
|
|
2
|
+
function _applyDecs(e, t, n, r, o, i) { var a, c, u, s, f, l, p, d = Symbol.metadata || Symbol.for("Symbol.metadata"), m = Object.defineProperty, h = Object.create, y = [h(null), h(null)], v = t.length; function g(t, n, r) { return function (o, i) { n && (i = o, o = e); for (var a = 0; a < t.length; a++) i = t[a].apply(o, r ? [i] : []); return r ? i : o; }; } function b(e, t, n, r) { if ("function" != typeof e && (r || void 0 !== e)) throw new TypeError(t + " must " + (n || "be") + " a function" + (r ? "" : " or undefined")); return e; } function applyDec(e, t, n, r, o, i, u, s, f, l, p) { function d(e) { if (!p(e)) throw new TypeError("Attempted to access private element on non-instance"); } var h = [].concat(t[0]), v = t[3], w = !u, D = 1 === o, S = 3 === o, j = 4 === o, E = 2 === o; function I(t, n, r) { return function (o, i) { return n && (i = o, o = e), r && r(o), P[t].call(o, i); }; } if (!w) { var P = {}, k = [], F = S ? "get" : j || D ? "set" : "value"; if (f ? (l || D ? P = { get: _setFunctionName(function () { return v(this); }, r, "get"), set: function (e) { t[4](this, e); } } : P[F] = v, l || _setFunctionName(P[F], r, E ? "" : F)) : l || (P = Object.getOwnPropertyDescriptor(e, r)), !l && !f) { if ((c = y[+s][r]) && 7 !== (c ^ o)) throw Error("Decorating two elements with the same name (" + P[F].name + ") is not supported yet"); y[+s][r] = o < 3 ? 1 : o; } } for (var N = e, O = h.length - 1; O >= 0; O -= n ? 2 : 1) { var T = b(h[O], "A decorator", "be", !0), z = n ? h[O - 1] : void 0, A = {}, H = { kind: ["field", "accessor", "method", "getter", "setter", "class"][o], name: r, metadata: a, addInitializer: function (e, t) { if (e.v) throw new TypeError("attempted to call addInitializer after decoration was finished"); b(t, "An initializer", "be", !0), i.push(t); }.bind(null, A) }; if (w) c = T.call(z, N, H), A.v = 1, b(c, "class decorators", "return") && (N = c);else if (H.static = s, H.private = f, c = H.access = { has: f ? p.bind() : function (e) { return r in e; } }, j || (c.get = f ? E ? function (e) { return d(e), P.value; } : I("get", 0, d) : function (e) { return e[r]; }), E || S || (c.set = f ? I("set", 0, d) : function (e, t) { e[r] = t; }), N = T.call(z, D ? { get: P.get, set: P.set } : P[F], H), A.v = 1, D) { if ("object" == typeof N && N) (c = b(N.get, "accessor.get")) && (P.get = c), (c = b(N.set, "accessor.set")) && (P.set = c), (c = b(N.init, "accessor.init")) && k.unshift(c);else if (void 0 !== N) throw new TypeError("accessor decorators must return an object with get, set, or init properties or undefined"); } else b(N, (l ? "field" : "method") + " decorators", "return") && (l ? k.unshift(N) : P[F] = N); } return o < 2 && u.push(g(k, s, 1), g(i, s, 0)), l || w || (f ? D ? u.splice(-1, 0, I("get", s), I("set", s)) : u.push(E ? P[F] : b.call.bind(P[F])) : m(e, r, P)), N; } function w(e) { return m(e, d, { configurable: !0, enumerable: !0, value: a }); } return void 0 !== i && (a = i[d]), a = h(null == a ? null : a), f = [], l = function (e) { e && f.push(g(e)); }, p = function (t, r) { for (var i = 0; i < n.length; i++) { var a = n[i], c = a[1], l = 7 & c; if ((8 & c) == t && !l == r) { var p = a[2], d = !!a[3], m = 16 & c; applyDec(t ? e : e.prototype, a, m, d ? "#" + p : _toPropertyKey(p), l, l < 2 ? [] : t ? s = s || [] : u = u || [], f, !!t, d, r, t && d ? function (t) { return _checkInRHS(t) === e; } : o); } } }, p(8, 0), p(0, 0), p(8, 1), p(0, 1), l(u), l(s), c = f, v || w(e), { e: c, get c() { var n = []; return v && [w(e = applyDec(e, [t], r, e.name, 5, n)), g(n, 1)]; } }; }
|
|
3
|
+
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
|
|
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
|
+
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
|
+
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 { AsyncLocalStorage } from 'node:async_hooks';
|
|
8
|
+
import { createRequire } from 'node:module';
|
|
9
|
+
import { Controller, Get, Post } from '@fluojs/http';
|
|
10
|
+
import { Inject } from '@fluojs/core';
|
|
11
|
+
import { APPLICATION_LOGGER, COMPILED_MODULES, HTTP_APPLICATION_ADAPTER, RUNTIME_CONTAINER } from '@fluojs/runtime/internal';
|
|
12
|
+
import { handleProtocols } from 'graphql-ws';
|
|
13
|
+
import { useServer } from 'graphql-ws/lib/use/ws';
|
|
14
|
+
import { WebSocketServer } from 'ws';
|
|
15
|
+
import { discoverResolverDescriptors } from './discovery.js';
|
|
16
|
+
import { createGraphqlValidationPlugin, resolveGraphqlRequestLimits } from './guardrails.js';
|
|
17
|
+
import { GRAPHQL_INTERNAL_MODULE_OPTIONS_TOKEN } from './internal-tokens.js';
|
|
18
|
+
import { createCodeFirstSchema, resolveSchema } from './schema/schema.js';
|
|
19
|
+
import { isGraphqlPath, toFetchRequest, writeFetchResponse } from './transport/transport.js';
|
|
20
|
+
import { GRAPHQL_OPERATION_CONTAINER } from './types.js';
|
|
21
|
+
const GRAPHQL_CONTEXT_OVERRIDE = Symbol('fluo.graphql.context.override');
|
|
22
|
+
const DEFAULT_GRAPHQL_WEBSOCKET_LIMITS = {
|
|
23
|
+
maxConnections: 100,
|
|
24
|
+
maxOperationsPerConnection: 25,
|
|
25
|
+
maxPayloadBytes: 64 * 1024
|
|
26
|
+
};
|
|
27
|
+
function hasNodeUpgradeServer(value) {
|
|
28
|
+
if (typeof value !== 'object' || value === null) {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
const server = value;
|
|
32
|
+
return typeof server.on === 'function' && typeof server.off === 'function';
|
|
33
|
+
}
|
|
34
|
+
function buildFrameworkRequestFromFetchRequest(request) {
|
|
35
|
+
const requestUrl = new URL(request.url);
|
|
36
|
+
return {
|
|
37
|
+
cookies: {},
|
|
38
|
+
headers: Object.fromEntries(request.headers.entries()),
|
|
39
|
+
method: request.method,
|
|
40
|
+
params: {},
|
|
41
|
+
path: requestUrl.pathname,
|
|
42
|
+
query: Object.fromEntries(requestUrl.searchParams.entries()),
|
|
43
|
+
raw: request,
|
|
44
|
+
signal: request.signal,
|
|
45
|
+
url: requestUrl.pathname + requestUrl.search
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
function buildFrameworkRequestFromIncomingMessage(request) {
|
|
49
|
+
const requestUrl = new URL(request.url ?? '/graphql', 'http://localhost');
|
|
50
|
+
return {
|
|
51
|
+
cookies: {},
|
|
52
|
+
headers: request.headers,
|
|
53
|
+
method: request.method ?? 'GET',
|
|
54
|
+
params: {},
|
|
55
|
+
path: requestUrl.pathname,
|
|
56
|
+
query: Object.fromEntries(requestUrl.searchParams.entries()),
|
|
57
|
+
raw: request,
|
|
58
|
+
url: requestUrl.pathname + requestUrl.search
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
function isConnectionParamsRecord(value) {
|
|
62
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
63
|
+
}
|
|
64
|
+
function closeWebSocketServer(server) {
|
|
65
|
+
return new Promise((resolve, reject) => {
|
|
66
|
+
server.close(error => {
|
|
67
|
+
if (error?.message === 'The server is not running') {
|
|
68
|
+
resolve();
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
if (error) {
|
|
72
|
+
reject(error);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
resolve();
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
const graphqlRequestContextStorage = new AsyncLocalStorage();
|
|
80
|
+
const runtimeRequire = createRequire(import.meta.url);
|
|
81
|
+
let graphqlInstanceOfPatched = false;
|
|
82
|
+
const allowedCrossRealmGraphqlObjects = new WeakSet();
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Declares the HTTP endpoints that receive GraphQL GET and POST requests.
|
|
86
|
+
*/
|
|
87
|
+
let _GraphqlEndpointContr;
|
|
88
|
+
class GraphqlEndpointController {
|
|
89
|
+
static {
|
|
90
|
+
({
|
|
91
|
+
e: [_initProto],
|
|
92
|
+
c: [_GraphqlEndpointContr, _initClass]
|
|
93
|
+
} = _applyDecs(this, [Controller('/graphql')], [[Get('/'), 2, "handleGet"], [Post('/'), 2, "handlePost"]]));
|
|
94
|
+
}
|
|
95
|
+
constructor() {
|
|
96
|
+
_initProto(this);
|
|
97
|
+
}
|
|
98
|
+
handleGet() {
|
|
99
|
+
return undefined;
|
|
100
|
+
}
|
|
101
|
+
handlePost() {
|
|
102
|
+
return undefined;
|
|
103
|
+
}
|
|
104
|
+
static {
|
|
105
|
+
_initClass();
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
export { _GraphqlEndpointContr as GraphqlEndpointController };
|
|
109
|
+
function getCrossRealmGraphqlTag(value, constructor) {
|
|
110
|
+
const className = constructor.prototype?.[Symbol.toStringTag];
|
|
111
|
+
if (typeof className !== 'string' || !className.startsWith('GraphQL')) {
|
|
112
|
+
return undefined;
|
|
113
|
+
}
|
|
114
|
+
if (typeof value !== 'object' || value === null) {
|
|
115
|
+
return undefined;
|
|
116
|
+
}
|
|
117
|
+
if (Symbol.toStringTag in value) {
|
|
118
|
+
const valueClassName = value[Symbol.toStringTag];
|
|
119
|
+
return valueClassName === className ? className : undefined;
|
|
120
|
+
}
|
|
121
|
+
const valueClassName = value.constructor?.name;
|
|
122
|
+
return valueClassName === className ? className : undefined;
|
|
123
|
+
}
|
|
124
|
+
function markAllowedCrossRealmGraphqlObjects(value, visited = new WeakSet()) {
|
|
125
|
+
if (typeof value !== 'object' || value === null) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
if (visited.has(value)) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
visited.add(value);
|
|
132
|
+
const tag = value[Symbol.toStringTag];
|
|
133
|
+
if (typeof tag === 'string' && tag.startsWith('GraphQL')) {
|
|
134
|
+
allowedCrossRealmGraphqlObjects.add(value);
|
|
135
|
+
}
|
|
136
|
+
if (Array.isArray(value)) {
|
|
137
|
+
for (const item of value) {
|
|
138
|
+
markAllowedCrossRealmGraphqlObjects(item, visited);
|
|
139
|
+
}
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
for (const nestedValue of Object.values(value)) {
|
|
143
|
+
markAllowedCrossRealmGraphqlObjects(nestedValue, visited);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
function isAllowedCrossRealmGraphqlObject(value, constructor) {
|
|
147
|
+
if (typeof value !== 'object' || value === null) {
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
return allowedCrossRealmGraphqlObjects.has(value) && getCrossRealmGraphqlTag(value, constructor) !== undefined;
|
|
151
|
+
}
|
|
152
|
+
function patchGraphqlInstanceOf() {
|
|
153
|
+
if (graphqlInstanceOfPatched) {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
const instanceOfModule = runtimeRequire('graphql/jsutils/instanceOf.js');
|
|
157
|
+
const originalInstanceOf = instanceOfModule.instanceOf;
|
|
158
|
+
instanceOfModule.instanceOf = (value, constructor) => {
|
|
159
|
+
try {
|
|
160
|
+
if (originalInstanceOf(value, constructor)) {
|
|
161
|
+
return true;
|
|
162
|
+
}
|
|
163
|
+
} catch (error) {
|
|
164
|
+
if (isAllowedCrossRealmGraphqlObject(value, constructor)) {
|
|
165
|
+
return true;
|
|
166
|
+
}
|
|
167
|
+
throw error;
|
|
168
|
+
}
|
|
169
|
+
return isAllowedCrossRealmGraphqlObject(value, constructor);
|
|
170
|
+
};
|
|
171
|
+
graphqlInstanceOfPatched = true;
|
|
172
|
+
}
|
|
173
|
+
async function loadGraphqlDeps() {
|
|
174
|
+
const graphqlMod = runtimeRequire('graphql');
|
|
175
|
+
const yogaMod = runtimeRequire('graphql-yoga');
|
|
176
|
+
return {
|
|
177
|
+
GraphQLError: graphqlMod.GraphQLError,
|
|
178
|
+
GraphQLBoolean: graphqlMod.GraphQLBoolean,
|
|
179
|
+
GraphQLFloat: graphqlMod.GraphQLFloat,
|
|
180
|
+
GraphQLID: graphqlMod.GraphQLID,
|
|
181
|
+
GraphQLInt: graphqlMod.GraphQLInt,
|
|
182
|
+
GraphQLList: graphqlMod.GraphQLList,
|
|
183
|
+
GraphQLObjectType: graphqlMod.GraphQLObjectType,
|
|
184
|
+
GraphQLSchema: graphqlMod.GraphQLSchema,
|
|
185
|
+
GraphQLString: graphqlMod.GraphQLString,
|
|
186
|
+
GraphQLUnionType: graphqlMod.GraphQLUnionType,
|
|
187
|
+
buildSchema: graphqlMod.buildSchema,
|
|
188
|
+
createYoga: yogaMod.createYoga,
|
|
189
|
+
execute: graphqlMod.execute,
|
|
190
|
+
subscribe: graphqlMod.subscribe
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Boots the GraphQL runtime, middleware, and subscription transports for the active adapter.
|
|
196
|
+
*/
|
|
197
|
+
let _GraphqlLifecycleServ;
|
|
198
|
+
class GraphqlLifecycleService {
|
|
199
|
+
static {
|
|
200
|
+
[_GraphqlLifecycleServ, _initClass2] = _applyDecs(this, [Inject(RUNTIME_CONTAINER, COMPILED_MODULES, APPLICATION_LOGGER, HTTP_APPLICATION_ADAPTER, GRAPHQL_INTERNAL_MODULE_OPTIONS_TOKEN)], []).c;
|
|
201
|
+
}
|
|
202
|
+
graphQLErrorConstructor;
|
|
203
|
+
middlewareRegistered = false;
|
|
204
|
+
operationContainers = new WeakMap();
|
|
205
|
+
websocketOperationContainers = new Map();
|
|
206
|
+
websocketDisposable;
|
|
207
|
+
websocketServer;
|
|
208
|
+
websocketUpgradeListener;
|
|
209
|
+
websocketUpgradeServer;
|
|
210
|
+
executeGraphqlOperation;
|
|
211
|
+
subscribeGraphqlOperation;
|
|
212
|
+
yoga;
|
|
213
|
+
middleware = {
|
|
214
|
+
handle: async (context, next) => {
|
|
215
|
+
if (!isGraphqlPath(context.request.path)) {
|
|
216
|
+
await next();
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
const yoga = this.yoga;
|
|
220
|
+
if (!yoga) {
|
|
221
|
+
this.logger.error('GraphQL middleware was invoked before GraphQL Yoga initialization.', undefined, 'GraphqlLifecycleService');
|
|
222
|
+
context.response.setStatus(500);
|
|
223
|
+
await context.response.send({
|
|
224
|
+
errors: [{
|
|
225
|
+
message: 'GraphQL server not initialized.'
|
|
226
|
+
}]
|
|
227
|
+
});
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
try {
|
|
231
|
+
const fetchRequest = toFetchRequest(context.request);
|
|
232
|
+
try {
|
|
233
|
+
const fetchResponse = await graphqlRequestContextStorage.run({
|
|
234
|
+
principal: context.requestContext.principal,
|
|
235
|
+
request: context.request
|
|
236
|
+
}, () => yoga.fetch(fetchRequest));
|
|
237
|
+
await writeFetchResponse(fetchResponse, context.response);
|
|
238
|
+
} finally {
|
|
239
|
+
await this.disposeOperationContainer(fetchRequest);
|
|
240
|
+
}
|
|
241
|
+
} catch (error) {
|
|
242
|
+
this.logger.error('Failed to process GraphQL request.', error, 'GraphqlLifecycleService');
|
|
243
|
+
if (!context.response.committed) {
|
|
244
|
+
context.response.setStatus(500);
|
|
245
|
+
await context.response.send({
|
|
246
|
+
errors: [{
|
|
247
|
+
message: 'Internal server error.'
|
|
248
|
+
}]
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
constructor(runtimeContainer, compiledModules, logger, adapter, options) {
|
|
255
|
+
this.runtimeContainer = runtimeContainer;
|
|
256
|
+
this.compiledModules = compiledModules;
|
|
257
|
+
this.logger = logger;
|
|
258
|
+
this.adapter = adapter;
|
|
259
|
+
this.options = options;
|
|
260
|
+
}
|
|
261
|
+
async onApplicationBootstrap() {
|
|
262
|
+
if (this.middlewareRegistered) {
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
const deps = await loadGraphqlDeps();
|
|
266
|
+
const schema = this.resolveSchema(deps);
|
|
267
|
+
this.executeGraphqlOperation = deps.execute;
|
|
268
|
+
this.graphQLErrorConstructor = deps.GraphQLError;
|
|
269
|
+
this.subscribeGraphqlOperation = deps.subscribe;
|
|
270
|
+
const requestLimits = resolveGraphqlRequestLimits(this.options.limits);
|
|
271
|
+
const validationPlugin = createGraphqlValidationPlugin({
|
|
272
|
+
introspection: this.resolveIntrospectionEnabled(),
|
|
273
|
+
limits: requestLimits
|
|
274
|
+
});
|
|
275
|
+
this.yoga = deps.createYoga({
|
|
276
|
+
context: contextValue => contextValue[GRAPHQL_CONTEXT_OVERRIDE] ?? this.buildGraphqlContext(contextValue.request),
|
|
277
|
+
graphqlEndpoint: '/graphql',
|
|
278
|
+
graphiql: this.resolveGraphiqlEnabled(),
|
|
279
|
+
...(validationPlugin || this.options.plugins && this.options.plugins.length > 0 ? {
|
|
280
|
+
plugins: [...(validationPlugin ? [validationPlugin] : []), ...(this.options.plugins ?? [])]
|
|
281
|
+
} : {}),
|
|
282
|
+
schema
|
|
283
|
+
});
|
|
284
|
+
this.registerWebSocketTransport();
|
|
285
|
+
this.registerMiddleware();
|
|
286
|
+
this.middlewareRegistered = true;
|
|
287
|
+
}
|
|
288
|
+
async onApplicationShutdown() {
|
|
289
|
+
await this.unregisterWebSocketTransport();
|
|
290
|
+
this.unregisterMiddleware();
|
|
291
|
+
this.middlewareRegistered = false;
|
|
292
|
+
this.executeGraphqlOperation = undefined;
|
|
293
|
+
this.graphQLErrorConstructor = undefined;
|
|
294
|
+
this.subscribeGraphqlOperation = undefined;
|
|
295
|
+
this.yoga = undefined;
|
|
296
|
+
}
|
|
297
|
+
resolveGraphiqlEnabled() {
|
|
298
|
+
return this.options.graphiql ?? false;
|
|
299
|
+
}
|
|
300
|
+
resolveIntrospectionEnabled() {
|
|
301
|
+
return this.options.introspection ?? this.resolveGraphiqlEnabled();
|
|
302
|
+
}
|
|
303
|
+
resolveWebSocketLimits() {
|
|
304
|
+
const limits = this.options.subscriptions?.websocket?.limits;
|
|
305
|
+
if (limits === false) {
|
|
306
|
+
return undefined;
|
|
307
|
+
}
|
|
308
|
+
return {
|
|
309
|
+
maxConnections: limits?.maxConnections ?? DEFAULT_GRAPHQL_WEBSOCKET_LIMITS.maxConnections,
|
|
310
|
+
maxOperationsPerConnection: limits?.maxOperationsPerConnection ?? DEFAULT_GRAPHQL_WEBSOCKET_LIMITS.maxOperationsPerConnection,
|
|
311
|
+
maxPayloadBytes: limits?.maxPayloadBytes ?? DEFAULT_GRAPHQL_WEBSOCKET_LIMITS.maxPayloadBytes
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
resolveSchema(deps) {
|
|
315
|
+
patchGraphqlInstanceOf();
|
|
316
|
+
return resolveSchema(deps, this.options.schema, () => this.createCodeFirstSchema(deps), markAllowedCrossRealmGraphqlObjects);
|
|
317
|
+
}
|
|
318
|
+
createCodeFirstSchema(deps) {
|
|
319
|
+
return createCodeFirstSchema(deps, this.runtimeContainer, this.discoverResolverDescriptors(), markAllowedCrossRealmGraphqlObjects);
|
|
320
|
+
}
|
|
321
|
+
discoverResolverDescriptors() {
|
|
322
|
+
return discoverResolverDescriptors(this.compiledModules, this.options);
|
|
323
|
+
}
|
|
324
|
+
registerMiddleware() {
|
|
325
|
+
for (const compiledModule of this.compiledModules) {
|
|
326
|
+
if (!compiledModule.providerTokens.has(_GraphqlLifecycleServ)) {
|
|
327
|
+
continue;
|
|
328
|
+
}
|
|
329
|
+
const middleware = compiledModule.definition.middleware ?? [];
|
|
330
|
+
if (!middleware.includes(this.middleware)) {
|
|
331
|
+
compiledModule.definition.middleware = [...middleware, this.middleware];
|
|
332
|
+
continue;
|
|
333
|
+
}
|
|
334
|
+
compiledModule.definition.middleware = [...middleware];
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
unregisterMiddleware() {
|
|
338
|
+
for (const compiledModule of this.compiledModules) {
|
|
339
|
+
if (!compiledModule.providerTokens.has(_GraphqlLifecycleServ)) {
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
342
|
+
const middleware = compiledModule.definition.middleware ?? [];
|
|
343
|
+
const remaining = [];
|
|
344
|
+
for (const entry of middleware) {
|
|
345
|
+
if (entry !== this.middleware) {
|
|
346
|
+
remaining.push(entry);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
compiledModule.definition.middleware = remaining;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
buildGraphqlContext(request, requestContextOverride, operationContainerOverride) {
|
|
353
|
+
const storedContext = graphqlRequestContextStorage.getStore();
|
|
354
|
+
const requestContext = {
|
|
355
|
+
connectionParams: requestContextOverride?.connectionParams,
|
|
356
|
+
principal: requestContextOverride?.principal ?? storedContext?.principal,
|
|
357
|
+
request: requestContextOverride?.request ?? storedContext?.request ?? buildFrameworkRequestFromFetchRequest(request),
|
|
358
|
+
socket: requestContextOverride?.socket
|
|
359
|
+
};
|
|
360
|
+
const customContext = this.options.context?.(requestContext) ?? {};
|
|
361
|
+
const operationContainer = operationContainerOverride ?? this.getOrCreateOperationContainer(request);
|
|
362
|
+
return {
|
|
363
|
+
...customContext,
|
|
364
|
+
connectionParams: requestContext.connectionParams,
|
|
365
|
+
principal: requestContext.principal,
|
|
366
|
+
request: requestContext.request,
|
|
367
|
+
socket: requestContext.socket,
|
|
368
|
+
[GRAPHQL_OPERATION_CONTAINER]: operationContainer
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
registerWebSocketTransport() {
|
|
372
|
+
if (!this.isWebSocketTransportEnabled() || this.yoga === undefined || this.websocketUpgradeListener !== undefined) {
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
const upgradeServer = this.resolveUpgradeServer();
|
|
376
|
+
const websocketLimits = this.resolveWebSocketLimits();
|
|
377
|
+
const websocketServer = new WebSocketServer({
|
|
378
|
+
handleProtocols: protocols => handleProtocols(protocols),
|
|
379
|
+
maxPayload: websocketLimits?.maxPayloadBytes ?? 0,
|
|
380
|
+
noServer: true
|
|
381
|
+
});
|
|
382
|
+
const upgradeListener = (request, socket, head) => {
|
|
383
|
+
const targetPath = new URL(request.url ?? '/', 'http://localhost').pathname;
|
|
384
|
+
if (!isGraphqlPath(targetPath)) {
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
if (websocketLimits && websocketServer.clients.size >= websocketLimits.maxConnections) {
|
|
388
|
+
this.rejectWebSocketUpgrade(socket, 503, 'GraphQL websocket connection count exceeds the configured limit.');
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
websocketServer.handleUpgrade(request, socket, head, websocket => {
|
|
392
|
+
websocketServer.emit('connection', websocket, request);
|
|
393
|
+
});
|
|
394
|
+
};
|
|
395
|
+
const websocketDisposable = useServer({
|
|
396
|
+
connectionInitWaitTimeout: this.options.subscriptions?.websocket?.connectionInitWaitTimeoutMs,
|
|
397
|
+
execute: args => {
|
|
398
|
+
if (!this.executeGraphqlOperation) {
|
|
399
|
+
throw new Error('GraphQL execute function not initialized.');
|
|
400
|
+
}
|
|
401
|
+
return this.executeGraphqlOperation(args);
|
|
402
|
+
},
|
|
403
|
+
onComplete: async (context, message) => {
|
|
404
|
+
await this.disposeWebSocketOperationContainer(context.extra.socket, message.id);
|
|
405
|
+
},
|
|
406
|
+
onDisconnect: async context => {
|
|
407
|
+
await this.disposeAllWebSocketOperationContainers(context.extra.socket);
|
|
408
|
+
},
|
|
409
|
+
onSubscribe: async (context, message) => this.handleWebSocketSubscribe(context, message.id, message.payload),
|
|
410
|
+
subscribe: args => {
|
|
411
|
+
if (!this.subscribeGraphqlOperation) {
|
|
412
|
+
throw new Error('GraphQL subscribe function not initialized.');
|
|
413
|
+
}
|
|
414
|
+
return this.subscribeGraphqlOperation(args);
|
|
415
|
+
}
|
|
416
|
+
}, websocketServer, this.options.subscriptions?.websocket?.keepAliveMs);
|
|
417
|
+
upgradeServer.on('upgrade', upgradeListener);
|
|
418
|
+
this.websocketDisposable = websocketDisposable;
|
|
419
|
+
this.websocketServer = websocketServer;
|
|
420
|
+
this.websocketUpgradeListener = upgradeListener;
|
|
421
|
+
this.websocketUpgradeServer = upgradeServer;
|
|
422
|
+
}
|
|
423
|
+
async unregisterWebSocketTransport() {
|
|
424
|
+
if (this.websocketUpgradeListener && this.websocketUpgradeServer) {
|
|
425
|
+
this.websocketUpgradeServer.off('upgrade', this.websocketUpgradeListener);
|
|
426
|
+
}
|
|
427
|
+
this.websocketUpgradeListener = undefined;
|
|
428
|
+
this.websocketUpgradeServer = undefined;
|
|
429
|
+
if (this.websocketServer) {
|
|
430
|
+
for (const client of this.websocketServer.clients) {
|
|
431
|
+
client.terminate();
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
if (this.websocketDisposable) {
|
|
435
|
+
try {
|
|
436
|
+
await this.websocketDisposable.dispose();
|
|
437
|
+
} catch (error) {
|
|
438
|
+
this.logger.error('Failed to dispose GraphQL websocket transport.', error, 'GraphqlLifecycleService');
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
this.websocketDisposable = undefined;
|
|
442
|
+
if (this.websocketServer) {
|
|
443
|
+
try {
|
|
444
|
+
await closeWebSocketServer(this.websocketServer);
|
|
445
|
+
} catch (error) {
|
|
446
|
+
this.logger.error('Failed to close GraphQL websocket server.', error, 'GraphqlLifecycleService');
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
this.websocketServer = undefined;
|
|
450
|
+
for (const socketKey of this.websocketOperationContainers.keys()) {
|
|
451
|
+
await this.disposeAllWebSocketOperationContainers(socketKey);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
isWebSocketTransportEnabled() {
|
|
455
|
+
return this.options.subscriptions?.websocket?.enabled === true;
|
|
456
|
+
}
|
|
457
|
+
resolveUpgradeServer() {
|
|
458
|
+
if (typeof this.adapter.getServer !== 'function') {
|
|
459
|
+
throw new Error('GraphQL websocket subscriptions require an HTTP adapter with getServer(). Use the Node HTTP adapter or provide a compatible adapter implementation.');
|
|
460
|
+
}
|
|
461
|
+
const server = this.adapter.getServer();
|
|
462
|
+
if (!hasNodeUpgradeServer(server)) {
|
|
463
|
+
throw new Error('GraphQL websocket subscriptions require adapter.getServer() to return a Node HTTP/S server that supports upgrade listeners.');
|
|
464
|
+
}
|
|
465
|
+
return server;
|
|
466
|
+
}
|
|
467
|
+
async handleWebSocketSubscribe(context, operationId, payload) {
|
|
468
|
+
const yoga = this.yoga;
|
|
469
|
+
if (!yoga) {
|
|
470
|
+
throw new Error('GraphQL server not initialized.');
|
|
471
|
+
}
|
|
472
|
+
const websocketLimitError = this.createWebSocketOperationLimitError(context.extra.socket, operationId);
|
|
473
|
+
if (websocketLimitError) {
|
|
474
|
+
return [websocketLimitError];
|
|
475
|
+
}
|
|
476
|
+
const frameworkRequest = buildFrameworkRequestFromIncomingMessage(context.extra.request);
|
|
477
|
+
const fetchRequest = toFetchRequest(frameworkRequest);
|
|
478
|
+
const operationContainer = this.getOrCreateWebSocketOperationContainer(context.extra.socket, operationId);
|
|
479
|
+
const graphqlContext = this.buildGraphqlContext(fetchRequest, {
|
|
480
|
+
connectionParams: isConnectionParamsRecord(context.connectionParams) ? context.connectionParams : undefined,
|
|
481
|
+
request: frameworkRequest,
|
|
482
|
+
socket: context.extra.socket
|
|
483
|
+
}, operationContainer);
|
|
484
|
+
try {
|
|
485
|
+
const {
|
|
486
|
+
contextFactory,
|
|
487
|
+
parse,
|
|
488
|
+
schema,
|
|
489
|
+
validate
|
|
490
|
+
} = yoga.getEnveloped({
|
|
491
|
+
request: fetchRequest,
|
|
492
|
+
[GRAPHQL_CONTEXT_OVERRIDE]: graphqlContext
|
|
493
|
+
});
|
|
494
|
+
const document = parse(payload.query);
|
|
495
|
+
const validationErrors = validate(schema, document);
|
|
496
|
+
if (validationErrors.length > 0) {
|
|
497
|
+
await this.disposeWebSocketOperationContainer(context.extra.socket, operationId);
|
|
498
|
+
return validationErrors;
|
|
499
|
+
}
|
|
500
|
+
return {
|
|
501
|
+
contextValue: await contextFactory(),
|
|
502
|
+
document,
|
|
503
|
+
operationName: payload.operationName ?? undefined,
|
|
504
|
+
schema,
|
|
505
|
+
variableValues: payload.variables
|
|
506
|
+
};
|
|
507
|
+
} catch (error) {
|
|
508
|
+
await this.disposeWebSocketOperationContainer(context.extra.socket, operationId);
|
|
509
|
+
throw error;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
createWebSocketOperationLimitError(socketKey, operationId) {
|
|
513
|
+
const limits = this.resolveWebSocketLimits();
|
|
514
|
+
if (!limits) {
|
|
515
|
+
return undefined;
|
|
516
|
+
}
|
|
517
|
+
const socketContainers = this.websocketOperationContainers.get(socketKey);
|
|
518
|
+
if (socketContainers?.has(operationId) || (socketContainers?.size ?? 0) < limits.maxOperationsPerConnection) {
|
|
519
|
+
return undefined;
|
|
520
|
+
}
|
|
521
|
+
const GraphQLError = this.graphQLErrorConstructor;
|
|
522
|
+
if (!GraphQLError) {
|
|
523
|
+
throw new Error('GraphQL error constructor not initialized.');
|
|
524
|
+
}
|
|
525
|
+
return new GraphQLError(`GraphQL websocket active operation count exceeds the configured limit of ${String(limits.maxOperationsPerConnection)}.`);
|
|
526
|
+
}
|
|
527
|
+
rejectWebSocketUpgrade(socket, statusCode, message) {
|
|
528
|
+
if (!socket.writable) {
|
|
529
|
+
socket.destroy();
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
const body = `${message}\n`;
|
|
533
|
+
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(Buffer.byteLength(body))}`, '', body].join('\r\n'));
|
|
534
|
+
socket.destroy();
|
|
535
|
+
}
|
|
536
|
+
getOrCreateWebSocketOperationContainer(socketKey, operationId) {
|
|
537
|
+
const existingSocketContainers = this.websocketOperationContainers.get(socketKey);
|
|
538
|
+
if (existingSocketContainers?.has(operationId)) {
|
|
539
|
+
return existingSocketContainers.get(operationId);
|
|
540
|
+
}
|
|
541
|
+
const created = this.runtimeContainer.createRequestScope();
|
|
542
|
+
const socketContainers = existingSocketContainers ?? new Map();
|
|
543
|
+
socketContainers.set(operationId, created);
|
|
544
|
+
this.websocketOperationContainers.set(socketKey, socketContainers);
|
|
545
|
+
return created;
|
|
546
|
+
}
|
|
547
|
+
async disposeWebSocketOperationContainer(socketKey, operationId) {
|
|
548
|
+
const socketContainers = this.websocketOperationContainers.get(socketKey);
|
|
549
|
+
const operationContainer = socketContainers?.get(operationId);
|
|
550
|
+
if (!operationContainer) {
|
|
551
|
+
return;
|
|
552
|
+
}
|
|
553
|
+
socketContainers?.delete(operationId);
|
|
554
|
+
if (socketContainers && socketContainers.size === 0) {
|
|
555
|
+
this.websocketOperationContainers.delete(socketKey);
|
|
556
|
+
}
|
|
557
|
+
try {
|
|
558
|
+
await operationContainer.dispose();
|
|
559
|
+
} catch (error) {
|
|
560
|
+
this.logger.error('Failed to dispose GraphQL websocket operation container.', error, 'GraphqlLifecycleService');
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
async disposeAllWebSocketOperationContainers(socketKey) {
|
|
564
|
+
const socketContainers = this.websocketOperationContainers.get(socketKey);
|
|
565
|
+
if (!socketContainers) {
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
568
|
+
this.websocketOperationContainers.delete(socketKey);
|
|
569
|
+
for (const operationContainer of socketContainers.values()) {
|
|
570
|
+
try {
|
|
571
|
+
await operationContainer.dispose();
|
|
572
|
+
} catch (error) {
|
|
573
|
+
this.logger.error('Failed to dispose GraphQL websocket operation container.', error, 'GraphqlLifecycleService');
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
getOrCreateOperationContainer(request) {
|
|
578
|
+
const existing = this.operationContainers.get(request);
|
|
579
|
+
if (existing) {
|
|
580
|
+
return existing;
|
|
581
|
+
}
|
|
582
|
+
const created = this.runtimeContainer.createRequestScope();
|
|
583
|
+
this.operationContainers.set(request, created);
|
|
584
|
+
return created;
|
|
585
|
+
}
|
|
586
|
+
async disposeOperationContainer(request) {
|
|
587
|
+
const operationContainer = this.operationContainers.get(request);
|
|
588
|
+
if (!operationContainer) {
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
this.operationContainers.delete(request);
|
|
592
|
+
try {
|
|
593
|
+
await operationContainer.dispose();
|
|
594
|
+
} catch (error) {
|
|
595
|
+
this.logger.error('Failed to dispose GraphQL operation container.', error, 'GraphqlLifecycleService');
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
static {
|
|
599
|
+
_initClass2();
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
export { _GraphqlLifecycleServ as GraphqlLifecycleService };
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { FrameworkRequest, FrameworkResponse } from '@fluojs/http';
|
|
2
|
+
export declare function isGraphqlPath(path: string): boolean;
|
|
3
|
+
export declare function toFetchRequest(request: FrameworkRequest): Request;
|
|
4
|
+
export declare function writeFetchResponse(fetchResponse: Response, frameworkResponse: FrameworkResponse): Promise<void>;
|
|
5
|
+
//# sourceMappingURL=transport.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../../src/transport/transport.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,iBAAiB,EAA2B,MAAM,cAAc,CAAC;AAEjG,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEnD;AAsED,wBAAgB,cAAc,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAUjE;AAsBD,wBAAsB,kBAAkB,CAAC,aAAa,EAAE,QAAQ,EAAE,iBAAiB,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAqDrH"}
|