-
-
Notifications
You must be signed in to change notification settings - Fork 822
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
and fix them by reworking batch delegation
- Loading branch information
Showing
23 changed files
with
482 additions
and
324 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,14 +1,60 @@ | ||
import { BatchDelegateOptions } from './types'; | ||
|
||
import { getNullableType, GraphQLError, GraphQLList } from 'graphql'; | ||
|
||
import { externalValueFromResult } from '@graphql-tools/delegate'; | ||
import { relocatedError } from '@graphql-tools/utils'; | ||
|
||
import { getLoader } from './getLoader'; | ||
|
||
export function batchDelegateToSchema<TContext = any>(options: BatchDelegateOptions<TContext>): any { | ||
export async function batchDelegateToSchema<TContext = any>(options: BatchDelegateOptions<TContext>): Promise<any> { | ||
const key = options.key; | ||
if (key == null) { | ||
return null; | ||
} else if (Array.isArray(key) && !key.length) { | ||
return []; | ||
} | ||
|
||
const { | ||
schema, | ||
info, | ||
fieldName = info.fieldName, | ||
returnType = info.returnType, | ||
context, | ||
onLocatedError = (originalError: GraphQLError) => | ||
relocatedError(originalError, originalError.path ? originalError.path.slice(1) : []), | ||
} = options; | ||
|
||
const loader = getLoader(options); | ||
return Array.isArray(key) ? loader.loadMany(key) : loader.load(key); | ||
|
||
if (Array.isArray(key)) { | ||
const results = await loader.loadMany(key); | ||
|
||
return results.map(result => | ||
result instanceof Error | ||
? result | ||
: externalValueFromResult({ | ||
result, | ||
schema, | ||
info, | ||
context, | ||
fieldName, | ||
returnType: (getNullableType(returnType) as GraphQLList<any>).ofType, | ||
onLocatedError, | ||
}) | ||
); | ||
} | ||
|
||
const result = await loader.load(key); | ||
return result instanceof Error | ||
? result | ||
: externalValueFromResult({ | ||
result, | ||
schema, | ||
info, | ||
context, | ||
fieldName, | ||
returnType, | ||
onLocatedError, | ||
}); | ||
} |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,100 +1,155 @@ | ||
import { getNamedType, GraphQLOutputType, GraphQLList, GraphQLSchema, FieldNode } from 'graphql'; | ||
import { GraphQLSchema, FieldNode } from 'graphql'; | ||
|
||
import DataLoader from 'dataloader'; | ||
|
||
import { delegateToSchema, SubschemaConfig } from '@graphql-tools/delegate'; | ||
import { relocatedError } from '@graphql-tools/utils'; | ||
import { | ||
SubschemaConfig, | ||
Transformer, | ||
DelegationContext, | ||
validateRequest, | ||
getExecutor, | ||
getDelegatingOperation, | ||
createRequestFromInfo, | ||
getDelegationContext, | ||
} from '@graphql-tools/delegate'; | ||
import { ExecutionRequest, ExecutionResult } from '@graphql-tools/utils'; | ||
|
||
import { BatchDelegateOptions } from './types'; | ||
|
||
const cache1: WeakMap< | ||
ReadonlyArray<FieldNode>, | ||
WeakMap<GraphQLSchema | SubschemaConfig<any, any, any, any>, Record<string, DataLoader<any, any>>> | ||
WeakMap<GraphQLSchema | SubschemaConfig, Record<string, DataLoader<any, any>>> | ||
> = new WeakMap(); | ||
|
||
function createBatchFn<K = any>(options: BatchDelegateOptions) { | ||
function createBatchFn<K = any>( | ||
options: BatchDelegateOptions | ||
): ( | ||
keys: ReadonlyArray<K>, | ||
request: ExecutionRequest, | ||
delegationContext: DelegationContext<any> | ||
) => Promise<Array<ExecutionResult<Record<string, any>>>> { | ||
const argsFromKeys = options.argsFromKeys ?? ((keys: ReadonlyArray<K>) => ({ ids: keys })); | ||
const fieldName = options.fieldName ?? options.info.fieldName; | ||
const { valuesFromResults, lazyOptionsFn } = options; | ||
|
||
return async (keys: ReadonlyArray<K>) => { | ||
const results = await delegateToSchema({ | ||
returnType: new GraphQLList(getNamedType(options.info.returnType) as GraphQLOutputType), | ||
onLocatedError: originalError => { | ||
if (originalError.path == null) { | ||
return originalError; | ||
} | ||
|
||
const [pathFieldName, pathNumber] = originalError.path; | ||
|
||
if (pathFieldName !== fieldName) { | ||
return originalError; | ||
} | ||
const pathNumberType = typeof pathNumber; | ||
if (pathNumberType !== 'number') { | ||
return originalError; | ||
} | ||
|
||
return relocatedError(originalError, originalError.path.slice(0, 0).concat(originalError.path.slice(2))); | ||
}, | ||
|
||
const { validateRequest: shouldValidateRequest } = options; | ||
|
||
return async (keys: ReadonlyArray<K>, request: ExecutionRequest, delegationContext: DelegationContext<any>) => { | ||
const { fieldName, context, info } = delegationContext; | ||
|
||
const transformer = new Transformer({ | ||
...delegationContext, | ||
args: argsFromKeys(keys), | ||
...(lazyOptionsFn == null ? options : lazyOptionsFn(options)), | ||
}); | ||
|
||
if (results instanceof Error) { | ||
return keys.map(() => results); | ||
const processedRequest = transformer.transformRequest(request); | ||
|
||
if (shouldValidateRequest) { | ||
validateRequest(delegationContext, processedRequest.document); | ||
} | ||
|
||
const values = valuesFromResults == null ? results : valuesFromResults(results, keys); | ||
const executor = getExecutor(delegationContext); | ||
|
||
const batchResult = (await executor({ | ||
...processedRequest, | ||
context, | ||
info, | ||
})) as ExecutionResult; | ||
|
||
return Array.isArray(values) ? values : keys.map(() => values); | ||
return splitResult(transformer.transformResult(batchResult), fieldName, keys.length); | ||
}; | ||
} | ||
|
||
const cacheKeyFn = (key: any) => (typeof key === 'object' ? JSON.stringify(key) : key); | ||
|
||
export function getLoader<K = any, V = any, C = K>(options: BatchDelegateOptions<any>): DataLoader<K, V, C> { | ||
const fieldName = options.fieldName ?? options.info.fieldName; | ||
|
||
let cache2: WeakMap<GraphQLSchema | SubschemaConfig, Record<string, DataLoader<K, V, C>>> | undefined = cache1.get( | ||
options.info.fieldNodes | ||
); | ||
export function getLoader<K = any, C = K>(options: BatchDelegateOptions<any>): DataLoader<K, ExecutionResult, C> { | ||
const { | ||
info, | ||
operationName, | ||
operation = getDelegatingOperation(info.parentType, info.schema), | ||
fieldName = info.fieldName, | ||
returnType = info.returnType, | ||
selectionSet, | ||
fieldNodes, | ||
} = options; | ||
|
||
if (operation !== 'query' && operation !== 'mutation') { | ||
throw new Error(`Batch delegation not possible for operation '${operation}'.`); | ||
} | ||
|
||
// Prevents the keys to be passed with the same structure | ||
const dataLoaderOptions: DataLoader.Options<any, any, any> = { | ||
cacheKeyFn, | ||
...options.dataLoaderOptions, | ||
}; | ||
const request = createRequestFromInfo({ | ||
info, | ||
operation, | ||
fieldName, | ||
selectionSet, | ||
fieldNodes, | ||
operationName, | ||
}); | ||
|
||
const delegationContext = getDelegationContext({ | ||
request, | ||
...options, | ||
operation, | ||
fieldName, | ||
returnType, | ||
}); | ||
|
||
let cache2 = cache1.get(options.info.fieldNodes); | ||
|
||
if (cache2 === undefined) { | ||
cache2 = new WeakMap(); | ||
cache1.set(options.info.fieldNodes, cache2); | ||
const loaders = Object.create(null); | ||
cache2.set(options.schema, loaders); | ||
const batchFn = createBatchFn(options); | ||
const loader = new DataLoader<K, V, C>(keys => batchFn(keys), dataLoaderOptions); | ||
const loader = new DataLoader<K, ExecutionResult, C>( | ||
keys => batchFn(keys, request, delegationContext), | ||
options.dataLoaderOptions | ||
); | ||
loaders[fieldName] = loader; | ||
return loader; | ||
} | ||
|
||
let loaders = cache2.get(options.schema); | ||
const loaders = cache2.get(options.schema); | ||
|
||
if (loaders === undefined) { | ||
loaders = Object.create(null) as Record<string, DataLoader<K, V, C>>; | ||
cache2.set(options.schema, loaders); | ||
const newLoaders = Object.create(null); | ||
cache2.set(options.schema, newLoaders); | ||
const batchFn = createBatchFn(options); | ||
const loader = new DataLoader<K, V, C>(keys => batchFn(keys), dataLoaderOptions); | ||
loaders[fieldName] = loader; | ||
const loader = new DataLoader<K, ExecutionResult, C>( | ||
keys => batchFn(keys, request, delegationContext), | ||
options.dataLoaderOptions | ||
); | ||
newLoaders[fieldName] = loader; | ||
return loader; | ||
} | ||
|
||
let loader = loaders[fieldName]; | ||
|
||
if (loader === undefined) { | ||
const batchFn = createBatchFn(options); | ||
loader = new DataLoader<K, V, C>(keys => batchFn(keys), dataLoaderOptions); | ||
loader = new DataLoader<K, ExecutionResult, C>( | ||
keys => batchFn(keys, request, delegationContext), | ||
options.dataLoaderOptions | ||
); | ||
loaders[fieldName] = loader; | ||
} | ||
|
||
return loader; | ||
} | ||
|
||
function splitResult(result: ExecutionResult, fieldName: string, numItems: number): Array<ExecutionResult> { | ||
const { data, errors } = result; | ||
const fieldData = data?.[fieldName]; | ||
|
||
if (fieldData === undefined) { | ||
if (errors === undefined) { | ||
return Array(numItems).fill({}); | ||
} | ||
|
||
return Array(numItems).fill({ errors }); | ||
} | ||
|
||
return fieldData.map((value: any) => ({ | ||
data: { | ||
[fieldName]: value, | ||
}, | ||
errors, | ||
})); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,3 @@ | ||
export * from './batchDelegateToSchema'; | ||
export * from './createBatchDelegateFn'; | ||
|
||
export * from './types'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,36 +1,20 @@ | ||
import { FieldNode, GraphQLSchema } from 'graphql'; | ||
|
||
import DataLoader from 'dataloader'; | ||
|
||
import { IDelegateToSchemaOptions, SubschemaConfig } from '@graphql-tools/delegate'; | ||
|
||
// TODO: remove in next major release | ||
export type DataLoaderCache<K = any, V = any, C = K> = WeakMap< | ||
ReadonlyArray<FieldNode>, | ||
WeakMap<GraphQLSchema | SubschemaConfig, DataLoader<K, V, C>> | ||
>; | ||
import { IDelegateToSchemaOptions } from '@graphql-tools/delegate'; | ||
|
||
export type BatchDelegateFn<TContext = Record<string, any>, K = any> = ( | ||
batchDelegateOptions: BatchDelegateOptions<TContext, K> | ||
) => any; | ||
|
||
export type BatchDelegateOptionsFn<TContext = Record<string, any>, K = any> = ( | ||
batchDelegateOptions: BatchDelegateOptions<TContext, K> | ||
) => IDelegateToSchemaOptions<TContext>; | ||
|
||
export interface BatchDelegateOptions<TContext = Record<string, any>, K = any, V = any, C = K> | ||
extends Omit<IDelegateToSchemaOptions<TContext>, 'args'> { | ||
export interface CreateBatchDelegateFnOptions<TContext = Record<string, any>, K = any, V = any, C = K> | ||
extends Partial<Omit<IDelegateToSchemaOptions<TContext>, 'args' | 'info'>> { | ||
dataLoaderOptions?: DataLoader.Options<K, V, C>; | ||
key: K; | ||
argsFromKeys?: (keys: ReadonlyArray<K>) => Record<string, any>; | ||
valuesFromResults?: (results: any, keys: ReadonlyArray<K>) => Array<V>; | ||
lazyOptionsFn?: BatchDelegateOptionsFn; | ||
} | ||
|
||
export interface CreateBatchDelegateFnOptions<TContext = Record<string, any>, K = any, V = any, C = K> | ||
extends Partial<Omit<IDelegateToSchemaOptions<TContext>, 'args' | 'info'>> { | ||
export interface BatchDelegateOptions<TContext = Record<string, any>, K = any, V = any, C = K> | ||
extends Omit<IDelegateToSchemaOptions<TContext>, 'args'> { | ||
dataLoaderOptions?: DataLoader.Options<K, V, C>; | ||
key: K | Array<K>; | ||
argsFromKeys?: (keys: ReadonlyArray<K>) => Record<string, any>; | ||
valuesFromResults?: (results: any, keys: ReadonlyArray<K>) => Array<V>; | ||
lazyOptionsFn?: (batchDelegateOptions: BatchDelegateOptions<TContext, K>) => IDelegateToSchemaOptions<TContext>; | ||
} |
Oops, something went wrong.