@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.
@@ -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
- return builtinScalarByGraphqlName(deps, maybeScalarName) ?? type;
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: clonedFields
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 normalizedTypes = config.types.map(itemType => normalizeObjectOutputType(deps, outputTypeCache, itemType));
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: normalizedTypes
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
- return new deps.GraphQLError('Validation failed.', {
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 input = await createResolverInput(deps, handler, args);
209
- return resolverMethod.call(instance, input, contextValue);
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 input = await createResolverInput(deps, handler, args);
217
- return await resolverMethod.call(instance, input, contextValue);
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 invokeResolver = createResolverInvoker(deps, runtimeContainer);
314
+ const objectFieldResolvers = new ObjectFieldResolverRegistry(resolverDescriptors);
315
+ const invokeResolver = createResolverInvoker(deps, runtimeContainer, markAllowedCrossRealmGraphqlObjects, objectFieldResolvers);
286
316
  const outputTypeCache = new Map();
287
- const queryFields = pickFieldsByType(deps, resolverDescriptors, 'query', markAllowedCrossRealmGraphqlObjects, outputTypeCache, invokeResolver);
288
- const mutationFields = pickFieldsByType(deps, resolverDescriptors, 'mutation', markAllowedCrossRealmGraphqlObjects, outputTypeCache, invokeResolver);
289
- const subscriptionFields = pickFieldsByType(deps, resolverDescriptors, 'subscription', markAllowedCrossRealmGraphqlObjects, outputTypeCache, invokeResolver);
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
- return new deps.GraphQLSchema({
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 websocketDisposable;
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;
@@ -1 +1 @@
1
- {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAGA,OAAO,EAAgD,KAAK,sBAAsB,EAAsD,MAAM,cAAc,CAAC;AAE7J,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,KAAK,EAAE,iBAAiB,EAAE,cAAc,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AA0BxH,OAAO,KAAK,EAEV,oBAAoB,EAGrB,MAAM,YAAY,CAAC;AAsIpB;;GAEG;AACH,qBACa,yBAAyB;IAEpC,SAAS,IAAI,SAAS;IAKtB,UAAU,IAAI,SAAS;CAGxB;AA+ID;;GAEG;AACH,qBACa,uBAAwB,YAAW,sBAAsB,EAAE,qBAAqB;IA6DzF,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;IAhE1B,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,mBAAmB,CAAkD;IAC7E,OAAO,CAAC,eAAe,CAA8B;IACrD,OAAO,CAAC,wBAAwB,CAAkC;IAClE,OAAO,CAAC,sBAAsB,CAAgC;IAC9D,OAAO,CAAC,uBAAuB,CAAyD;IACxF,OAAO,CAAC,6BAA6B,CAA2B;IAChE,OAAO,CAAC,yBAAyB,CAAyD;IAC1F,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;YA4E1B,4BAA4B;IAuC1C,OAAO,CAAC,2BAA2B;IAInC,OAAO,CAAC,oBAAoB;YAkBd,wBAAwB;IAwDtC,OAAO,CAAC,kCAAkC;IAwB1C,OAAO,CAAC,sBAAsB;IAoB9B,OAAO,CAAC,sCAAsC;YAchC,kCAAkC;YAqBlC,sCAAsC;IAkBpD,OAAO,CAAC,6BAA6B;YAYvB,yBAAyB;CAexC"}
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 className = constructor.prototype?.[Symbol.toStringTag];
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
- if (Symbol.toStringTag in value) {
112
- const valueClassName = value[Symbol.toStringTag];
113
- return valueClassName === className ? className : undefined;
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
- if (typeof tag === 'string' && tag.startsWith('GraphQL')) {
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
- createRequire
189
- } = await import('node:module');
190
- const runtimeRequire = createRequire(import.meta.url);
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
- websocketDisposable;
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.websocketUpgradeListener !== undefined) {
369
+ if (!this.isWebSocketTransportEnabled() || this.yoga === undefined || this.websocketTransport !== undefined) {
410
370
  return;
411
371
  }
412
- const [{
413
- handleProtocols
414
- }, {
415
- useServer
416
- }, {
417
- WebSocketServer
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
- onComplete: async (context, message) => {
448
- await this.disposeWebSocketOperationContainer(context.extra.socket, message.id);
449
- },
450
- onDisconnect: async context => {
451
- await this.disposeAllWebSocketOperationContainers(context.extra.socket);
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
- }, websocketServer, this.options.subscriptions?.websocket?.keepAliveMs);
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.websocketUpgradeListener && this.websocketUpgradeServer) {
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.websocketDisposable.dispose();
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.websocketDisposable = undefined;
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
- resolveUpgradeServer() {
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(context.extra.socket, operationId);
418
+ const websocketLimitError = this.createWebSocketOperationLimitError(request.socket, request.operationId);
517
419
  if (websocketLimitError) {
518
420
  return [websocketLimitError];
519
421
  }
520
- const frameworkRequest = buildFrameworkRequestFromIncomingMessage(context.extra.request);
521
- const fetchRequest = toFetchRequest(frameworkRequest);
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: isConnectionParamsRecord(context.connectionParams) ? context.connectionParams : undefined,
525
- request: frameworkRequest,
526
- socket: context.extra.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(context.extra.socket, operationId);
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(context.extra.socket, operationId);
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.