Skip to content

Commit

Permalink
allow key and args functions to return promises
Browse files Browse the repository at this point in the history
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
  • Loading branch information
yaacovCR committed Sep 28, 2021
1 parent b151843 commit e40ad99
Show file tree
Hide file tree
Showing 6 changed files with 88 additions and 49 deletions.
32 changes: 19 additions & 13 deletions packages/stitch/src/createMergedTypeResolver.ts
Original file line number Diff line number Diff line change
@@ -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';

Expand Down Expand Up @@ -29,19 +31,23 @@ export function createMergedTypeResolver<TContext = any>(

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();
};
}

Expand Down
7 changes: 5 additions & 2 deletions packages/stitch/src/stitchingInfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -119,8 +121,9 @@ function createMergedTypes<TContext = Record<string, any>>(
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
);
Expand Down
3 changes: 2 additions & 1 deletion packages/stitching-directives/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
71 changes: 49 additions & 22 deletions packages/stitching-directives/src/properties.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { ValueOrPromise } from 'value-or-promise';

import { PropertyTree } from './types';

export function addProperty(object: Record<string, any>, path: Array<string | number>, value: any) {
Expand Down Expand Up @@ -37,29 +39,48 @@ export function getProperty(object: Record<string, any>, path: Array<string>): a
return getProperty(prop, newPath);
}

export function getProperties(object: Record<string, any>, propertyTree: PropertyTree): any {
// c.f. https://github.com/graphql/graphql-js/blob/main/src/jsutils/promiseForObject.ts
export function getProperties(object: Record<string, any>, propertyTree: PropertyTree): any | Promise<any> {
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<ValueOrPromise<unknown>> = [];

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<Array<string>>): PropertyTree {
Expand All @@ -70,10 +91,16 @@ export function propertyTreeFromPaths(paths: Array<Array<string>>): PropertyTree
return propertyTree;
}

function deepMap(arrayOrItem: any, fn: (item: any) => any): any {
function deepMap(arrayOrItem: any, fn: (item: any) => any | Promise<any>): ValueOrPromise<any> {
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));
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<any> {
return function keyFn(originalResult: any): Promise<any> {
return getProperties(originalResult, mergedTypeResolverInfo.usedProperties);
};
}
Expand Down Expand Up @@ -498,12 +498,14 @@ function generateArgsFromKeysFn(
};
}

function generateArgsFn(mergedTypeResolverInfo: MergedTypeResolverInfo): (originalResult: any) => Record<string, any> {
function generateArgsFn(
mergedTypeResolverInfo: MergedTypeResolverInfo
): (originalResult: any) => Promise<Record<string, any>> {
const { mappingInstructions, args, usedProperties } = mergedTypeResolverInfo;

return function generateArgs(originalResult: any): Record<string, any> {
return async function generateArgs(originalResult: any): Promise<Record<string, any>> {
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;
Expand Down
12 changes: 6 additions & 6 deletions packages/stitching-directives/tests/properties.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down

0 comments on commit e40ad99

Please sign in to comment.