diff --git a/packages/batch-delegate/tests/typeMerging.example.test.ts b/packages/batch-delegate/tests/typeMerging.example.test.ts index 302b26ee8aa..ff8f9f13d40 100644 --- a/packages/batch-delegate/tests/typeMerging.example.test.ts +++ b/packages/batch-delegate/tests/typeMerging.example.test.ts @@ -225,7 +225,7 @@ describe('merging using type merging', () => { User: { selectionSet: '{ id }', fieldName: '_userById', - args: ({ id }) => ({ id }) + args: async ({ id }) => ({ id: await id }) } }, batch: true, @@ -242,7 +242,7 @@ describe('merging using type merging', () => { }, }, fieldName: '_products', - key: ({ upc, weight, price }) => ({ upc, weight, price }), + key: async ({ upc, weight, price }) => ({ upc: await upc, weight: await weight, price: await price }), argsFromKeys: (representations) => ({ representations }), } }, @@ -254,7 +254,7 @@ describe('merging using type merging', () => { Product: { selectionSet: '{ upc }', fieldName: '_productByUpc', - args: ({ upc }) => ({ upc }), + args: async ({ upc }) => ({ upc: await upc }), } }, batch: true, @@ -266,12 +266,12 @@ describe('merging using type merging', () => { selectionSet: '{ id }', fieldName: '_usersById', argsFromKeys: (ids) => ({ ids }), - key: ({ id }) => id, + key: async ({ id }) => id, }, Product: { selectionSet: '{ upc }', fieldName: '_productByUpc', - args: ({ upc }) => ({ upc }), + args: async ({ upc }) => ({ upc: await upc }), }, }, batch: true, diff --git a/packages/delegate/src/types.ts b/packages/delegate/src/types.ts index 7899ea8ee7c..851e2719453 100644 --- a/packages/delegate/src/types.ts +++ b/packages/delegate/src/types.ts @@ -170,13 +170,13 @@ export interface MergedTypeConfig> extends MergedTypeResolverOptions { selectionSet?: string; - key?: (originalResult: any) => K; + key?: (originalResult: any) => Promise; resolve?: MergedTypeResolver; } export interface MergedTypeResolverOptions { fieldName?: string; - args?: (originalResult: any) => Record; + args?: (originalResult: any) => Promise>; argsFromKeys?: (keys: ReadonlyArray) => Record; valuesFromResults?: (results: any, keys: ReadonlyArray) => Array; } diff --git a/packages/stitch/src/createMergedTypeResolver.ts b/packages/stitch/src/createMergedTypeResolver.ts index 76735bf923b..2f9fa50d5b5 100644 --- a/packages/stitch/src/createMergedTypeResolver.ts +++ b/packages/stitch/src/createMergedTypeResolver.ts @@ -28,7 +28,7 @@ export function createMergedTypeResolver( } if (args != null) { - return function mergedTypeResolver(originalResult, context, info, subschema, selectionSet) { + return async function mergedTypeResolver(originalResult, context, info, subschema, selectionSet) { return delegateToSchema({ schema: subschema, operation: 'query', @@ -36,7 +36,7 @@ export function createMergedTypeResolver( returnType: getNamedType( info.schema.getType(originalResult.__typename) ?? info.returnType ) as GraphQLOutputType, - args: args(originalResult), + args: await args(originalResult), selectionSet, context, info, diff --git a/packages/stitch/src/stitchingInfo.ts b/packages/stitch/src/stitchingInfo.ts index 05f9b5fd1cb..cb224308ba8 100644 --- a/packages/stitch/src/stitchingInfo.ts +++ b/packages/stitch/src/stitchingInfo.ts @@ -118,8 +118,8 @@ function createMergedTypes>( resolvers.set( subschema, keyFn - ? (originalResult, context, info, subschema, selectionSet) => { - const key = keyFn(originalResult); + ? async (originalResult, context, info, subschema, selectionSet) => { + const key = await keyFn(originalResult); return resolver(originalResult, context, info, subschema, selectionSet, key); } : resolver diff --git a/packages/stitching-directives/src/properties.ts b/packages/stitching-directives/src/properties.ts index f33aaec9066..50b61d736b8 100644 --- a/packages/stitching-directives/src/properties.ts +++ b/packages/stitching-directives/src/properties.ts @@ -37,25 +37,26 @@ export function getProperty(object: Record, path: Array): a return getProperty(prop, newPath); } -export function getProperties(object: Record, propertyTree: PropertyTree): any { +// c.f. https://github.com/graphql/graphql-js/blob/main/src/jsutils/promiseForObject.ts +export async function getPropertiesAsync(object: Record, propertyTree: PropertyTree): Promise { if (object == null) { return object; } - const newObject = Object.create(null); + const keys = Object.keys(propertyTree); - for (const key in propertyTree) { - const subKey = propertyTree[key]; + const values = await Promise.all(keys.map(key => object[key])); + const newObject = Object.create(null); + for (const [i, key] of keys.entries()) { + const subKey = propertyTree[key]; if (subKey == null) { - newObject[key] = object[key]; + newObject[key] = values[i]; continue; } - const prop = object[key]; - - newObject[key] = deepMap(prop, function deepMapFn(item) { - return getProperties(item, subKey); + newObject[key] = await deepMapAsync(values[i], function deepMapFn(item) { + return getPropertiesAsync(item, subKey); }); } @@ -70,9 +71,10 @@ export function propertyTreeFromPaths(paths: Array>): PropertyTree return propertyTree; } -function deepMap(arrayOrItem: any, fn: (item: any) => any): any { +async function deepMapAsync(arrayOrItem: any, fn: (item: any) => Promise): Promise { if (Array.isArray(arrayOrItem)) { - return arrayOrItem.map(nestedArrayOrItem => deepMap(nestedArrayOrItem, fn)); + const arr = await Promise.all(arrayOrItem); + return Promise.all(arr.map(nestedArrayOrItem => deepMapAsync(nestedArrayOrItem, fn))); } return fn(arrayOrItem); diff --git a/packages/stitching-directives/src/stitchingDirectivesTransformer.ts b/packages/stitching-directives/src/stitchingDirectivesTransformer.ts index 570aed10ac2..78edc651227 100644 --- a/packages/stitching-directives/src/stitchingDirectivesTransformer.ts +++ b/packages/stitching-directives/src/stitchingDirectivesTransformer.ts @@ -29,7 +29,7 @@ import { MergedTypeResolverInfo, StitchingDirectivesOptions } from './types'; import { defaultStitchingDirectiveOptions } from './defaultStitchingDirectiveOptions'; import { parseMergeArgsExpr } from './parseMergeArgsExpr'; -import { addProperty, getProperty, getProperties } from './properties'; +import { addProperty, getProperty, getPropertiesAsync } from './properties'; import { stitchingDirectivesValidator } from './stitchingDirectivesValidator'; export function stitchingDirectivesTransformer( @@ -464,9 +464,9 @@ function forEachConcreteType( } } -function generateKeyFn(mergedTypeResolverInfo: MergedTypeResolverInfo): (originalResult: any) => any { - return function keyFn(originalResult: any) { - return getProperties(originalResult, mergedTypeResolverInfo.usedProperties); +function generateKeyFn(mergedTypeResolverInfo: MergedTypeResolverInfo): (originalResult: any) => Promise { + return function keyFn(originalResult: any): Promise { + return getPropertiesAsync(originalResult, mergedTypeResolverInfo.usedProperties); }; } @@ -498,12 +498,14 @@ function generateArgsFromKeysFn( }; } -function generateArgsFn(mergedTypeResolverInfo: MergedTypeResolverInfo): (originalResult: any) => Record { +function generateArgsFn( + mergedTypeResolverInfo: MergedTypeResolverInfo +): (originalResult: any) => Promise> { const { mappingInstructions, args, usedProperties } = mergedTypeResolverInfo; - return function generateArgs(originalResult: any): Record { + return async function generateArgs(originalResult: any): Promise> { const newArgs = mergeDeep([{}, args]); - const filteredResult = getProperties(originalResult, usedProperties); + const filteredResult = await getPropertiesAsync(originalResult, usedProperties); if (mappingInstructions) { for (const mappingInstruction of mappingInstructions) { const { destinationPath, sourcePath } = mappingInstruction; diff --git a/packages/stitching-directives/tests/properties.test.ts b/packages/stitching-directives/tests/properties.test.ts index 5a34543e5e6..aa4de672782 100644 --- a/packages/stitching-directives/tests/properties.test.ts +++ b/packages/stitching-directives/tests/properties.test.ts @@ -1,4 +1,4 @@ -import { addProperty, getProperties } from "../src/properties"; +import { addProperty, getPropertiesAsync } from "../src/properties"; describe('addProperty', () => { test('can add a key to an object', () => { @@ -29,16 +29,16 @@ describe('addProperty', () => { }); describe('getProperties', () => { - test('can getProperties', () => { + test('can getProperties', async () => { const object = { - field1: 'value1', - field2: { - subfieldA: 'valueA', - subfieldB: 'valueB', - }, + field1: Promise.resolve('value1'), + field2: Promise.resolve({ + subfieldA: Promise.resolve('valueA'), + subfieldB: Promise.resolve('valueB'), + }), } - const extracted = getProperties(object, { + const extracted = await getPropertiesAsync(object, { field1: null, field2: { subfieldA: null, diff --git a/packages/stitching-directives/tests/stitchingDirectivesTransformer.test.ts b/packages/stitching-directives/tests/stitchingDirectivesTransformer.test.ts index 72104b768d7..193bcfa4d77 100644 --- a/packages/stitching-directives/tests/stitchingDirectivesTransformer.test.ts +++ b/packages/stitching-directives/tests/stitchingDirectivesTransformer.test.ts @@ -96,7 +96,7 @@ describe('type merging directives', () => { expect(transformedSubschemaConfig.merge?.['User'].fieldName).toEqual('_entity'); }); - test('adds type selection sets when returns list', () => { + test('adds type selection sets when returns list', async () => { const typeDefs = /* GraphQL */` ${allStitchingDirectivesTypeDefs} scalar _Key @@ -131,7 +131,7 @@ describe('type merging directives', () => { ] }; - const args = argsFn(originalResult); + const args = await argsFn(originalResult); expect(args).toEqual({ key: { @@ -147,7 +147,7 @@ describe('type merging directives', () => { }); }); - test('adds type selection sets when returns multi-layered list', () => { + test('adds type selection sets when returns multi-layered list', async () => { const typeDefs = /* GraphQL */` ${allStitchingDirectivesTypeDefs} scalar _Key @@ -189,7 +189,7 @@ describe('type merging directives', () => { ] }; - const args = argsFn(originalResult); + const args = await argsFn(originalResult); expect(args).toEqual({ key: { @@ -212,7 +212,7 @@ describe('type merging directives', () => { }); }); - test('adds type selection sets when returns null', () => { + test('adds type selection sets when returns null', async () => { const typeDefs = /* GraphQL */` ${allStitchingDirectivesTypeDefs} scalar _Key @@ -240,7 +240,7 @@ describe('type merging directives', () => { nestedField: null }; - const args = argsFn(originalResult); + const args = await argsFn(originalResult); expect(args).toEqual({ key: { @@ -277,7 +277,7 @@ describe('type merging directives', () => { expect(transformedSubschemaConfig.merge?.['User'].fieldName).toEqual('_user'); }); - test('adds args function when used without arguments', () => { + test('adds args function when used without arguments', async () => { const typeDefs = /* GraphQL */` ${allStitchingDirectivesTypeDefs} scalar _Key @@ -307,7 +307,7 @@ describe('type merging directives', () => { email: 'email@email.com', }; - const args = argsFn(originalResult); + const args = await argsFn(originalResult); expect(args).toEqual({ key: { @@ -316,7 +316,7 @@ describe('type merging directives', () => { }); }); - test('adds args function when used with argsExpr argument using an unqualified key', () => { + test('adds args function when used with argsExpr argument using an unqualified key', async () => { const typeDefs = /* GraphQL */` ${allStitchingDirectivesTypeDefs} scalar _Key @@ -346,7 +346,7 @@ describe('type merging directives', () => { email: 'email@email.com', }; - const args = argsFn(originalResult); + const args = await argsFn(originalResult); expect(args).toEqual({ key: { @@ -355,7 +355,7 @@ describe('type merging directives', () => { }); }); - test('adds args function when used with argsExpr argument using a fully qualified key', () => { + test('adds args function when used with argsExpr argument using a fully qualified key', async () => { const typeDefs = /* GraphQL */` ${allStitchingDirectivesTypeDefs} scalar _Key @@ -385,7 +385,7 @@ describe('type merging directives', () => { email: 'email@email.com', }; - const args = argsFn(originalResult); + const args = await argsFn(originalResult); expect(args).toEqual({ key: { @@ -394,7 +394,7 @@ describe('type merging directives', () => { }); }); - test('adds args function when used with keyArg argument', () => { + test('adds args function when used with keyArg argument', async () => { const typeDefs = /* GraphQL */` ${allStitchingDirectivesTypeDefs} scalar _Key @@ -424,7 +424,7 @@ describe('type merging directives', () => { email: 'email@email.com', }; - const args = argsFn(originalResult); + const args = await argsFn(originalResult); expect(args).toEqual({ key: { @@ -433,7 +433,7 @@ describe('type merging directives', () => { }); }); - test('adds args function when used with nested keyArg argument', () => { + test('adds args function when used with nested keyArg argument', async () => { const typeDefs = /* GraphQL */` ${allStitchingDirectivesTypeDefs} scalar _Key @@ -467,7 +467,7 @@ describe('type merging directives', () => { email: 'email@email.com', }; - const args = argsFn(originalResult); + const args = await argsFn(originalResult); expect(args).toEqual({ input: { @@ -479,7 +479,7 @@ describe('type merging directives', () => { }); }); - test('adds args function when used with keyArg and additionalArgs arguments', () => { + test('adds args function when used with keyArg and additionalArgs arguments', async () => { const typeDefs = /* GraphQL */` ${allStitchingDirectivesTypeDefs} scalar _Key @@ -509,7 +509,7 @@ describe('type merging directives', () => { email: 'email@email.com', }; - const args = argsFn(originalResult); + const args = await argsFn(originalResult); expect(args).toEqual({ key: { @@ -519,7 +519,7 @@ describe('type merging directives', () => { }); }); - test('adds key and args function when @merge is used with keyField argument', () => { + test('adds key and args function when @merge is used with keyField argument', async () => { const typeDefs = /* GraphQL */` ${allStitchingDirectivesTypeDefs} type Query { @@ -549,14 +549,14 @@ describe('type merging directives', () => { email: 'email@email.com', }; - const args = argsFn(originalResult); + const args = await argsFn(originalResult); expect(args).toEqual({ id: '5', }); }); - test('adds args function when used with key argument', () => { + test('adds args function when used with key argument', async () => { const typeDefs = /* GraphQL */` ${allStitchingDirectivesTypeDefs} scalar _Key @@ -588,7 +588,7 @@ describe('type merging directives', () => { }, }; - const args = argsFn(originalResult); + const args = await argsFn(originalResult); expect(args).toEqual({ key: { @@ -602,7 +602,7 @@ describe('type merging directives', () => { }); }); - test('adds key and argsFromKeys functions when used without arguments', () => { + test('adds key and argsFromKeys functions when used without arguments', async () => { const typeDefs = /* GraphQL */` ${allStitchingDirectivesTypeDefs} scalar _Key @@ -633,7 +633,7 @@ describe('type merging directives', () => { email: 'email@email.com', }; - const key = keyFn(originalResult); + const key = await keyFn(originalResult); const args = argsFromKeysFn([key]); expect(key).toEqual({ @@ -646,7 +646,7 @@ describe('type merging directives', () => { }); }); - test('adds key and argsFromKeys functions when used without arguments and returns union', () => { + test('adds key and argsFromKeys functions when used without arguments and returns union', async () => { const typeDefs = /* GraphQL */` ${allStitchingDirectivesTypeDefs} scalar _Key @@ -680,7 +680,7 @@ describe('type merging directives', () => { email: 'email@email.com', }; - const key = keyFn(originalResult); + const key = await keyFn(originalResult); const args = argsFromKeysFn([key]); expect(key).toEqual({ @@ -695,7 +695,7 @@ describe('type merging directives', () => { }); }); - test('adds key and argsFromKeys functions when used without arguments and returns interface', () => { + test('adds key and argsFromKeys functions when used without arguments and returns interface', async () => { const typeDefs = /* GraphQL */` ${allStitchingDirectivesTypeDefs} scalar _Key @@ -731,7 +731,7 @@ describe('type merging directives', () => { email: 'email@email.com', }; - const key = keyFn(originalResult); + const key = await keyFn(originalResult); const args = argsFromKeysFn([key]); expect(key).toEqual({ @@ -746,7 +746,7 @@ describe('type merging directives', () => { }); }); - test('adds key and argsFromKeys functions with argsExpr argument using an unqualified key', () => { + test('adds key and argsFromKeys functions with argsExpr argument using an unqualified key', async () => { const typeDefs = /* GraphQL */` ${allStitchingDirectivesTypeDefs} scalar _Key @@ -777,7 +777,7 @@ describe('type merging directives', () => { email: 'email@email.com', }; - const key = keyFn(originalResult); + const key = await keyFn(originalResult); const args = argsFromKeysFn([key]); expect(key).toEqual({ @@ -790,7 +790,7 @@ describe('type merging directives', () => { }); }); - test('adds key and argsFromKeys functions with argsExpr argument using a fully qualified key', () => { + test('adds key and argsFromKeys functions with argsExpr argument using a fully qualified key', async () => { const typeDefs = /* GraphQL */` ${allStitchingDirectivesTypeDefs} scalar _Key @@ -821,7 +821,7 @@ describe('type merging directives', () => { email: 'email@email.com', }; - const key = keyFn(originalResult); + const key = await keyFn(originalResult); const args = argsFromKeysFn([key]); expect(key).toEqual({