Skip to content

Commit bc6cddd

Browse files
authored
Transformation support with Rust Query Planner Handler (#1708)
1 parent 1dbc653 commit bc6cddd

27 files changed

+461
-499
lines changed

.changeset/five-pillows-remain.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@graphql-hive/router-runtime': patch
3+
---
4+
5+
Handle listed enum values correctly
6+
Previously when a field like `[MyEnum!]!` is projected, it was projecting it like it is `MyEnum`.

.changeset/olive-buttons-report.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@graphql-hive/router-runtime': minor
3+
---
4+
5+
Support Stitching transforms (w/ Mesh directives)

.changeset/shiny-spoons-rescue.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@graphql-mesh/fusion-runtime': minor
3+
---
4+
5+
Export `getSubgraph` method so other handlers can use extracted transforms and subgraph schema

.changeset/sixty-shoes-camp.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@graphql-tools/wrap': minor
3+
---
4+
5+
Improvements to make transforms more generic than stitching

.changeset/tall-pens-agree.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
'@graphql-tools/delegate': major
3+
---
4+
5+
Breaking changes in `createRequest` function;
6+
- No more `sourceParentType`, `sourceFieldName`, `variableDefinitions`, `variableValues` and `targetRootValue`
7+
- `targetRootValue` has been renamed to `rootValue`
8+
- `targetSchema` is a required option now and `args` is also accepted as a map of the arguments of the target field
9+
- `fragments` is now an array of `FragmentDefinitionNode` instead of a record `{ [fragmentName: string]: FragmentDefinitionNode }`
10+
11+
Breaking changes in `delegateRequest` and `delegateToSchema` functions;
12+
- No more `transformedSchema` option, it has been renamed to `targetSchema`
13+
- `targetSchema` is a required option now

internal/perf/tests/heapsnapshot.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
import fs from 'node:fs/promises';
22
import path from 'node:path';
33
import { spawn } from '@internal/proc';
4+
import { getEnvBool } from '~internal/env';
45
import { expect, it } from 'vitest';
56
import { leakingObjectsInHeapSnapshotFiles } from '../src/heapsnapshot';
67

78
const __fixtures = path.resolve(__dirname, '__fixtures__');
89

910
it.skipIf(
1011
// no need to test in bun (also, bun does not support increasing timeouts per test)
11-
globalThis.Bun,
12+
globalThis.Bun || getEnvBool('SKIP_HEAP_SNAPSHOT_TESTS'),
1213
)(
1314
'should correctly calculate no leaking objects',
1415
{
Lines changed: 119 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
import { ExecutionRequest, serializeInputValue } from '@graphql-tools/utils';
1+
import {
2+
asArray,
3+
astFromArg,
4+
astFromValueUntyped,
5+
ExecutionRequest,
6+
} from '@graphql-tools/utils';
27
import {
38
ArgumentNode,
49
DefinitionNode,
@@ -7,21 +12,18 @@ import {
712
GraphQLInputType,
813
GraphQLObjectType,
914
GraphQLSchema,
15+
isInputObjectType,
16+
isListType,
17+
isNonNullType,
1018
Kind,
11-
NamedTypeNode,
1219
NameNode,
1320
OperationDefinitionNode,
1421
OperationTypeNode,
1522
SelectionNode,
1623
SelectionSetNode,
17-
typeFromAST,
1824
VariableDefinitionNode,
1925
} from 'graphql';
2026
import { ICreateRequest } from './types.js';
21-
import {
22-
createVariableNameGenerator,
23-
updateArgument,
24-
} from './updateArguments.js';
2527

2628
export function getDelegatingOperation(
2729
parentType: GraphQLObjectType,
@@ -38,23 +40,19 @@ export function getDelegatingOperation(
3840

3941
export function createRequest({
4042
subgraphName,
41-
sourceSchema,
42-
sourceParentType,
43-
sourceFieldName,
4443
fragments,
45-
variableDefinitions,
46-
variableValues,
47-
targetRootValue,
44+
rootValue,
4845
targetOperationName,
4946
targetOperation,
47+
targetSchema,
5048
targetFieldName,
5149
selectionSet,
5250
fieldNodes,
5351
context,
5452
info,
53+
args,
5554
}: ICreateRequest): ExecutionRequest {
5655
let newSelectionSet: SelectionSetNode | undefined;
57-
const argumentNodeMap: Record<string, ArgumentNode> = Object.create(null);
5856

5957
if (selectionSet != null) {
6058
newSelectionSet = selectionSet;
@@ -74,45 +72,8 @@ export function createRequest({
7472
selections,
7573
}
7674
: undefined;
77-
78-
const args = fieldNodes?.[0]?.arguments;
79-
if (args) {
80-
for (const argNode of args) {
81-
argumentNodeMap[argNode.name.value] = argNode;
82-
}
83-
}
8475
}
8576

86-
const newVariables = Object.create(null);
87-
const variableDefinitionMap = Object.create(null);
88-
89-
if (sourceSchema != null && variableDefinitions != null) {
90-
for (const def of variableDefinitions) {
91-
const varName = def.variable.name.value;
92-
variableDefinitionMap[varName] = def;
93-
const varType = typeFromAST(
94-
sourceSchema,
95-
def.type as NamedTypeNode,
96-
) as GraphQLInputType;
97-
const serializedValue = serializeInputValue(
98-
varType,
99-
variableValues?.[varName],
100-
);
101-
if (serializedValue !== undefined) {
102-
newVariables[varName] = serializedValue;
103-
}
104-
}
105-
}
106-
107-
if (sourceParentType != null && sourceFieldName != null) {
108-
updateArgumentsWithDefaults(
109-
sourceParentType,
110-
sourceFieldName,
111-
argumentNodeMap,
112-
variableDefinitionMap,
113-
newVariables,
114-
);
115-
}
11677
const fieldNode = fieldNodes?.[0];
11778
const rootFieldName = targetFieldName ?? fieldNode?.name.value;
11879

@@ -122,9 +83,81 @@ export function createRequest({
12283
);
12384
}
12485

86+
const newVariables = Object.create(null);
87+
const variableDefinitions: VariableDefinitionNode[] = [];
88+
const argNodes: ArgumentNode[] = [];
89+
90+
if (args != null) {
91+
const rootType = targetSchema?.getRootType(targetOperation);
92+
const rootField = rootType?.getFields()[rootFieldName];
93+
const rootFieldArgs = rootField?.args;
94+
for (const argName in args) {
95+
const argValue = args[argName];
96+
const argInstance = rootFieldArgs?.find((arg) => arg.name === argName);
97+
if (argInstance) {
98+
const argAst = astFromArg(argInstance, targetSchema);
99+
const varExists = (varName: string) =>
100+
variableDefinitions.some(
101+
(varDef) => varDef.variable.name.value === varName,
102+
);
103+
let varName = argName;
104+
// Try `<argName>`, then `<rootFieldName>_<argName>`, then `_0_<rootFieldName>_<argName>`, etc.
105+
if (varExists(varName)) {
106+
varName = `_${rootFieldName}_${argName}`;
107+
let i = 0;
108+
while (varExists(varName)) {
109+
varName = `_${i++}_${rootFieldName}_${argName}`;
110+
}
111+
}
112+
variableDefinitions.push({
113+
kind: Kind.VARIABLE_DEFINITION,
114+
variable: {
115+
kind: Kind.VARIABLE,
116+
name: {
117+
kind: Kind.NAME,
118+
value: varName,
119+
},
120+
},
121+
type: argAst.type,
122+
});
123+
newVariables[varName] = projectArgumentValue(
124+
argValue,
125+
argInstance.type,
126+
);
127+
argNodes.push({
128+
kind: Kind.ARGUMENT,
129+
name: {
130+
kind: Kind.NAME,
131+
value: argName,
132+
},
133+
value: {
134+
kind: Kind.VARIABLE,
135+
name: {
136+
kind: Kind.NAME,
137+
value: varName,
138+
},
139+
},
140+
});
141+
} else {
142+
// For arguments that are not defined in the target schema, we inline them.
143+
const valueNode = astFromValueUntyped(argValue);
144+
if (valueNode != null) {
145+
argNodes.push({
146+
kind: Kind.ARGUMENT,
147+
name: {
148+
kind: Kind.NAME,
149+
value: argName,
150+
},
151+
value: valueNode,
152+
});
153+
}
154+
}
155+
}
156+
}
157+
125158
const rootfieldNode: FieldNode = {
126159
kind: Kind.FIELD,
127-
arguments: Object.values(argumentNodeMap),
160+
arguments: argNodes,
128161
name: {
129162
kind: Kind.NAME,
130163
value: rootFieldName,
@@ -144,7 +177,7 @@ export function createRequest({
144177
kind: Kind.OPERATION_DEFINITION,
145178
name: operationName,
146179
operation: targetOperation,
147-
variableDefinitions: Object.values(variableDefinitionMap),
180+
variableDefinitions,
148181
selectionSet: {
149182
kind: Kind.SELECTION_SET,
150183
selections: [rootfieldNode],
@@ -154,12 +187,7 @@ export function createRequest({
154187
const definitions: Array<DefinitionNode> = [operationDefinition];
155188

156189
if (fragments != null) {
157-
for (const fragmentName in fragments) {
158-
const fragment = fragments[fragmentName];
159-
if (fragment) {
160-
definitions.push(fragment);
161-
}
162-
}
190+
definitions.push(...fragments);
163191
}
164192

165193
const document: DocumentNode = {
@@ -171,49 +199,48 @@ export function createRequest({
171199
subgraphName,
172200
document,
173201
variables: newVariables,
174-
rootValue: targetRootValue,
202+
rootValue,
175203
operationName: targetOperationName,
176204
context,
177205
info,
178206
operationType: targetOperation,
179207
};
180208
}
181209

182-
function updateArgumentsWithDefaults(
183-
sourceParentType: GraphQLObjectType,
184-
sourceFieldName: string,
185-
argumentNodeMap: Record<string, ArgumentNode>,
186-
variableDefinitionMap: Record<string, VariableDefinitionNode>,
187-
variableValues: Record<string, any>,
188-
): void {
189-
const generateVariableName = createVariableNameGenerator(
190-
variableDefinitionMap,
191-
);
192-
193-
const sourceField = sourceParentType.getFields()[sourceFieldName];
194-
if (!sourceField) {
195-
throw new Error(
196-
`Field "${sourceFieldName}" was not found in type "${sourceParentType}".`,
210+
function projectArgumentValue(argValue: any, argType: GraphQLInputType): any {
211+
if (isNonNullType(argType)) {
212+
return projectArgumentValue(argValue, argType.ofType);
213+
}
214+
if (isListType(argType)) {
215+
return asArray(argValue).map((item: any) =>
216+
projectArgumentValue(item, argType.ofType),
197217
);
198218
}
199-
for (const argument of sourceField.args) {
200-
const argName = argument.name;
201-
const sourceArgType = argument.type;
202-
203-
if (argumentNodeMap[argName] === undefined) {
204-
const defaultValue = argument.defaultValue;
205-
206-
if (defaultValue !== undefined) {
207-
updateArgument(
208-
argumentNodeMap,
209-
variableDefinitionMap,
210-
variableValues,
211-
argName,
212-
generateVariableName(argName),
213-
sourceArgType,
214-
serializeInputValue(sourceArgType, defaultValue),
215-
);
219+
if (
220+
isInputObjectType(argType) &&
221+
typeof argValue === 'object' &&
222+
argValue !== null
223+
) {
224+
const projectedValue: any = {};
225+
const fields = argType.getFields();
226+
for (const key in argValue) {
227+
const field = fields[key];
228+
if (field) {
229+
projectedValue[key] = projectArgumentValue(argValue[key], field.type);
216230
}
217231
}
232+
return projectedValue;
233+
}
234+
if (argValue != null) {
235+
if (argType.name === 'Boolean') {
236+
return Boolean(argValue);
237+
}
238+
if (argType.name === 'Int' || argType.name === 'Float') {
239+
return Number(argValue);
240+
}
241+
if (argType.name === 'String') {
242+
return String(argValue);
243+
}
218244
}
245+
return argValue;
219246
}

0 commit comments

Comments
 (0)