@graphql-tools/stitch 10.1.25 → 10.2.0-alpha-1c2de90cd18cb3a8fc5edcb2be2a464ee5b900e5

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,12 +1,45 @@
1
1
  # @graphql-tools/stitch
2
2
 
3
- ## 10.1.25
3
+ ## 10.2.0-alpha-1c2de90cd18cb3a8fc5edcb2be2a464ee5b900e5
4
+ ### Minor Changes
5
+
6
+
7
+
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)! - Automatically resolve plain merged-type references returned by local stitching resolvers.
9
+
10
+ Local fields introduced through `typeDefs` or `resolvers` are wrapped by `stitchSchemas`. When they return a partial merged type containing a usable key, stitching performs one initial delegation with type merging enabled. The existing stitching planner then handles computed fields, `@requires` dependencies, batching, nested entities, and fields owned by other subschemas.
11
+
4
12
  ### Patch Changes
5
13
 
6
- - Updated dependencies [[`8a08de3`](https://github.com/graphql-hive/gateway/commit/8a08de36be598f975ba500a28a9bd9710ead2d66), [`65ce370`](https://github.com/graphql-hive/gateway/commit/65ce3702231068c946d4f95147b556cab57ad28c)]:
7
- - @graphql-tools/delegate@12.0.20
8
- - @graphql-tools/batch-delegate@10.0.27
9
- - @graphql-tools/wrap@11.1.19
14
+
15
+
16
+ - [#2473](https://github.com/graphql-hive/gateway/pull/2473) [`b42de25`](https://github.com/graphql-hive/gateway/commit/b42de25be0b656a0fbfe76f85f3cea287865184e) Thanks [@enisdenjo](https://github.com/enisdenjo)! - Fields that are not provided by any subschema (added through `typeDefs` or `resolvers`) can now return partial objects of merged types; the missing fields are resolved from the owning subschema automatically
17
+
18
+ ```graphql
19
+ type Query {
20
+ personCreated: PersonCreated
21
+ }
22
+
23
+ type PersonCreated {
24
+ person: Person # merged type, owned by a subschema
25
+ cursor: String
26
+ }
27
+ ```
28
+
29
+ ```ts
30
+ const resolvers = {
31
+ Query: {
32
+ // only the key of `Person` is provided locally
33
+ personCreated: () => ({ person: { id: '1' }, cursor: 'c1' }),
34
+ },
35
+ };
36
+ ```
37
+
38
+ Before, `person` had to be resolved manually even though the stitched schema knows `Person` and its keys. Now the key is enough: `{ person { name } }` runs through the standard stitching planner, while local data (`cursor`) and local field resolvers on `Person` keep working as before. Computed fields, `@requires` dependencies, batching, and fields from other subschemas use the same type-merging flow as regular delegated results.
39
+ - Updated dependencies [[`b42de25`](https://github.com/graphql-hive/gateway/commit/b42de25be0b656a0fbfe76f85f3cea287865184e), [`b42de25`](https://github.com/graphql-hive/gateway/commit/b42de25be0b656a0fbfe76f85f3cea287865184e)]:
40
+ - @graphql-tools/delegate@12.1.0-alpha-1c2de90cd18cb3a8fc5edcb2be2a464ee5b900e5
41
+ - @graphql-tools/batch-delegate@10.0.27-alpha-1c2de90cd18cb3a8fc5edcb2be2a464ee5b900e5
42
+ - @graphql-tools/wrap@11.1.19-alpha-1c2de90cd18cb3a8fc5edcb2be2a464ee5b900e5
10
43
 
11
44
  ## 10.1.24
12
45
  ### Patch Changes
package/dist/index.cjs CHANGED
@@ -4,12 +4,189 @@ var delegate = require('@graphql-tools/delegate');
4
4
  var merge = require('@graphql-tools/merge');
5
5
  var schema = require('@graphql-tools/schema');
6
6
  var utils = require('@graphql-tools/utils');
7
- var graphql = require('graphql');
8
7
  var promiseHelpers = require('@whatwg-node/promise-helpers');
8
+ var graphql = require('graphql');
9
9
  var batchDelegate = require('@graphql-tools/batch-delegate');
10
10
  var wrap = require('@graphql-tools/wrap');
11
11
  var executor = require('@graphql-tools/executor');
12
12
 
13
+ function valueSatisfiesSelectionSet(value, selectionSet) {
14
+ if (Array.isArray(value)) {
15
+ return value.every(
16
+ (item) => valueSatisfiesSelectionSet(item, selectionSet)
17
+ );
18
+ }
19
+ if (value == null || typeof value !== "object") {
20
+ return false;
21
+ }
22
+ const objectValue = value;
23
+ return selectionSet.selections.every((selection) => {
24
+ if (selection.kind === graphql.Kind.INLINE_FRAGMENT) {
25
+ return valueSatisfiesSelectionSet(value, selection.selectionSet);
26
+ }
27
+ if (selection.kind !== graphql.Kind.FIELD) {
28
+ return false;
29
+ }
30
+ const responseKey = selection.alias?.value || selection.name.value;
31
+ return objectValue[responseKey] !== void 0 && (selection.selectionSet == null || valueSatisfiesSelectionSet(
32
+ objectValue[responseKey],
33
+ selection.selectionSet
34
+ ));
35
+ });
36
+ }
37
+ function resolveLocalFieldResult(result, context, info, stitchingInfo = info.schema.extensions?.["stitchingInfo"], providedFields) {
38
+ if (stitchingInfo == null || result == null) {
39
+ return result;
40
+ }
41
+ return resolveOne(result, context, info, stitchingInfo, providedFields);
42
+ }
43
+ function getMissingSelections(value, selections, fragments = {}) {
44
+ if (value == null || typeof value !== "object") {
45
+ return [...selections];
46
+ }
47
+ const objectValue = value;
48
+ const missing = [];
49
+ for (const selection of selections) {
50
+ if (selection.kind === graphql.Kind.INLINE_FRAGMENT) {
51
+ const sub = getMissingSelections(
52
+ value,
53
+ selection.selectionSet.selections,
54
+ fragments
55
+ );
56
+ if (sub.length) {
57
+ missing.push({
58
+ ...selection,
59
+ selectionSet: { kind: graphql.Kind.SELECTION_SET, selections: sub }
60
+ });
61
+ }
62
+ continue;
63
+ }
64
+ if (selection.kind === graphql.Kind.FRAGMENT_SPREAD) {
65
+ const fragment = fragments[selection.name.value];
66
+ if (fragment == null) {
67
+ missing.push(selection);
68
+ continue;
69
+ }
70
+ const sub = getMissingSelections(
71
+ value,
72
+ fragment.selectionSet.selections,
73
+ fragments
74
+ );
75
+ if (sub.length) {
76
+ missing.push({
77
+ kind: graphql.Kind.INLINE_FRAGMENT,
78
+ typeCondition: fragment.typeCondition,
79
+ directives: [
80
+ ...fragment.directives ?? [],
81
+ ...selection.directives ?? []
82
+ ],
83
+ selectionSet: { kind: graphql.Kind.SELECTION_SET, selections: sub }
84
+ });
85
+ }
86
+ continue;
87
+ }
88
+ const fieldValue = objectValue[selection.name.value];
89
+ if (fieldValue === void 0 || fieldValue === null && selection.selectionSet != null) {
90
+ missing.push(selection);
91
+ continue;
92
+ }
93
+ if (selection.selectionSet != null) {
94
+ if (Array.isArray(fieldValue)) {
95
+ const anyMissing = fieldValue.some(
96
+ (item) => getMissingSelections(
97
+ item,
98
+ selection.selectionSet.selections,
99
+ fragments
100
+ ).length > 0
101
+ );
102
+ if (anyMissing) {
103
+ missing.push(selection);
104
+ }
105
+ } else {
106
+ const sub = getMissingSelections(
107
+ fieldValue,
108
+ selection.selectionSet.selections,
109
+ fragments
110
+ );
111
+ if (sub.length) {
112
+ missing.push({
113
+ ...selection,
114
+ selectionSet: { kind: graphql.Kind.SELECTION_SET, selections: sub }
115
+ });
116
+ }
117
+ }
118
+ }
119
+ }
120
+ return missing;
121
+ }
122
+ function resolveOne(value, context, info, stitchingInfo, providedFields) {
123
+ if (Array.isArray(value)) {
124
+ return value.map(
125
+ (item) => resolveOne(item, context, info, stitchingInfo, providedFields)
126
+ );
127
+ }
128
+ if (value == null || typeof value !== "object" || value instanceof Error || delegate.isExternalObject(value)) {
129
+ return value;
130
+ }
131
+ const objectValue = value;
132
+ const returnType = graphql.getNamedType(info.returnType);
133
+ const typeName = typeof objectValue["__typename"] === "string" ? objectValue["__typename"] : returnType.name;
134
+ const mergedTypeInfo = stitchingInfo.mergedTypes[typeName];
135
+ if (mergedTypeInfo == null) {
136
+ return value;
137
+ }
138
+ let selections = info.fieldNodes.flatMap(
139
+ (fieldNode) => fieldNode.selectionSet?.selections ?? []
140
+ );
141
+ if (providedFields?.size) {
142
+ selections = selections.filter(
143
+ (selection) => selection.kind !== graphql.Kind.FIELD || !providedFields.has(selection.name.value)
144
+ );
145
+ }
146
+ const missingSelections = getMissingSelections(
147
+ value,
148
+ selections,
149
+ info.fragments
150
+ );
151
+ if (!missingSelections.length) {
152
+ return value;
153
+ }
154
+ const selectionSet = {
155
+ kind: graphql.Kind.SELECTION_SET,
156
+ selections: missingSelections
157
+ };
158
+ const candidates = [...mergedTypeInfo.selectionSets].sort(
159
+ ([a], [b]) => Number(Boolean(a.merge?.[typeName]?.fields)) - Number(Boolean(b.merge?.[typeName]?.fields))
160
+ );
161
+ for (const [subschema, keySelectionSet] of candidates) {
162
+ if (valueSatisfiesSelectionSet(value, keySelectionSet)) {
163
+ const resolver = mergedTypeInfo.resolvers.get(subschema);
164
+ if (resolver != null) {
165
+ if (objectValue["__typename"] == null && !graphql.isAbstractType(returnType)) {
166
+ objectValue["__typename"] = typeName;
167
+ }
168
+ return promiseHelpers.handleMaybePromise(
169
+ () => resolver(
170
+ value,
171
+ context,
172
+ info,
173
+ subschema,
174
+ selectionSet,
175
+ void 0,
176
+ returnType,
177
+ false
178
+ ),
179
+ // value comes first so the delegation result wins on overlaps, while
180
+ // payload fields outside the delegated selection (like the key) survive;
181
+ // respectNonEnumerableSymbols keeps the external object annotation intact
182
+ (resolved) => utils.mergeDeep([value, resolved], false, false, false, true)
183
+ );
184
+ }
185
+ }
186
+ }
187
+ return value;
188
+ }
189
+
13
190
  function getFieldsNotInSubschema(schema, stitchingInfo, gatewayType, subschemaType, fieldNodes, fragments, variableValues, subschema, providedSelectionNode, context, info) {
14
191
  const sourceSchema = subschema.transformedSchema;
15
192
  let { fields: subFieldNodesByResponseKey, patches } = utils.collectSubFields(
@@ -817,7 +994,7 @@ function createMergedTypeResolver(mergedTypeResolverOptions, mergedType) {
817
994
  return mergedType;
818
995
  }
819
996
  if (argsFromKeys != null) {
820
- return function mergedBatchedTypeResolver(_originalResult, context, info, subschema, selectionSet, key, type = getType(info)) {
997
+ return function mergedBatchedTypeResolver(_originalResult, context, info, subschema, selectionSet, key, type = getType(info), skipTypeMerging = true) {
821
998
  return batchDelegate.batchDelegateToSchema({
822
999
  schema: subschema,
823
1000
  operation: "query",
@@ -829,13 +1006,13 @@ function createMergedTypeResolver(mergedTypeResolverOptions, mergedType) {
829
1006
  selectionSet,
830
1007
  context,
831
1008
  info,
832
- skipTypeMerging: true,
1009
+ skipTypeMerging,
833
1010
  dataLoaderOptions: mergedTypeResolverOptions.dataLoaderOptions
834
1011
  });
835
1012
  };
836
1013
  }
837
1014
  if (args != null) {
838
- return function mergedTypeResolver(originalResult, context, info, subschema, selectionSet, _key, type = getType(info)) {
1015
+ return function mergedTypeResolver(originalResult, context, info, subschema, selectionSet, _key, type = getType(info), skipTypeMerging = true) {
839
1016
  return delegate.delegateToSchema({
840
1017
  schema: subschema,
841
1018
  operation: "query",
@@ -845,7 +1022,7 @@ function createMergedTypeResolver(mergedTypeResolverOptions, mergedType) {
845
1022
  selectionSet,
846
1023
  context,
847
1024
  info,
848
- skipTypeMerging: true
1025
+ skipTypeMerging
849
1026
  });
850
1027
  };
851
1028
  }
@@ -989,7 +1166,7 @@ function createMergedTypes(typeCandidates, mergeTypes) {
989
1166
  const keyFn = mergedTypeConfig2.key;
990
1167
  resolvers.set(
991
1168
  subschema,
992
- keyFn ? function batchMergedTypeResolverWrapper(originalResult, context, info, subschema2, selectionSet2, type2) {
1169
+ keyFn ? function batchMergedTypeResolverWrapper(originalResult, context, info, subschema2, selectionSet2, keyOrType, type2, skipTypeMerging) {
993
1170
  return promiseHelpers.handleMaybePromise(
994
1171
  () => keyFn(originalResult),
995
1172
  (key) => resolver(
@@ -999,7 +1176,8 @@ function createMergedTypes(typeCandidates, mergeTypes) {
999
1176
  subschema2,
1000
1177
  selectionSet2,
1001
1178
  key,
1002
- type2
1179
+ keyOrType ?? type2,
1180
+ skipTypeMerging
1003
1181
  )
1004
1182
  );
1005
1183
  } : resolver
@@ -3122,6 +3300,7 @@ function stitchSchemas({
3122
3300
  const resolverMap = merge.mergeResolvers(resolvers);
3123
3301
  const finalResolvers = inheritResolversFromInterfaces ? schema.extendResolversFromInterfaces(schema$1, resolverMap) : resolverMap;
3124
3302
  stitchingInfo = completeStitchingInfo(stitchingInfo, finalResolvers, schema$1);
3303
+ addLocalFieldResolvers(schema$1, finalResolvers, stitchingInfo, typeCandidates);
3125
3304
  schema$1 = schema.addResolversToSchema({
3126
3305
  schema: schema$1,
3127
3306
  defaultFieldResolver: delegate.defaultMergedResolver,
@@ -3187,6 +3366,91 @@ function stripCustomDirectiveUsages(types) {
3187
3366
  }
3188
3367
  }
3189
3368
  }
3369
+ function addLocalFieldResolvers(schema, resolvers, stitchingInfo, typeCandidates) {
3370
+ const subschemaFields = /* @__PURE__ */ new Set();
3371
+ const localFields = /* @__PURE__ */ new Map();
3372
+ for (const typeName in typeCandidates) {
3373
+ for (const candidate of typeCandidates[typeName]) {
3374
+ if (!graphql.isObjectType(candidate.type) && !graphql.isInterfaceType(candidate.type)) {
3375
+ continue;
3376
+ }
3377
+ const fieldNames = Object.keys(candidate.type.getFields());
3378
+ if (candidate.transformedSubschema != null || candidate.subschema != null) {
3379
+ for (const fieldName of fieldNames) {
3380
+ subschemaFields.add(`${typeName}.${fieldName}`);
3381
+ }
3382
+ } else {
3383
+ let fields = localFields.get(typeName);
3384
+ if (fields == null) {
3385
+ fields = /* @__PURE__ */ new Set();
3386
+ localFields.set(typeName, fields);
3387
+ }
3388
+ for (const fieldName of fieldNames) {
3389
+ fields.add(fieldName);
3390
+ }
3391
+ }
3392
+ }
3393
+ }
3394
+ for (const typeName in resolvers) {
3395
+ const typeResolvers = resolvers[typeName];
3396
+ if (typeResolvers == null || typeof typeResolvers !== "object") {
3397
+ continue;
3398
+ }
3399
+ let fields = localFields.get(typeName);
3400
+ if (fields == null) {
3401
+ fields = /* @__PURE__ */ new Set();
3402
+ localFields.set(typeName, fields);
3403
+ }
3404
+ for (const fieldName in typeResolvers) {
3405
+ if (fieldName.startsWith("__")) {
3406
+ continue;
3407
+ }
3408
+ fields.add(fieldName);
3409
+ }
3410
+ }
3411
+ for (const [typeName, fieldNames] of localFields) {
3412
+ const type = schema.getType(typeName);
3413
+ if (!graphql.isObjectType(type) && !graphql.isInterfaceType(type)) {
3414
+ continue;
3415
+ }
3416
+ const fields = type.getFields();
3417
+ for (const fieldName of fieldNames) {
3418
+ const field = fields[fieldName];
3419
+ const existing = resolvers[typeName]?.[fieldName];
3420
+ if (field == null || // subschema-owned fields keep their proxying resolver unless the user overrode it
3421
+ existing == null && subschemaFields.has(`${typeName}.${fieldName}`)) {
3422
+ continue;
3423
+ }
3424
+ const namedType = graphql.getNamedType(field.type);
3425
+ if (stitchingInfo.mergedTypes[namedType.name] == null) {
3426
+ continue;
3427
+ }
3428
+ const namedTypeResolvers = resolvers[namedType.name];
3429
+ const providedFields = namedTypeResolvers != null && typeof namedTypeResolvers === "object" ? new Set(
3430
+ Object.keys(namedTypeResolvers).filter(
3431
+ (fieldName2) => !fieldName2.startsWith("__")
3432
+ )
3433
+ ) : void 0;
3434
+ const originalResolve = typeof existing === "function" ? existing : existing?.resolve;
3435
+ const baseResolve = originalResolve ?? delegate.defaultMergedResolver;
3436
+ const wrappedResolve = (parent, args, context, info) => promiseHelpers.handleMaybePromise(
3437
+ () => baseResolve(parent, args, context, info),
3438
+ (result) => resolveLocalFieldResult(
3439
+ result,
3440
+ context,
3441
+ info,
3442
+ stitchingInfo,
3443
+ providedFields
3444
+ )
3445
+ );
3446
+ if (existing != null && typeof existing === "object") {
3447
+ existing.resolve = wrappedResolve;
3448
+ } else {
3449
+ (resolvers[typeName] ||= {})[fieldName] = wrappedResolve;
3450
+ }
3451
+ }
3452
+ }
3453
+ }
3190
3454
  const subschemaConfigTransformerPresets = [isolateComputedFieldsTransformer, splitMergedTypeEntryPointsTransformer];
3191
3455
  function applySubschemaConfigTransforms(subschemaConfigTransforms, subschemaOrSubschemaConfig, subschemaMap, originalSubschemaMap) {
3192
3456
  let subschemaConfig;
package/dist/index.js CHANGED
@@ -1,13 +1,190 @@
1
- import { handleOverrideByDelegation, subtractSelectionSets, extractUnavailableFields, leftOverByDelegationPlan, extractUnavailableFieldsFromSelectionSet, delegateToSchema, cloneSubschemaConfig, isSubschemaConfig, defaultMergedResolver, Subschema } from '@graphql-tools/delegate';
1
+ import { isExternalObject, handleOverrideByDelegation, subtractSelectionSets, extractUnavailableFields, leftOverByDelegationPlan, extractUnavailableFieldsFromSelectionSet, delegateToSchema, cloneSubschemaConfig, isSubschemaConfig, defaultMergedResolver, Subschema } from '@graphql-tools/delegate';
2
2
  import { mergeType, mergeInputType, mergeInterface, mergeUnion, mergeEnum, mergeScalar, mergeTypeDefs, mergeResolvers, mergeExtensions, applyExtensions } from '@graphql-tools/merge';
3
3
  import { extendResolversFromInterfaces, addResolversToSchema, assertResolversPresent, makeExecutableSchema } from '@graphql-tools/schema';
4
- import { collectSubFields, memoize3, memoize2, memoize1, parseSelectionSet, collectFields, isSome, getImplementingTypes, getRootTypeNames, filterSchema, mergeDeep, getDescription, createNamedStub, createStub, getRootTypeMap, getRootTypes, inspect, rewireTypes, getOperationASTFromRequest, getDefinedRootType, getArgumentValues } from '@graphql-tools/utils';
5
- import { isAbstractType, Kind, TypeNameMetaFieldDef, getNamedType, isObjectType, GraphQLList, isLeafType, isInputObjectType, isUnionType, print, isInterfaceType, visit, isScalarType, isCompositeType, isNonNullType, isEnumType, isListType, getNullableType, isNullableType, GraphQLObjectType, GraphQLInputObjectType, GraphQLInterfaceType, GraphQLUnionType, GraphQLEnumType, GraphQLScalarType, GraphQLDirective, valueFromASTUntyped, getDirectiveValues, GraphQLDeprecatedDirective, DirectiveLocation, isNamedType, isIntrospectionType, isDirective, isSpecifiedScalarType, specifiedDirectives, GraphQLSchema, extendSchema, isSpecifiedDirective } from 'graphql';
4
+ import { mergeDeep, collectSubFields, memoize3, memoize2, memoize1, parseSelectionSet, collectFields, isSome, getImplementingTypes, getRootTypeNames, filterSchema, getDescription, createNamedStub, createStub, getRootTypeMap, getRootTypes, inspect, rewireTypes, getOperationASTFromRequest, getDefinedRootType, getArgumentValues } from '@graphql-tools/utils';
6
5
  import { handleMaybePromise } from '@whatwg-node/promise-helpers';
6
+ import { getNamedType, Kind, isAbstractType, TypeNameMetaFieldDef, isObjectType, GraphQLList, isLeafType, isInputObjectType, isUnionType, print, isInterfaceType, visit, isScalarType, isCompositeType, isNonNullType, isEnumType, isListType, getNullableType, isNullableType, GraphQLObjectType, GraphQLInputObjectType, GraphQLInterfaceType, GraphQLUnionType, GraphQLEnumType, GraphQLScalarType, GraphQLDirective, valueFromASTUntyped, getDirectiveValues, GraphQLDeprecatedDirective, DirectiveLocation, isNamedType, isIntrospectionType, isDirective, isSpecifiedScalarType, specifiedDirectives, GraphQLSchema, extendSchema, isSpecifiedDirective } from 'graphql';
7
7
  import { batchDelegateToSchema } from '@graphql-tools/batch-delegate';
8
8
  import { FilterTypes, TransformCompositeFields, wrapSchema } from '@graphql-tools/wrap';
9
9
  import { getFragmentsFromDocument } from '@graphql-tools/executor';
10
10
 
11
+ function valueSatisfiesSelectionSet(value, selectionSet) {
12
+ if (Array.isArray(value)) {
13
+ return value.every(
14
+ (item) => valueSatisfiesSelectionSet(item, selectionSet)
15
+ );
16
+ }
17
+ if (value == null || typeof value !== "object") {
18
+ return false;
19
+ }
20
+ const objectValue = value;
21
+ return selectionSet.selections.every((selection) => {
22
+ if (selection.kind === Kind.INLINE_FRAGMENT) {
23
+ return valueSatisfiesSelectionSet(value, selection.selectionSet);
24
+ }
25
+ if (selection.kind !== Kind.FIELD) {
26
+ return false;
27
+ }
28
+ const responseKey = selection.alias?.value || selection.name.value;
29
+ return objectValue[responseKey] !== void 0 && (selection.selectionSet == null || valueSatisfiesSelectionSet(
30
+ objectValue[responseKey],
31
+ selection.selectionSet
32
+ ));
33
+ });
34
+ }
35
+ function resolveLocalFieldResult(result, context, info, stitchingInfo = info.schema.extensions?.["stitchingInfo"], providedFields) {
36
+ if (stitchingInfo == null || result == null) {
37
+ return result;
38
+ }
39
+ return resolveOne(result, context, info, stitchingInfo, providedFields);
40
+ }
41
+ function getMissingSelections(value, selections, fragments = {}) {
42
+ if (value == null || typeof value !== "object") {
43
+ return [...selections];
44
+ }
45
+ const objectValue = value;
46
+ const missing = [];
47
+ for (const selection of selections) {
48
+ if (selection.kind === Kind.INLINE_FRAGMENT) {
49
+ const sub = getMissingSelections(
50
+ value,
51
+ selection.selectionSet.selections,
52
+ fragments
53
+ );
54
+ if (sub.length) {
55
+ missing.push({
56
+ ...selection,
57
+ selectionSet: { kind: Kind.SELECTION_SET, selections: sub }
58
+ });
59
+ }
60
+ continue;
61
+ }
62
+ if (selection.kind === Kind.FRAGMENT_SPREAD) {
63
+ const fragment = fragments[selection.name.value];
64
+ if (fragment == null) {
65
+ missing.push(selection);
66
+ continue;
67
+ }
68
+ const sub = getMissingSelections(
69
+ value,
70
+ fragment.selectionSet.selections,
71
+ fragments
72
+ );
73
+ if (sub.length) {
74
+ missing.push({
75
+ kind: Kind.INLINE_FRAGMENT,
76
+ typeCondition: fragment.typeCondition,
77
+ directives: [
78
+ ...fragment.directives ?? [],
79
+ ...selection.directives ?? []
80
+ ],
81
+ selectionSet: { kind: Kind.SELECTION_SET, selections: sub }
82
+ });
83
+ }
84
+ continue;
85
+ }
86
+ const fieldValue = objectValue[selection.name.value];
87
+ if (fieldValue === void 0 || fieldValue === null && selection.selectionSet != null) {
88
+ missing.push(selection);
89
+ continue;
90
+ }
91
+ if (selection.selectionSet != null) {
92
+ if (Array.isArray(fieldValue)) {
93
+ const anyMissing = fieldValue.some(
94
+ (item) => getMissingSelections(
95
+ item,
96
+ selection.selectionSet.selections,
97
+ fragments
98
+ ).length > 0
99
+ );
100
+ if (anyMissing) {
101
+ missing.push(selection);
102
+ }
103
+ } else {
104
+ const sub = getMissingSelections(
105
+ fieldValue,
106
+ selection.selectionSet.selections,
107
+ fragments
108
+ );
109
+ if (sub.length) {
110
+ missing.push({
111
+ ...selection,
112
+ selectionSet: { kind: Kind.SELECTION_SET, selections: sub }
113
+ });
114
+ }
115
+ }
116
+ }
117
+ }
118
+ return missing;
119
+ }
120
+ function resolveOne(value, context, info, stitchingInfo, providedFields) {
121
+ if (Array.isArray(value)) {
122
+ return value.map(
123
+ (item) => resolveOne(item, context, info, stitchingInfo, providedFields)
124
+ );
125
+ }
126
+ if (value == null || typeof value !== "object" || value instanceof Error || isExternalObject(value)) {
127
+ return value;
128
+ }
129
+ const objectValue = value;
130
+ const returnType = getNamedType(info.returnType);
131
+ const typeName = typeof objectValue["__typename"] === "string" ? objectValue["__typename"] : returnType.name;
132
+ const mergedTypeInfo = stitchingInfo.mergedTypes[typeName];
133
+ if (mergedTypeInfo == null) {
134
+ return value;
135
+ }
136
+ let selections = info.fieldNodes.flatMap(
137
+ (fieldNode) => fieldNode.selectionSet?.selections ?? []
138
+ );
139
+ if (providedFields?.size) {
140
+ selections = selections.filter(
141
+ (selection) => selection.kind !== Kind.FIELD || !providedFields.has(selection.name.value)
142
+ );
143
+ }
144
+ const missingSelections = getMissingSelections(
145
+ value,
146
+ selections,
147
+ info.fragments
148
+ );
149
+ if (!missingSelections.length) {
150
+ return value;
151
+ }
152
+ const selectionSet = {
153
+ kind: Kind.SELECTION_SET,
154
+ selections: missingSelections
155
+ };
156
+ const candidates = [...mergedTypeInfo.selectionSets].sort(
157
+ ([a], [b]) => Number(Boolean(a.merge?.[typeName]?.fields)) - Number(Boolean(b.merge?.[typeName]?.fields))
158
+ );
159
+ for (const [subschema, keySelectionSet] of candidates) {
160
+ if (valueSatisfiesSelectionSet(value, keySelectionSet)) {
161
+ const resolver = mergedTypeInfo.resolvers.get(subschema);
162
+ if (resolver != null) {
163
+ if (objectValue["__typename"] == null && !isAbstractType(returnType)) {
164
+ objectValue["__typename"] = typeName;
165
+ }
166
+ return handleMaybePromise(
167
+ () => resolver(
168
+ value,
169
+ context,
170
+ info,
171
+ subschema,
172
+ selectionSet,
173
+ void 0,
174
+ returnType,
175
+ false
176
+ ),
177
+ // value comes first so the delegation result wins on overlaps, while
178
+ // payload fields outside the delegated selection (like the key) survive;
179
+ // respectNonEnumerableSymbols keeps the external object annotation intact
180
+ (resolved) => mergeDeep([value, resolved], false, false, false, true)
181
+ );
182
+ }
183
+ }
184
+ }
185
+ return value;
186
+ }
187
+
11
188
  function getFieldsNotInSubschema(schema, stitchingInfo, gatewayType, subschemaType, fieldNodes, fragments, variableValues, subschema, providedSelectionNode, context, info) {
12
189
  const sourceSchema = subschema.transformedSchema;
13
190
  let { fields: subFieldNodesByResponseKey, patches } = collectSubFields(
@@ -815,7 +992,7 @@ function createMergedTypeResolver(mergedTypeResolverOptions, mergedType) {
815
992
  return mergedType;
816
993
  }
817
994
  if (argsFromKeys != null) {
818
- return function mergedBatchedTypeResolver(_originalResult, context, info, subschema, selectionSet, key, type = getType(info)) {
995
+ return function mergedBatchedTypeResolver(_originalResult, context, info, subschema, selectionSet, key, type = getType(info), skipTypeMerging = true) {
819
996
  return batchDelegateToSchema({
820
997
  schema: subschema,
821
998
  operation: "query",
@@ -827,13 +1004,13 @@ function createMergedTypeResolver(mergedTypeResolverOptions, mergedType) {
827
1004
  selectionSet,
828
1005
  context,
829
1006
  info,
830
- skipTypeMerging: true,
1007
+ skipTypeMerging,
831
1008
  dataLoaderOptions: mergedTypeResolverOptions.dataLoaderOptions
832
1009
  });
833
1010
  };
834
1011
  }
835
1012
  if (args != null) {
836
- return function mergedTypeResolver(originalResult, context, info, subschema, selectionSet, _key, type = getType(info)) {
1013
+ return function mergedTypeResolver(originalResult, context, info, subschema, selectionSet, _key, type = getType(info), skipTypeMerging = true) {
837
1014
  return delegateToSchema({
838
1015
  schema: subschema,
839
1016
  operation: "query",
@@ -843,7 +1020,7 @@ function createMergedTypeResolver(mergedTypeResolverOptions, mergedType) {
843
1020
  selectionSet,
844
1021
  context,
845
1022
  info,
846
- skipTypeMerging: true
1023
+ skipTypeMerging
847
1024
  });
848
1025
  };
849
1026
  }
@@ -987,7 +1164,7 @@ function createMergedTypes(typeCandidates, mergeTypes) {
987
1164
  const keyFn = mergedTypeConfig2.key;
988
1165
  resolvers.set(
989
1166
  subschema,
990
- keyFn ? function batchMergedTypeResolverWrapper(originalResult, context, info, subschema2, selectionSet2, type2) {
1167
+ keyFn ? function batchMergedTypeResolverWrapper(originalResult, context, info, subschema2, selectionSet2, keyOrType, type2, skipTypeMerging) {
991
1168
  return handleMaybePromise(
992
1169
  () => keyFn(originalResult),
993
1170
  (key) => resolver(
@@ -997,7 +1174,8 @@ function createMergedTypes(typeCandidates, mergeTypes) {
997
1174
  subschema2,
998
1175
  selectionSet2,
999
1176
  key,
1000
- type2
1177
+ keyOrType ?? type2,
1178
+ skipTypeMerging
1001
1179
  )
1002
1180
  );
1003
1181
  } : resolver
@@ -3120,6 +3298,7 @@ function stitchSchemas({
3120
3298
  const resolverMap = mergeResolvers(resolvers);
3121
3299
  const finalResolvers = inheritResolversFromInterfaces ? extendResolversFromInterfaces(schema, resolverMap) : resolverMap;
3122
3300
  stitchingInfo = completeStitchingInfo(stitchingInfo, finalResolvers, schema);
3301
+ addLocalFieldResolvers(schema, finalResolvers, stitchingInfo, typeCandidates);
3123
3302
  schema = addResolversToSchema({
3124
3303
  schema,
3125
3304
  defaultFieldResolver: defaultMergedResolver,
@@ -3185,6 +3364,91 @@ function stripCustomDirectiveUsages(types) {
3185
3364
  }
3186
3365
  }
3187
3366
  }
3367
+ function addLocalFieldResolvers(schema, resolvers, stitchingInfo, typeCandidates) {
3368
+ const subschemaFields = /* @__PURE__ */ new Set();
3369
+ const localFields = /* @__PURE__ */ new Map();
3370
+ for (const typeName in typeCandidates) {
3371
+ for (const candidate of typeCandidates[typeName]) {
3372
+ if (!isObjectType(candidate.type) && !isInterfaceType(candidate.type)) {
3373
+ continue;
3374
+ }
3375
+ const fieldNames = Object.keys(candidate.type.getFields());
3376
+ if (candidate.transformedSubschema != null || candidate.subschema != null) {
3377
+ for (const fieldName of fieldNames) {
3378
+ subschemaFields.add(`${typeName}.${fieldName}`);
3379
+ }
3380
+ } else {
3381
+ let fields = localFields.get(typeName);
3382
+ if (fields == null) {
3383
+ fields = /* @__PURE__ */ new Set();
3384
+ localFields.set(typeName, fields);
3385
+ }
3386
+ for (const fieldName of fieldNames) {
3387
+ fields.add(fieldName);
3388
+ }
3389
+ }
3390
+ }
3391
+ }
3392
+ for (const typeName in resolvers) {
3393
+ const typeResolvers = resolvers[typeName];
3394
+ if (typeResolvers == null || typeof typeResolvers !== "object") {
3395
+ continue;
3396
+ }
3397
+ let fields = localFields.get(typeName);
3398
+ if (fields == null) {
3399
+ fields = /* @__PURE__ */ new Set();
3400
+ localFields.set(typeName, fields);
3401
+ }
3402
+ for (const fieldName in typeResolvers) {
3403
+ if (fieldName.startsWith("__")) {
3404
+ continue;
3405
+ }
3406
+ fields.add(fieldName);
3407
+ }
3408
+ }
3409
+ for (const [typeName, fieldNames] of localFields) {
3410
+ const type = schema.getType(typeName);
3411
+ if (!isObjectType(type) && !isInterfaceType(type)) {
3412
+ continue;
3413
+ }
3414
+ const fields = type.getFields();
3415
+ for (const fieldName of fieldNames) {
3416
+ const field = fields[fieldName];
3417
+ const existing = resolvers[typeName]?.[fieldName];
3418
+ if (field == null || // subschema-owned fields keep their proxying resolver unless the user overrode it
3419
+ existing == null && subschemaFields.has(`${typeName}.${fieldName}`)) {
3420
+ continue;
3421
+ }
3422
+ const namedType = getNamedType(field.type);
3423
+ if (stitchingInfo.mergedTypes[namedType.name] == null) {
3424
+ continue;
3425
+ }
3426
+ const namedTypeResolvers = resolvers[namedType.name];
3427
+ const providedFields = namedTypeResolvers != null && typeof namedTypeResolvers === "object" ? new Set(
3428
+ Object.keys(namedTypeResolvers).filter(
3429
+ (fieldName2) => !fieldName2.startsWith("__")
3430
+ )
3431
+ ) : void 0;
3432
+ const originalResolve = typeof existing === "function" ? existing : existing?.resolve;
3433
+ const baseResolve = originalResolve ?? defaultMergedResolver;
3434
+ const wrappedResolve = (parent, args, context, info) => handleMaybePromise(
3435
+ () => baseResolve(parent, args, context, info),
3436
+ (result) => resolveLocalFieldResult(
3437
+ result,
3438
+ context,
3439
+ info,
3440
+ stitchingInfo,
3441
+ providedFields
3442
+ )
3443
+ );
3444
+ if (existing != null && typeof existing === "object") {
3445
+ existing.resolve = wrappedResolve;
3446
+ } else {
3447
+ (resolvers[typeName] ||= {})[fieldName] = wrappedResolve;
3448
+ }
3449
+ }
3450
+ }
3451
+ }
3188
3452
  const subschemaConfigTransformerPresets = [isolateComputedFieldsTransformer, splitMergedTypeEntryPointsTransformer];
3189
3453
  function applySubschemaConfigTransforms(subschemaConfigTransforms, subschemaOrSubschemaConfig, subschemaMap, originalSubschemaMap) {
3190
3454
  let subschemaConfig;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@graphql-tools/stitch",
3
- "version": "10.1.25",
3
+ "version": "10.2.0-alpha-1c2de90cd18cb3a8fc5edcb2be2a464ee5b900e5",
4
4
  "type": "module",
5
5
  "description": "A set of utils for faster development of GraphQL tools",
6
6
  "repository": {
@@ -38,13 +38,13 @@
38
38
  "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0"
39
39
  },
40
40
  "dependencies": {
41
- "@graphql-tools/batch-delegate": "^10.0.27",
42
- "@graphql-tools/delegate": "^12.0.20",
41
+ "@graphql-tools/batch-delegate": "10.0.27-alpha-1c2de90cd18cb3a8fc5edcb2be2a464ee5b900e5",
42
+ "@graphql-tools/delegate": "12.1.0-alpha-1c2de90cd18cb3a8fc5edcb2be2a464ee5b900e5",
43
43
  "@graphql-tools/executor": "^1.4.13",
44
44
  "@graphql-tools/merge": "^9.1.5",
45
45
  "@graphql-tools/schema": "^10.0.29",
46
46
  "@graphql-tools/utils": "^11.0.0",
47
- "@graphql-tools/wrap": "^11.1.19",
47
+ "@graphql-tools/wrap": "11.1.19-alpha-1c2de90cd18cb3a8fc5edcb2be2a464ee5b900e5",
48
48
  "@whatwg-node/promise-helpers": "^1.3.2",
49
49
  "tslib": "^2.8.1"
50
50
  },