Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

incremental: disable early execution by default #4097

Merged
merged 2 commits into from
Jun 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions src/execution/IncrementalGraph.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { BoxedPromiseOrValue } from '../jsutils/BoxedPromiseOrValue.js';
import { isPromise } from '../jsutils/isPromise.js';
import { promiseWithResolvers } from '../jsutils/promiseWithResolvers.js';

Expand Down Expand Up @@ -120,7 +121,12 @@ export class IncrementalGraph {
incrementalDataRecord.streamItemQueue,
);
} else {
const result = incrementalDataRecord.result.value;
const deferredGroupedFieldSetResult = incrementalDataRecord.result;
const result =
deferredGroupedFieldSetResult instanceof BoxedPromiseOrValue
? deferredGroupedFieldSetResult.value
: deferredGroupedFieldSetResult().value;

if (isPromise(result)) {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
result.then((resolved) => this._enqueue(resolved));
Expand Down Expand Up @@ -299,7 +305,10 @@ export class IncrementalGraph {
let incrementalDataRecords: Array<IncrementalDataRecord> = [];
let streamItemRecord: StreamItemRecord | undefined;
while ((streamItemRecord = streamItemQueue.shift()) !== undefined) {
let result = streamItemRecord.value;
let result =
streamItemRecord instanceof BoxedPromiseOrValue
? streamItemRecord.value
: streamItemRecord().value;
if (isPromise(result)) {
if (items.length > 0) {
this._enqueue({
Expand Down
171 changes: 150 additions & 21 deletions src/execution/__tests__/defer-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,11 +135,16 @@ const query = new GraphQLObjectType({

const schema = new GraphQLSchema({ query });

async function complete(document: DocumentNode, rootValue: unknown = { hero }) {
async function complete(
document: DocumentNode,
rootValue: unknown = { hero },
enableEarlyExecution = false,
) {
const result = await experimentalExecuteIncrementally({
schema,
document,
rootValue,
enableEarlyExecution,
});

if ('initialResult' in result) {
Expand Down Expand Up @@ -247,6 +252,118 @@ describe('Execute: defer directive', () => {
},
]);
});
it('Does not execute deferred fragments early when not specified', async () => {
const document = parse(`
query HeroNameQuery {
hero {
id
...NameFragment @defer
}
}
fragment NameFragment on Hero {
name
}
`);
const order: Array<string> = [];
const result = await complete(document, {
hero: {
...hero,
id: async () => {
await resolveOnNextTick();
await resolveOnNextTick();
order.push('slow-id');
return hero.id;
},
name: () => {
order.push('fast-name');
return hero.name;
},
},
});

expectJSON(result).toDeepEqual([
{
data: {
hero: {
id: '1',
},
},
pending: [{ id: '0', path: ['hero'] }],
hasNext: true,
},
{
incremental: [
{
data: {
name: 'Luke',
},
id: '0',
},
],
completed: [{ id: '0' }],
hasNext: false,
},
]);
expect(order).to.deep.equal(['slow-id', 'fast-name']);
});
it('Does execute deferred fragments early when specified', async () => {
const document = parse(`
query HeroNameQuery {
hero {
id
...NameFragment @defer
}
}
fragment NameFragment on Hero {
name
}
`);
const order: Array<string> = [];
const result = await complete(
document,
{
hero: {
...hero,
id: async () => {
await resolveOnNextTick();
await resolveOnNextTick();
order.push('slow-id');
return hero.id;
},
name: () => {
order.push('fast-name');
return hero.name;
},
},
},
true,
);

expectJSON(result).toDeepEqual([
{
data: {
hero: {
id: '1',
},
},
pending: [{ id: '0', path: ['hero'] }],
hasNext: true,
},
{
incremental: [
{
data: {
name: 'Luke',
},
id: '0',
},
],
completed: [{ id: '0' }],
hasNext: false,
},
]);
expect(order).to.deep.equal(['fast-name', 'slow-id']);
});
it('Can defer fragments on the top level Query field', async () => {
const document = parse(`
query HeroNameQuery {
Expand Down Expand Up @@ -1492,20 +1609,24 @@ describe('Execute: defer directive', () => {
}
}
`);
const result = await complete(document, {
a: {
b: {
c: {
d: 'd',
nonNullErrorField: async () => {
await resolveOnNextTick();
return null;
const result = await complete(
document,
{
a: {
b: {
c: {
d: 'd',
nonNullErrorField: async () => {
await resolveOnNextTick();
return null;
},
},
},
someField: 'someField',
},
someField: 'someField',
},
});
true,
);
expectJSON(result).toDeepEqual([
{
data: {
Expand Down Expand Up @@ -1564,12 +1685,16 @@ describe('Execute: defer directive', () => {
}
}
`);
const result = await complete(document, {
hero: {
...hero,
nonNullName: () => null,
const result = await complete(
document,
{
hero: {
...hero,
nonNullName: () => null,
},
},
});
true,
);
expectJSON(result).toDeepEqual({
data: {
hero: null,
Expand All @@ -1596,12 +1721,16 @@ describe('Execute: defer directive', () => {
}
}
`);
const result = await complete(document, {
hero: {
...hero,
nonNullName: () => null,
const result = await complete(
document,
{
hero: {
...hero,
nonNullName: () => null,
},
},
});
true,
);
expectJSON(result).toDeepEqual([
{
data: {},
Expand Down
Loading
Loading