Skip to content

Commit

Permalink
proof of concept
Browse files Browse the repository at this point in the history
  • Loading branch information
yaacovCR committed Aug 26, 2020
1 parent c608ed8 commit 9843139
Show file tree
Hide file tree
Showing 4 changed files with 121 additions and 20 deletions.
26 changes: 18 additions & 8 deletions packages/delegate/src/defaultMergedResolver.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { defaultFieldResolver, GraphQLResolveInfo } from 'graphql';

import { getResponseKeyFromInfo } from '@graphql-tools/utils';
import { getResponseKeyFromInfo, ExecutionResult } from '@graphql-tools/utils';

import { resolveExternalValue } from './resolveExternalValue';
import { getSubschema } from './Subschema';
Expand All @@ -18,7 +18,7 @@ export function defaultMergedResolver(
args: Record<string, any>,
context: Record<string, any>,
info: GraphQLResolveInfo
) {
): any {
if (!parent) {
return null;
}
Expand All @@ -34,16 +34,26 @@ export function defaultMergedResolver(
const data = parent[responseKey];
const unpathedErrors = getUnpathedErrors(parent);

// To Do: extract this section to separate function.
// If proxied result with defer or stream, result may be initially undefined
// so must call out to a to-be-created Receiver abstraction
// (as opposed to Dispatcher within graphql-js) that will notify of data arrival
// To Do:
// properly turn the patch into an external value that can handle nested fields, transforms

if (data === undefined) {
return 'test';
if (data === undefined && 'ASYNC_ITERABLE' in parent) {
const asyncIterable = parent['ASYNC_ITERABLE'];
return asyncIterableToResult(asyncIterable).then(patch => {
return defaultMergedResolver(patch.data, args, context, info);
});
}

const subschema = getSubschema(parent, responseKey);

return resolveExternalValue(data, unpathedErrors, subschema, context, info);
}

async function asyncIterableToResult(asyncIterable: AsyncIterable<ExecutionResult>): Promise<any> {
const asyncIterator = asyncIterable[Symbol.asyncIterator]();
const payload = await asyncIterator.next();
return {
...payload.value,
ASYNC_ITERABLE: asyncIterable,
};
}
24 changes: 19 additions & 5 deletions packages/delegate/src/delegateToSchema.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import {
subscribe,
execute,
validate,
GraphQLSchema,
isSchema,
Expand All @@ -13,9 +12,11 @@ import {
GraphQLObjectType,
} from 'graphql';

import { execute } from 'graphql/experimental';

import isPromise from 'is-promise';

import { mapAsyncIterator, ExecutionResult } from '@graphql-tools/utils';
import { mapAsyncIterator, ExecutionResult, isAsyncIterable } from '@graphql-tools/utils';

import {
IDelegateToSchemaOptions,
Expand Down Expand Up @@ -170,8 +171,12 @@ export function delegateRequest({
info,
});

if (isPromise(executionResult)) {
return executionResult.then(originalResult => transformer.transformResult(originalResult));
if (isAsyncIterable(executionResult)) {
return asyncIterableToResult(executionResult).then(originalResult => transformer.transformResult(originalResult));
} else if (isPromise(executionResult)) {
return (executionResult as Promise<ExecutionResult>).then(originalResult =>
transformer.transformResult(originalResult)
);
}
return transformer.transformResult(executionResult);
}
Expand All @@ -185,7 +190,7 @@ export function delegateRequest({
context,
info,
}).then((subscriptionResult: AsyncIterableIterator<ExecutionResult> | ExecutionResult) => {
if (Symbol.asyncIterator in subscriptionResult) {
if (isAsyncIterable(subscriptionResult)) {
// "subscribe" to the subscription result and map the result through the transforms
return mapAsyncIterator<ExecutionResult, any>(
subscriptionResult as AsyncIterableIterator<ExecutionResult>,
Expand Down Expand Up @@ -232,3 +237,12 @@ function createDefaultSubscriber(schema: GraphQLSchema, rootValue: Record<string
rootValue: rootValue ?? info?.rootValue,
}) as any;
}

async function asyncIterableToResult(asyncIterable: AsyncIterable<ExecutionResult>): Promise<any> {
const asyncIterator = asyncIterable[Symbol.asyncIterator]();
const payload = await asyncIterator.next();
return {
...payload.value,
ASYNC_ITERABLE: asyncIterable,
};
}
16 changes: 15 additions & 1 deletion packages/delegate/src/transforms/CheckResultAndHandleErrors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,21 @@ export function checkResultAndHandleErrors(
info ? responsePathAsArray(info.path) : undefined
);

return resolveExternalValue(data, unpathedErrors, subschema, context, info, returnType, skipTypeMerging);
const externalValue = resolveExternalValue(
data,
unpathedErrors,
subschema,
context,
info,
returnType,
skipTypeMerging
);

if ('ASYNC_ITERABLE' in result) {
externalValue['ASYNC_ITERABLE'] = result['ASYNC_ITERABLE'];
}

return externalValue;
}

export function mergeDataAndErrors(
Expand Down
75 changes: 69 additions & 6 deletions packages/delegate/tests/deferStream.test.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,41 @@
import { graphql } from 'graphql/experimental';

import { makeExecutableSchema } from '@graphql-tools/schema';
import { stitchSchemas } from '@graphql-tools/stitch';
import { isAsyncIterable } from '@graphql-tools/utils';

describe('defer support', () => {
test('should work', async () => {
test('should work for root fields', async () => {
const schema = makeExecutableSchema({
typeDefs: `
type Query {
test(input: String): String
test: String
}
`,
resolvers: {
Query: {
test: (_root, args) => args.input,
test: () => 'test',
}
},
});

const stitchedSchema = stitchSchemas({
subschemas: [schema]
});

const result = await graphql(
schema,
stitchedSchema,
`
query {
... on Query @defer {
test(input: "test")
test
}
}
`,
);

const results = [];
if (result[Symbol.asyncIterator]) {
if (isAsyncIterable(result)) {
for await (let patch of result) {
results.push(patch);
}
Expand All @@ -46,6 +52,63 @@ describe('defer support', () => {
hasNext: false,
path: [],
});
});

test('should work for nested fields', async () => {
const schema = makeExecutableSchema({
typeDefs: `
type Object {
test: String
}
type Query {
object: Object
}
`,
resolvers: {
Object: {
test: () => 'test',
},
Query: {
object: () => ({}),
}
},
});

const stitchedSchema = stitchSchemas({
subschemas: [schema]
});

const result = await graphql(
stitchedSchema,
`
query {
object {
... on Object @defer {
test
}
}
}
`,
);

const results = [];
if (isAsyncIterable(result)) {
for await (let patch of result) {
results.push(patch);
}
}

expect(results[0]).toEqual({
data: { object: {} },
hasNext: true,
});
expect(results[1]).toEqual({
data: {
test: 'test'
},
hasNext: false,
path: ['object'],
});
});
});

0 comments on commit 9843139

Please sign in to comment.