@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 +38 -5
- package/dist/index.cjs +271 -7
- package/dist/index.js +273 -9
- package/package.json +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,12 +1,45 @@
|
|
|
1
1
|
# @graphql-tools/stitch
|
|
2
2
|
|
|
3
|
-
## 10.
|
|
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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
|
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
|
|
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,
|
|
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
|
|
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
|
|
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.
|
|
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": "
|
|
42
|
-
"@graphql-tools/delegate": "
|
|
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": "
|
|
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
|
},
|