@graphql-tools/delegate 12.0.20 → 12.1.0-alpha-cf04a323a695c2668e4b2edf5e8755dafaf41e8f
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 +33 -6
- package/dist/index.cjs +171 -21
- package/dist/index.d.cts +35 -1
- package/dist/index.d.ts +35 -1
- package/dist/index.js +172 -23
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,18 +1,45 @@
|
|
|
1
1
|
# @graphql-tools/delegate
|
|
2
2
|
|
|
3
|
-
## 12.0
|
|
4
|
-
###
|
|
3
|
+
## 12.1.0-alpha-cf04a323a695c2668e4b2edf5e8755dafaf41e8f
|
|
4
|
+
### Minor Changes
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
- [#
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
2357
|
+
if (existingArgNode?.value.kind === graphql.Kind.VARIABLE) {
|
|
2358
2358
|
const varName = existingArgNode.value.name.value;
|
|
2359
2359
|
const varValue = newVariables[varName];
|
|
2360
|
-
|
|
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
|
|
2983
|
+
targetSchema,
|
|
2999
2984
|
rootValue,
|
|
3000
2985
|
targetOperationName: operationName,
|
|
3001
2986
|
targetOperation: operation,
|
|
@@ -3204,6 +3189,170 @@ 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
|
+
return selectionSet.selections.every((selection) => {
|
|
3202
|
+
if (selection.kind === graphql.Kind.INLINE_FRAGMENT) {
|
|
3203
|
+
return valueSatisfiesSelectionSet(value, selection.selectionSet);
|
|
3204
|
+
}
|
|
3205
|
+
if (selection.kind !== graphql.Kind.FIELD) {
|
|
3206
|
+
return false;
|
|
3207
|
+
}
|
|
3208
|
+
const responseKey = selection.alias?.value || selection.name.value;
|
|
3209
|
+
return value[responseKey] !== void 0 && (selection.selectionSet == null || valueSatisfiesSelectionSet(value[responseKey], selection.selectionSet));
|
|
3210
|
+
});
|
|
3211
|
+
}
|
|
3212
|
+
function resolveMergedTypeReference(result, context, info, stitchingInfo = info.schema.extensions?.["stitchingInfo"], providedFields) {
|
|
3213
|
+
if (stitchingInfo == null || result == null) {
|
|
3214
|
+
return result;
|
|
3215
|
+
}
|
|
3216
|
+
return resolveOne(result, context, info, stitchingInfo, providedFields);
|
|
3217
|
+
}
|
|
3218
|
+
function getMissingSelections(value, selections, fragments = {}) {
|
|
3219
|
+
const missing = [];
|
|
3220
|
+
for (const selection of selections) {
|
|
3221
|
+
if (selection.kind === graphql.Kind.INLINE_FRAGMENT) {
|
|
3222
|
+
const sub = getMissingSelections(
|
|
3223
|
+
value,
|
|
3224
|
+
selection.selectionSet.selections,
|
|
3225
|
+
fragments
|
|
3226
|
+
);
|
|
3227
|
+
if (sub.length) {
|
|
3228
|
+
missing.push({
|
|
3229
|
+
...selection,
|
|
3230
|
+
selectionSet: { kind: graphql.Kind.SELECTION_SET, selections: sub }
|
|
3231
|
+
});
|
|
3232
|
+
}
|
|
3233
|
+
continue;
|
|
3234
|
+
}
|
|
3235
|
+
if (selection.kind === graphql.Kind.FRAGMENT_SPREAD) {
|
|
3236
|
+
const fragment = fragments[selection.name.value];
|
|
3237
|
+
if (fragment == null) {
|
|
3238
|
+
missing.push(selection);
|
|
3239
|
+
continue;
|
|
3240
|
+
}
|
|
3241
|
+
const sub = getMissingSelections(
|
|
3242
|
+
value,
|
|
3243
|
+
fragment.selectionSet.selections,
|
|
3244
|
+
fragments
|
|
3245
|
+
);
|
|
3246
|
+
if (sub.length) {
|
|
3247
|
+
missing.push({
|
|
3248
|
+
kind: graphql.Kind.INLINE_FRAGMENT,
|
|
3249
|
+
typeCondition: fragment.typeCondition,
|
|
3250
|
+
directives: [
|
|
3251
|
+
...fragment.directives ?? [],
|
|
3252
|
+
...selection.directives ?? []
|
|
3253
|
+
],
|
|
3254
|
+
selectionSet: { kind: graphql.Kind.SELECTION_SET, selections: sub }
|
|
3255
|
+
});
|
|
3256
|
+
}
|
|
3257
|
+
continue;
|
|
3258
|
+
}
|
|
3259
|
+
const fieldValue = value[selection.name.value];
|
|
3260
|
+
if (fieldValue === void 0 || fieldValue === null && selection.selectionSet != null) {
|
|
3261
|
+
missing.push(selection);
|
|
3262
|
+
continue;
|
|
3263
|
+
}
|
|
3264
|
+
if (selection.selectionSet != null) {
|
|
3265
|
+
if (Array.isArray(fieldValue)) {
|
|
3266
|
+
const anyMissing = fieldValue.some(
|
|
3267
|
+
(item) => getMissingSelections(
|
|
3268
|
+
item,
|
|
3269
|
+
selection.selectionSet.selections,
|
|
3270
|
+
fragments
|
|
3271
|
+
).length > 0
|
|
3272
|
+
);
|
|
3273
|
+
if (anyMissing) {
|
|
3274
|
+
missing.push(selection);
|
|
3275
|
+
}
|
|
3276
|
+
} else {
|
|
3277
|
+
const sub = getMissingSelections(
|
|
3278
|
+
fieldValue,
|
|
3279
|
+
selection.selectionSet.selections,
|
|
3280
|
+
fragments
|
|
3281
|
+
);
|
|
3282
|
+
if (sub.length) {
|
|
3283
|
+
missing.push({
|
|
3284
|
+
...selection,
|
|
3285
|
+
selectionSet: { kind: graphql.Kind.SELECTION_SET, selections: sub }
|
|
3286
|
+
});
|
|
3287
|
+
}
|
|
3288
|
+
}
|
|
3289
|
+
}
|
|
3290
|
+
}
|
|
3291
|
+
return missing;
|
|
3292
|
+
}
|
|
3293
|
+
function resolveOne(value, context, info, stitchingInfo, providedFields) {
|
|
3294
|
+
if (Array.isArray(value)) {
|
|
3295
|
+
return value.map(
|
|
3296
|
+
(item) => resolveOne(item, context, info, stitchingInfo, providedFields)
|
|
3297
|
+
);
|
|
3298
|
+
}
|
|
3299
|
+
if (value == null || typeof value !== "object" || value instanceof Error || isExternalObject(value)) {
|
|
3300
|
+
return value;
|
|
3301
|
+
}
|
|
3302
|
+
const returnType = graphql.getNamedType(info.returnType);
|
|
3303
|
+
const typeName = value["__typename"] ?? returnType.name;
|
|
3304
|
+
const mergedTypeInfo = stitchingInfo.mergedTypes[typeName];
|
|
3305
|
+
if (mergedTypeInfo == null) {
|
|
3306
|
+
return value;
|
|
3307
|
+
}
|
|
3308
|
+
let selections = info.fieldNodes.flatMap(
|
|
3309
|
+
(fieldNode) => fieldNode.selectionSet?.selections ?? []
|
|
3310
|
+
);
|
|
3311
|
+
if (providedFields?.size) {
|
|
3312
|
+
selections = selections.filter(
|
|
3313
|
+
(selection) => selection.kind !== graphql.Kind.FIELD || !providedFields.has(selection.name.value)
|
|
3314
|
+
);
|
|
3315
|
+
}
|
|
3316
|
+
const missingSelections = getMissingSelections(
|
|
3317
|
+
value,
|
|
3318
|
+
selections,
|
|
3319
|
+
info.fragments
|
|
3320
|
+
);
|
|
3321
|
+
if (!missingSelections.length) {
|
|
3322
|
+
return value;
|
|
3323
|
+
}
|
|
3324
|
+
const selectionSet = {
|
|
3325
|
+
kind: graphql.Kind.SELECTION_SET,
|
|
3326
|
+
selections: missingSelections
|
|
3327
|
+
};
|
|
3328
|
+
for (const [subschema, keySelectionSet] of mergedTypeInfo.selectionSets) {
|
|
3329
|
+
if (valueSatisfiesSelectionSet(value, keySelectionSet)) {
|
|
3330
|
+
const resolver = mergedTypeInfo.resolvers.get(subschema);
|
|
3331
|
+
if (resolver != null) {
|
|
3332
|
+
if (value["__typename"] == null && !graphql.isAbstractType(returnType)) {
|
|
3333
|
+
value["__typename"] = typeName;
|
|
3334
|
+
}
|
|
3335
|
+
return promiseHelpers.handleMaybePromise(
|
|
3336
|
+
() => resolver(
|
|
3337
|
+
value,
|
|
3338
|
+
context,
|
|
3339
|
+
info,
|
|
3340
|
+
subschema,
|
|
3341
|
+
selectionSet,
|
|
3342
|
+
void 0,
|
|
3343
|
+
returnType
|
|
3344
|
+
),
|
|
3345
|
+
// value comes first so the delegation result wins on overlaps, while
|
|
3346
|
+
// payload fields outside the delegated selection (like the key) survive;
|
|
3347
|
+
// respectNonEnumerableSymbols keeps the external object annotation intact
|
|
3348
|
+
(resolved) => utils.mergeDeep([value, resolved], false, false, false, true)
|
|
3349
|
+
);
|
|
3350
|
+
}
|
|
3351
|
+
}
|
|
3352
|
+
}
|
|
3353
|
+
return value;
|
|
3354
|
+
}
|
|
3355
|
+
|
|
3207
3356
|
function extractUnavailableFieldsFromSelectionSet(schema, fieldType, fieldSelectionSet, shouldAdd, fragments = {}) {
|
|
3208
3357
|
if (graphql.isLeafType(fieldType)) {
|
|
3209
3358
|
return [];
|
|
@@ -3505,4 +3654,5 @@ exports.isSubschemaConfig = isSubschemaConfig;
|
|
|
3505
3654
|
exports.leftOverByDelegationPlan = leftOverByDelegationPlan;
|
|
3506
3655
|
exports.mergeFields = mergeFields;
|
|
3507
3656
|
exports.resolveExternalValue = resolveExternalValue;
|
|
3657
|
+
exports.resolveMergedTypeReference = resolveMergedTypeReference;
|
|
3508
3658
|
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,
|
|
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
|
|
2357
|
+
if (existingArgNode?.value.kind === Kind.VARIABLE) {
|
|
2358
2358
|
const varName = existingArgNode.value.name.value;
|
|
2359
2359
|
const varValue = newVariables[varName];
|
|
2360
|
-
|
|
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
|
|
2983
|
+
targetSchema,
|
|
2999
2984
|
rootValue,
|
|
3000
2985
|
targetOperationName: operationName,
|
|
3001
2986
|
targetOperation: operation,
|
|
@@ -3204,6 +3189,170 @@ 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
|
+
return selectionSet.selections.every((selection) => {
|
|
3202
|
+
if (selection.kind === Kind.INLINE_FRAGMENT) {
|
|
3203
|
+
return valueSatisfiesSelectionSet(value, selection.selectionSet);
|
|
3204
|
+
}
|
|
3205
|
+
if (selection.kind !== Kind.FIELD) {
|
|
3206
|
+
return false;
|
|
3207
|
+
}
|
|
3208
|
+
const responseKey = selection.alias?.value || selection.name.value;
|
|
3209
|
+
return value[responseKey] !== void 0 && (selection.selectionSet == null || valueSatisfiesSelectionSet(value[responseKey], selection.selectionSet));
|
|
3210
|
+
});
|
|
3211
|
+
}
|
|
3212
|
+
function resolveMergedTypeReference(result, context, info, stitchingInfo = info.schema.extensions?.["stitchingInfo"], providedFields) {
|
|
3213
|
+
if (stitchingInfo == null || result == null) {
|
|
3214
|
+
return result;
|
|
3215
|
+
}
|
|
3216
|
+
return resolveOne(result, context, info, stitchingInfo, providedFields);
|
|
3217
|
+
}
|
|
3218
|
+
function getMissingSelections(value, selections, fragments = {}) {
|
|
3219
|
+
const missing = [];
|
|
3220
|
+
for (const selection of selections) {
|
|
3221
|
+
if (selection.kind === Kind.INLINE_FRAGMENT) {
|
|
3222
|
+
const sub = getMissingSelections(
|
|
3223
|
+
value,
|
|
3224
|
+
selection.selectionSet.selections,
|
|
3225
|
+
fragments
|
|
3226
|
+
);
|
|
3227
|
+
if (sub.length) {
|
|
3228
|
+
missing.push({
|
|
3229
|
+
...selection,
|
|
3230
|
+
selectionSet: { kind: Kind.SELECTION_SET, selections: sub }
|
|
3231
|
+
});
|
|
3232
|
+
}
|
|
3233
|
+
continue;
|
|
3234
|
+
}
|
|
3235
|
+
if (selection.kind === Kind.FRAGMENT_SPREAD) {
|
|
3236
|
+
const fragment = fragments[selection.name.value];
|
|
3237
|
+
if (fragment == null) {
|
|
3238
|
+
missing.push(selection);
|
|
3239
|
+
continue;
|
|
3240
|
+
}
|
|
3241
|
+
const sub = getMissingSelections(
|
|
3242
|
+
value,
|
|
3243
|
+
fragment.selectionSet.selections,
|
|
3244
|
+
fragments
|
|
3245
|
+
);
|
|
3246
|
+
if (sub.length) {
|
|
3247
|
+
missing.push({
|
|
3248
|
+
kind: Kind.INLINE_FRAGMENT,
|
|
3249
|
+
typeCondition: fragment.typeCondition,
|
|
3250
|
+
directives: [
|
|
3251
|
+
...fragment.directives ?? [],
|
|
3252
|
+
...selection.directives ?? []
|
|
3253
|
+
],
|
|
3254
|
+
selectionSet: { kind: Kind.SELECTION_SET, selections: sub }
|
|
3255
|
+
});
|
|
3256
|
+
}
|
|
3257
|
+
continue;
|
|
3258
|
+
}
|
|
3259
|
+
const fieldValue = value[selection.name.value];
|
|
3260
|
+
if (fieldValue === void 0 || fieldValue === null && selection.selectionSet != null) {
|
|
3261
|
+
missing.push(selection);
|
|
3262
|
+
continue;
|
|
3263
|
+
}
|
|
3264
|
+
if (selection.selectionSet != null) {
|
|
3265
|
+
if (Array.isArray(fieldValue)) {
|
|
3266
|
+
const anyMissing = fieldValue.some(
|
|
3267
|
+
(item) => getMissingSelections(
|
|
3268
|
+
item,
|
|
3269
|
+
selection.selectionSet.selections,
|
|
3270
|
+
fragments
|
|
3271
|
+
).length > 0
|
|
3272
|
+
);
|
|
3273
|
+
if (anyMissing) {
|
|
3274
|
+
missing.push(selection);
|
|
3275
|
+
}
|
|
3276
|
+
} else {
|
|
3277
|
+
const sub = getMissingSelections(
|
|
3278
|
+
fieldValue,
|
|
3279
|
+
selection.selectionSet.selections,
|
|
3280
|
+
fragments
|
|
3281
|
+
);
|
|
3282
|
+
if (sub.length) {
|
|
3283
|
+
missing.push({
|
|
3284
|
+
...selection,
|
|
3285
|
+
selectionSet: { kind: Kind.SELECTION_SET, selections: sub }
|
|
3286
|
+
});
|
|
3287
|
+
}
|
|
3288
|
+
}
|
|
3289
|
+
}
|
|
3290
|
+
}
|
|
3291
|
+
return missing;
|
|
3292
|
+
}
|
|
3293
|
+
function resolveOne(value, context, info, stitchingInfo, providedFields) {
|
|
3294
|
+
if (Array.isArray(value)) {
|
|
3295
|
+
return value.map(
|
|
3296
|
+
(item) => resolveOne(item, context, info, stitchingInfo, providedFields)
|
|
3297
|
+
);
|
|
3298
|
+
}
|
|
3299
|
+
if (value == null || typeof value !== "object" || value instanceof Error || isExternalObject(value)) {
|
|
3300
|
+
return value;
|
|
3301
|
+
}
|
|
3302
|
+
const returnType = getNamedType(info.returnType);
|
|
3303
|
+
const typeName = value["__typename"] ?? returnType.name;
|
|
3304
|
+
const mergedTypeInfo = stitchingInfo.mergedTypes[typeName];
|
|
3305
|
+
if (mergedTypeInfo == null) {
|
|
3306
|
+
return value;
|
|
3307
|
+
}
|
|
3308
|
+
let selections = info.fieldNodes.flatMap(
|
|
3309
|
+
(fieldNode) => fieldNode.selectionSet?.selections ?? []
|
|
3310
|
+
);
|
|
3311
|
+
if (providedFields?.size) {
|
|
3312
|
+
selections = selections.filter(
|
|
3313
|
+
(selection) => selection.kind !== Kind.FIELD || !providedFields.has(selection.name.value)
|
|
3314
|
+
);
|
|
3315
|
+
}
|
|
3316
|
+
const missingSelections = getMissingSelections(
|
|
3317
|
+
value,
|
|
3318
|
+
selections,
|
|
3319
|
+
info.fragments
|
|
3320
|
+
);
|
|
3321
|
+
if (!missingSelections.length) {
|
|
3322
|
+
return value;
|
|
3323
|
+
}
|
|
3324
|
+
const selectionSet = {
|
|
3325
|
+
kind: Kind.SELECTION_SET,
|
|
3326
|
+
selections: missingSelections
|
|
3327
|
+
};
|
|
3328
|
+
for (const [subschema, keySelectionSet] of mergedTypeInfo.selectionSets) {
|
|
3329
|
+
if (valueSatisfiesSelectionSet(value, keySelectionSet)) {
|
|
3330
|
+
const resolver = mergedTypeInfo.resolvers.get(subschema);
|
|
3331
|
+
if (resolver != null) {
|
|
3332
|
+
if (value["__typename"] == null && !isAbstractType(returnType)) {
|
|
3333
|
+
value["__typename"] = typeName;
|
|
3334
|
+
}
|
|
3335
|
+
return handleMaybePromise(
|
|
3336
|
+
() => resolver(
|
|
3337
|
+
value,
|
|
3338
|
+
context,
|
|
3339
|
+
info,
|
|
3340
|
+
subschema,
|
|
3341
|
+
selectionSet,
|
|
3342
|
+
void 0,
|
|
3343
|
+
returnType
|
|
3344
|
+
),
|
|
3345
|
+
// value comes first so the delegation result wins on overlaps, while
|
|
3346
|
+
// payload fields outside the delegated selection (like the key) survive;
|
|
3347
|
+
// respectNonEnumerableSymbols keeps the external object annotation intact
|
|
3348
|
+
(resolved) => mergeDeep([value, resolved], false, false, false, true)
|
|
3349
|
+
);
|
|
3350
|
+
}
|
|
3351
|
+
}
|
|
3352
|
+
}
|
|
3353
|
+
return value;
|
|
3354
|
+
}
|
|
3355
|
+
|
|
3207
3356
|
function extractUnavailableFieldsFromSelectionSet(schema, fieldType, fieldSelectionSet, shouldAdd, fragments = {}) {
|
|
3208
3357
|
if (isLeafType(fieldType)) {
|
|
3209
3358
|
return [];
|
|
@@ -3464,4 +3613,4 @@ function subtractSelectionSets(selectionSetA, selectionSetB, fragments = {}) {
|
|
|
3464
3613
|
};
|
|
3465
3614
|
}
|
|
3466
3615
|
|
|
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 };
|
|
3616
|
+
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