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 29, 2021
1 parent b151843 commit c2146cc
Show file tree
Hide file tree
Showing 5 changed files with 105 additions and 32 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
31 changes: 31 additions & 0 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 @@ -62,6 +64,35 @@ export function getProperties(object: Record<string, any>, propertyTree: Propert
return newObject;
}

// c.f. https://github.com/graphql/graphql-js/blob/main/src/jsutils/promiseForObject.ts
export function getResolvedPropertiesOrPromise(
object: Record<string, any>,
propertyTree: PropertyTree
): ValueOrPromise<any> {
if (object == null) {
return new ValueOrPromise(() => object);
}

const keys = Object.keys(propertyTree);

const newObject = Object.create(null);
return ValueOrPromise.all(
keys.map(key =>
new ValueOrPromise(() => object[key]).then(value => {
const subKey = propertyTree[key];
if (subKey == null) {
newObject[key] = value;
return;
}

newObject[key] = deepMap(value, function deepMapFn(item) {
return getProperties(item, subKey);
});
})
)
).then(() => newObject);
}

export function propertyTreeFromPaths(paths: Array<Array<string>>): PropertyTree {
const propertyTree = Object.create(null);
for (const path of paths) {
Expand Down
33 changes: 19 additions & 14 deletions packages/stitching-directives/src/stitchingDirectivesTransformer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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, getResolvedPropertiesOrPromise } from './properties';
import { stitchingDirectivesValidator } from './stitchingDirectivesValidator';

export function stitchingDirectivesTransformer(
Expand Down Expand Up @@ -465,8 +465,8 @@ function forEachConcreteType(
}

function generateKeyFn(mergedTypeResolverInfo: MergedTypeResolverInfo): (originalResult: any) => any {
return function keyFn(originalResult: any) {
return getProperties(originalResult, mergedTypeResolverInfo.usedProperties);
return function keyFn(originalResult: any): any {
return getResolvedPropertiesOrPromise(originalResult, mergedTypeResolverInfo.usedProperties).resolve();
};
}

Expand Down Expand Up @@ -498,19 +498,24 @@ function generateArgsFromKeysFn(
};
}

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

return function generateArgs(originalResult: any): Record<string, any> {
const newArgs = mergeDeep([{}, args]);
const filteredResult = getProperties(originalResult, usedProperties);
if (mappingInstructions) {
for (const mappingInstruction of mappingInstructions) {
const { destinationPath, sourcePath } = mappingInstruction;
addProperty(newArgs, destinationPath, getProperty(filteredResult, sourcePath));
}
}
return newArgs;
return function generateArgs(originalResult: any): Record<string, any> | Promise<Record<string, any>> {
return getResolvedPropertiesOrPromise(originalResult, usedProperties)
.then(filteredResult => {
const newArgs = mergeDeep([{}, args]);
if (mappingInstructions) {
for (const mappingInstruction of mappingInstructions) {
const { destinationPath, sourcePath } = mappingInstruction;
addProperty(newArgs, destinationPath, getProperty(filteredResult, sourcePath));
}
}
return newArgs;
})
.resolve();
};
}

Expand Down
34 changes: 31 additions & 3 deletions packages/stitching-directives/tests/properties.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { addProperty, getProperties } from "../src/properties";
import { addProperty, getProperties, getResolvedPropertiesOrPromise } from "../src/properties";

describe('addProperty', () => {
test('can add a key to an object', () => {
Expand Down Expand Up @@ -29,7 +29,7 @@ describe('addProperty', () => {
});

describe('getProperties', () => {
test('can getProperties', () => {
test('can getProperties', async () => {
const object = {
field1: 'value1',
field2: {
Expand All @@ -43,7 +43,35 @@ describe('getProperties', () => {
field2: {
subfieldA: null,
}
})
});

const expectedExtracted = {
field1: 'value1',
field2: {
subfieldA: 'valueA',
}
}

expect(extracted).toEqual(expectedExtracted);
});
});

describe('getResolvedPropertiesOrPromise', () => {
test('can getResolvedPropertiesOrPromise', async () => {
const object = {
field1: 'value1',
field2: Promise.resolve({
subfieldA: 'valueA',
subfieldB: 'valueB',
}),
}

const extracted = await getResolvedPropertiesOrPromise(object, {
field1: null,
field2: {
subfieldA: null,
}
}).resolve();

const expectedExtracted = {
field1: 'value1',
Expand Down

0 comments on commit c2146cc

Please sign in to comment.