Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: use engine plugin #1535

Merged
merged 3 commits into from
Sep 28, 2022
Merged
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
24 changes: 24 additions & 0 deletions .changeset/hip-ravens-serve.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
---
'@envelop/core': minor
---

A new plugin that can be used to customize the GraphQL Engine.

```ts
import { envelop, useEngine } from '@envelop/core'
import { parse, validate, execute, subscribe } from 'graphql'
import { parser } from 'my-custom-graphql-parser'

const getEnveloped = envelop({
parse,
validate,
execute,
subscribe,
plugins: [
useEngine({
// Now your envelop will use the custom parser instead of the default one provided.
parse: parser
})
]
})
```
1 change: 1 addition & 0 deletions packages/core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ This is the core package for Envelop. You can find a complete documentation here
- [`useLogger`](./docs/use-logger.md)
- [`useMaskedErrors`](./docs/use-masked-errors.md)
- [`usePayloadFormatter`](./docs/use-payload-formatter.md)
- [`useEngine`](./docs/use-engine.md)
19 changes: 19 additions & 0 deletions packages/core/docs/use-engine.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#### `useEngine`

This plugin can be used to customize the GraphQL Engine.

```ts
import { envelop, useEngine } from '@envelop/core'
import { parse, validate, execute, subscribe } from 'graphql'

const getEnveloped = envelop({
plugins: [
useEngine({
parse,
validate,
execute,
subscribe
})
]
})
```
7 changes: 2 additions & 5 deletions packages/core/docs/use-error-handler.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,12 @@
This plugin triggers a custom function when execution encounters an error.

```ts
import { envelop, useErrorHandler } from '@envelop/core'
import { envelop, useEngine, useErrorHandler } from '@envelop/core'
import { parse, validate, execute, subscribe } from 'graphql'

const getEnveloped = envelop({
parse,
validate,
execute,
subscribe,
plugins: [
useEngine({ parse, validate, execute, subscribe }),
useErrorHandler((errors, args) => {
// This callback is called once, containing all GraphQLError emitted during execution phase
})
Expand Down
7 changes: 2 additions & 5 deletions packages/core/docs/use-extend-context.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,12 @@
Easily extends the context with custom fields.

```ts
import { envelop, useExtendContext } from '@envelop/core'
import { envelop, useEngine, useExtendContext } from '@envelop/core'
import { parse, validate, execute, subscribe } from 'graphql'

const getEnveloped = envelop({
parse,
validate,
execute,
subscribe,
plugins: [
useEngine({ parse, validate, execute, subscribe }),
useExtendContext(async contextSoFar => {
return {
myCustomField: {
Expand Down
7 changes: 2 additions & 5 deletions packages/core/docs/use-logger.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,12 @@
Logs parameters and information about the execution phases. You can easily plug your custom logger.

```ts
import { envelop, useLogger } from '@envelop/core'
import { envelop, useEngine, useLogger } from '@envelop/core'
import { parse, validate, execute, subscribe } from 'graphql'

const getEnveloped = envelop({
parse,
validate,
execute,
subscribe,
plugins: [
useEngine({ parse, validate, execute, subscribe }),
useLogger({
logFn: (eventName, args) => {
// Event could be `execute-start` / `execute-end` / `subscribe-start` / `subscribe-end`
Expand Down
25 changes: 19 additions & 6 deletions packages/core/docs/use-masked-errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
Prevent unexpected error messages from leaking to the GraphQL clients.

```ts
import { envelop, useSchema, useMaskedErrors } from '@envelop/core'
import { makeExecutableSchema, GraphQLError } from 'graphql'
import { envelop, useSchema, useMaskedErrors, useEngine } from '@envelop/core'
import { makeExecutableSchema, GraphQLError, parse, validate, execute, subscribe } from 'graphql'

const schema = makeExecutableSchema({
typeDefs: /* GraphQL */ `
Expand Down Expand Up @@ -33,22 +33,31 @@ const schema = makeExecutableSchema({
})

const getEnveloped = envelop({
plugins: [useSchema(schema), useMaskedErrors()]
plugins: [useEngine({ parse, validate, execute, subscribe }), useSchema(schema), useMaskedErrors()]
})
```

You may customize the default error message `Unexpected error.` with your own `errorMessage`:

```ts
import { envelop, useSchema, useMaskedErrors, useEngine } from '@envelop/core'
import { parse, validate, execute, subscribe } from 'graphql'
import { schema } from './schema'

const getEnveloped = envelop({
plugins: [useSchema(schema), useMaskedErrors({ errorMessage: 'Something went wrong.' })]
plugins: [
useEngine({ parse, validate, execute, subscribe }),
useSchema(schema),
useMaskedErrors({ errorMessage: 'Something went wrong.' })
]
})
```

Or provide a custom formatter when masking the output:

```ts
import { isGraphQLError, MaskError } from '@envelop/core'
import { isGraphQLError, MaskError, useEngine } from '@envelop/core'
import { parse, validate, execute, subscribe, GraphQLError } from 'graphql'

export const customFormatError: MaskError = err => {
if (isGraphQLError(err)) {
Expand All @@ -59,6 +68,10 @@ export const customFormatError: MaskError = err => {
}

const getEnveloped = envelop({
plugins: [useSchema(schema), useMaskedErrors({ maskErrorFn: customFormatError })]
plugins: [
useEngine({ parse, validate, execute, subscribe }),
useSchema(schema),
useMaskedErrors({ maskErrorFn: customFormatError })
]
})
```
7 changes: 2 additions & 5 deletions packages/core/docs/use-payload-formatter.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,12 @@ Allow you to format/modify execution result payload before returning it to your
The second argument `executionArgs` provides additional information for your formatter. It consists of contextValue, variableValues, document, operationName, and other properties.

```ts
import { envelop, usePayloadFormatter } from '@envelop/core'
import { envelop, usePayloadFormatter, useEngine } from '@envelop/core'
import { parse, validate, execute, subscribe } from 'graphql'

const getEnveloped = envelop({
parse,
validate,
execute,
subscribe,
plugins: [
useEngine({ parse, validate, execute, subscribe }),
usePayloadFormatter((result, executionArgs) => {
// Return a modified result here,
// Or `false`y value to keep it as-is.
Expand Down
7 changes: 2 additions & 5 deletions packages/core/docs/use-schema-by-context.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
This plugin is the simplest plugin for specifying your GraphQL schema. You can specify a schema created from any tool that emits `GraphQLSchema` object, and you can choose to load the schema based on the initial context (or the incoming request).

```ts
import { envelop, useSchemaByContext } from '@envelop/core'
import { envelop, useSchemaByContext, useEngine } from '@envelop/core'
import { parse, validate, execute, subscribe } from 'graphql'

async function getSchema({ req }): GraphQLSchema {
Expand All @@ -15,11 +15,8 @@ async function getSchema({ req }): GraphQLSchema {
}

const getEnveloped = envelop({
parse,
validate,
execute,
subscribe,
plugins: [
useEngine({ parse, validate, execute, subscribe }),
useSchemaByContext(getSchema)
// ... other plugins ...
]
Expand Down
7 changes: 2 additions & 5 deletions packages/core/docs/use-schema.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,14 @@
This plugin is the simplest plugin for specifying your GraphQL schema. You can specify a schema created from any tool that emits `GraphQLSchema` object.

```ts
import { envelop, useSchema } from '@envelop/core'
import { envelop, useSchema, useEngine } from '@envelop/core'
import { parse, validate, execute, subscribe } from 'graphql'

const mySchema = buildSchema(/* ... */)

const getEnveloped = envelop({
parse,
validate,
execute,
subscribe,
plugins: [
useEngine({ parse, validate, execute, subscribe }),
useSchema(mySchema)
// ... other plugins ...
]
Expand Down
8 changes: 0 additions & 8 deletions packages/core/src/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,10 @@ function notEmpty<T>(value: Optional<T>): value is T {
export function envelop<PluginsType extends Optional<Plugin<any>>[]>(options: {
plugins: PluginsType;
enableInternalTracing?: boolean;
parse: ParseFunction;
execute: ExecuteFunction;
validate: ValidateFunction;
subscribe: SubscribeFunction;
}): GetEnvelopedFn<ComposeContext<ExcludeFalsy<PluginsType>>> {
const plugins = options.plugins.filter(notEmpty);
const orchestrator = createEnvelopOrchestrator<ComposeContext<ExcludeFalsy<PluginsType>>>({
plugins,
parse: options.parse,
execute: options.execute,
validate: options.validate,
subscribe: options.subscribe,
});

const getEnveloped = <TInitialContext extends ArbitraryObject>(
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ export * from './plugins/use-error-handler.js';
export * from './plugins/use-extend-context.js';
export * from './plugins/use-payload-formatter.js';
export * from './plugins/use-masked-errors.js';
export * from './plugins/use-engine.js';
17 changes: 9 additions & 8 deletions packages/core/src/orchestrator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,22 +55,23 @@ export type EnvelopOrchestrator<

type EnvelopOrchestratorOptions = {
plugins: Plugin[];
parse: ParseFunction;
execute: ExecuteFunction;
subscribe: SubscribeFunction;
validate: ValidateFunction;
};

function throwEngineFunctionError(name: string) {
throw Error(`No \`${name}\` function found! Register it using "useEngine" plugin.`);
}

export function createEnvelopOrchestrator<PluginsContext extends DefaultContext>({
plugins,
parse,
execute,
subscribe,
validate,
}: EnvelopOrchestratorOptions): EnvelopOrchestrator<any, PluginsContext> {
let schema: any | undefined | null = null;
let initDone = false;

const parse: ParseFunction = () => throwEngineFunctionError('parse');
const validate: ValidateFunction = () => throwEngineFunctionError('validate');
const execute: ExecuteFunction = () => throwEngineFunctionError('execute');
const subscribe: SubscribeFunction = () => throwEngineFunctionError('subscribe');

// Define the initial method for replacing the GraphQL schema, this is needed in order
// to allow setting the schema from the onPluginInit callback. We also need to make sure
// here not to call the same plugin that initiated the schema switch.
Expand Down
33 changes: 33 additions & 0 deletions packages/core/src/plugins/use-engine.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { ExecuteFunction, ParseFunction, Plugin, SubscribeFunction, ValidateFunction } from '@envelop/types';

type UseEngineOptions = {
execute?: ExecuteFunction;
parse?: ParseFunction;
validate?: ValidateFunction;
subscribe?: SubscribeFunction;
};

export const useEngine = (engine: UseEngineOptions): Plugin => {
return {
onExecute: ({ setExecuteFn }) => {
if (engine.execute) {
setExecuteFn(engine.execute);
}
},
onParse: ({ setParseFn }) => {
if (engine.parse) {
setParseFn(engine.parse);
}
},
onValidate: ({ setValidationFn }) => {
if (engine.validate) {
setValidationFn(engine.validate);
}
},
onSubscribe: ({ setSubscribeFn }) => {
if (engine.subscribe) {
setSubscribeFn(engine.subscribe);
}
},
};
};
15 changes: 3 additions & 12 deletions packages/core/test/extends.spec.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,14 @@
import { createSpiedPlugin, createTestkit } from '@envelop/testing';
import { envelop, useExtendContext, useLogger, useSchema } from '../src/index.js';
import { createSpiedPlugin, createTestkit, useGraphQLJSEngine } from '@envelop/testing';
import { envelop, useLogger, useSchema } from '../src/index.js';
import { useEnvelop } from '../src/plugins/use-envelop.js';
import { schema, query } from './common.js';
import { parse, execute, validate, subscribe } from 'graphql';

describe('extending envelops', () => {
it('should allow to extend envelops', async () => {
const spiedPlugin = createSpiedPlugin();

const baseEnvelop = envelop({
plugins: [useLogger(), spiedPlugin.plugin],
parse,
execute,
validate,
subscribe,
plugins: [useGraphQLJSEngine(), useLogger(), spiedPlugin.plugin],
});

const onExecuteChildSpy = jest.fn();
Expand All @@ -26,10 +21,6 @@ describe('extending envelops', () => {
onExecute: onExecuteChildSpy,
},
],
parse,
execute,
validate,
subscribe,
});

const teskit = createTestkit(instance);
Expand Down
34 changes: 34 additions & 0 deletions packages/core/test/plugins/use-engine.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { createTestkit } from '@envelop/testing';
import { query, schema, subscriptionOperationString } from '../common.js';
import { useEngine } from '@envelop/core';
import { parse, validate } from 'graphql';

describe('useEngine', () => {
it('should invoke custom execute', async () => {
const custom = jest.fn();
const testInstance = createTestkit([useEngine({ execute: custom })], schema);
await testInstance.execute(query);
expect(custom).toHaveBeenCalledTimes(1);
});

it('should invoke custom subscribe', async () => {
const custom = jest.fn();
const testInstance = createTestkit([useEngine({ subscribe: custom })], schema);
await testInstance.execute(subscriptionOperationString);
expect(custom).toHaveBeenCalledTimes(1);
});

it('should invoke custom validate', async () => {
const custom = jest.fn(validate);
const testInstance = createTestkit([useEngine({ validate: custom })], schema);
await testInstance.execute(query);
expect(custom).toHaveBeenCalledTimes(1);
});

it('should invoke custom parse', async () => {
const custom = jest.fn(parse);
const testInstance = createTestkit([useEngine({ parse: custom })], schema);
await testInstance.execute(query);
expect(custom).toHaveBeenCalledTimes(1);
});
});
Loading