Skip to content

Commit 406cb8d

Browse files
committed
use lazy query planner
refactor externalObject internals into separate file add tests from #2951 and fix them by reworking batch delegation buildDelegationPlan can be used to calculate the rounds of delegation necessary to completely merge an object given the stitching metadata stored within the schema and a given set of fieldNodes TODO: change buildDelegationPlan function to method on MergedTypeInfo as class? move back to stitch package?
1 parent 14a8c22 commit 406cb8d

37 files changed

+1477
-907
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,60 @@
11
import { BatchDelegateOptions } from './types';
22

3+
import { getNullableType, GraphQLError, GraphQLList } from 'graphql';
4+
5+
import { externalValueFromResult } from '@graphql-tools/delegate';
6+
import { relocatedError } from '@graphql-tools/utils';
7+
38
import { getLoader } from './getLoader';
49

5-
export function batchDelegateToSchema<TContext = any>(options: BatchDelegateOptions<TContext>): any {
10+
export async function batchDelegateToSchema<TContext = any>(options: BatchDelegateOptions<TContext>): Promise<any> {
611
const key = options.key;
712
if (key == null) {
813
return null;
914
} else if (Array.isArray(key) && !key.length) {
1015
return [];
1116
}
17+
18+
const {
19+
schema,
20+
info,
21+
fieldName = info.fieldName,
22+
returnType = info.returnType,
23+
context,
24+
onLocatedError = (originalError: GraphQLError) =>
25+
relocatedError(originalError, originalError.path ? originalError.path.slice(1) : []),
26+
} = options;
27+
1228
const loader = getLoader(options);
13-
return Array.isArray(key) ? loader.loadMany(key) : loader.load(key);
29+
30+
if (Array.isArray(key)) {
31+
const results = await loader.loadMany(key);
32+
33+
return results.map(result =>
34+
result instanceof Error
35+
? result
36+
: externalValueFromResult({
37+
result,
38+
schema,
39+
info,
40+
context,
41+
fieldName,
42+
returnType: (getNullableType(returnType) as GraphQLList<any>).ofType,
43+
onLocatedError,
44+
})
45+
);
46+
}
47+
48+
const result = await loader.load(key);
49+
return result instanceof Error
50+
? result
51+
: externalValueFromResult({
52+
result,
53+
schema,
54+
info,
55+
context,
56+
fieldName,
57+
returnType,
58+
onLocatedError,
59+
});
1460
}

packages/batch-delegate/src/createBatchDelegateFn.ts

-31
This file was deleted.
+101-38
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,59 @@
1-
import { getNamedType, GraphQLOutputType, GraphQLList, GraphQLSchema, FieldNode } from 'graphql';
1+
import { GraphQLSchema, FieldNode } from 'graphql';
22

33
import DataLoader from 'dataloader';
44

5-
import { delegateToSchema, SubschemaConfig } from '@graphql-tools/delegate';
6-
import { memoize2, relocatedError } from '@graphql-tools/utils';
5+
import {
6+
SubschemaConfig,
7+
Transformer,
8+
DelegationContext,
9+
validateRequest,
10+
getExecutor,
11+
getDelegatingOperation,
12+
createRequestFromInfo,
13+
getDelegationContext,
14+
} from '@graphql-tools/delegate';
15+
import { ExecutionRequest, ExecutionResult, memoize2 } from '@graphql-tools/utils';
716

817
import { BatchDelegateOptions } from './types';
918

10-
function createBatchFn<K = any>(options: BatchDelegateOptions) {
19+
function createBatchFn<K = any>(
20+
options: BatchDelegateOptions
21+
): (
22+
keys: ReadonlyArray<K>,
23+
request: ExecutionRequest,
24+
delegationContext: DelegationContext<any>
25+
) => Promise<Array<ExecutionResult<Record<string, any>>>> {
1126
const argsFromKeys = options.argsFromKeys ?? ((keys: ReadonlyArray<K>) => ({ ids: keys }));
12-
const fieldName = options.fieldName ?? options.info.fieldName;
13-
const { valuesFromResults, lazyOptionsFn } = options;
14-
15-
return async function batchFn(keys: ReadonlyArray<K>) {
16-
const results = await delegateToSchema({
17-
returnType: new GraphQLList(getNamedType(options.info.returnType) as GraphQLOutputType),
18-
onLocatedError: originalError => {
19-
if (originalError.path == null) {
20-
return originalError;
21-
}
22-
23-
const [pathFieldName, pathNumber] = originalError.path;
24-
25-
if (pathFieldName !== fieldName) {
26-
return originalError;
27-
}
28-
const pathNumberType = typeof pathNumber;
29-
if (pathNumberType !== 'number') {
30-
return originalError;
31-
}
32-
33-
return relocatedError(originalError, originalError.path.slice(0, 0).concat(originalError.path.slice(2)));
34-
},
27+
28+
const { validateRequest: shouldValidateRequest } = options;
29+
30+
return async function batchFn(
31+
keys: ReadonlyArray<K>,
32+
request: ExecutionRequest,
33+
delegationContext: DelegationContext<any>
34+
) {
35+
const { fieldName, context, info } = delegationContext;
36+
37+
const transformer = new Transformer({
38+
...delegationContext,
3539
args: argsFromKeys(keys),
36-
...(lazyOptionsFn == null ? options : lazyOptionsFn(options)),
3740
});
3841

39-
if (results instanceof Error) {
40-
return keys.map(() => results);
42+
const processedRequest = transformer.transformRequest(request);
43+
44+
if (shouldValidateRequest) {
45+
validateRequest(delegationContext, processedRequest.document);
4146
}
4247

43-
const values = valuesFromResults == null ? results : valuesFromResults(results, keys);
48+
const executor = getExecutor(delegationContext);
4449

45-
return Array.isArray(values) ? values : keys.map(() => values);
50+
const batchResult = (await executor({
51+
...processedRequest,
52+
context,
53+
info,
54+
})) as ExecutionResult;
55+
56+
return splitResult(transformer.transformResult(batchResult), fieldName, keys.length);
4657
};
4758
}
4859

@@ -60,23 +71,75 @@ const getLoadersMap = memoize2(function getLoadersMap<K, V, C>(
6071
return new Map<string, DataLoader<K, V, C>>();
6172
});
6273

63-
export function getLoader<K = any, V = any, C = K>(options: BatchDelegateOptions<any>): DataLoader<K, V, C> {
64-
const fieldName = options.fieldName ?? options.info.fieldName;
65-
const loaders = getLoadersMap<K, V, C>(options.info.fieldNodes, options.schema);
74+
export function getLoader<K = any, C = K>(options: BatchDelegateOptions<any>): DataLoader<K, ExecutionResult, C> {
75+
const {
76+
info,
77+
operationName,
78+
operation = getDelegatingOperation(info.parentType, info.schema),
79+
fieldName = info.fieldName,
80+
returnType = info.returnType,
81+
selectionSet,
82+
fieldNodes,
83+
} = options;
84+
85+
if (operation !== 'query' && operation !== 'mutation') {
86+
throw new Error(`Batch delegation not possible for operation '${operation}'.`);
87+
}
6688

67-
let loader = loaders.get(fieldName);
89+
const request = createRequestFromInfo({
90+
info,
91+
operation,
92+
fieldName,
93+
selectionSet,
94+
fieldNodes,
95+
operationName,
96+
});
97+
98+
const delegationContext = getDelegationContext({
99+
request,
100+
...options,
101+
operation,
102+
fieldName,
103+
returnType,
104+
});
68105

69106
// Prevents the keys to be passed with the same structure
70-
const dataLoaderOptions: DataLoader.Options<any, any, any> = {
107+
const dataLoaderOptions: DataLoader.Options<K, ExecutionResult, C> = {
71108
cacheKeyFn: defaultCacheKeyFn,
72109
...options.dataLoaderOptions,
73110
};
74111

112+
const loaders = getLoadersMap<K, ExecutionResult, C>(info.fieldNodes, options.schema);
113+
let loader = loaders.get(fieldName);
114+
75115
if (loader === undefined) {
76116
const batchFn = createBatchFn(options);
77-
loader = new DataLoader<K, V, C>(batchFn, dataLoaderOptions);
117+
loader = new DataLoader<K, ExecutionResult, C>(
118+
keys => batchFn(keys, request, delegationContext),
119+
dataLoaderOptions
120+
);
78121
loaders.set(fieldName, loader);
79122
}
80123

81124
return loader;
82125
}
126+
127+
function splitResult(result: ExecutionResult, fieldName: string, numItems: number): Array<ExecutionResult> {
128+
const { data, errors } = result;
129+
const fieldData = data?.[fieldName];
130+
131+
if (fieldData === undefined) {
132+
if (errors === undefined) {
133+
return Array(numItems).fill({});
134+
}
135+
136+
return Array(numItems).fill({ errors });
137+
}
138+
139+
return fieldData.map((value: any) => ({
140+
data: {
141+
[fieldName]: value,
142+
},
143+
errors,
144+
}));
145+
}

packages/batch-delegate/src/index.ts

-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
11
export * from './batchDelegateToSchema';
2-
export * from './createBatchDelegateFn';
32

43
export * from './types';

packages/batch-delegate/src/types.ts

+5-13
Original file line numberDiff line numberDiff line change
@@ -6,23 +6,15 @@ export type BatchDelegateFn<TContext = Record<string, any>, K = any> = (
66
batchDelegateOptions: BatchDelegateOptions<TContext, K>
77
) => any;
88

9-
export type BatchDelegateOptionsFn<TContext = Record<string, any>, K = any> = (
10-
batchDelegateOptions: BatchDelegateOptions<TContext, K>
11-
) => IDelegateToSchemaOptions<TContext>;
12-
13-
export interface BatchDelegateOptions<TContext = Record<string, any>, K = any, V = any, C = K>
14-
extends Omit<IDelegateToSchemaOptions<TContext>, 'args'> {
9+
export interface CreateBatchDelegateFnOptions<TContext = Record<string, any>, K = any, V = any, C = K>
10+
extends Partial<Omit<IDelegateToSchemaOptions<TContext>, 'args' | 'info'>> {
1511
dataLoaderOptions?: DataLoader.Options<K, V, C>;
16-
key: K;
1712
argsFromKeys?: (keys: ReadonlyArray<K>) => Record<string, any>;
18-
valuesFromResults?: (results: any, keys: ReadonlyArray<K>) => Array<V>;
19-
lazyOptionsFn?: BatchDelegateOptionsFn;
2013
}
2114

22-
export interface CreateBatchDelegateFnOptions<TContext = Record<string, any>, K = any, V = any, C = K>
23-
extends Partial<Omit<IDelegateToSchemaOptions<TContext>, 'args' | 'info'>> {
15+
export interface BatchDelegateOptions<TContext = Record<string, any>, K = any, V = any, C = K>
16+
extends Omit<IDelegateToSchemaOptions<TContext>, 'args'> {
2417
dataLoaderOptions?: DataLoader.Options<K, V, C>;
18+
key: K | Array<K>;
2519
argsFromKeys?: (keys: ReadonlyArray<K>) => Record<string, any>;
26-
valuesFromResults?: (results: any, keys: ReadonlyArray<K>) => Array<V>;
27-
lazyOptionsFn?: (batchDelegateOptions: BatchDelegateOptions<TContext, K>) => IDelegateToSchemaOptions<TContext>;
2820
}

0 commit comments

Comments
 (0)