From e40ad9993ca7cc1b8607b89f55a6bb893a73c9a8 Mon Sep 17 00:00:00 2001 From: Yaacov Rydzinski Date: Tue, 28 Sep 2021 22:14:18 +0300 Subject: [PATCH] allow key and args functions to return promises may be helpful if ExternalObject access were to be managed by a store -- in which case the store could be updated after initial result returned from original schema either with additional incremental delivery results or with results from other schemas --- .../stitch/src/createMergedTypeResolver.ts | 32 +++++---- packages/stitch/src/stitchingInfo.ts | 7 +- packages/stitching-directives/package.json | 3 +- .../stitching-directives/src/properties.ts | 71 +++++++++++++------ .../src/stitchingDirectivesTransformer.ts | 12 ++-- .../tests/properties.test.ts | 12 ++-- 6 files changed, 88 insertions(+), 49 deletions(-) diff --git a/packages/stitch/src/createMergedTypeResolver.ts b/packages/stitch/src/createMergedTypeResolver.ts index 76735bf923b..a096f1f1e81 100644 --- a/packages/stitch/src/createMergedTypeResolver.ts +++ b/packages/stitch/src/createMergedTypeResolver.ts @@ -1,4 +1,6 @@ import { getNamedType, GraphQLOutputType, GraphQLList } from 'graphql'; +import { ValueOrPromise } from 'value-or-promise'; + import { delegateToSchema, MergedTypeResolver, MergedTypeResolverOptions } from '@graphql-tools/delegate'; import { batchDelegateToSchema } from '@graphql-tools/batch-delegate'; @@ -29,19 +31,23 @@ export function createMergedTypeResolver( if (args != null) { return function mergedTypeResolver(originalResult, context, info, subschema, selectionSet) { - return delegateToSchema({ - schema: subschema, - operation: 'query', - fieldName, - returnType: getNamedType( - info.schema.getType(originalResult.__typename) ?? info.returnType - ) as GraphQLOutputType, - args: args(originalResult), - selectionSet, - context, - info, - skipTypeMerging: true, - }); + return new ValueOrPromise(() => args(originalResult)) + .then(resolvedArgs => + delegateToSchema({ + schema: subschema, + operation: 'query', + fieldName, + returnType: getNamedType( + info.schema.getType(originalResult.__typename) ?? info.returnType + ) as GraphQLOutputType, + args: resolvedArgs, + selectionSet, + context, + info, + skipTypeMerging: true, + }) + ) + .resolve(); }; } diff --git a/packages/stitch/src/stitchingInfo.ts b/packages/stitch/src/stitchingInfo.ts index 05f9b5fd1cb..10bdc392e32 100644 --- a/packages/stitch/src/stitchingInfo.ts +++ b/packages/stitch/src/stitchingInfo.ts @@ -15,6 +15,8 @@ import { GraphQLNamedType, } from 'graphql'; +import { ValueOrPromise } from 'value-or-promise'; + import { collectFields, parseSelectionSet, IResolvers, IFieldResolverOptions, isSome } from '@graphql-tools/utils'; import { MergedTypeResolver, Subschema, SubschemaConfig, MergedTypeInfo, StitchingInfo } from '@graphql-tools/delegate'; @@ -119,8 +121,9 @@ function createMergedTypes>( subschema, keyFn ? (originalResult, context, info, subschema, selectionSet) => { - const key = keyFn(originalResult); - return resolver(originalResult, context, info, subschema, selectionSet, key); + return new ValueOrPromise(() => keyFn(originalResult)) + .then(key => resolver(originalResult, context, info, subschema, selectionSet, key)) + .resolve(); } : resolver ); diff --git a/packages/stitching-directives/package.json b/packages/stitching-directives/package.json index 8b208e8485f..fa2d22af04e 100644 --- a/packages/stitching-directives/package.json +++ b/packages/stitching-directives/package.json @@ -34,7 +34,8 @@ "dependencies": { "@graphql-tools/delegate": "^8.2.0", "@graphql-tools/utils": "^8.2.0", - "tslib": "~2.3.0" + "tslib": "~2.3.0", + "value-or-promise": "1.0.10" }, "devDependencies": { "@graphql-tools/schema": "8.2.0" diff --git a/packages/stitching-directives/src/properties.ts b/packages/stitching-directives/src/properties.ts index f33aaec9066..23a60095a79 100644 --- a/packages/stitching-directives/src/properties.ts +++ b/packages/stitching-directives/src/properties.ts @@ -1,3 +1,5 @@ +import { ValueOrPromise } from 'value-or-promise'; + import { PropertyTree } from './types'; export function addProperty(object: Record, path: Array, value: any) { @@ -37,29 +39,48 @@ 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 function getProperties(object: Record, propertyTree: PropertyTree): any | Promise { if (object == null) { return object; } - const newObject = Object.create(null); - - for (const key in propertyTree) { - const subKey = propertyTree[key]; - - if (subKey == null) { - newObject[key] = object[key]; - continue; - } - - const prop = object[key]; - - newObject[key] = deepMap(prop, function deepMapFn(item) { - return getProperties(item, subKey); - }); - } - - return newObject; + const keys = Object.keys(propertyTree); + + return ValueOrPromise.all(keys.map(key => new ValueOrPromise(() => object[key]))) + .then(values => { + const newValues: Array> = []; + + const newObject = Object.create(null); + for (const [i, key] of keys.entries()) { + const subKey = propertyTree[key]; + if (subKey == null) { + newValues.push( + new ValueOrPromise(() => values[i]).then(resolvedValue => { + newObject[key] = resolvedValue; + }) + ); + continue; + } + + newValues.push( + new ValueOrPromise(() => values[i]) + .then(resolvedValue => + deepMap(resolvedValue, function deepMapFn(item) { + return getProperties(item, subKey); + }).resolve() + ) + .then(mappedValue => { + newObject[key] = mappedValue; + }) + ); + } + + return ValueOrPromise.all(newValues) + .then(() => newObject) + .resolve(); + }) + .resolve(); } export function propertyTreeFromPaths(paths: Array>): PropertyTree { @@ -70,10 +91,16 @@ export function propertyTreeFromPaths(paths: Array>): PropertyTree return propertyTree; } -function deepMap(arrayOrItem: any, fn: (item: any) => any): any { +function deepMap(arrayOrItem: any, fn: (item: any) => any | Promise): ValueOrPromise { if (Array.isArray(arrayOrItem)) { - return arrayOrItem.map(nestedArrayOrItem => deepMap(nestedArrayOrItem, fn)); + return ValueOrPromise.all( + arrayOrItem.map(nestedArrayOrItem => + new ValueOrPromise(() => nestedArrayOrItem).then(resolvedNestedArrayOrItem => + deepMap(resolvedNestedArrayOrItem, fn).resolve() + ) + ) + ); } - return fn(arrayOrItem); + return new ValueOrPromise(() => fn(arrayOrItem)); } diff --git a/packages/stitching-directives/src/stitchingDirectivesTransformer.ts b/packages/stitching-directives/src/stitchingDirectivesTransformer.ts index 570aed10ac2..b7f0a8e8dab 100644 --- a/packages/stitching-directives/src/stitchingDirectivesTransformer.ts +++ b/packages/stitching-directives/src/stitchingDirectivesTransformer.ts @@ -464,8 +464,8 @@ function forEachConcreteType( } } -function generateKeyFn(mergedTypeResolverInfo: MergedTypeResolverInfo): (originalResult: any) => any { - return function keyFn(originalResult: any) { +function generateKeyFn(mergedTypeResolverInfo: MergedTypeResolverInfo): (originalResult: any) => Promise { + return function keyFn(originalResult: any): Promise { return getProperties(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 getProperties(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..3921ac8aad4 100644 --- a/packages/stitching-directives/tests/properties.test.ts +++ b/packages/stitching-directives/tests/properties.test.ts @@ -29,16 +29,16 @@ describe('addProperty', () => { }); describe('getProperties', () => { - test('can getProperties', () => { + test('can getProperties', async () => { const object = { field1: 'value1', - field2: { - subfieldA: 'valueA', - subfieldB: 'valueB', - }, + field2: Promise.resolve({ + subfieldA: Promise.resolve('valueA'), + subfieldB: Promise.resolve('valueB'), + }), } - const extracted = getProperties(object, { + const extracted = await getProperties(object, { field1: null, field2: { subfieldA: null,