Skip to content

Commit 04be954

Browse files
committed
add helpers
1 parent e1c8c16 commit 04be954

File tree

5 files changed

+145
-4
lines changed

5 files changed

+145
-4
lines changed

src/execution/IncrementalPublisher.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,12 @@ export class IncrementalPublisher {
307307
path,
308308
deferredFragmentRecords,
309309
groupedFieldSet,
310+
priority:
311+
incrementalDataRecord === undefined
312+
? 1
313+
: shouldInitiateDefer
314+
? incrementalDataRecord.priority + 1
315+
: incrementalDataRecord.priority,
310316
shouldInitiateDefer,
311317
});
312318
for (const deferredFragmentRecord of deferredFragmentRecords) {
@@ -345,6 +351,10 @@ export class IncrementalPublisher {
345351
const streamItemsRecord = new StreamItemsRecord({
346352
streamRecord,
347353
path,
354+
priority:
355+
incrementalDataRecord === undefined
356+
? 1
357+
: incrementalDataRecord.priority + 1,
348358
parents,
349359
});
350360

@@ -672,13 +682,17 @@ export class IncrementalPublisher {
672682

673683
if (isStreamItemsRecord(subsequentResultRecord)) {
674684
this._introduce(subsequentResultRecord);
685+
subsequentResultRecord.publish();
675686
return;
676687
}
677688

678689
if (subsequentResultRecord._pending.size === 0) {
679690
subsequentResultRecord.isCompleted = true;
680691
this._push(subsequentResultRecord);
681692
} else {
693+
for (const deferredGroupedFieldSetRecord of subsequentResultRecord.deferredGroupedFieldSetRecords) {
694+
deferredGroupedFieldSetRecord.publish();
695+
}
682696
this._introduce(subsequentResultRecord);
683697
}
684698
}
@@ -748,24 +762,38 @@ export class IncrementalPublisher {
748762
/** @internal */
749763
export class DeferredGroupedFieldSetRecord {
750764
path: ReadonlyArray<string | number>;
765+
priority: number;
751766
deferredFragmentRecords: ReadonlyArray<DeferredFragmentRecord>;
752767
groupedFieldSet: GroupedFieldSet;
753768
shouldInitiateDefer: boolean;
754769
errors: Array<GraphQLError>;
755770
data: ObjMap<unknown> | undefined;
771+
published: true | Promise<void>;
772+
publish: () => void;
756773
sent: boolean;
757774

758775
constructor(opts: {
759776
path: Path | undefined;
777+
priority: number;
760778
deferredFragmentRecords: ReadonlyArray<DeferredFragmentRecord>;
761779
groupedFieldSet: GroupedFieldSet;
762780
shouldInitiateDefer: boolean;
763781
}) {
764782
this.path = pathToArray(opts.path);
783+
this.priority = opts.priority;
765784
this.deferredFragmentRecords = opts.deferredFragmentRecords;
766785
this.groupedFieldSet = opts.groupedFieldSet;
767786
this.shouldInitiateDefer = opts.shouldInitiateDefer;
768787
this.errors = [];
788+
// promiseWithResolvers uses void only as a generic type parameter
789+
// see: https://typescript-eslint.io/rules/no-invalid-void-type/
790+
// eslint-disable-next-line @typescript-eslint/no-invalid-void-type
791+
const { promise: published, resolve } = promiseWithResolvers<void>();
792+
this.published = published;
793+
this.publish = () => {
794+
resolve();
795+
this.published = true;
796+
};
769797
this.sent = false;
770798
}
771799
}
@@ -822,26 +850,40 @@ export class StreamItemsRecord {
822850
errors: Array<GraphQLError>;
823851
streamRecord: StreamRecord;
824852
path: ReadonlyArray<string | number>;
853+
priority: number;
825854
items: Array<unknown>;
826855
parents: ReadonlyArray<SubsequentResultRecord> | undefined;
827856
children: Set<SubsequentResultRecord>;
828857
isFinalRecord?: boolean;
829858
isCompletedAsyncIterator?: boolean;
830859
isCompleted: boolean;
860+
published: true | Promise<void>;
861+
publish: () => void;
831862
sent: boolean;
832863

833864
constructor(opts: {
834865
streamRecord: StreamRecord;
835866
path: Path | undefined;
867+
priority: number;
836868
parents: ReadonlyArray<SubsequentResultRecord> | undefined;
837869
}) {
838870
this.streamRecord = opts.streamRecord;
839871
this.path = pathToArray(opts.path);
872+
this.priority = opts.priority;
840873
this.parents = opts.parents;
841874
this.children = new Set();
842875
this.errors = [];
843876
this.isCompleted = false;
844877
this.items = [];
878+
// promiseWithResolvers uses void only as a generic type parameter
879+
// see: https://typescript-eslint.io/rules/no-invalid-void-type/
880+
// eslint-disable-next-line @typescript-eslint/no-invalid-void-type
881+
const { promise: published, resolve } = promiseWithResolvers<void>();
882+
this.published = published;
883+
this.publish = () => {
884+
resolve();
885+
this.published = true;
886+
};
845887
this.sent = false;
846888
}
847889
}

src/execution/__tests__/defer-test.ts

Lines changed: 88 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import { expectJSON } from '../../__testUtils__/expectJSON.js';
55
import { expectPromise } from '../../__testUtils__/expectPromise.js';
66
import { resolveOnNextTick } from '../../__testUtils__/resolveOnNextTick.js';
77

8+
import { isPromise } from '../../jsutils/isPromise.js';
9+
810
import type { DocumentNode } from '../../language/ast.js';
911
import { parse } from '../../language/parser.js';
1012

@@ -141,9 +143,13 @@ const query = new GraphQLObjectType({
141143
name: 'Query',
142144
});
143145

144-
const schema = new GraphQLSchema({ query });
146+
const heroSchema = new GraphQLSchema({ query });
145147

146-
async function complete(document: DocumentNode, rootValue: unknown = { hero }) {
148+
async function complete(
149+
document: DocumentNode,
150+
rootValue: unknown = { hero },
151+
schema = heroSchema,
152+
) {
147153
const result = await experimentalExecuteIncrementally({
148154
schema,
149155
document,
@@ -224,6 +230,84 @@ describe('Execute: defer directive', () => {
224230
},
225231
});
226232
});
233+
it('Can provides correct info about deferred execution state when resolver could defer', async () => {
234+
let priority;
235+
let published;
236+
237+
const SomeType = new GraphQLObjectType({
238+
name: 'SomeType',
239+
fields: {
240+
someField: {
241+
type: GraphQLString,
242+
resolve: () => Promise.resolve('someField'),
243+
},
244+
deferredField: {
245+
type: GraphQLString,
246+
resolve: (_parent, _args, _context, info) => {
247+
priority = info.priority;
248+
published = info.published;
249+
},
250+
},
251+
},
252+
});
253+
254+
const someSchema = new GraphQLSchema({ query: SomeType });
255+
256+
const document = parse(`
257+
query {
258+
someField
259+
... @defer {
260+
deferredField
261+
}
262+
}
263+
`);
264+
265+
const result = complete(document, undefined, someSchema);
266+
267+
expect(priority).to.equal(undefined);
268+
expect(published).to.equal(undefined);
269+
270+
await result;
271+
272+
expect(priority).to.equal(1);
273+
expect(isPromise(published)).to.equal(true);
274+
});
275+
it('Can provides correct info about deferred execution state when resolver no longer need defer', async () => {
276+
let priority;
277+
let published;
278+
const SomeType = new GraphQLObjectType({
279+
name: 'SomeType',
280+
fields: {
281+
deferredField: {
282+
type: GraphQLString,
283+
resolve: (_parent, _args, _context, info) => {
284+
priority = info.priority;
285+
published = info.published;
286+
},
287+
},
288+
},
289+
});
290+
291+
const someSchema = new GraphQLSchema({ query: SomeType });
292+
293+
const document = parse(`
294+
query {
295+
... @defer {
296+
deferredField
297+
}
298+
}
299+
`);
300+
301+
const result = complete(document, undefined, someSchema);
302+
303+
expect(priority).to.equal(undefined);
304+
expect(published).to.equal(undefined);
305+
306+
await result;
307+
308+
expect(priority).to.equal(1);
309+
expect(published).to.equal(true);
310+
});
227311
it('Does not disable defer with null if argument', async () => {
228312
const document = parse(`
229313
query HeroNameQuery($shouldDefer: Boolean) {
@@ -2035,7 +2119,7 @@ describe('Execute: defer directive', () => {
20352119
`;
20362120
expect(() =>
20372121
execute({
2038-
schema,
2122+
schema: heroSchema,
20392123
document: parse(doc),
20402124
rootValue: {},
20412125
}),
@@ -2053,7 +2137,7 @@ describe('Execute: defer directive', () => {
20532137
`;
20542138
await expectPromise(
20552139
execute({
2056-
schema,
2140+
schema: heroSchema,
20572141
document: parse(doc),
20582142
rootValue: {
20592143
hero: {

src/execution/__tests__/executor-test.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,8 @@ describe('Execute: Handles basic execution tasks', () => {
222222
'rootValue',
223223
'operation',
224224
'variableValues',
225+
'priority',
226+
'published',
225227
);
226228

227229
const operation = document.definitions[0];
@@ -234,6 +236,8 @@ describe('Execute: Handles basic execution tasks', () => {
234236
schema,
235237
rootValue,
236238
operation,
239+
priority: 0,
240+
published: true,
237241
});
238242

239243
const field = operation.selectionSet.selections[0];

src/execution/execute.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -725,6 +725,7 @@ function executeField(
725725
fieldGroup,
726726
parentType,
727727
path,
728+
incrementalDataRecord,
728729
);
729730

730731
// Get the resolve function, regardless of if its result is normal or abrupt (error).
@@ -810,6 +811,7 @@ export function buildResolveInfo(
810811
fieldGroup: FieldGroup,
811812
parentType: GraphQLObjectType,
812813
path: Path,
814+
incrementalDataRecord?: IncrementalDataRecord | undefined,
813815
): GraphQLResolveInfo {
814816
// The resolve function's optional fourth argument is a collection of
815817
// information about the current execution state.
@@ -824,6 +826,13 @@ export function buildResolveInfo(
824826
rootValue: exeContext.rootValue,
825827
operation: exeContext.operation,
826828
variableValues: exeContext.variableValues,
829+
priority:
830+
incrementalDataRecord === undefined ? 0 : incrementalDataRecord.priority,
831+
published:
832+
incrementalDataRecord === undefined ||
833+
incrementalDataRecord.published === true
834+
? true
835+
: incrementalDataRecord.published,
827836
};
828837
}
829838

src/type/definition.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -896,6 +896,8 @@ export interface GraphQLResolveInfo {
896896
readonly rootValue: unknown;
897897
readonly operation: OperationDefinitionNode;
898898
readonly variableValues: { [variable: string]: unknown };
899+
readonly priority: number;
900+
readonly published: true | Promise<void>;
899901
}
900902

901903
/**

0 commit comments

Comments
 (0)