Skip to content

Commit 9843139

Browse files
committed
proof of concept
1 parent c608ed8 commit 9843139

File tree

4 files changed

+121
-20
lines changed

4 files changed

+121
-20
lines changed

packages/delegate/src/defaultMergedResolver.ts

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { defaultFieldResolver, GraphQLResolveInfo } from 'graphql';
22

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

55
import { resolveExternalValue } from './resolveExternalValue';
66
import { getSubschema } from './Subschema';
@@ -18,7 +18,7 @@ export function defaultMergedResolver(
1818
args: Record<string, any>,
1919
context: Record<string, any>,
2020
info: GraphQLResolveInfo
21-
) {
21+
): any {
2222
if (!parent) {
2323
return null;
2424
}
@@ -34,16 +34,26 @@ export function defaultMergedResolver(
3434
const data = parent[responseKey];
3535
const unpathedErrors = getUnpathedErrors(parent);
3636

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

42-
if (data === undefined) {
43-
return 'test';
40+
if (data === undefined && 'ASYNC_ITERABLE' in parent) {
41+
const asyncIterable = parent['ASYNC_ITERABLE'];
42+
return asyncIterableToResult(asyncIterable).then(patch => {
43+
return defaultMergedResolver(patch.data, args, context, info);
44+
});
4445
}
4546

4647
const subschema = getSubschema(parent, responseKey);
4748

4849
return resolveExternalValue(data, unpathedErrors, subschema, context, info);
4950
}
51+
52+
async function asyncIterableToResult(asyncIterable: AsyncIterable<ExecutionResult>): Promise<any> {
53+
const asyncIterator = asyncIterable[Symbol.asyncIterator]();
54+
const payload = await asyncIterator.next();
55+
return {
56+
...payload.value,
57+
ASYNC_ITERABLE: asyncIterable,
58+
};
59+
}

packages/delegate/src/delegateToSchema.ts

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import {
22
subscribe,
3-
execute,
43
validate,
54
GraphQLSchema,
65
isSchema,
@@ -13,9 +12,11 @@ import {
1312
GraphQLObjectType,
1413
} from 'graphql';
1514

15+
import { execute } from 'graphql/experimental';
16+
1617
import isPromise from 'is-promise';
1718

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

2021
import {
2122
IDelegateToSchemaOptions,
@@ -170,8 +171,12 @@ export function delegateRequest({
170171
info,
171172
});
172173

173-
if (isPromise(executionResult)) {
174-
return executionResult.then(originalResult => transformer.transformResult(originalResult));
174+
if (isAsyncIterable(executionResult)) {
175+
return asyncIterableToResult(executionResult).then(originalResult => transformer.transformResult(originalResult));
176+
} else if (isPromise(executionResult)) {
177+
return (executionResult as Promise<ExecutionResult>).then(originalResult =>
178+
transformer.transformResult(originalResult)
179+
);
175180
}
176181
return transformer.transformResult(executionResult);
177182
}
@@ -185,7 +190,7 @@ export function delegateRequest({
185190
context,
186191
info,
187192
}).then((subscriptionResult: AsyncIterableIterator<ExecutionResult> | ExecutionResult) => {
188-
if (Symbol.asyncIterator in subscriptionResult) {
193+
if (isAsyncIterable(subscriptionResult)) {
189194
// "subscribe" to the subscription result and map the result through the transforms
190195
return mapAsyncIterator<ExecutionResult, any>(
191196
subscriptionResult as AsyncIterableIterator<ExecutionResult>,
@@ -232,3 +237,12 @@ function createDefaultSubscriber(schema: GraphQLSchema, rootValue: Record<string
232237
rootValue: rootValue ?? info?.rootValue,
233238
}) as any;
234239
}
240+
241+
async function asyncIterableToResult(asyncIterable: AsyncIterable<ExecutionResult>): Promise<any> {
242+
const asyncIterator = asyncIterable[Symbol.asyncIterator]();
243+
const payload = await asyncIterator.next();
244+
return {
245+
...payload.value,
246+
ASYNC_ITERABLE: asyncIterable,
247+
};
248+
}

packages/delegate/src/transforms/CheckResultAndHandleErrors.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,21 @@ export function checkResultAndHandleErrors(
7070
info ? responsePathAsArray(info.path) : undefined
7171
);
7272

73-
return resolveExternalValue(data, unpathedErrors, subschema, context, info, returnType, skipTypeMerging);
73+
const externalValue = resolveExternalValue(
74+
data,
75+
unpathedErrors,
76+
subschema,
77+
context,
78+
info,
79+
returnType,
80+
skipTypeMerging
81+
);
82+
83+
if ('ASYNC_ITERABLE' in result) {
84+
externalValue['ASYNC_ITERABLE'] = result['ASYNC_ITERABLE'];
85+
}
86+
87+
return externalValue;
7488
}
7589

7690
export function mergeDataAndErrors(

packages/delegate/tests/deferStream.test.ts

Lines changed: 69 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,41 @@
11
import { graphql } from 'graphql/experimental';
22

33
import { makeExecutableSchema } from '@graphql-tools/schema';
4+
import { stitchSchemas } from '@graphql-tools/stitch';
5+
import { isAsyncIterable } from '@graphql-tools/utils';
46

57
describe('defer support', () => {
6-
test('should work', async () => {
8+
test('should work for root fields', async () => {
79
const schema = makeExecutableSchema({
810
typeDefs: `
911
type Query {
10-
test(input: String): String
12+
test: String
1113
}
1214
`,
1315
resolvers: {
1416
Query: {
15-
test: (_root, args) => args.input,
17+
test: () => 'test',
1618
}
1719
},
1820
});
1921

22+
const stitchedSchema = stitchSchemas({
23+
subschemas: [schema]
24+
});
25+
2026
const result = await graphql(
21-
schema,
27+
stitchedSchema,
2228
`
2329
query {
2430
... on Query @defer {
25-
test(input: "test")
31+
test
2632
}
2733
}
2834
`,
2935
);
3036

3137
const results = [];
32-
if (result[Symbol.asyncIterator]) {
38+
if (isAsyncIterable(result)) {
3339
for await (let patch of result) {
3440
results.push(patch);
3541
}
@@ -46,6 +52,63 @@ describe('defer support', () => {
4652
hasNext: false,
4753
path: [],
4854
});
55+
});
4956

57+
test('should work for nested fields', async () => {
58+
const schema = makeExecutableSchema({
59+
typeDefs: `
60+
type Object {
61+
test: String
62+
}
63+
type Query {
64+
object: Object
65+
}
66+
`,
67+
resolvers: {
68+
Object: {
69+
test: () => 'test',
70+
},
71+
Query: {
72+
object: () => ({}),
73+
}
74+
},
75+
});
76+
77+
const stitchedSchema = stitchSchemas({
78+
subschemas: [schema]
79+
});
80+
81+
const result = await graphql(
82+
stitchedSchema,
83+
`
84+
query {
85+
object {
86+
... on Object @defer {
87+
test
88+
}
89+
}
90+
}
91+
`,
92+
);
93+
94+
const results = [];
95+
if (isAsyncIterable(result)) {
96+
for await (let patch of result) {
97+
results.push(patch);
98+
}
99+
}
100+
101+
expect(results[0]).toEqual({
102+
data: { object: {} },
103+
hasNext: true,
104+
});
105+
expect(results[1]).toEqual({
106+
data: {
107+
test: 'test'
108+
},
109+
hasNext: false,
110+
path: ['object'],
111+
});
50112
});
51113
});
114+

0 commit comments

Comments
 (0)