Skip to content

Commit 8d09aa1

Browse files
committed
feat(execution): add experimental schema batch resolver
Add schema-level batch resolution so a GraphQLSchema can handle pending object field batches across object types before object-level and field-level batch resolvers run. This gives schema owners a single place to route batches to a global loader or execution planner. Schema-level API shape: GraphQLSchemaConfig { experimentalBatchResolve?: GraphQLSchemaBatchResolver<TContext>; } Relevant public types: GraphQLSchemaBatchResolver<TContext> GraphQLSchemaBatchInfo GraphQLSchemaBatchResolveInfo GraphQLObjectBatchFieldInfo Example schema batch resolver: const resolveSchemaBatch: GraphQLSchemaBatchResolver<Context> = async (batches, context, info) => { const result = new Map< GraphQLObjectBatchFieldInfo, ReadonlyArray<unknown> >(); for (const batch of batches) { if (batch.parentType.name !== 'User') { continue; } const rows = await context.users.loadMany( batch.sources.map((source) => source.id), ); for (const field of batch.fields) { if (field.fieldName === 'name') { result.set(field, rows.map((row) => row.name)); } else if (field.fieldName === 'email') { result.set(field, rows.map((row) => row.email)); } } } return result; }; const schema = new GraphQLSchema({ query: QueryType, experimentalBatchResolve: resolveSchemaBatch, }); Each GraphQLSchemaBatchInfo groups one parent object type, its sources, and the field batches pending for those sources. The returned Map uses the field info objects as keys and arrays of values in source order.
1 parent c411341 commit 8d09aa1

12 files changed

Lines changed: 1216 additions & 7 deletions

benchmark/batch-resolution-benchmark.js

Lines changed: 58 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,35 @@ import {
1010

1111
const WidgetType = new GraphQLObjectType({
1212
name: 'Widget',
13+
experimentalBatchResolve: async (sources, fields) => {
14+
await Promise.resolve();
15+
const result = new Map();
16+
for (const field of fields) {
17+
if (field.fieldName === 'color') {
18+
result.set(
19+
field,
20+
sources.map((source) => source.color),
21+
);
22+
}
23+
}
24+
return result;
25+
},
1326
fields: () => ({
1427
id: { type: GraphQLID },
28+
label: {
29+
type: GraphQLID,
30+
experimentalBatchResolve: async (sources) => {
31+
await Promise.resolve();
32+
return sources.map((source) => source.label);
33+
},
34+
},
35+
color: {
36+
type: GraphQLID,
37+
experimentalBatchResolve: async (sources) => {
38+
await Promise.resolve();
39+
return sources.map((source) => source.color);
40+
},
41+
},
1542
widget: {
1643
type: WidgetType,
1744
experimentalBatchResolve: async (sources) => {
@@ -34,18 +61,45 @@ const QueryType = new GraphQLObjectType({
3461
},
3562
});
3663

37-
const schema = new GraphQLSchema({ query: QueryType });
64+
const schema = new GraphQLSchema({
65+
query: QueryType,
66+
experimentalBatchResolve: async (batches) => {
67+
await Promise.resolve();
68+
const result = new Map();
69+
for (const batch of batches) {
70+
for (const field of batch.fields) {
71+
switch (field.fieldName) {
72+
case 'label':
73+
result.set(
74+
field,
75+
batch.sources.map((source) => source.label),
76+
);
77+
break;
78+
}
79+
}
80+
}
81+
return result;
82+
},
83+
});
3884

3985
const rootValue = {
4086
widgets: Array.from({ length: 10000 }, () => ({
4187
id: 'gid://owner/Widget/1',
42-
widget: { id: 'gid://owner/Widget/1' },
88+
label: 'Widget 1',
89+
color: 'blue',
90+
widget: {
91+
id: 'gid://owner/Widget/1',
92+
label: 'Nested Widget 1',
93+
color: 'green',
94+
},
4395
})),
4496
};
45-
const document = parse(`{ widgets(first: 10000) { id widget { id } } }`);
97+
const document = parse(
98+
`{ widgets(first: 10000) { id label color widget { id label color } } }`,
99+
);
46100

47101
export const benchmark = {
48-
name: 'Execute Batch Resolved Widget Field',
102+
name: 'Execute Schema and Object Batch Resolved Widget Fields',
49103
measure: () =>
50104
execute({
51105
schema,

0 commit comments

Comments
 (0)