Skip to content

Commit

Permalink
Improvements on runtime and JIT logic
Browse files Browse the repository at this point in the history
  • Loading branch information
ardatan committed Oct 9, 2023
1 parent 181f0e4 commit 406c4d2
Show file tree
Hide file tree
Showing 13 changed files with 186 additions and 84 deletions.
5 changes: 5 additions & 0 deletions .changeset/grumpy-onions-fold.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@graphql-mesh/transform-federation': patch
---

Remove other directives in scalars just like it is done for objects and other types
5 changes: 5 additions & 0 deletions .changeset/hot-bugs-hammer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@graphql-mesh/runtime': patch
---

Simplify the logic and use GraphQL Tools executor
5 changes: 5 additions & 0 deletions .changeset/many-turkeys-share.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@graphql-mesh/grpc': patch
---

Add response streams as subscriptions
5 changes: 5 additions & 0 deletions .changeset/seven-bags-exist.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@graphql-mesh/runtime': patch
---

Do not cache entire request but only DocumentNode
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
subscription SearchMoviesByCast {
exampleSearchMoviesByCast(input: { castName: "Tom Cruise" }) {
name
year
rating
cast
}
}
50 changes: 46 additions & 4 deletions examples/grpc-example/tests/__snapshots__/grpc.test.ts.snap
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`gRPC Example should fetch movies by cast as a stream correctly: movies-by-cast-grpc-example-result-0 1`] = `
exports[`gRPC Example should fetch movies by cast as a stream correctly: movies-by-cast-grpc-example-result-stream-0 1`] = `
{
"data": {
"exampleSearchMoviesByCast": [],
Expand All @@ -9,7 +9,7 @@ exports[`gRPC Example should fetch movies by cast as a stream correctly: movies-
}
`;

exports[`gRPC Example should fetch movies by cast as a stream correctly: movies-by-cast-grpc-example-result-1 1`] = `
exports[`gRPC Example should fetch movies by cast as a stream correctly: movies-by-cast-grpc-example-result-stream-1 1`] = `
{
"hasNext": true,
"incremental": [
Expand All @@ -35,7 +35,7 @@ exports[`gRPC Example should fetch movies by cast as a stream correctly: movies-
}
`;

exports[`gRPC Example should fetch movies by cast as a stream correctly: movies-by-cast-grpc-example-result-2 1`] = `
exports[`gRPC Example should fetch movies by cast as a stream correctly: movies-by-cast-grpc-example-result-stream-2 1`] = `
{
"hasNext": true,
"incremental": [
Expand All @@ -61,15 +61,50 @@ exports[`gRPC Example should fetch movies by cast as a stream correctly: movies-
}
`;

exports[`gRPC Example should fetch movies by cast as a stream correctly: movies-by-cast-grpc-example-result-3 1`] = `
exports[`gRPC Example should fetch movies by cast as a stream correctly: movies-by-cast-grpc-example-result-stream-3 1`] = `
{
"hasNext": false,
}
`;

exports[`gRPC Example should fetch movies by cast as a subscription correctly: movies-by-cast-grpc-example-result-subscription-0 1`] = `
{
"data": {
"exampleSearchMoviesByCast": {
"cast": [
"Tom Cruise",
"Simon Pegg",
"Jeremy Renner",
],
"name": "Mission: Impossible Rogue Nation",
"rating": 0.9700000286102295,
"year": 2015,
},
},
}
`;

exports[`gRPC Example should fetch movies by cast as a subscription correctly: movies-by-cast-grpc-example-result-subscription-1 1`] = `
{
"data": {
"exampleSearchMoviesByCast": {
"cast": [
"Tom Cruise",
"Simon Pegg",
"Henry Cavill",
],
"name": "Mission: Impossible - Fallout",
"rating": 0.9300000071525574,
"year": 2018,
},
},
}
`;

exports[`gRPC Example should generate correct schema: grpc-schema 1`] = `
"schema {
query: Query
subscription: Subscription
}
directive @grpcMethod(rootJsonName: String, objPath: String, methodName: String, responseStream: Boolean) on FIELD_DEFINITION
Expand Down Expand Up @@ -166,6 +201,13 @@ enum ConnectivityState {
SHUTDOWN
}
type Subscription {
"search movies by the name of the cast"
exampleSearchMoviesByCast(input: SearchByCastRequest_Input): Movie @grpcMethod(rootJsonName: "Root0", objPath: "Example", methodName: "SearchMoviesByCast", responseStream: true)
"search movies by the name of the cast"
anotherExampleSearchMoviesByCast(input: SearchByCastRequest_Input): Movie @grpcMethod(rootJsonName: "Root0", objPath: "AnotherExample", methodName: "SearchMoviesByCast", responseStream: true)
}
scalar ObjMap"
`;

Expand Down
13 changes: 12 additions & 1 deletion examples/grpc-example/tests/grpc.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,18 @@ describe('gRPC Example', () => {
const result = await mesh.execute(MoviesByCastStream, undefined);
let i = 0;
for await (const item of result as AsyncIterable<any>) {
expect(item).toMatchSnapshot(`movies-by-cast-grpc-example-result-${i++}`);
expect(item).toMatchSnapshot(`movies-by-cast-grpc-example-result-stream-${i++}`);
}
});
it('should fetch movies by cast as a subscription correctly', async () => {
const MoviesByCastSubscription = await readFile(
join(__dirname, '../example-queries/MoviesByCast.subscription.graphql'),
'utf8',
);
const result = await mesh.execute(MoviesByCastSubscription, undefined);
let i = 0;
for await (const item of result as AsyncIterable<any>) {
expect(item).toMatchSnapshot(`movies-by-cast-grpc-example-result-subscription-${i++}`);
}
});
afterAll(async () => {
Expand Down
73 changes: 54 additions & 19 deletions packages/handlers/grpc/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -419,11 +419,22 @@ export default class GrpcHandler implements MeshHandler {
objPath,
creds,
});
field.resolve = this.getFieldResolver({
client,
methodName,
isResponseStream: responseStream,
});
if (rootType.name === 'Subscription') {
field.subscribe = this.getFieldResolver({
client,
methodName,
isResponseStream: responseStream,
});
field.resolve = function identityFn(root) {
return root;
};
} else {
field.resolve = this.getFieldResolver({
client,
methodName,
isResponseStream: responseStream,
});
}
break;
}
case 'grpcConnectivityState': {
Expand Down Expand Up @@ -598,26 +609,29 @@ export default class GrpcHandler implements MeshHandler {
for (const methodName in nested.methods) {
const method = nested.methods[methodName];
const rootFieldName = [...pathWithName, methodName].join('_');
const fieldConfigTypeFactory = () => {
const baseResponseTypePath = method.responseType?.split('.');
if (baseResponseTypePath) {
const responseTypePath = this.walkToFindTypePath(
rootJson,
pathWithName,
baseResponseTypePath,
);
return getTypeName(this.schemaComposer, responseTypePath, false);
}
return 'Void';
};
const fieldConfig: ObjectTypeComposerFieldConfigAsObjectDefinition<any, any> = {
type: () => {
const baseResponseTypePath = method.responseType?.split('.');
if (baseResponseTypePath) {
const responseTypePath = this.walkToFindTypePath(
rootJson,
pathWithName,
baseResponseTypePath,
);
let typeName = getTypeName(this.schemaComposer, responseTypePath, false);
if (method.responseStream) {
typeName = `[${typeName}]`;
}
return typeName;
const typeName = fieldConfigTypeFactory();
if (method.responseStream) {
return `[${typeName}]`;
}
return 'Void';
return typeName;
},
description: method.comment,
};
fieldConfig.args = {
const fieldConfigArgs = {
input: () => {
if (method.requestStream) {
return 'File';
Expand All @@ -635,6 +649,7 @@ export default class GrpcHandler implements MeshHandler {
return undefined;
},
};
fieldConfig.args = fieldConfigArgs;
const methodNameLowerCased = methodName.toLowerCase();
const prefixQueryMethod = this.config.prefixQueryMethod || QUERY_METHOD_PREFIXES;
const rootTypeComposer = prefixQueryMethod.some(prefix =>
Expand All @@ -659,6 +674,26 @@ export default class GrpcHandler implements MeshHandler {
],
},
});
if (method.responseStream) {
this.schemaComposer.Subscription.addFields({
[rootFieldName]: {
args: fieldConfigArgs,
description: method.comment,
type: fieldConfigTypeFactory,
directives: [
{
name: 'grpcMethod',
args: {
rootJsonName,
objPath,
methodName,
responseStream: true,
},
},
],
},
});
}
}
const connectivityStateFieldName = pathWithName.join('_') + '_connectivityState';
this.schemaComposer.addDirective(grpcConnectivityStateDirective);
Expand Down
1 change: 1 addition & 0 deletions packages/runtime/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
"@graphql-mesh/string-interpolation": "^0.5.2",
"@graphql-tools/batch-delegate": "^9.0.0",
"@graphql-tools/delegate": "^10.0.0",
"@graphql-tools/executor": "^1.2.0",
"@graphql-tools/wrap": "^10.0.0",
"@whatwg-node/fetch": "^0.9.0",
"graphql-jit": "0.8.2"
Expand Down
39 changes: 5 additions & 34 deletions packages/runtime/src/get-mesh.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import {
DocumentNode,
execute,
getOperationAST,
GraphQLObjectType,
GraphQLSchema,
OperationTypeNode,
Expand Down Expand Up @@ -35,10 +33,10 @@ import {
PubSub,
} from '@graphql-mesh/utils';
import { CreateProxyingResolverFn, Subschema, SubschemaConfig } from '@graphql-tools/delegate';
import { normalizedExecutor } from '@graphql-tools/executor';
import {
ExecutionResult,
getRootTypeMap,
inspect,
isAsyncIterable,
isPromise,
mapAsyncIterator,
Expand Down Expand Up @@ -76,14 +74,6 @@ const memoizedGetEnvelopedFactory = memoize1(function getEnvelopedFactory(
});
});

const memoizedGetOperationType = memoize1((document: DocumentNode) => {
const operationAST = getOperationAST(document, undefined);
if (!operationAST) {
throw new Error('Must provide document with a valid operation');
}
return operationAST.operation;
});

export function wrapFetchWithPlugins(plugins: MeshPlugin<any>[]): MeshFetch {
const onFetchHooks: OnFetchHook<any>[] = [];
for (const plugin of plugins as MeshPlugin<any>[]) {
Expand All @@ -92,24 +82,6 @@ export function wrapFetchWithPlugins(plugins: MeshPlugin<any>[]): MeshFetch {
}
}
return function wrappedFetchFn(url, options, context, info) {
if (url != null && typeof url !== 'string') {
throw new TypeError(`First parameter(url) of 'fetch' must be a string, got ${inspect(url)}`);
}
if (options != null && typeof options !== 'object') {
throw new TypeError(
`Second parameter(options) of 'fetch' must be an object, got ${inspect(options)}`,
);
}
if (context != null && typeof context !== 'object') {
throw new TypeError(
`Third parameter(context) of 'fetch' must be an object, got ${inspect(context)}`,
);
}
if (info != null && typeof info !== 'object') {
throw new TypeError(
`Fourth parameter(info) of 'fetch' must be an object, got ${inspect(info)}`,
);
}
let fetchFn: MeshFetch;
const doneHooks: OnFetchHookDone[] = [];
function setFetchFn(newFetchFn: MeshFetch) {
Expand Down Expand Up @@ -318,7 +290,7 @@ export async function getMesh(options: GetMeshOptions): Promise<MeshInstance> {

const plugins = [
useEngine({
execute,
execute: normalizedExecutor,
validate,
parse: parseWithCache,
specifiedRules,
Expand Down Expand Up @@ -368,7 +340,7 @@ export async function getMesh(options: GetMeshOptions): Promise<MeshInstance> {

function createExecutor(globalContext: any = EMPTY_CONTEXT_VALUE): MeshExecutor {
const getEnveloped = memoizedGetEnvelopedFactory(plugins);
const { schema, parse, execute, subscribe, contextFactory } = getEnveloped(globalContext);
const { schema, parse, execute, contextFactory } = getEnveloped(globalContext);
return function meshExecutor<TVariables = any, TContext = any, TRootValue = any, TData = any>(
documentOrSDL: GraphQLOperation<TData, TVariables>,
variableValues: TVariables = EMPTY_VARIABLES_VALUE,
Expand All @@ -377,11 +349,10 @@ export async function getMesh(options: GetMeshOptions): Promise<MeshInstance> {
operationName?: string,
) {
const document = typeof documentOrSDL === 'string' ? parse(documentOrSDL) : documentOrSDL;
const executeFn = memoizedGetOperationType(document) === 'subscription' ? subscribe : execute;
const contextValue$ = contextFactory(contextValue);
if (isPromise(contextValue$)) {
return contextValue$.then(contextValue =>
executeFn({
execute({
schema,
document,
contextValue,
Expand All @@ -391,7 +362,7 @@ export async function getMesh(options: GetMeshOptions): Promise<MeshInstance> {
}),
);
}
return executeFn({
return execute({
schema,
document,
contextValue: contextValue$,
Expand Down
Loading

0 comments on commit 406c4d2

Please sign in to comment.