@apollo/gateway 2.2.2-rc0 → 2.3.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/executeQueryPlan.d.ts.map +1 -1
- package/dist/executeQueryPlan.js +81 -7
- package/dist/executeQueryPlan.js.map +1 -1
- package/package.json +5 -5
- package/src/__tests__/executeQueryPlan.test.ts +1267 -0
- package/src/__tests__/gateway/lifecycle-hooks.test.ts +1 -1
- package/src/executeQueryPlan.ts +96 -5
|
@@ -147,7 +147,7 @@ describe('lifecycle hooks', () => {
|
|
|
147
147
|
// the supergraph (even just formatting differences), this ID will change
|
|
148
148
|
// and this test will have to updated.
|
|
149
149
|
expect(secondCall[0]!.compositionId).toEqual(
|
|
150
|
-
'
|
|
150
|
+
'ed8cb418d55e7cd069f11d093b2ea29316e1a913a5757f383cc78ed399414104',
|
|
151
151
|
);
|
|
152
152
|
// second call should have previous info in the second arg
|
|
153
153
|
expect(secondCall[1]!.compositionId).toEqual(expectedFirstId);
|
package/src/executeQueryPlan.ts
CHANGED
|
@@ -24,12 +24,14 @@ import {
|
|
|
24
24
|
QueryPlanSelectionNode,
|
|
25
25
|
QueryPlanFieldNode,
|
|
26
26
|
getResponseName,
|
|
27
|
+
FetchDataInputRewrite,
|
|
28
|
+
FetchDataOutputRewrite,
|
|
27
29
|
} from '@apollo/query-planner';
|
|
28
30
|
import { deepMerge } from './utilities/deepMerge';
|
|
29
31
|
import { isNotNullOrUndefined } from './utilities/array';
|
|
30
32
|
import { SpanStatusCode } from "@opentelemetry/api";
|
|
31
33
|
import { OpenTelemetrySpanNames, tracer } from "./utilities/opentelemetry";
|
|
32
|
-
import { assert, defaultRootName, errorCodeDef, ERRORS } from '@apollo/federation-internals';
|
|
34
|
+
import { assert, defaultRootName, errorCodeDef, ERRORS, isDefined } from '@apollo/federation-internals';
|
|
33
35
|
import { GatewayGraphQLRequestContext, GatewayExecutionResult } from '@apollo/server-gateway-interface';
|
|
34
36
|
|
|
35
37
|
export type ServiceMap = {
|
|
@@ -307,7 +309,7 @@ async function executeFetch(
|
|
|
307
309
|
);
|
|
308
310
|
|
|
309
311
|
for (const entity of entities) {
|
|
310
|
-
deepMerge(entity, dataReceivedFromService);
|
|
312
|
+
deepMerge(entity, withFetchRewrites(dataReceivedFromService, fetch.outputRewrites));
|
|
311
313
|
}
|
|
312
314
|
} else {
|
|
313
315
|
const requires = fetch.requires;
|
|
@@ -323,6 +325,7 @@ async function executeFetch(
|
|
|
323
325
|
context.supergraphSchema,
|
|
324
326
|
entity,
|
|
325
327
|
requires,
|
|
328
|
+
fetch.inputRewrites,
|
|
326
329
|
);
|
|
327
330
|
if (representation && representation[TypeNameMetaFieldDef.name]) {
|
|
328
331
|
representations.push(representation);
|
|
@@ -368,7 +371,7 @@ async function executeFetch(
|
|
|
368
371
|
}
|
|
369
372
|
|
|
370
373
|
for (let i = 0; i < entities.length; i++) {
|
|
371
|
-
deepMerge(entities[representationToEntity[i]], receivedEntities[i]);
|
|
374
|
+
deepMerge(entities[representationToEntity[i]], withFetchRewrites(receivedEntities[i], filterEntityRewrites(representations[i], fetch.outputRewrites)));
|
|
372
375
|
}
|
|
373
376
|
}
|
|
374
377
|
}
|
|
@@ -484,6 +487,84 @@ async function executeFetch(
|
|
|
484
487
|
}
|
|
485
488
|
}
|
|
486
489
|
|
|
490
|
+
function applyOrMapRecursive(value: any | any[], fct: (v: any) => any | undefined): any | any[] | undefined {
|
|
491
|
+
if (Array.isArray(value)) {
|
|
492
|
+
const res = value.map((elt) => applyOrMapRecursive(elt, fct)).filter(isDefined);
|
|
493
|
+
return res.length === 0 ? undefined : res;
|
|
494
|
+
}
|
|
495
|
+
return fct(value);
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
function withFetchRewrites(fetchResult: ResultMap | null | void, rewrites: FetchDataOutputRewrite[] | undefined): ResultMap | null | void {
|
|
499
|
+
if (!rewrites || !fetchResult) {
|
|
500
|
+
return fetchResult;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
for (const rewrite of rewrites) {
|
|
504
|
+
let obj: any = fetchResult;
|
|
505
|
+
let i = 0;
|
|
506
|
+
while (obj && i < rewrite.path.length - 1) {
|
|
507
|
+
const p = rewrite.path[i++];
|
|
508
|
+
if (p.startsWith('... on ')) {
|
|
509
|
+
const typename = p.slice('... on '.length);
|
|
510
|
+
// Filter only objects that match the condition.
|
|
511
|
+
obj = applyOrMapRecursive(obj, (elt) => elt[TypeNameMetaFieldDef.name] === typename ? elt : undefined);
|
|
512
|
+
} else {
|
|
513
|
+
obj = applyOrMapRecursive(obj, (elt) => elt[p]);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
if (obj) {
|
|
517
|
+
applyOrMapRecursive(obj, (elt) => {
|
|
518
|
+
if (typeof elt === 'object') {
|
|
519
|
+
// We need to move the value at path[i] to `renameKeyTo`.
|
|
520
|
+
const removedKey = rewrite.path[i];
|
|
521
|
+
elt[rewrite.renameKeyTo] = elt[removedKey];
|
|
522
|
+
elt[removedKey] = undefined;
|
|
523
|
+
}
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
return fetchResult;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
function filterEntityRewrites(entity: Record<string, any>, rewrites: FetchDataOutputRewrite[] | undefined): FetchDataOutputRewrite[] | undefined {
|
|
531
|
+
if (!rewrites) {
|
|
532
|
+
return undefined;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
const typename = entity[TypeNameMetaFieldDef.name] as string;
|
|
536
|
+
const typenameAsFragment = `... on ${typename}`;
|
|
537
|
+
return rewrites.map((r) => r.path[0] === typenameAsFragment ? { ...r, path: r.path.slice(1) } : undefined).filter(isDefined)
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
function updateRewrites(rewrites: FetchDataInputRewrite[] | undefined, pathElement: string): {
|
|
541
|
+
updated: FetchDataInputRewrite[],
|
|
542
|
+
completeRewrite?: any,
|
|
543
|
+
} | undefined {
|
|
544
|
+
if (!rewrites) {
|
|
545
|
+
return undefined;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
let completeRewrite: any = undefined;
|
|
549
|
+
const updated = rewrites
|
|
550
|
+
.map((r) => {
|
|
551
|
+
let u: FetchDataInputRewrite | undefined = undefined;
|
|
552
|
+
if (r.path[0] === pathElement) {
|
|
553
|
+
const updatedPath = r.path.slice(1);
|
|
554
|
+
if (updatedPath.length === 0) {
|
|
555
|
+
completeRewrite = r.setValueTo;
|
|
556
|
+
} else {
|
|
557
|
+
u = { ...r, path: updatedPath };
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
return u;
|
|
561
|
+
})
|
|
562
|
+
.filter(isDefined);
|
|
563
|
+
return updated.length === 0 && completeRewrite === undefined
|
|
564
|
+
? undefined
|
|
565
|
+
: { updated, completeRewrite };
|
|
566
|
+
}
|
|
567
|
+
|
|
487
568
|
/**
|
|
488
569
|
*
|
|
489
570
|
* @param source Result of GraphQL execution.
|
|
@@ -493,6 +574,7 @@ function executeSelectionSet(
|
|
|
493
574
|
schema: GraphQLSchema,
|
|
494
575
|
source: Record<string, any> | null,
|
|
495
576
|
selections: QueryPlanSelectionNode[],
|
|
577
|
+
activeRewrites?: FetchDataInputRewrite[],
|
|
496
578
|
): Record<string, any> | null {
|
|
497
579
|
|
|
498
580
|
// If the underlying service has returned null for the parent (source)
|
|
@@ -522,10 +604,17 @@ function executeSelectionSet(
|
|
|
522
604
|
// here.
|
|
523
605
|
return null;
|
|
524
606
|
}
|
|
607
|
+
|
|
608
|
+
const updatedRewrites = updateRewrites(activeRewrites, responseName);
|
|
609
|
+
if (updatedRewrites?.completeRewrite !== undefined) {
|
|
610
|
+
result[responseName] = updatedRewrites.completeRewrite;
|
|
611
|
+
continue;
|
|
612
|
+
}
|
|
613
|
+
|
|
525
614
|
if (Array.isArray(source[responseName])) {
|
|
526
615
|
result[responseName] = source[responseName].map((value: any) =>
|
|
527
616
|
selections
|
|
528
|
-
? executeSelectionSet(schema, value, selections)
|
|
617
|
+
? executeSelectionSet(schema, value, selections, updatedRewrites?.updated)
|
|
529
618
|
: value,
|
|
530
619
|
);
|
|
531
620
|
} else if (selections) {
|
|
@@ -533,6 +622,7 @@ function executeSelectionSet(
|
|
|
533
622
|
schema,
|
|
534
623
|
source[responseName],
|
|
535
624
|
selections,
|
|
625
|
+
updatedRewrites?.updated,
|
|
536
626
|
);
|
|
537
627
|
} else {
|
|
538
628
|
result[responseName] = source[responseName];
|
|
@@ -545,9 +635,10 @@ function executeSelectionSet(
|
|
|
545
635
|
if (!typename) continue;
|
|
546
636
|
|
|
547
637
|
if (doesTypeConditionMatch(schema, selection.typeCondition, typename)) {
|
|
638
|
+
const updatedRewrites = activeRewrites ? updateRewrites(activeRewrites, `... on ${selection.typeCondition}`) : undefined;
|
|
548
639
|
deepMerge(
|
|
549
640
|
result,
|
|
550
|
-
executeSelectionSet(schema, source, selection.selections),
|
|
641
|
+
executeSelectionSet(schema, source, selection.selections, updatedRewrites?.updated),
|
|
551
642
|
);
|
|
552
643
|
}
|
|
553
644
|
break;
|