Skip to content

Commit c19b07a

Browse files
committed
use lazy query planner
refactor externalObject internals into separate file add tests from #2951 and fix them by reworking batch delegation create memoized, static buidDelegationPlan 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: this function could be extracted to work on the stiched schema itself rather than the extracted metadata, and might be useful as part of a graphiql-type interface
1 parent 9f551bd commit c19b07a

39 files changed

+1549
-1106
lines changed
Lines changed: 48 additions & 2 deletions
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

Lines changed: 0 additions & 31 deletions
This file was deleted.
Lines changed: 108 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,100 +1,155 @@
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 { 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 } from '@graphql-tools/utils';
716

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

1019
const cache1: WeakMap<
1120
ReadonlyArray<FieldNode>,
12-
WeakMap<GraphQLSchema | SubschemaConfig<any, any, any, any>, Record<string, DataLoader<any, any>>>
21+
WeakMap<GraphQLSchema | SubschemaConfig, Record<string, DataLoader<any, any>>>
1322
> = new WeakMap();
1423

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

44-
if (results instanceof Error) {
45-
return keys.map(() => results);
43+
const processedRequest = transformer.transformRequest(request);
44+
45+
if (shouldValidateRequest) {
46+
validateRequest(delegationContext, processedRequest.document);
4647
}
4748

48-
const values = valuesFromResults == null ? results : valuesFromResults(results, keys);
49+
const executor = getExecutor(delegationContext);
50+
51+
const batchResult = (await executor({
52+
...processedRequest,
53+
context,
54+
info,
55+
})) as ExecutionResult;
4956

50-
return Array.isArray(values) ? values : keys.map(() => values);
57+
return splitResult(transformer.transformResult(batchResult), fieldName, keys.length);
5158
};
5259
}
5360

54-
const cacheKeyFn = (key: any) => (typeof key === 'object' ? JSON.stringify(key) : key);
55-
56-
export function getLoader<K = any, V = any, C = K>(options: BatchDelegateOptions<any>): DataLoader<K, V, C> {
57-
const fieldName = options.fieldName ?? options.info.fieldName;
58-
59-
let cache2: WeakMap<GraphQLSchema | SubschemaConfig, Record<string, DataLoader<K, V, C>>> | undefined = cache1.get(
60-
options.info.fieldNodes
61-
);
61+
export function getLoader<K = any, C = K>(options: BatchDelegateOptions<any>): DataLoader<K, ExecutionResult, C> {
62+
const {
63+
info,
64+
operationName,
65+
operation = getDelegatingOperation(info.parentType, info.schema),
66+
fieldName = info.fieldName,
67+
returnType = info.returnType,
68+
selectionSet,
69+
fieldNodes,
70+
} = options;
71+
72+
if (operation !== 'query' && operation !== 'mutation') {
73+
throw new Error(`Batch delegation not possible for operation '${operation}'.`);
74+
}
6275

63-
// Prevents the keys to be passed with the same structure
64-
const dataLoaderOptions: DataLoader.Options<any, any, any> = {
65-
cacheKeyFn,
66-
...options.dataLoaderOptions,
67-
};
76+
const request = createRequestFromInfo({
77+
info,
78+
operation,
79+
fieldName,
80+
selectionSet,
81+
fieldNodes,
82+
operationName,
83+
});
84+
85+
const delegationContext = getDelegationContext({
86+
request,
87+
...options,
88+
operation,
89+
fieldName,
90+
returnType,
91+
});
92+
93+
let cache2 = cache1.get(options.info.fieldNodes);
6894

6995
if (cache2 === undefined) {
7096
cache2 = new WeakMap();
7197
cache1.set(options.info.fieldNodes, cache2);
7298
const loaders = Object.create(null);
7399
cache2.set(options.schema, loaders);
74100
const batchFn = createBatchFn(options);
75-
const loader = new DataLoader<K, V, C>(keys => batchFn(keys), dataLoaderOptions);
101+
const loader = new DataLoader<K, ExecutionResult, C>(
102+
keys => batchFn(keys, request, delegationContext),
103+
options.dataLoaderOptions
104+
);
76105
loaders[fieldName] = loader;
77106
return loader;
78107
}
79108

80-
let loaders = cache2.get(options.schema);
109+
const loaders = cache2.get(options.schema);
81110

82111
if (loaders === undefined) {
83-
loaders = Object.create(null) as Record<string, DataLoader<K, V, C>>;
84-
cache2.set(options.schema, loaders);
112+
const newLoaders = Object.create(null);
113+
cache2.set(options.schema, newLoaders);
85114
const batchFn = createBatchFn(options);
86-
const loader = new DataLoader<K, V, C>(keys => batchFn(keys), dataLoaderOptions);
87-
loaders[fieldName] = loader;
115+
const loader = new DataLoader<K, ExecutionResult, C>(
116+
keys => batchFn(keys, request, delegationContext),
117+
options.dataLoaderOptions
118+
);
119+
newLoaders[fieldName] = loader;
88120
return loader;
89121
}
90122

91123
let loader = loaders[fieldName];
92124

93125
if (loader === undefined) {
94126
const batchFn = createBatchFn(options);
95-
loader = new DataLoader<K, V, C>(keys => batchFn(keys), dataLoaderOptions);
127+
loader = new DataLoader<K, ExecutionResult, C>(
128+
keys => batchFn(keys, request, delegationContext),
129+
options.dataLoaderOptions
130+
);
96131
loaders[fieldName] = loader;
97132
}
98133

99134
return loader;
100135
}
136+
137+
function splitResult(result: ExecutionResult, fieldName: string, numItems: number): Array<ExecutionResult> {
138+
const { data, errors } = result;
139+
const fieldData = data?.[fieldName];
140+
141+
if (fieldData === undefined) {
142+
if (errors === undefined) {
143+
return Array(numItems).fill({});
144+
}
145+
146+
return Array(numItems).fill({ errors });
147+
}
148+
149+
return fieldData.map((value: any) => ({
150+
data: {
151+
[fieldName]: value,
152+
},
153+
errors,
154+
}));
155+
}
Lines changed: 0 additions & 1 deletion
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';
Lines changed: 6 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,20 @@
1-
import { FieldNode, GraphQLSchema } from 'graphql';
2-
31
import DataLoader from 'dataloader';
42

5-
import { IDelegateToSchemaOptions, SubschemaConfig } from '@graphql-tools/delegate';
6-
7-
// TODO: remove in next major release
8-
export type DataLoaderCache<K = any, V = any, C = K> = WeakMap<
9-
ReadonlyArray<FieldNode>,
10-
WeakMap<GraphQLSchema | SubschemaConfig, DataLoader<K, V, C>>
11-
>;
3+
import { IDelegateToSchemaOptions } from '@graphql-tools/delegate';
124

135
export type BatchDelegateFn<TContext = Record<string, any>, K = any> = (
146
batchDelegateOptions: BatchDelegateOptions<TContext, K>
157
) => any;
168

17-
export type BatchDelegateOptionsFn<TContext = Record<string, any>, K = any> = (
18-
batchDelegateOptions: BatchDelegateOptions<TContext, K>
19-
) => IDelegateToSchemaOptions<TContext>;
20-
21-
export interface BatchDelegateOptions<TContext = Record<string, any>, K = any, V = any, C = K>
22-
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'>> {
2311
dataLoaderOptions?: DataLoader.Options<K, V, C>;
24-
key: K;
2512
argsFromKeys?: (keys: ReadonlyArray<K>) => Record<string, any>;
26-
valuesFromResults?: (results: any, keys: ReadonlyArray<K>) => Array<V>;
27-
lazyOptionsFn?: BatchDelegateOptionsFn;
2813
}
2914

30-
export interface CreateBatchDelegateFnOptions<TContext = Record<string, any>, K = any, V = any, C = K>
31-
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'> {
3217
dataLoaderOptions?: DataLoader.Options<K, V, C>;
18+
key: K | Array<K>;
3319
argsFromKeys?: (keys: ReadonlyArray<K>) => Record<string, any>;
34-
valuesFromResults?: (results: any, keys: ReadonlyArray<K>) => Array<V>;
35-
lazyOptionsFn?: (batchDelegateOptions: BatchDelegateOptions<TContext, K>) => IDelegateToSchemaOptions<TContext>;
3620
}

0 commit comments

Comments
 (0)