Skip to content

Commit eb6fe95

Browse files
committed
Improve performance
1 parent 5fbbc78 commit eb6fe95

File tree

2 files changed

+97
-50
lines changed

2 files changed

+97
-50
lines changed

packages/plugins/graphql-jit/src/index.ts

+72-50
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,29 @@
11
/* eslint-disable no-console */
2-
import { Plugin } from '@envelop/core';
3-
import { DocumentNode, Source, ExecutionArgs, ExecutionResult } from 'graphql';
4-
import { compileQuery, isCompiledQuery, CompilerOptions, CompiledQuery } from 'graphql-jit';
2+
import { EnvelopError, Plugin, PromiseOrValue } from '@envelop/core';
3+
import { DocumentNode, Source, ExecutionArgs, ExecutionResult, print, GraphQLSchema, getOperationAST } from 'graphql';
4+
import { compileQuery, CompilerOptions, CompiledQuery } from 'graphql-jit';
55
import lru from 'tiny-lru';
66

77
const DEFAULT_MAX = 1000;
88
const DEFAULT_TTL = 3600000;
99

10-
type JITCacheEntry = {
11-
query: CompiledQuery['query'];
12-
subscribe?: CompiledQuery['subscribe'];
13-
};
10+
type CompilationResult = ReturnType<typeof compileQuery>;
1411

1512
export interface JITCache {
16-
get(key: string): JITCacheEntry | undefined;
17-
set(key: string, value: JITCacheEntry): void;
13+
get(key: string): CompilationResult | undefined;
14+
set(key: string, value: CompilationResult): void;
15+
}
16+
17+
function isSource(source: any): source is Source {
18+
return source.body != null;
19+
}
20+
21+
function isCompiledQuery(compilationResult: any): compilationResult is CompiledQuery<unknown, unknown> {
22+
return compilationResult.query != null;
1823
}
1924

25+
const MUST_DEFINE_OPERATION_NAME_ERR = 'Must provide operation name if query contains multiple operations.';
26+
2027
export const useGraphQlJit = (
2128
compilerOptions: Partial<CompilerOptions> = {},
2229
pluginOptions: {
@@ -36,74 +43,89 @@ export const useGraphQlJit = (
3643
): Plugin => {
3744
const documentSourceMap = new WeakMap<DocumentNode, string>();
3845
const jitCache =
39-
typeof pluginOptions.cache !== 'undefined' ? pluginOptions.cache : lru<JITCacheEntry>(DEFAULT_MAX, DEFAULT_TTL);
40-
41-
function compile(args: ExecutionArgs) {
42-
const compilationResult = compileQuery(args.schema, args.document, args.operationName ?? undefined, compilerOptions);
43-
44-
if (isCompiledQuery(compilationResult)) {
45-
return compilationResult;
46-
} else {
47-
if (pluginOptions?.onError) {
48-
pluginOptions.onError(compilationResult);
49-
} else {
50-
console.error(compilationResult);
46+
typeof pluginOptions.cache !== 'undefined' ? pluginOptions.cache : lru<CompilationResult>(DEFAULT_MAX, DEFAULT_TTL);
47+
48+
function getCacheKey(document: DocumentNode, operationName?: string) {
49+
if (!operationName) {
50+
const operationAST = getOperationAST(document);
51+
if (!operationAST) {
52+
throw new EnvelopError(MUST_DEFINE_OPERATION_NAME_ERR);
5153
}
52-
return {
53-
query: () => compilationResult,
54-
};
54+
operationName = operationAST.name?.value;
5555
}
56+
const documentSource = getDocumentSource(document);
57+
return `${documentSource}::${operationName}`;
5658
}
5759

58-
function getCacheEntry(args: ExecutionArgs): JITCacheEntry {
59-
const documentSource = documentSourceMap.get(args.document);
60-
60+
function getDocumentSource(document: DocumentNode): string {
61+
let documentSource = documentSourceMap.get(document);
6162
if (!documentSource) {
62-
return compile(args);
63+
documentSource = print(document);
64+
documentSourceMap.set(document, documentSource);
6365
}
66+
return documentSource;
67+
}
6468

65-
let compiledQuery = jitCache.get(documentSource);
69+
function getCompilationResult(schema: GraphQLSchema, document: DocumentNode, operationName?: string) {
70+
const cacheKey = getCacheKey(document, operationName);
71+
72+
let compiledQuery = jitCache.get(cacheKey);
6673

6774
if (!compiledQuery) {
68-
compiledQuery = compile(args);
69-
jitCache.set(documentSource, compiledQuery);
75+
compiledQuery = compileQuery(schema, document, operationName, compilerOptions);
76+
jitCache.set(cacheKey, compiledQuery);
7077
}
7178

7279
return compiledQuery;
7380
}
7481

75-
function jitExecute(args: ExecutionArgs) {
76-
const cacheEntry = getCacheEntry(args);
77-
78-
return cacheEntry.query(args.rootValue, args.contextValue, args.variableValues);
79-
}
80-
81-
function jitSubscribe(args: ExecutionArgs) {
82-
const cacheEntry = getCacheEntry(args);
83-
84-
return cacheEntry.subscribe
85-
? (cacheEntry.subscribe(args.rootValue, args.contextValue, args.variableValues) as any)
86-
: cacheEntry.query(args.rootValue, args.contextValue, args.variableValues);
87-
}
88-
8982
return {
9083
onParse({ params: { source } }) {
91-
const key = source instanceof Source ? source.body : source;
84+
const key = isSource(source) ? source.body : source;
9285

9386
return ({ result }) => {
9487
if (!result || result instanceof Error) return;
9588

9689
documentSourceMap.set(result, key);
9790
};
9891
},
99-
async onExecute({ args, setExecuteFn }) {
92+
onValidate({ params, setResult }) {
93+
try {
94+
const compilationResult = getCompilationResult(params.schema, params.documentAST);
95+
if (!isCompiledQuery(compilationResult) && compilationResult?.errors != null) {
96+
setResult(compilationResult.errors);
97+
}
98+
} catch (e: any) {
99+
// Validate doesn't work in case of multiple operations
100+
if (e.message !== MUST_DEFINE_OPERATION_NAME_ERR) {
101+
throw e;
102+
}
103+
}
104+
},
105+
async onExecute({ args, setExecuteFn, setResultAndStopExecution }) {
100106
if (!pluginOptions.enableIf || (pluginOptions.enableIf && (await pluginOptions.enableIf(args)))) {
101-
setExecuteFn(jitExecute);
107+
const compilationResult = getCompilationResult(args.schema, args.document, args.operationName ?? undefined);
108+
if (isCompiledQuery(compilationResult)) {
109+
setExecuteFn(function jitExecute(args): PromiseOrValue<ExecutionResult<any, any>> {
110+
return compilationResult.query(args.rootValue, args.contextValue, args.variableValues);
111+
});
112+
} else if (compilationResult != null) {
113+
setResultAndStopExecution(compilationResult as ExecutionResult);
114+
}
102115
}
103116
},
104-
async onSubscribe({ args, setSubscribeFn }) {
117+
async onSubscribe({ args, setSubscribeFn, setResultAndStopExecution }) {
105118
if (!pluginOptions.enableIf || (pluginOptions.enableIf && (await pluginOptions.enableIf(args)))) {
106-
setSubscribeFn(jitSubscribe);
119+
const compilationResult = getCompilationResult(args.schema, args.document, args.operationName ?? undefined);
120+
if (isCompiledQuery(compilationResult)) {
121+
setSubscribeFn(function jitSubscribe(args: ExecutionArgs) {
122+
return compilationResult.subscribe
123+
? compilationResult.subscribe(args.rootValue, args.contextValue, args.variableValues)
124+
: compilationResult.query(args.rootValue, args.contextValue, args.variableValues);
125+
} as any);
126+
} else if (compilationResult != null) {
127+
setResultAndStopExecution(compilationResult as ExecutionResult);
128+
}
107129
}
108130
},
109131
};

packages/plugins/graphql-jit/test/graphql-jit.spec.ts

+25
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { makeExecutableSchema } from '@graphql-tools/schema';
88
import { execute, subscribe } from 'graphql';
99
import { useGraphQlJit } from '../src';
1010
import lru from 'tiny-lru';
11+
import { GraphQLError } from 'graphql';
1112

1213
describe('useGraphQlJit', () => {
1314
const schema = makeExecutableSchema({
@@ -163,4 +164,28 @@ describe('useGraphQlJit', () => {
163164
expect(cache.get).toHaveBeenCalled();
164165
expect(cache.set).toHaveBeenCalled();
165166
});
167+
168+
it('Should throw validation errors if compilation fails', async () => {
169+
const plugin = useGraphQlJit(
170+
{},
171+
{
172+
cache: {
173+
get() {
174+
return {
175+
errors: [new GraphQLError('Some random error')],
176+
};
177+
},
178+
set() {},
179+
},
180+
}
181+
);
182+
jest.spyOn(plugin, 'onValidate');
183+
jest.spyOn(plugin, 'onExecute');
184+
const testInstance = createTestkit([plugin], schema);
185+
186+
const result = await testInstance.execute(`query Foo { test }`);
187+
expect(result['errors']).toBeTruthy();
188+
expect(plugin.onValidate).toHaveBeenCalled();
189+
expect(plugin.onExecute).not.toHaveBeenCalled();
190+
});
166191
});

0 commit comments

Comments
 (0)