@graphql-tools/delegate 12.0.20 → 12.1.0-alpha-9de3a237bd0e11b54357e42eb93360646f7edf5b

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,18 +1,45 @@
1
1
  # @graphql-tools/delegate
2
2
 
3
- ## 12.0.20
4
- ### Patch Changes
3
+ ## 12.1.0-alpha-9de3a237bd0e11b54357e42eb93360646f7edf5b
4
+ ### Minor Changes
5
5
 
6
6
 
7
7
 
8
- - [#2474](https://github.com/graphql-hive/gateway/pull/2474) [`8a08de3`](https://github.com/graphql-hive/gateway/commit/8a08de36be598f975ba500a28a9bd9710ead2d66) Thanks [@enisdenjo](https://github.com/enisdenjo)! - Fix delegated argument variable type compatibility
8
+ - [#2473](https://github.com/graphql-hive/gateway/pull/2473) [`b42de25`](https://github.com/graphql-hive/gateway/commit/b42de25be0b656a0fbfe76f85f3cea287865184e) Thanks [@enisdenjo](https://github.com/enisdenjo)! - Introduce `resolveMergedTypeReference`, a helper that resolves plain objects returned by local resolvers through type merging
9
+
10
+ When a resolver returns an object containing only the key fields of a merged type, the helper delegates just the missing requested fields to the owning subschema and returns a regular external object, so nested fields keep merging as usual. Fields the object already carries are never fetched again, and objects that already satisfy the request (or satisfy no merge key) are returned untouched.
11
+
12
+ ```ts
13
+ // local resolver returns only the key
14
+ const payload = { id: '1' };
9
15
 
10
- Avoid reusing variables whose types are incompatible with target schema argument types, and create correctly typed variables instead.
16
+ // client asks for { name surname } -> only `name` and `surname` are
17
+ // delegated to the subschema owning `Person`, `id` is kept as-is
18
+ resolveMergedTypeReference(payload, context, info);
19
+ ```
20
+
21
+ ### Patch Changes
11
22
 
12
23
 
13
- - [#2475](https://github.com/graphql-hive/gateway/pull/2475) [`65ce370`](https://github.com/graphql-hive/gateway/commit/65ce3702231068c946d4f95147b556cab57ad28c) Thanks [@enisdenjo](https://github.com/enisdenjo)! - Fix enum argument serialization when delegating to a field removed from the transformed schema.
24
+
25
+ - [#2473](https://github.com/graphql-hive/gateway/pull/2473) [`b42de25`](https://github.com/graphql-hive/gateway/commit/b42de25be0b656a0fbfe76f85f3cea287865184e) Thanks [@enisdenjo](https://github.com/enisdenjo)! - `defaultMergedResolver` now falls back to the literal field name when an external object does not have the requested response key
26
+
27
+ Plain resolver data merged into an external object is keyed by field name, so an aliased request used to resolve to `null` even though the value was there:
28
+
29
+ ```graphql
30
+ {
31
+ person {
32
+ fullName: name
33
+ }
34
+ }
35
+ ```
36
+
37
+ ```ts
38
+ // merged object carries resolver data by field name
39
+ { id: '1', name: 'Local' }
40
+ ```
14
41
 
15
- When a delegated query field is filtered from the transformed schema, its arguments now use the original subschema for type lookup. This ensures enum values are sent as typed variables instead of invalid quoted enum literals while preserving input field transforms for fields that remain exposed.
42
+ Previously `fullName` was `null` because only the alias was looked up. Now it resolves to `'Local'` by field name, matching plain graphql-js behavior. When the response key is present (e.g. the field came from a subschema), it is still preferred.
16
43
 
17
44
  ## 12.0.19
18
45
  ### Patch Changes
package/dist/index.cjs CHANGED
@@ -2354,18 +2354,10 @@ function createRequest({
2354
2354
  const existingArgNode = fieldNode?.arguments?.find(
2355
2355
  (argNode) => argNode.name.value === argName
2356
2356
  );
2357
- if (existingArgNode?.value.kind === graphql.Kind.VARIABLE && argInstance) {
2357
+ if (existingArgNode?.value.kind === graphql.Kind.VARIABLE) {
2358
2358
  const varName = existingArgNode.value.name.value;
2359
2359
  const varValue = newVariables[varName];
2360
- const variableDefinition = variableDefinitions.find(
2361
- (definition) => definition.variable.name.value === varName
2362
- );
2363
- const variableType = variableDefinition && targetSchema ? graphql.typeFromAST(info?.schema ?? targetSchema, variableDefinition.type) : void 0;
2364
- if (varValue === argValue && variableType && graphql.isTypeSubTypeOf(
2365
- info?.schema ?? targetSchema,
2366
- variableType,
2367
- argInstance.type
2368
- )) {
2360
+ if (varValue === argValue) {
2369
2361
  argNodes.push(existingArgNode);
2370
2362
  continue;
2371
2363
  }
@@ -2386,16 +2378,6 @@ function createRequest({
2386
2378
  varName = `_${i++}_${rootFieldName}_${argName}`;
2387
2379
  }
2388
2380
  }
2389
- if (existingArgNode?.value.kind === graphql.Kind.VARIABLE) {
2390
- const existingVarName = existingArgNode.value.name.value;
2391
- const existingVarIndex = variableDefinitions.findIndex(
2392
- (definition) => definition.variable.name.value === existingVarName
2393
- );
2394
- if (existingVarIndex !== -1) {
2395
- variableDefinitions.splice(existingVarIndex, 1);
2396
- delete newVariables[existingVarName];
2397
- }
2398
- }
2399
2381
  variableDefinitions.push({
2400
2382
  kind: graphql.Kind.VARIABLE_DEFINITION,
2401
2383
  variable: {
@@ -2567,6 +2549,9 @@ function defaultMergedResolver(parent, args, context, info) {
2567
2549
  }
2568
2550
  return deferred.promise;
2569
2551
  }
2552
+ if (Object.prototype.hasOwnProperty.call(parent, info.fieldName)) {
2553
+ return graphql.defaultFieldResolver(parent, args, context, info);
2554
+ }
2570
2555
  return void 0;
2571
2556
  }
2572
2557
  return handleResult(parent, responseKey, context, info);
@@ -2995,7 +2980,7 @@ function delegateToSchema(options) {
2995
2980
  const request = createRequest({
2996
2981
  subgraphName: schema.name,
2997
2982
  fragments,
2998
- targetSchema: targetSchema.getQueryType()?.getFields()[fieldName] == null && isSubschemaConfig(schema) ? schema.schema : targetSchema,
2983
+ targetSchema,
2999
2984
  rootValue,
3000
2985
  targetOperationName: operationName,
3001
2986
  targetOperation: operation,
@@ -3204,6 +3189,179 @@ function setObjectKeyPath(obj, path, value) {
3204
3189
  current[lastKey] = existingValue ? utils.mergeDeep([existingValue, value]) : value;
3205
3190
  }
3206
3191
 
3192
+ function valueSatisfiesSelectionSet(value, selectionSet) {
3193
+ if (Array.isArray(value)) {
3194
+ return value.every(
3195
+ (item) => valueSatisfiesSelectionSet(item, selectionSet)
3196
+ );
3197
+ }
3198
+ if (value == null || typeof value !== "object") {
3199
+ return false;
3200
+ }
3201
+ const objectValue = value;
3202
+ return selectionSet.selections.every((selection) => {
3203
+ if (selection.kind === graphql.Kind.INLINE_FRAGMENT) {
3204
+ return valueSatisfiesSelectionSet(value, selection.selectionSet);
3205
+ }
3206
+ if (selection.kind !== graphql.Kind.FIELD) {
3207
+ return false;
3208
+ }
3209
+ const responseKey = selection.alias?.value || selection.name.value;
3210
+ return objectValue[responseKey] !== void 0 && (selection.selectionSet == null || valueSatisfiesSelectionSet(
3211
+ objectValue[responseKey],
3212
+ selection.selectionSet
3213
+ ));
3214
+ });
3215
+ }
3216
+ function resolveMergedTypeReference(result, context, info, stitchingInfo = info.schema.extensions?.["stitchingInfo"], providedFields) {
3217
+ if (stitchingInfo == null || result == null) {
3218
+ return result;
3219
+ }
3220
+ return resolveOne(result, context, info, stitchingInfo, providedFields);
3221
+ }
3222
+ function getMissingSelections(value, selections, fragments = {}) {
3223
+ if (value == null || typeof value !== "object") {
3224
+ return [...selections];
3225
+ }
3226
+ const objectValue = value;
3227
+ const missing = [];
3228
+ for (const selection of selections) {
3229
+ if (selection.kind === graphql.Kind.INLINE_FRAGMENT) {
3230
+ const sub = getMissingSelections(
3231
+ value,
3232
+ selection.selectionSet.selections,
3233
+ fragments
3234
+ );
3235
+ if (sub.length) {
3236
+ missing.push({
3237
+ ...selection,
3238
+ selectionSet: { kind: graphql.Kind.SELECTION_SET, selections: sub }
3239
+ });
3240
+ }
3241
+ continue;
3242
+ }
3243
+ if (selection.kind === graphql.Kind.FRAGMENT_SPREAD) {
3244
+ const fragment = fragments[selection.name.value];
3245
+ if (fragment == null) {
3246
+ missing.push(selection);
3247
+ continue;
3248
+ }
3249
+ const sub = getMissingSelections(
3250
+ value,
3251
+ fragment.selectionSet.selections,
3252
+ fragments
3253
+ );
3254
+ if (sub.length) {
3255
+ missing.push({
3256
+ kind: graphql.Kind.INLINE_FRAGMENT,
3257
+ typeCondition: fragment.typeCondition,
3258
+ directives: [
3259
+ ...fragment.directives ?? [],
3260
+ ...selection.directives ?? []
3261
+ ],
3262
+ selectionSet: { kind: graphql.Kind.SELECTION_SET, selections: sub }
3263
+ });
3264
+ }
3265
+ continue;
3266
+ }
3267
+ const fieldValue = objectValue[selection.name.value];
3268
+ if (fieldValue === void 0 || fieldValue === null && selection.selectionSet != null) {
3269
+ missing.push(selection);
3270
+ continue;
3271
+ }
3272
+ if (selection.selectionSet != null) {
3273
+ if (Array.isArray(fieldValue)) {
3274
+ const anyMissing = fieldValue.some(
3275
+ (item) => getMissingSelections(
3276
+ item,
3277
+ selection.selectionSet.selections,
3278
+ fragments
3279
+ ).length > 0
3280
+ );
3281
+ if (anyMissing) {
3282
+ missing.push(selection);
3283
+ }
3284
+ } else {
3285
+ const sub = getMissingSelections(
3286
+ fieldValue,
3287
+ selection.selectionSet.selections,
3288
+ fragments
3289
+ );
3290
+ if (sub.length) {
3291
+ missing.push({
3292
+ ...selection,
3293
+ selectionSet: { kind: graphql.Kind.SELECTION_SET, selections: sub }
3294
+ });
3295
+ }
3296
+ }
3297
+ }
3298
+ }
3299
+ return missing;
3300
+ }
3301
+ function resolveOne(value, context, info, stitchingInfo, providedFields) {
3302
+ if (Array.isArray(value)) {
3303
+ return value.map(
3304
+ (item) => resolveOne(item, context, info, stitchingInfo, providedFields)
3305
+ );
3306
+ }
3307
+ if (value == null || typeof value !== "object" || value instanceof Error || isExternalObject(value)) {
3308
+ return value;
3309
+ }
3310
+ const objectValue = value;
3311
+ const returnType = graphql.getNamedType(info.returnType);
3312
+ const typeName = typeof objectValue["__typename"] === "string" ? objectValue["__typename"] : returnType.name;
3313
+ const mergedTypeInfo = stitchingInfo.mergedTypes[typeName];
3314
+ if (mergedTypeInfo == null) {
3315
+ return value;
3316
+ }
3317
+ let selections = info.fieldNodes.flatMap(
3318
+ (fieldNode) => fieldNode.selectionSet?.selections ?? []
3319
+ );
3320
+ if (providedFields?.size) {
3321
+ selections = selections.filter(
3322
+ (selection) => selection.kind !== graphql.Kind.FIELD || !providedFields.has(selection.name.value)
3323
+ );
3324
+ }
3325
+ const missingSelections = getMissingSelections(
3326
+ value,
3327
+ selections,
3328
+ info.fragments
3329
+ );
3330
+ if (!missingSelections.length) {
3331
+ return value;
3332
+ }
3333
+ const selectionSet = {
3334
+ kind: graphql.Kind.SELECTION_SET,
3335
+ selections: missingSelections
3336
+ };
3337
+ for (const [subschema, keySelectionSet] of mergedTypeInfo.selectionSets) {
3338
+ if (valueSatisfiesSelectionSet(value, keySelectionSet)) {
3339
+ const resolver = mergedTypeInfo.resolvers.get(subschema);
3340
+ if (resolver != null) {
3341
+ if (objectValue["__typename"] == null && !graphql.isAbstractType(returnType)) {
3342
+ objectValue["__typename"] = typeName;
3343
+ }
3344
+ return promiseHelpers.handleMaybePromise(
3345
+ () => resolver(
3346
+ value,
3347
+ context,
3348
+ info,
3349
+ subschema,
3350
+ selectionSet,
3351
+ void 0,
3352
+ returnType
3353
+ ),
3354
+ // value comes first so the delegation result wins on overlaps, while
3355
+ // payload fields outside the delegated selection (like the key) survive;
3356
+ // respectNonEnumerableSymbols keeps the external object annotation intact
3357
+ (resolved) => utils.mergeDeep([value, resolved], false, false, false, true)
3358
+ );
3359
+ }
3360
+ }
3361
+ }
3362
+ return value;
3363
+ }
3364
+
3207
3365
  function extractUnavailableFieldsFromSelectionSet(schema, fieldType, fieldSelectionSet, shouldAdd, fragments = {}) {
3208
3366
  if (graphql.isLeafType(fieldType)) {
3209
3367
  return [];
@@ -3505,4 +3663,5 @@ exports.isSubschemaConfig = isSubschemaConfig;
3505
3663
  exports.leftOverByDelegationPlan = leftOverByDelegationPlan;
3506
3664
  exports.mergeFields = mergeFields;
3507
3665
  exports.resolveExternalValue = resolveExternalValue;
3666
+ exports.resolveMergedTypeReference = resolveMergedTypeReference;
3508
3667
  exports.subtractSelectionSets = subtractSelectionSets;
package/dist/index.d.cts CHANGED
@@ -187,6 +187,8 @@ declare function createRequest({ subgraphName, fragments, rootValue, targetOpera
187
187
  * a) handle aliases for proxied schemas
188
188
  * b) handle errors from proxied schemas
189
189
  * c) handle external to internal enum conversion
190
+ * d) fall back to the field name for plain resolver data merged into
191
+ * external objects, which is keyed by literal field name
190
192
  */
191
193
  declare function defaultMergedResolver(parent: ExternalObject, args: Record<string, any>, context: Record<string, any>, info: GraphQLResolveInfo): any;
192
194
 
@@ -205,6 +207,38 @@ declare function handleResolverResult(resolverResult: any, subschema: Subschema,
205
207
 
206
208
  declare function resolveExternalValue<TContext extends Record<string, any>>(result: any, unpathedErrors: Array<GraphQLError>, subschema: GraphQLSchema | SubschemaConfig<any, any, any, TContext>, context?: Record<string, any>, info?: GraphQLResolveInfo, returnType?: GraphQLOutputType, skipTypeMerging?: boolean): any;
207
209
 
210
+ /**
211
+ * Takes a plain object produced by a local resolver and resolves it through
212
+ * type merging when needed:
213
+ *
214
+ * - If the object already satisfies the requested selection set, it is
215
+ * returned as-is and no delegation happens. This is the fast path for
216
+ * resolvers that return complete values.
217
+ * - If requested fields are missing but the object satisfies a merge key of
218
+ * the return type, only the missing fields are delegated to the owning
219
+ * subschema. The merge key is only the entry ticket; fields the object
220
+ * already carries are kept as-is and never fetched from the subschema, and
221
+ * the merged result keeps merging nested fields through the usual
222
+ * stitching flow.
223
+ * - Payloads are raw resolver results keyed by literal field name, so
224
+ * aliased requests are matched by field name and aliased fields already in
225
+ * the payload are not fetched again. Hydrated results carry only literal
226
+ * field names; `defaultMergedResolver` falls back to the field name when
227
+ * the aliased response key is absent, so incoming query aliases resolve
228
+ * with plain graphql-js semantics.
229
+ * - When merge keys of several subschemas are satisfied, the first match
230
+ * wins. Any match works because the delegation is pruned to the fields the
231
+ * chosen subschema provides, and nested merging covers the rest.
232
+ * - Fields listed in `providedFields` (fields that have their own resolver on
233
+ * the stitched schema) are excluded from the required selection, because
234
+ * they are resolved locally anyway. When the remaining selection is already
235
+ * satisfied by the object, no delegation happens at all.
236
+ * - Objects that satisfy no merge key, are already external, or are not
237
+ * objects at all are returned untouched, so missing non-nullable fields
238
+ * error downstream exactly as they would without this helper.
239
+ */
240
+ declare function resolveMergedTypeReference<TContext extends Record<string, any> = Record<string, any>>(result: any, context: TContext, info: GraphQLResolveInfo, stitchingInfo?: StitchingInfo<TContext>, providedFields?: ReadonlySet<string>): any;
241
+
208
242
  declare function isSubschemaConfig(value: any): value is SubschemaConfig<any, any, any, any>;
209
243
  declare function cloneSubschemaConfig(subschemaConfig: SubschemaConfig): SubschemaConfig;
210
244
 
@@ -237,4 +271,4 @@ declare function isPrototypePollutingKey(key: string): key is PrototypePolluting
237
271
 
238
272
  declare const handleOverrideByDelegation: (info: GraphQLResolveInfo$1, context: any, overrideHandler: OverrideHandler) => boolean;
239
273
 
240
- export { type BatchingOptions, type CreateProxyingResolverFn, type Deferred, type DelegationContext, type DelegationPlanBuilder, type DelegationPlanLeftOver, EMPTY_ARRAY, EMPTY_OBJECT, type ExternalObject, FIELD_SUBSCHEMA_MAP_SYMBOL, type ICreateProxyingResolverOptions, type ICreateRequest, type IDelegateRequestOptions, type IDelegateToSchemaOptions, type MergedFieldConfig, type MergedTypeConfig, type MergedTypeEntryPoint, type MergedTypeInfo, type MergedTypeResolver, type MergedTypeResolverOptions, OBJECT_SUBSCHEMA_SYMBOL, type OverrideHandler, PLAN_LEFT_OVER, type RequestTransform, type ResultTransform, type SchemaTransform, type StitchingInfo, Subschema, type SubschemaConfig, type Transform, Transformer, UNPATHED_ERRORS_SYMBOL, annotateExternalObject, applySchemaTransforms, cloneSubschemaConfig, createRequest, defaultMergedResolver, delegateRequest, delegateToSchema, extractUnavailableFields, extractUnavailableFieldsFromSelectionSet, getActualFieldNodes, getDelegatingOperation, getPlanLeftOverFromParent, getSubschema, getTypeInfo, getTypeInfoWithType, getUnpathedErrors, handleOverrideByDelegation, handleResolverResult, isExternalObject, isPrototypePollutingKey, isSubschema, isSubschemaConfig, leftOverByDelegationPlan, mergeFields, resolveExternalValue, subtractSelectionSets };
274
+ export { type BatchingOptions, type CreateProxyingResolverFn, type Deferred, type DelegationContext, type DelegationPlanBuilder, type DelegationPlanLeftOver, EMPTY_ARRAY, EMPTY_OBJECT, type ExternalObject, FIELD_SUBSCHEMA_MAP_SYMBOL, type ICreateProxyingResolverOptions, type ICreateRequest, type IDelegateRequestOptions, type IDelegateToSchemaOptions, type MergedFieldConfig, type MergedTypeConfig, type MergedTypeEntryPoint, type MergedTypeInfo, type MergedTypeResolver, type MergedTypeResolverOptions, OBJECT_SUBSCHEMA_SYMBOL, type OverrideHandler, PLAN_LEFT_OVER, type RequestTransform, type ResultTransform, type SchemaTransform, type StitchingInfo, Subschema, type SubschemaConfig, type Transform, Transformer, UNPATHED_ERRORS_SYMBOL, annotateExternalObject, applySchemaTransforms, cloneSubschemaConfig, createRequest, defaultMergedResolver, delegateRequest, delegateToSchema, extractUnavailableFields, extractUnavailableFieldsFromSelectionSet, getActualFieldNodes, getDelegatingOperation, getPlanLeftOverFromParent, getSubschema, getTypeInfo, getTypeInfoWithType, getUnpathedErrors, handleOverrideByDelegation, handleResolverResult, isExternalObject, isPrototypePollutingKey, isSubschema, isSubschemaConfig, leftOverByDelegationPlan, mergeFields, resolveExternalValue, resolveMergedTypeReference, subtractSelectionSets };
package/dist/index.d.ts CHANGED
@@ -187,6 +187,8 @@ declare function createRequest({ subgraphName, fragments, rootValue, targetOpera
187
187
  * a) handle aliases for proxied schemas
188
188
  * b) handle errors from proxied schemas
189
189
  * c) handle external to internal enum conversion
190
+ * d) fall back to the field name for plain resolver data merged into
191
+ * external objects, which is keyed by literal field name
190
192
  */
191
193
  declare function defaultMergedResolver(parent: ExternalObject, args: Record<string, any>, context: Record<string, any>, info: GraphQLResolveInfo): any;
192
194
 
@@ -205,6 +207,38 @@ declare function handleResolverResult(resolverResult: any, subschema: Subschema,
205
207
 
206
208
  declare function resolveExternalValue<TContext extends Record<string, any>>(result: any, unpathedErrors: Array<GraphQLError>, subschema: GraphQLSchema | SubschemaConfig<any, any, any, TContext>, context?: Record<string, any>, info?: GraphQLResolveInfo, returnType?: GraphQLOutputType, skipTypeMerging?: boolean): any;
207
209
 
210
+ /**
211
+ * Takes a plain object produced by a local resolver and resolves it through
212
+ * type merging when needed:
213
+ *
214
+ * - If the object already satisfies the requested selection set, it is
215
+ * returned as-is and no delegation happens. This is the fast path for
216
+ * resolvers that return complete values.
217
+ * - If requested fields are missing but the object satisfies a merge key of
218
+ * the return type, only the missing fields are delegated to the owning
219
+ * subschema. The merge key is only the entry ticket; fields the object
220
+ * already carries are kept as-is and never fetched from the subschema, and
221
+ * the merged result keeps merging nested fields through the usual
222
+ * stitching flow.
223
+ * - Payloads are raw resolver results keyed by literal field name, so
224
+ * aliased requests are matched by field name and aliased fields already in
225
+ * the payload are not fetched again. Hydrated results carry only literal
226
+ * field names; `defaultMergedResolver` falls back to the field name when
227
+ * the aliased response key is absent, so incoming query aliases resolve
228
+ * with plain graphql-js semantics.
229
+ * - When merge keys of several subschemas are satisfied, the first match
230
+ * wins. Any match works because the delegation is pruned to the fields the
231
+ * chosen subschema provides, and nested merging covers the rest.
232
+ * - Fields listed in `providedFields` (fields that have their own resolver on
233
+ * the stitched schema) are excluded from the required selection, because
234
+ * they are resolved locally anyway. When the remaining selection is already
235
+ * satisfied by the object, no delegation happens at all.
236
+ * - Objects that satisfy no merge key, are already external, or are not
237
+ * objects at all are returned untouched, so missing non-nullable fields
238
+ * error downstream exactly as they would without this helper.
239
+ */
240
+ declare function resolveMergedTypeReference<TContext extends Record<string, any> = Record<string, any>>(result: any, context: TContext, info: GraphQLResolveInfo, stitchingInfo?: StitchingInfo<TContext>, providedFields?: ReadonlySet<string>): any;
241
+
208
242
  declare function isSubschemaConfig(value: any): value is SubschemaConfig<any, any, any, any>;
209
243
  declare function cloneSubschemaConfig(subschemaConfig: SubschemaConfig): SubschemaConfig;
210
244
 
@@ -237,4 +271,4 @@ declare function isPrototypePollutingKey(key: string): key is PrototypePolluting
237
271
 
238
272
  declare const handleOverrideByDelegation: (info: GraphQLResolveInfo$1, context: any, overrideHandler: OverrideHandler) => boolean;
239
273
 
240
- export { type BatchingOptions, type CreateProxyingResolverFn, type Deferred, type DelegationContext, type DelegationPlanBuilder, type DelegationPlanLeftOver, EMPTY_ARRAY, EMPTY_OBJECT, type ExternalObject, FIELD_SUBSCHEMA_MAP_SYMBOL, type ICreateProxyingResolverOptions, type ICreateRequest, type IDelegateRequestOptions, type IDelegateToSchemaOptions, type MergedFieldConfig, type MergedTypeConfig, type MergedTypeEntryPoint, type MergedTypeInfo, type MergedTypeResolver, type MergedTypeResolverOptions, OBJECT_SUBSCHEMA_SYMBOL, type OverrideHandler, PLAN_LEFT_OVER, type RequestTransform, type ResultTransform, type SchemaTransform, type StitchingInfo, Subschema, type SubschemaConfig, type Transform, Transformer, UNPATHED_ERRORS_SYMBOL, annotateExternalObject, applySchemaTransforms, cloneSubschemaConfig, createRequest, defaultMergedResolver, delegateRequest, delegateToSchema, extractUnavailableFields, extractUnavailableFieldsFromSelectionSet, getActualFieldNodes, getDelegatingOperation, getPlanLeftOverFromParent, getSubschema, getTypeInfo, getTypeInfoWithType, getUnpathedErrors, handleOverrideByDelegation, handleResolverResult, isExternalObject, isPrototypePollutingKey, isSubschema, isSubschemaConfig, leftOverByDelegationPlan, mergeFields, resolveExternalValue, subtractSelectionSets };
274
+ export { type BatchingOptions, type CreateProxyingResolverFn, type Deferred, type DelegationContext, type DelegationPlanBuilder, type DelegationPlanLeftOver, EMPTY_ARRAY, EMPTY_OBJECT, type ExternalObject, FIELD_SUBSCHEMA_MAP_SYMBOL, type ICreateProxyingResolverOptions, type ICreateRequest, type IDelegateRequestOptions, type IDelegateToSchemaOptions, type MergedFieldConfig, type MergedTypeConfig, type MergedTypeEntryPoint, type MergedTypeInfo, type MergedTypeResolver, type MergedTypeResolverOptions, OBJECT_SUBSCHEMA_SYMBOL, type OverrideHandler, PLAN_LEFT_OVER, type RequestTransform, type ResultTransform, type SchemaTransform, type StitchingInfo, Subschema, type SubschemaConfig, type Transform, Transformer, UNPATHED_ERRORS_SYMBOL, annotateExternalObject, applySchemaTransforms, cloneSubschemaConfig, createRequest, defaultMergedResolver, delegateRequest, delegateToSchema, extractUnavailableFields, extractUnavailableFieldsFromSelectionSet, getActualFieldNodes, getDelegatingOperation, getPlanLeftOverFromParent, getSubschema, getTypeInfo, getTypeInfoWithType, getUnpathedErrors, handleOverrideByDelegation, handleResolverResult, isExternalObject, isPrototypePollutingKey, isSubschema, isSubschemaConfig, leftOverByDelegationPlan, mergeFields, resolveExternalValue, resolveMergedTypeReference, subtractSelectionSets };
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { memoize2, memoize1, collectFields, relocatedError, mergeDeep, promiseReduce, pathToArray, getResponseKeyFromInfo, memoize3, getDefinedRootType, createGraphQLError, implementsAbstractType, getRootTypeNames, astFromArg, astFromValueUntyped, asArray, isAsyncIterable, getOperationASTFromRequest } from '@graphql-tools/utils';
2
2
  export { createDeferred } from '@graphql-tools/utils';
3
- import { GraphQLError, locatedError, isAbstractType, getNullableType, isLeafType, isCompositeType, isListType, responsePathAsArray, Kind, TypeInfo, versionInfo, visit, visitWithTypeInfo, isNullableType, isUnionType, getNamedType, isObjectType, isInterfaceType, typeFromAST, isTypeSubTypeOf, isNonNullType, isInputObjectType, defaultFieldResolver, isSchema, validate } from 'graphql';
3
+ import { GraphQLError, locatedError, isAbstractType, getNullableType, isLeafType, isCompositeType, isListType, responsePathAsArray, Kind, TypeInfo, versionInfo, visit, visitWithTypeInfo, isNullableType, isUnionType, getNamedType, isObjectType, isInterfaceType, isNonNullType, isInputObjectType, defaultFieldResolver, isSchema, validate } from 'graphql';
4
4
  import { handleMaybePromise, isPromise, createDeferredPromise, mapAsyncIterator } from '@whatwg-node/promise-helpers';
5
5
  import { CRITICAL_ERROR, executorFromSchema } from '@graphql-tools/executor';
6
6
  export { executorFromSchema as createDefaultExecutor } from '@graphql-tools/executor';
@@ -2354,18 +2354,10 @@ function createRequest({
2354
2354
  const existingArgNode = fieldNode?.arguments?.find(
2355
2355
  (argNode) => argNode.name.value === argName
2356
2356
  );
2357
- if (existingArgNode?.value.kind === Kind.VARIABLE && argInstance) {
2357
+ if (existingArgNode?.value.kind === Kind.VARIABLE) {
2358
2358
  const varName = existingArgNode.value.name.value;
2359
2359
  const varValue = newVariables[varName];
2360
- const variableDefinition = variableDefinitions.find(
2361
- (definition) => definition.variable.name.value === varName
2362
- );
2363
- const variableType = variableDefinition && targetSchema ? typeFromAST(info?.schema ?? targetSchema, variableDefinition.type) : void 0;
2364
- if (varValue === argValue && variableType && isTypeSubTypeOf(
2365
- info?.schema ?? targetSchema,
2366
- variableType,
2367
- argInstance.type
2368
- )) {
2360
+ if (varValue === argValue) {
2369
2361
  argNodes.push(existingArgNode);
2370
2362
  continue;
2371
2363
  }
@@ -2386,16 +2378,6 @@ function createRequest({
2386
2378
  varName = `_${i++}_${rootFieldName}_${argName}`;
2387
2379
  }
2388
2380
  }
2389
- if (existingArgNode?.value.kind === Kind.VARIABLE) {
2390
- const existingVarName = existingArgNode.value.name.value;
2391
- const existingVarIndex = variableDefinitions.findIndex(
2392
- (definition) => definition.variable.name.value === existingVarName
2393
- );
2394
- if (existingVarIndex !== -1) {
2395
- variableDefinitions.splice(existingVarIndex, 1);
2396
- delete newVariables[existingVarName];
2397
- }
2398
- }
2399
2381
  variableDefinitions.push({
2400
2382
  kind: Kind.VARIABLE_DEFINITION,
2401
2383
  variable: {
@@ -2567,6 +2549,9 @@ function defaultMergedResolver(parent, args, context, info) {
2567
2549
  }
2568
2550
  return deferred.promise;
2569
2551
  }
2552
+ if (Object.prototype.hasOwnProperty.call(parent, info.fieldName)) {
2553
+ return defaultFieldResolver(parent, args, context, info);
2554
+ }
2570
2555
  return void 0;
2571
2556
  }
2572
2557
  return handleResult(parent, responseKey, context, info);
@@ -2995,7 +2980,7 @@ function delegateToSchema(options) {
2995
2980
  const request = createRequest({
2996
2981
  subgraphName: schema.name,
2997
2982
  fragments,
2998
- targetSchema: targetSchema.getQueryType()?.getFields()[fieldName] == null && isSubschemaConfig(schema) ? schema.schema : targetSchema,
2983
+ targetSchema,
2999
2984
  rootValue,
3000
2985
  targetOperationName: operationName,
3001
2986
  targetOperation: operation,
@@ -3204,6 +3189,179 @@ function setObjectKeyPath(obj, path, value) {
3204
3189
  current[lastKey] = existingValue ? mergeDeep([existingValue, value]) : value;
3205
3190
  }
3206
3191
 
3192
+ function valueSatisfiesSelectionSet(value, selectionSet) {
3193
+ if (Array.isArray(value)) {
3194
+ return value.every(
3195
+ (item) => valueSatisfiesSelectionSet(item, selectionSet)
3196
+ );
3197
+ }
3198
+ if (value == null || typeof value !== "object") {
3199
+ return false;
3200
+ }
3201
+ const objectValue = value;
3202
+ return selectionSet.selections.every((selection) => {
3203
+ if (selection.kind === Kind.INLINE_FRAGMENT) {
3204
+ return valueSatisfiesSelectionSet(value, selection.selectionSet);
3205
+ }
3206
+ if (selection.kind !== Kind.FIELD) {
3207
+ return false;
3208
+ }
3209
+ const responseKey = selection.alias?.value || selection.name.value;
3210
+ return objectValue[responseKey] !== void 0 && (selection.selectionSet == null || valueSatisfiesSelectionSet(
3211
+ objectValue[responseKey],
3212
+ selection.selectionSet
3213
+ ));
3214
+ });
3215
+ }
3216
+ function resolveMergedTypeReference(result, context, info, stitchingInfo = info.schema.extensions?.["stitchingInfo"], providedFields) {
3217
+ if (stitchingInfo == null || result == null) {
3218
+ return result;
3219
+ }
3220
+ return resolveOne(result, context, info, stitchingInfo, providedFields);
3221
+ }
3222
+ function getMissingSelections(value, selections, fragments = {}) {
3223
+ if (value == null || typeof value !== "object") {
3224
+ return [...selections];
3225
+ }
3226
+ const objectValue = value;
3227
+ const missing = [];
3228
+ for (const selection of selections) {
3229
+ if (selection.kind === Kind.INLINE_FRAGMENT) {
3230
+ const sub = getMissingSelections(
3231
+ value,
3232
+ selection.selectionSet.selections,
3233
+ fragments
3234
+ );
3235
+ if (sub.length) {
3236
+ missing.push({
3237
+ ...selection,
3238
+ selectionSet: { kind: Kind.SELECTION_SET, selections: sub }
3239
+ });
3240
+ }
3241
+ continue;
3242
+ }
3243
+ if (selection.kind === Kind.FRAGMENT_SPREAD) {
3244
+ const fragment = fragments[selection.name.value];
3245
+ if (fragment == null) {
3246
+ missing.push(selection);
3247
+ continue;
3248
+ }
3249
+ const sub = getMissingSelections(
3250
+ value,
3251
+ fragment.selectionSet.selections,
3252
+ fragments
3253
+ );
3254
+ if (sub.length) {
3255
+ missing.push({
3256
+ kind: Kind.INLINE_FRAGMENT,
3257
+ typeCondition: fragment.typeCondition,
3258
+ directives: [
3259
+ ...fragment.directives ?? [],
3260
+ ...selection.directives ?? []
3261
+ ],
3262
+ selectionSet: { kind: Kind.SELECTION_SET, selections: sub }
3263
+ });
3264
+ }
3265
+ continue;
3266
+ }
3267
+ const fieldValue = objectValue[selection.name.value];
3268
+ if (fieldValue === void 0 || fieldValue === null && selection.selectionSet != null) {
3269
+ missing.push(selection);
3270
+ continue;
3271
+ }
3272
+ if (selection.selectionSet != null) {
3273
+ if (Array.isArray(fieldValue)) {
3274
+ const anyMissing = fieldValue.some(
3275
+ (item) => getMissingSelections(
3276
+ item,
3277
+ selection.selectionSet.selections,
3278
+ fragments
3279
+ ).length > 0
3280
+ );
3281
+ if (anyMissing) {
3282
+ missing.push(selection);
3283
+ }
3284
+ } else {
3285
+ const sub = getMissingSelections(
3286
+ fieldValue,
3287
+ selection.selectionSet.selections,
3288
+ fragments
3289
+ );
3290
+ if (sub.length) {
3291
+ missing.push({
3292
+ ...selection,
3293
+ selectionSet: { kind: Kind.SELECTION_SET, selections: sub }
3294
+ });
3295
+ }
3296
+ }
3297
+ }
3298
+ }
3299
+ return missing;
3300
+ }
3301
+ function resolveOne(value, context, info, stitchingInfo, providedFields) {
3302
+ if (Array.isArray(value)) {
3303
+ return value.map(
3304
+ (item) => resolveOne(item, context, info, stitchingInfo, providedFields)
3305
+ );
3306
+ }
3307
+ if (value == null || typeof value !== "object" || value instanceof Error || isExternalObject(value)) {
3308
+ return value;
3309
+ }
3310
+ const objectValue = value;
3311
+ const returnType = getNamedType(info.returnType);
3312
+ const typeName = typeof objectValue["__typename"] === "string" ? objectValue["__typename"] : returnType.name;
3313
+ const mergedTypeInfo = stitchingInfo.mergedTypes[typeName];
3314
+ if (mergedTypeInfo == null) {
3315
+ return value;
3316
+ }
3317
+ let selections = info.fieldNodes.flatMap(
3318
+ (fieldNode) => fieldNode.selectionSet?.selections ?? []
3319
+ );
3320
+ if (providedFields?.size) {
3321
+ selections = selections.filter(
3322
+ (selection) => selection.kind !== Kind.FIELD || !providedFields.has(selection.name.value)
3323
+ );
3324
+ }
3325
+ const missingSelections = getMissingSelections(
3326
+ value,
3327
+ selections,
3328
+ info.fragments
3329
+ );
3330
+ if (!missingSelections.length) {
3331
+ return value;
3332
+ }
3333
+ const selectionSet = {
3334
+ kind: Kind.SELECTION_SET,
3335
+ selections: missingSelections
3336
+ };
3337
+ for (const [subschema, keySelectionSet] of mergedTypeInfo.selectionSets) {
3338
+ if (valueSatisfiesSelectionSet(value, keySelectionSet)) {
3339
+ const resolver = mergedTypeInfo.resolvers.get(subschema);
3340
+ if (resolver != null) {
3341
+ if (objectValue["__typename"] == null && !isAbstractType(returnType)) {
3342
+ objectValue["__typename"] = typeName;
3343
+ }
3344
+ return handleMaybePromise(
3345
+ () => resolver(
3346
+ value,
3347
+ context,
3348
+ info,
3349
+ subschema,
3350
+ selectionSet,
3351
+ void 0,
3352
+ returnType
3353
+ ),
3354
+ // value comes first so the delegation result wins on overlaps, while
3355
+ // payload fields outside the delegated selection (like the key) survive;
3356
+ // respectNonEnumerableSymbols keeps the external object annotation intact
3357
+ (resolved) => mergeDeep([value, resolved], false, false, false, true)
3358
+ );
3359
+ }
3360
+ }
3361
+ }
3362
+ return value;
3363
+ }
3364
+
3207
3365
  function extractUnavailableFieldsFromSelectionSet(schema, fieldType, fieldSelectionSet, shouldAdd, fragments = {}) {
3208
3366
  if (isLeafType(fieldType)) {
3209
3367
  return [];
@@ -3464,4 +3622,4 @@ function subtractSelectionSets(selectionSetA, selectionSetB, fragments = {}) {
3464
3622
  };
3465
3623
  }
3466
3624
 
3467
- export { EMPTY_ARRAY, EMPTY_OBJECT, FIELD_SUBSCHEMA_MAP_SYMBOL, OBJECT_SUBSCHEMA_SYMBOL, PLAN_LEFT_OVER, Subschema, Transformer, UNPATHED_ERRORS_SYMBOL, annotateExternalObject, applySchemaTransforms, cloneSubschemaConfig, createRequest, defaultMergedResolver, delegateRequest, delegateToSchema, extractUnavailableFields, extractUnavailableFieldsFromSelectionSet, getActualFieldNodes, getDelegatingOperation, getPlanLeftOverFromParent, getSubschema, getTypeInfo, getTypeInfoWithType, getUnpathedErrors, handleOverrideByDelegation, handleResolverResult, isExternalObject, isPrototypePollutingKey, isSubschema, isSubschemaConfig, leftOverByDelegationPlan, mergeFields, resolveExternalValue, subtractSelectionSets };
3625
+ export { EMPTY_ARRAY, EMPTY_OBJECT, FIELD_SUBSCHEMA_MAP_SYMBOL, OBJECT_SUBSCHEMA_SYMBOL, PLAN_LEFT_OVER, Subschema, Transformer, UNPATHED_ERRORS_SYMBOL, annotateExternalObject, applySchemaTransforms, cloneSubschemaConfig, createRequest, defaultMergedResolver, delegateRequest, delegateToSchema, extractUnavailableFields, extractUnavailableFieldsFromSelectionSet, getActualFieldNodes, getDelegatingOperation, getPlanLeftOverFromParent, getSubschema, getTypeInfo, getTypeInfoWithType, getUnpathedErrors, handleOverrideByDelegation, handleResolverResult, isExternalObject, isPrototypePollutingKey, isSubschema, isSubschemaConfig, leftOverByDelegationPlan, mergeFields, resolveExternalValue, resolveMergedTypeReference, subtractSelectionSets };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@graphql-tools/delegate",
3
- "version": "12.0.20",
3
+ "version": "12.1.0-alpha-9de3a237bd0e11b54357e42eb93360646f7edf5b",
4
4
  "type": "module",
5
5
  "description": "A set of utils for faster development of GraphQL tools",
6
6
  "repository": {