Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/grumpy-melons-deny.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@envelop/core': patch
'@envelop/types': patch
---

Expose utility functions `getExecuteArgs`, `getSubscribeArgs`, `makeExecute`, and `makeSubscribe` for easier handling of composition with the polymorphic arguments.
5 changes: 5 additions & 0 deletions .changeset/many-bugs-peel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@envelop/execute-subscription-event': patch
---

Add `useContextValuePerExecuteSubscriptionEvent` for creating a new context for each `ExecuteSubscriptionEvent` phase.
205 changes: 82 additions & 123 deletions packages/core/src/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,30 @@ import {
DocumentNode,
execute,
subscribe,
ExecutionArgs,
GraphQLError,
GraphQLFieldResolver,
GraphQLSchema,
GraphQLTypeResolver,
isIntrospectionType,
isObjectType,
parse,
validate,
specifiedRules,
ValidationRule,
ExecutionResult,
SubscriptionArgs,
} from 'graphql';
import { AfterCallback, AfterResolverPayload, Envelop, OnResolverCalledHooks, Plugin } from '@envelop/types';
import { Maybe } from 'graphql/jsutils/Maybe';
import {
AfterCallback,
AfterResolverPayload,
Envelop,
ExecuteFunction,
OnResolverCalledHooks,
Plugin,
SubscribeFunction,
} from '@envelop/types';
import { makeSubscribe, makeExecute } from './util';

const trackedSchemaSymbol = Symbol('TRACKED_SCHEMA');
const resolversHooksSymbol = Symbol('RESOLVERS_HOOKS');
export const resolversHooksSymbol = Symbol('RESOLVERS_HOOKS');

export function envelop({ plugins }: { plugins: Plugin[] }): Envelop {
let schema: GraphQLSchema | undefined | null = null;
Expand Down Expand Up @@ -206,32 +211,9 @@ export function envelop({ plugins }: { plugins: Plugin[] }): Envelop {
}
: (ctx: any) => ctx;

const customSubscribe = async (
argsOrSchema: SubscriptionArgs | GraphQLSchema,
document?: DocumentNode,
rootValue?: any,
contextValue?: any,
variableValues?: Maybe<{ [key: string]: any }>,
operationName?: Maybe<string>,
fieldResolver?: Maybe<GraphQLFieldResolver<any, any>>,
subscribeFieldResolver?: Maybe<GraphQLFieldResolver<any, any>>
) => {
const args: SubscriptionArgs =
argsOrSchema instanceof GraphQLSchema
? {
schema: argsOrSchema,
document: document!,
rootValue,
contextValue,
variableValues,
operationName,
fieldResolver,
subscribeFieldResolver,
}
: argsOrSchema;

const customSubscribe: SubscribeFunction = makeSubscribe(async args => {
const onResolversHandlers: OnResolverCalledHooks[] = [];
let subscribeFn: typeof subscribe = subscribe;
let subscribeFn = subscribe as SubscribeFunction;

const afterCalls: ((options: {
result: AsyncIterableIterator<ExecutionResult> | ExecutionResult;
Expand Down Expand Up @@ -289,107 +271,84 @@ export function envelop({ plugins }: { plugins: Plugin[] }): Envelop {
}

return result;
};

const customExecute = onExecuteCbs.length
? async (
argsOrSchema: ExecutionArgs | GraphQLSchema,
document?: DocumentNode,
rootValue?: any,
contextValue?: any,
variableValues?: Maybe<{ [key: string]: any }>,
operationName?: Maybe<string>,
fieldResolver?: Maybe<GraphQLFieldResolver<any, any>>,
typeResolver?: Maybe<GraphQLTypeResolver<any, any>>
) => {
const args: ExecutionArgs =
argsOrSchema instanceof GraphQLSchema
? {
schema: argsOrSchema,
document: document!,
rootValue,
contextValue,
variableValues,
operationName,
fieldResolver,
typeResolver,
}
: argsOrSchema;

const onResolversHandlers: OnResolverCalledHooks[] = [];
let executeFn: typeof execute = execute;
let result: ExecutionResult;

const afterCalls: ((options: {
result: ExecutionResult;
setResult: (newResult: ExecutionResult) => void;
}) => void)[] = [];
let context = args.contextValue;
});

const customExecute = (
onExecuteCbs.length
? makeExecute(async args => {
const onResolversHandlers: OnResolverCalledHooks[] = [];
let executeFn: ExecuteFunction = execute as ExecuteFunction;
let result: ExecutionResult;

const afterCalls: ((options: { result: ExecutionResult; setResult: (newResult: ExecutionResult) => void }) => void)[] =
[];
let context = args.contextValue;

for (const onExecute of onExecuteCbs) {
let stopCalled = false;

const after = onExecute({
executeFn,
setExecuteFn: newExecuteFn => {
executeFn = newExecuteFn;
},
setResultAndStopExecution: stopResult => {
stopCalled = true;
result = stopResult;
},
extendContext: extension => {
if (typeof extension === 'object') {
context = {
...(context || {}),
...extension,
};
} else {
throw new Error(
`Invalid context extension provided! Expected "object", got: "${JSON.stringify(
extension
)}" (${typeof extension})`
);
}
},
args,
});

for (const onExecute of onExecuteCbs) {
let stopCalled = false;
if (stopCalled) {
return result!;
}

const after = onExecute({
executeFn,
setExecuteFn: newExecuteFn => {
executeFn = newExecuteFn;
},
setResultAndStopExecution: stopResult => {
stopCalled = true;
result = stopResult;
},
extendContext: extension => {
if (typeof extension === 'object') {
context = {
...(context || {}),
...extension,
};
} else {
throw new Error(
`Invalid context extension provided! Expected "object", got: "${JSON.stringify(
extension
)}" (${typeof extension})`
);
if (after) {
if (after.onExecuteDone) {
afterCalls.push(after.onExecuteDone);
}
if (after.onResolverCalled) {
onResolversHandlers.push(after.onResolverCalled);
}
},
args,
});

if (stopCalled) {
return result!;
}

if (after) {
if (after.onExecuteDone) {
afterCalls.push(after.onExecuteDone);
}
if (after.onResolverCalled) {
onResolversHandlers.push(after.onResolverCalled);
}
}
}

if (onResolversHandlers.length) {
context[resolversHooksSymbol] = onResolversHandlers;
}

result = await executeFn({
...args,
contextValue: context,
});
if (onResolversHandlers.length) {
context[resolversHooksSymbol] = onResolversHandlers;
}

for (const afterCb of afterCalls) {
afterCb({
result,
setResult: newResult => {
result = newResult;
},
result = await executeFn({
...args,
contextValue: context,
});
}

return result;
}
: execute;
for (const afterCb of afterCalls) {
afterCb({
result,
setResult: newResult => {
result = newResult;
},
});
}

return result;
})
: execute
) as ExecuteFunction;

function prepareSchema() {
if (!schema || schema[trackedSchemaSymbol]) {
Expand Down
4 changes: 4 additions & 0 deletions packages/core/src/graphql-typings.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
declare module 'graphql/jsutils/isAsyncIterable' {
function isAsyncIterable(input: unknown): input is AsyncIterable<any>;
export default isAsyncIterable;
}
1 change: 1 addition & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from '@envelop/types';
export * from './create';
export * from './util';
export * from './plugins/use-logger';
export * from './plugins/use-timing';
export * from './plugins/use-schema';
Expand Down
49 changes: 49 additions & 0 deletions packages/core/src/util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { PolymorphicExecuteArguments, PolymorphicSubscribeArguments } from '@envelop/types';
import { ExecutionArgs, ExecutionResult, SubscriptionArgs } from 'graphql';
import { PromiseOrValue } from 'graphql/jsutils/PromiseOrValue';

export function getExecuteArgs(args: PolymorphicExecuteArguments): ExecutionArgs {
return args.length === 1
? args[0]
: {
schema: args[0],
document: args[1],
rootValue: args[2],
contextValue: args[3],
variableValues: args[4],
operationName: args[5],
fieldResolver: args[6],
typeResolver: args[7],
};
}

/**
* Utility function for making a execute function that handles polymorphic arguments.
*/
export const makeExecute =
(executeFn: (args: ExecutionArgs) => PromiseOrValue<ExecutionResult>) =>
(...polyArgs: PolymorphicExecuteArguments): PromiseOrValue<ExecutionResult> =>
executeFn(getExecuteArgs(polyArgs));

export function getSubscribeArgs(args: PolymorphicSubscribeArguments): SubscriptionArgs {
return args.length === 1
? args[0]
: {
schema: args[0],
document: args[1],
rootValue: args[2],
contextValue: args[3],
variableValues: args[4],
operationName: args[5],
fieldResolver: args[6],
subscribeFieldResolver: args[7],
};
}

/**
* Utility function for making a subscribe function that handles polymorphic arguments.
*/
export const makeSubscribe =
(subscribeFn: (args: SubscriptionArgs) => PromiseOrValue<AsyncIterableIterator<ExecutionResult> | ExecutionResult>) =>
(...polyArgs: PolymorphicSubscribeArguments): PromiseOrValue<AsyncIterableIterator<ExecutionResult> | ExecutionResult> =>
subscribeFn(getSubscribeArgs(polyArgs));
Loading