Skip to content
This repository has been archived by the owner on Apr 28, 2023. It is now read-only.

Commit

Permalink
feat(performance): optimize resolvers execution paths (MichalLytek#488)
Browse files Browse the repository at this point in the history
  • Loading branch information
sixmen committed Mar 26, 2020
1 parent 23e9b25 commit 23dd294
Show file tree
Hide file tree
Showing 3 changed files with 70 additions and 56 deletions.
24 changes: 13 additions & 11 deletions src/resolvers/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { getParams, applyMiddlewares, applyAuthChecker } from "./helpers";
import { convertToType } from "../helpers/types";
import { BuildContext } from "../schema/build-context";
import { ResolverData } from "../interfaces";
import isPromiseLike from "../utils/isPromiseLike";

export function createHandlerResolver(
resolverMetadata: BaseResolverMetadata,
Expand All @@ -24,17 +25,23 @@ export function createHandlerResolver(
const middlewares = globalMiddlewares.concat(resolverMetadata.middlewares!);
applyAuthChecker(middlewares, authMode, authChecker, resolverMetadata.roles);

return async (root, args, context, info) => {
return (root, args, context, info) => {
const resolverData: ResolverData<any> = { root, args, context, info };
const targetInstance = container.getInstance(resolverMetadata.target, resolverData);
return applyMiddlewares(container, resolverData, middlewares, async () => {
const params: any[] = await getParams(
return applyMiddlewares(container, resolverData, middlewares, () => {
const params: Promise<any[]> | any[] = getParams(
resolverMetadata.params!,
resolverData,
globalValidate,
pubSub,
);
return targetInstance[resolverMetadata.methodName].apply(targetInstance, params);
if (isPromiseLike(params)) {
return params.then(resolvedParams =>
targetInstance[resolverMetadata.methodName].apply(targetInstance, resolvedParams),
);
} else {
return targetInstance[resolverMetadata.methodName].apply(targetInstance, params);
}
});
};
}
Expand Down Expand Up @@ -86,13 +93,8 @@ export function createSimpleFieldResolver(
const middlewares = globalMiddlewares.concat(fieldMetadata.middlewares!);
applyAuthChecker(middlewares, authMode, authChecker, fieldMetadata.roles);

return async (root, args, context, info) => {
return (root, args, context, info) => {
const resolverData: ResolverData<any> = { root, args, context, info };
return await applyMiddlewares(
container,
resolverData,
middlewares,
() => root[fieldMetadata.name],
);
return applyMiddlewares(container, resolverData, middlewares, () => root[fieldMetadata.name]);
};
}
97 changes: 52 additions & 45 deletions src/resolvers/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,55 +8,59 @@ import { ResolverData, AuthChecker, AuthMode } from "../interfaces";
import { Middleware, MiddlewareFn, MiddlewareClass } from "../interfaces/Middleware";
import { IOCContainer } from "../utils/container";
import { AuthMiddleware } from "../helpers/auth-middleware";
import isPromiseLike from "../utils/isPromiseLike";

export async function getParams(
export function getParams(
params: ParamMetadata[],
resolverData: ResolverData<any>,
globalValidate: boolean | ValidatorOptions,
pubSub: PubSubEngine,
): Promise<any[]> {
return Promise.all(
params
.sort((a, b) => a.index - b.index)
.map(async paramInfo => {
switch (paramInfo.kind) {
case "args":
return await validateArg(
convertToType(paramInfo.getType(), resolverData.args),
globalValidate,
paramInfo.validate,
);
case "arg":
return await validateArg(
convertToType(paramInfo.getType(), resolverData.args[paramInfo.name]),
globalValidate,
paramInfo.validate,
);
case "context":
if (paramInfo.propertyName) {
return resolverData.context[paramInfo.propertyName];
}
return resolverData.context;
case "root":
const rootValue = paramInfo.propertyName
? resolverData.root[paramInfo.propertyName]
: resolverData.root;
if (!paramInfo.getType) {
return rootValue;
}
return convertToType(paramInfo.getType(), rootValue);
case "info":
return resolverData.info;
case "pubSub":
if (paramInfo.triggerKey) {
return (payload: any) => pubSub.publish(paramInfo.triggerKey!, payload);
}
return pubSub;
case "custom":
return await paramInfo.resolver(resolverData);
}
}),
);
): Promise<any[]> | any[] {
const paramValues = params
.sort((a, b) => a.index - b.index)
.map(paramInfo => {
switch (paramInfo.kind) {
case "args":
return validateArg(
convertToType(paramInfo.getType(), resolverData.args),
globalValidate,
paramInfo.validate,
);
case "arg":
return validateArg(
convertToType(paramInfo.getType(), resolverData.args[paramInfo.name]),
globalValidate,
paramInfo.validate,
);
case "context":
if (paramInfo.propertyName) {
return resolverData.context[paramInfo.propertyName];
}
return resolverData.context;
case "root":
const rootValue = paramInfo.propertyName
? resolverData.root[paramInfo.propertyName]
: resolverData.root;
if (!paramInfo.getType) {
return rootValue;
}
return convertToType(paramInfo.getType(), rootValue);
case "info":
return resolverData.info;
case "pubSub":
if (paramInfo.triggerKey) {
return (payload: any) => pubSub.publish(paramInfo.triggerKey!, payload);
}
return pubSub;
case "custom":
return paramInfo.resolver(resolverData);
}
});
if (paramValues.some(isPromiseLike)) {
return Promise.all(paramValues);
} else {
return paramValues;
}
}

export function applyAuthChecker(
Expand All @@ -70,12 +74,15 @@ export function applyAuthChecker(
}
}

export async function applyMiddlewares(
export function applyMiddlewares(
container: IOCContainer,
resolverData: ResolverData<any>,
middlewares: Array<Middleware<any>>,
resolverHandlerFunction: () => any,
): Promise<any> {
if (middlewares.length === 0) {
return resolverHandlerFunction();
}
let middlewaresIndex = -1;
async function dispatchHandler(currentIndex: number): Promise<void> {
if (currentIndex <= middlewaresIndex) {
Expand Down
5 changes: 5 additions & 0 deletions src/utils/isPromiseLike.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export default function isPromiseLike<TValue>(
value: PromiseLike<TValue> | TValue,
): value is PromiseLike<TValue> {
return value != null && typeof (value as PromiseLike<TValue>).then === "function";
}

0 comments on commit 23dd294

Please sign in to comment.