From ba27af6f90ea2bdd4c846f6933a1a2e11c744bcf Mon Sep 17 00:00:00 2001 From: Kamil Kisiela Date: Wed, 28 Sep 2022 14:17:52 +0000 Subject: [PATCH 1/5] Make ExecutionContext opt-in --- .changeset/tame-oranges-exercise.md | 5 ++ .../src/application/application.ts | 10 ++- .../src/application/execution-context.ts | 75 +++++++++++++------ .../graphql-modules/src/application/types.ts | 17 +++++ packages/graphql-modules/src/di/decorators.ts | 3 +- .../graphql-modules/tests/di-hooks.spec.ts | 2 + .../tests/di-providers.spec.ts | 5 ++ .../tests/execution-context.spec.ts | 6 ++ .../pages/docs/advanced/execution-context.mdx | 22 +++++- 9 files changed, 115 insertions(+), 30 deletions(-) create mode 100644 .changeset/tame-oranges-exercise.md diff --git a/.changeset/tame-oranges-exercise.md b/.changeset/tame-oranges-exercise.md new file mode 100644 index 0000000000..c5bdd6d93f --- /dev/null +++ b/.changeset/tame-oranges-exercise.md @@ -0,0 +1,5 @@ +--- +'graphql-modules': major +--- + +Make ExecutionContext opt-in and graphql-modules not node specific diff --git a/packages/graphql-modules/src/application/application.ts b/packages/graphql-modules/src/application/application.ts index f5d7fb25be..92f21b0584 100644 --- a/packages/graphql-modules/src/application/application.ts +++ b/packages/graphql-modules/src/application/application.ts @@ -19,6 +19,7 @@ import { instantiateSingletonProviders, } from './di'; import { createContextBuilder } from './context'; +import { enableExecutionContext } from './execution-context'; import { executionCreator } from './execution'; import { subscriptionCreator } from './subscription'; import { apolloSchemaCreator, apolloExecutorCreator } from './apollo'; @@ -45,6 +46,7 @@ export interface InternalAppContext { * * ```typescript * import { createApplication } from 'graphql-modules'; + * import { createHook, executionAsyncId } from 'async_hooks'; * import { usersModule } from './users'; * import { postsModule } from './posts'; * import { commentsModule } from './comments'; @@ -54,7 +56,8 @@ export interface InternalAppContext { * usersModule, * postsModule, * commentsModule - * ] + * ], + * executionContext: { createHook, executionAsyncId }, * }) * ``` */ @@ -63,6 +66,11 @@ export function createApplication( ): Application { function applicationFactory(cfg?: ApplicationConfig): Application { const config = cfg || applicationConfig; + + if (config.executionContext) { + enableExecutionContext(config.executionContext); + } + const providers = config.providers && typeof config.providers === 'function' ? config.providers() diff --git a/packages/graphql-modules/src/application/execution-context.ts b/packages/graphql-modules/src/application/execution-context.ts index b4e04eb19a..85926cee02 100644 --- a/packages/graphql-modules/src/application/execution-context.ts +++ b/packages/graphql-modules/src/application/execution-context.ts @@ -1,4 +1,10 @@ -import { createHook, executionAsyncId } from 'async_hooks'; +export interface ExecutionContextConfig { + executionAsyncId: () => number; + createHook(config: { + init(asyncId: number, _: string, triggerAsyncId: number): void; + destroy(asyncId: number): void; + }): void; +} export interface ExecutionContextPicker { getModuleContext(moduleId: string): GraphQLModules.ModuleContext; @@ -8,26 +14,8 @@ export interface ExecutionContextPicker { const executionContextStore = new Map(); const executionContextDependencyStore = new Map>(); -const executionContextHook = createHook({ - init(asyncId, _, triggerAsyncId) { - // Store same context data for child async resources - const ctx = executionContextStore.get(triggerAsyncId); - if (ctx) { - const dependencies = - executionContextDependencyStore.get(triggerAsyncId) ?? - executionContextDependencyStore - .set(triggerAsyncId, new Set()) - .get(triggerAsyncId)!; - dependencies.add(asyncId); - executionContextStore.set(asyncId, ctx); - } - }, - destroy(asyncId) { - if (executionContextStore.has(asyncId)) { - executionContextStore.delete(asyncId); - } - }, -}); +let executionContextHook = null; +let executionAsyncId: () => number = () => 0; function destroyContextAndItsChildren(id: number) { if (executionContextStore.has(id)) { @@ -44,12 +32,20 @@ function destroyContextAndItsChildren(id: number) { } } +let executionContextEnabled = false; + export const executionContext: { create(picker: ExecutionContextPicker): () => void; getModuleContext: ExecutionContextPicker['getModuleContext']; getApplicationContext: ExecutionContextPicker['getApplicationContext']; } = { create(picker) { + if (!executionContextEnabled) { + return function destroyContextNoop() { + // noop + }; + } + const id = executionAsyncId(); executionContextStore.set(id, picker); return function destroyContext() { @@ -57,20 +53,51 @@ export const executionContext: { }; }, getModuleContext(moduleId) { + assertExecutionContext(); + const picker = executionContextStore.get(executionAsyncId())!; return picker.getModuleContext(moduleId); }, getApplicationContext() { + assertExecutionContext(); + const picker = executionContextStore.get(executionAsyncId())!; return picker.getApplicationContext(); }, }; -let executionContextEnabled = false; +export function enableExecutionContext(config: ExecutionContextConfig) { + if (!executionContextEnabled) { + executionContextHook = config.createHook({ + init(asyncId, _, triggerAsyncId) { + // Store same context data for child async resources + const ctx = executionContextStore.get(triggerAsyncId); + if (ctx) { + const dependencies = + executionContextDependencyStore.get(triggerAsyncId) ?? + executionContextDependencyStore + .set(triggerAsyncId, new Set()) + .get(triggerAsyncId)!; + dependencies.add(asyncId); + executionContextStore.set(asyncId, ctx); + } + }, + destroy(asyncId) { + if (executionContextStore.has(asyncId)) { + executionContextStore.delete(asyncId); + } + }, + }); + executionAsyncId = config.executionAsyncId; + executionContextEnabled = true; + } +} -export function enableExecutionContext() { +export function assertExecutionContext(): void | never { if (!executionContextEnabled) { - executionContextHook.enable(); + throw new Error( + 'Execution Context is not enabled. Please set `executionContext` option in `createApplication`' + ); } } diff --git a/packages/graphql-modules/src/application/types.ts b/packages/graphql-modules/src/application/types.ts index 18191b42d5..afda51eb69 100644 --- a/packages/graphql-modules/src/application/types.ts +++ b/packages/graphql-modules/src/application/types.ts @@ -8,6 +8,7 @@ import { import type { Provider, Injector } from '../di'; import type { Resolvers, Module, MockedModule } from '../module/types'; import type { MiddlewareMap } from '../shared/middleware'; +import type { ExecutionContextConfig } from './execution-context'; import type { ApolloRequestContext } from './apollo'; import type { Single } from '../shared/types'; import type { InternalAppContext } from './application'; @@ -139,4 +140,20 @@ export interface ApplicationConfig { typeDefs: DocumentNode[]; resolvers: Record[]; }): GraphQLSchema; + + /** + * Enables ExecutionContext + * + * @example + * + * ```typescript + * import { createHook, executionAsyncId } from 'async_hooks'; + * + * const app = createApplication({ + * modules: [], + * executionContext: { createHook, executionAsyncId } + * }); + * ``` + */ + executionContext?: ExecutionContextConfig; } diff --git a/packages/graphql-modules/src/di/decorators.ts b/packages/graphql-modules/src/di/decorators.ts index ca23ed43d0..85c4bdaeee 100644 --- a/packages/graphql-modules/src/di/decorators.ts +++ b/packages/graphql-modules/src/di/decorators.ts @@ -6,7 +6,7 @@ import { ensureInjectableMetadata, } from './metadata'; import { Injector } from './injector'; -import { enableExecutionContext } from '../application/execution-context'; +import { assertExecutionContext } from '../application/execution-context'; function ensureReflect() { if (!(Reflect && Reflect.getOwnMetadata)) { @@ -17,7 +17,6 @@ function ensureReflect() { export function Injectable(options?: ProviderOptions): ClassDecorator { return (target) => { ensureReflect(); - enableExecutionContext(); const params: Type[] = ( Reflect.getMetadata('design:paramtypes', target) || [] diff --git a/packages/graphql-modules/tests/di-hooks.spec.ts b/packages/graphql-modules/tests/di-hooks.spec.ts index 969c4cdd51..40c5e4bede 100644 --- a/packages/graphql-modules/tests/di-hooks.spec.ts +++ b/packages/graphql-modules/tests/di-hooks.spec.ts @@ -1,4 +1,5 @@ import 'reflect-metadata'; +import { createHook, executionAsyncId } from 'async_hooks'; import { createApplication, createModule, @@ -79,6 +80,7 @@ test('OnDestroy hook', async () => { const app = createApplication({ modules: [postsModule], + executionContext: { createHook, executionAsyncId }, }); const createContext = () => ({ request: {}, response: {} }); diff --git a/packages/graphql-modules/tests/di-providers.spec.ts b/packages/graphql-modules/tests/di-providers.spec.ts index a9942ab3b6..f5001bf60d 100644 --- a/packages/graphql-modules/tests/di-providers.spec.ts +++ b/packages/graphql-modules/tests/di-providers.spec.ts @@ -1,4 +1,5 @@ import 'reflect-metadata'; +import { createHook, executionAsyncId } from 'async_hooks'; import { createApplication, createModule, @@ -301,6 +302,10 @@ test('useFactory with dependencies', async () => { const app = createApplication({ modules: [postsModule], + executionContext: { + createHook, + executionAsyncId, + }, }); const createContext = () => ({ request: {}, response: {} }); diff --git a/packages/graphql-modules/tests/execution-context.spec.ts b/packages/graphql-modules/tests/execution-context.spec.ts index 766b3c7cd4..47e0693dc6 100644 --- a/packages/graphql-modules/tests/execution-context.spec.ts +++ b/packages/graphql-modules/tests/execution-context.spec.ts @@ -1,4 +1,5 @@ import 'reflect-metadata'; +import { createHook, executionAsyncId } from 'async_hooks'; import { createApplication, createModule, @@ -95,6 +96,7 @@ test('ExecutionContext on module level provider', async () => { const app = createApplication({ modules: [postsModule], + executionContext: { createHook, executionAsyncId }, }); const createContext = () => ({ request: {}, response: {} }); @@ -209,6 +211,7 @@ test('ExecutionContext on application level provider', async () => { const app = createApplication({ modules: [postsModule], providers: [Posts, PostsConnection], + executionContext: { createHook, executionAsyncId }, }); const createContext = () => ({ request: {}, response: {} }); @@ -304,6 +307,7 @@ test('ExecutionContext on module level global provider', async () => { const app = createApplication({ modules: [postsModule], + executionContext: { createHook, executionAsyncId }, }); const createContext = () => ({ request: {}, response: {} }); @@ -389,6 +393,7 @@ test('ExecutionContext on application level global provider', async () => { const app = createApplication({ modules: [postsModule], providers: [Posts], + executionContext: { createHook, executionAsyncId }, }); const createContext = () => ({ noop() {} }); @@ -488,6 +493,7 @@ test('accessing a singleton provider with execution context in another singleton useValue: expectedName, }, ], + executionContext: { createHook, executionAsyncId }, }); const result = await testkit.execute(app, { diff --git a/website/src/pages/docs/advanced/execution-context.mdx b/website/src/pages/docs/advanced/execution-context.mdx index e10e30d3e2..d232b20a78 100644 --- a/website/src/pages/docs/advanced/execution-context.mdx +++ b/website/src/pages/docs/advanced/execution-context.mdx @@ -4,13 +4,30 @@ import { Callout } from '@theguild/components' Execution Context means the context of the execution of GraphQL Operation. It's related to Dependency Injection, especially Singletons and represents the context object created by your GraphQL server. +## Enabling Execution Context + +To enable Execution Context, you need to pass `createHook` and `executionAsyncId` functions from the `async_hooks` module in `createApplication`. + +```ts +import { createApplication } from 'graphql-modules'; +import { createHook, executionAsyncId } from 'async_hooks'; + +const app = createApplication({ + modules: [/* ... */], + executionContext: { + createHook, + executionAsyncId, + } +}); +``` + Why "especially useful in `Singleton`s"? As you know from ["Introduction to Dependency Injection"](../di/introduction) chapter, `Singleton`s can't directly access Operation scoped services, meaning they probably can't also directly access the context object created per each operation. Directly. Thanks to `@ExecutionContext` decorator, every `Singleton` provider gets access to the GraphQL Context and the Operation scoped Injector. -Take a look at the example below: +## Example ```ts import { Injectable, ExecutionContext } from 'graphql-modules' @@ -43,6 +60,5 @@ It also means you gain a lot in terms of performance because the `Data` class is `@ExecutionContext` impacts the performance, depending on your Node version (execution context uses `async_hooks` module). - GraphQL Modules is smart enough to enable `async_hooks` only when - `@ExecutionContext` is used. + `@ExecutionContext` is available only for Node environments. From 7a4b57c72a75f98dc7a10a5f7bccc83429ee5e55 Mon Sep 17 00:00:00 2001 From: Kamil Kisiela Date: Wed, 28 Sep 2022 14:22:45 +0000 Subject: [PATCH 2/5] Fix build --- packages/graphql-modules/src/application/execution-context.ts | 3 +-- packages/graphql-modules/src/di/decorators.ts | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/graphql-modules/src/application/execution-context.ts b/packages/graphql-modules/src/application/execution-context.ts index 85926cee02..6f629c0727 100644 --- a/packages/graphql-modules/src/application/execution-context.ts +++ b/packages/graphql-modules/src/application/execution-context.ts @@ -14,7 +14,6 @@ export interface ExecutionContextPicker { const executionContextStore = new Map(); const executionContextDependencyStore = new Map>(); -let executionContextHook = null; let executionAsyncId: () => number = () => 0; function destroyContextAndItsChildren(id: number) { @@ -68,7 +67,7 @@ export const executionContext: { export function enableExecutionContext(config: ExecutionContextConfig) { if (!executionContextEnabled) { - executionContextHook = config.createHook({ + config.createHook({ init(asyncId, _, triggerAsyncId) { // Store same context data for child async resources const ctx = executionContextStore.get(triggerAsyncId); diff --git a/packages/graphql-modules/src/di/decorators.ts b/packages/graphql-modules/src/di/decorators.ts index 85c4bdaeee..0336dc52a7 100644 --- a/packages/graphql-modules/src/di/decorators.ts +++ b/packages/graphql-modules/src/di/decorators.ts @@ -6,7 +6,6 @@ import { ensureInjectableMetadata, } from './metadata'; import { Injector } from './injector'; -import { assertExecutionContext } from '../application/execution-context'; function ensureReflect() { if (!(Reflect && Reflect.getOwnMetadata)) { From 23c44b3b6f9eb71ea99b9778c608bbd47d9db9d1 Mon Sep 17 00:00:00 2001 From: Kamil Kisiela Date: Thu, 29 Sep 2022 07:50:39 +0000 Subject: [PATCH 3/5] Force users to define executionContext:false or { ... } --- .changeset/tame-oranges-exercise.md | 2 +- .../graphql-modules/src/application/types.ts | 2 +- .../graphql-modules/tests/bootstrap.spec.ts | 7 ++++ .../graphql-modules/tests/context.spec.ts | 1 + .../graphql-modules/tests/di-errors.spec.ts | 7 +++- .../tests/di-providers.spec.ts | 17 +++++++- .../graphql-modules/tests/federation.spec.ts | 2 + .../tests/middleware-resolvers.spec.ts | 3 ++ .../tests/operation-controller.spec.ts | 3 ++ .../tests/subscription.spec.ts | 1 + .../graphql-modules/tests/testing.spec.ts | 5 ++- .../tests/third-parties.spec.ts | 2 + website/src/pages/docs/recipes/migration.mdx | 39 +++++++++++++++++++ 13 files changed, 85 insertions(+), 6 deletions(-) diff --git a/.changeset/tame-oranges-exercise.md b/.changeset/tame-oranges-exercise.md index c5bdd6d93f..36835ec0e5 100644 --- a/.changeset/tame-oranges-exercise.md +++ b/.changeset/tame-oranges-exercise.md @@ -2,4 +2,4 @@ 'graphql-modules': major --- -Make ExecutionContext opt-in and graphql-modules not node specific +Make ExecutionContext opt-in and graphql-modules platform agnostic diff --git a/packages/graphql-modules/src/application/types.ts b/packages/graphql-modules/src/application/types.ts index afda51eb69..87daf8cc59 100644 --- a/packages/graphql-modules/src/application/types.ts +++ b/packages/graphql-modules/src/application/types.ts @@ -155,5 +155,5 @@ export interface ApplicationConfig { * }); * ``` */ - executionContext?: ExecutionContextConfig; + executionContext: ExecutionContextConfig | false; } diff --git a/packages/graphql-modules/tests/bootstrap.spec.ts b/packages/graphql-modules/tests/bootstrap.spec.ts index 0673766edb..13fc313db2 100644 --- a/packages/graphql-modules/tests/bootstrap.spec.ts +++ b/packages/graphql-modules/tests/bootstrap.spec.ts @@ -25,6 +25,7 @@ test('fail when modules have non-unique ids', async () => { expect(() => { createApplication({ modules: [modFoo, modBar], + executionContext: false, }); }).toThrow(`Modules with non-unique ids: foo`); }); @@ -57,6 +58,7 @@ test('should allow multiple type extensions in the same module', async () => { const app = createApplication({ modules: [m1], + executionContext: false, }); const schema = app.schema; @@ -97,6 +99,7 @@ test('should not thrown when isTypeOf is used', async () => { const app = createApplication({ modules: [m1], + executionContext: false, }); const result = await testkit.execute(app, { @@ -152,6 +155,7 @@ test('should allow to add __isTypeOf to type resolvers', () => { expect(() => { createApplication({ modules: [m1], + executionContext: false, }); }).not.toThrow(); }); @@ -209,6 +213,7 @@ test('should support __resolveType', async () => { const app = createApplication({ modules: [m1], + executionContext: false, }); const result = await testkit.execute(app, { @@ -296,6 +301,7 @@ test('allow field resolvers in an interface without objects inheriting them', as const app = createApplication({ modules: [mod], + executionContext: false, }); const result = await testkit.execute(app, { @@ -362,6 +368,7 @@ test('pass field resolvers of an interface to schemaBuilder', async () => { inheritResolversFromInterfaces: true, }); }, + executionContext: false, }); const result = await testkit.execute(app, { diff --git a/packages/graphql-modules/tests/context.spec.ts b/packages/graphql-modules/tests/context.spec.ts index 6fd7f75b6b..701ebc842f 100644 --- a/packages/graphql-modules/tests/context.spec.ts +++ b/packages/graphql-modules/tests/context.spec.ts @@ -77,6 +77,7 @@ test('Global context and module context should be reachable', async () => { const app = createApplication({ modules: [postsModule], + executionContext: false, }); const contextValue = () => ({ diff --git a/packages/graphql-modules/tests/di-errors.spec.ts b/packages/graphql-modules/tests/di-errors.spec.ts index c7e71ce0cf..6a740b1d57 100644 --- a/packages/graphql-modules/tests/di-errors.spec.ts +++ b/packages/graphql-modules/tests/di-errors.spec.ts @@ -176,6 +176,7 @@ test('No error in case of module without providers', async () => { const app = createApplication({ modules: [mod], providers: [Data], + executionContext: false, }); const contextValue = { request: {}, response: {} }; @@ -238,7 +239,9 @@ test('Make sure we have readable error', async () => { }, }); - expect(() => createApplication({ modules: [m2, m1] })).toThrowError( + expect(() => + createApplication({ modules: [m2, m1], executionContext: false }) + ).toThrowError( 'No provider for P1! (P2 -> P1) - in Module "m2" (Singleton Scope)' ); }); @@ -311,6 +314,7 @@ test('Detect collision of two identical global providers (singleton)', async () createApplication({ modules: [fooModule, barModule], providers: [AppData], + executionContext: false, }); }).toThrowError( `Failed to define 'Data' token as global. Token provided by two modules: 'bar', 'foo'` @@ -385,6 +389,7 @@ test('Detect collision of two identical global providers (operation)', async () createApplication({ modules: [fooModule, barModule], providers: [AppData], + executionContext: false, }); }).toThrowError( `Failed to define 'Data' token as global. Token provided by two modules: 'bar', 'foo'` diff --git a/packages/graphql-modules/tests/di-providers.spec.ts b/packages/graphql-modules/tests/di-providers.spec.ts index f5001bf60d..2332e5eff0 100644 --- a/packages/graphql-modules/tests/di-providers.spec.ts +++ b/packages/graphql-modules/tests/di-providers.spec.ts @@ -178,6 +178,7 @@ test('general test', async () => { useValue: 'app', }, ], + executionContext: false, }); const createContext = () => ({ request: {}, response: {} }); @@ -370,7 +371,7 @@ test('Use @Inject decorator in constructor', async () => { }, }); - const app = createApplication({ modules: [mod] }); + const app = createApplication({ modules: [mod], executionContext: false }); const result = await testkit.execute(app, { contextValue: { request }, @@ -429,7 +430,7 @@ test('Use useFactory with deps', async () => { }, }); - const app = createApplication({ modules: [mod] }); + const app = createApplication({ modules: [mod], executionContext: false }); const result = await testkit.execute(app, { contextValue: { request }, @@ -451,6 +452,7 @@ test('Application allows injector access', () => { const { injector } = createApplication({ modules: [], providers: [SomeProvider], + executionContext: false, }); expect(injector.get(SomeProvider)).toBeInstanceOf(SomeProvider); }); @@ -504,6 +506,7 @@ test('Operation scoped provider should be created once per GraphQL Operation', a const app = createApplication({ modules: [postsModule], + executionContext: false, }); const contextValue = { request: {}, response: {} }; @@ -596,6 +599,7 @@ test('Operation scoped provider should be created once per GraphQL Operation (Ap const app = createApplication({ modules: [postsModule], + executionContext: false, }); const schema = app.createSchemaForApollo(); @@ -682,6 +686,7 @@ test('Singleton scoped provider should be created once', async () => { const app = createApplication({ modules: [mod], providers: [Data], + executionContext: false, }); const contextValue = { request: {}, response: {} }; @@ -764,6 +769,7 @@ test('Global Token provided by one module should be accessible by other modules const app = createApplication({ modules: [fooModule, barModule], + executionContext: false, }); const contextValue = { request: {}, response: {} }; @@ -864,6 +870,7 @@ test('Global Token (module) should use other local tokens (operation)', async () scope: Scope.Operation, }, ], + executionContext: false, }); const contextValue = { request: {}, response: {} }; @@ -948,6 +955,7 @@ test('Global Token provided by one module should be accessible by other modules const app = createApplication({ modules: [fooModule, barModule], providers: [AppData], + executionContext: false, }); const contextValue = { request: {}, response: {} }; @@ -1046,6 +1054,7 @@ test('Global Token (module) should use other local tokens (singleton)', async () useValue: 'verbose', }, ], + executionContext: false, }); const contextValue = { request: {}, response: {} }; @@ -1124,6 +1133,7 @@ test('instantiate all singleton providers', async () => { const app = createApplication({ modules: [fooModule], providers: [AppData, MyLogger], + executionContext: false, }); // make sure all providers are instantiated @@ -1222,6 +1232,7 @@ test('instantiate all singleton and global providers', async () => { const app = createApplication({ modules: [fooModule, barModule], providers: [AppData, MyLogger], + executionContext: false, }); // make sure all providers are instantiated @@ -1310,6 +1321,7 @@ test('instantiate operation-scoped provider once per many fields', async () => { const app = createApplication({ modules: [mod], + executionContext: false, }); let result = await testkit.execute(app, { @@ -1389,6 +1401,7 @@ test('Last operation-scoped provider in the list wins', async () => { useValue: 'last', }, ], + executionContext: false, }); const result = await testkit.execute(app, { diff --git a/packages/graphql-modules/tests/federation.spec.ts b/packages/graphql-modules/tests/federation.spec.ts index f3b937ca2b..1247248feb 100644 --- a/packages/graphql-modules/tests/federation.spec.ts +++ b/packages/graphql-modules/tests/federation.spec.ts @@ -38,6 +38,7 @@ describe('federation', () => { expect(() => createApplication({ + executionContext: false, modules: [mod], schemaBuilder(input) { return buildSubgraphSchema({ @@ -71,6 +72,7 @@ describe('federation', () => { expect(() => createApplication({ + executionContext: false, modules: [mod], schemaBuilder(input) { return buildSubgraphSchema({ diff --git a/packages/graphql-modules/tests/middleware-resolvers.spec.ts b/packages/graphql-modules/tests/middleware-resolvers.spec.ts index 5fb91e3ca4..aafe87cbc6 100644 --- a/packages/graphql-modules/tests/middleware-resolvers.spec.ts +++ b/packages/graphql-modules/tests/middleware-resolvers.spec.ts @@ -242,6 +242,7 @@ describe('should use a middleware on a field with no implemented resolver', () = }); const app = createApplication({ modules: [mod], + executionContext: false, }); const result = await testkit.execute(app, { @@ -268,6 +269,7 @@ describe('should use a middleware on a field with no implemented resolver', () = }); const app = createApplication({ modules: [mod], + executionContext: false, }); const result = await testkit.execute(app, { @@ -294,6 +296,7 @@ describe('should use a middleware on a field with no implemented resolver', () = }); const app = createApplication({ modules: [mod], + executionContext: false, }); const result = await testkit.execute(app, { diff --git a/packages/graphql-modules/tests/operation-controller.spec.ts b/packages/graphql-modules/tests/operation-controller.spec.ts index 5a727882f2..bfb7ae0fb9 100644 --- a/packages/graphql-modules/tests/operation-controller.spec.ts +++ b/packages/graphql-modules/tests/operation-controller.spec.ts @@ -42,6 +42,7 @@ test('should share context and injection', async () => { const app = createApplication({ modules: [mod], providers: [Data], + executionContext: false, }); const controller = app.createOperationController({ @@ -120,6 +121,7 @@ test('execution should not destroy operation', async () => { const app = createApplication({ modules: [mod], providers: [Data], + executionContext: false, }); const controller = app.createOperationController({ @@ -184,6 +186,7 @@ test('autoDestroy enabled should destroy operation after execution', async () => const app = createApplication({ modules: [mod], providers: [Data], + executionContext: false, }); const controller = app.createOperationController({ diff --git a/packages/graphql-modules/tests/subscription.spec.ts b/packages/graphql-modules/tests/subscription.spec.ts index cca1b7a28f..02ef8d9b0b 100644 --- a/packages/graphql-modules/tests/subscription.spec.ts +++ b/packages/graphql-modules/tests/subscription.spec.ts @@ -103,6 +103,7 @@ test('Operation-Scope provider instantiated on every subscription', async () => const app = createApplication({ modules: [postsModule], + executionContext: false, }); const createContext = () => ({}); diff --git a/packages/graphql-modules/tests/testing.spec.ts b/packages/graphql-modules/tests/testing.spec.ts index 9405e4c4e9..0002c3b482 100644 --- a/packages/graphql-modules/tests/testing.spec.ts +++ b/packages/graphql-modules/tests/testing.spec.ts @@ -423,7 +423,7 @@ describe('execute', () => { }, }); - const app = createApplication({ modules: [mod] }); + const app = createApplication({ modules: [mod], executionContext: false }); const query: TypedDocumentNode< { foo: { id: string } }, { id: string } @@ -548,6 +548,7 @@ describe('mockApplication', () => { }, ], modules: [envModule], + executionContext: false, }); const app = testkit.mockApplication(originalApp).addProviders([ @@ -598,6 +599,7 @@ describe('mockApplication', () => { const originalApp = createApplication({ providers: [Config], modules: [envModule], + executionContext: false, }); const app = testkit.mockApplication(originalApp).replaceModule( @@ -687,6 +689,7 @@ describe('mockApplication', () => { }, ], modules: [envModule, extraModule], + executionContext: false, }); const app = testkit diff --git a/packages/graphql-modules/tests/third-parties.spec.ts b/packages/graphql-modules/tests/third-parties.spec.ts index a292141327..368f18b9a8 100644 --- a/packages/graphql-modules/tests/third-parties.spec.ts +++ b/packages/graphql-modules/tests/third-parties.spec.ts @@ -29,6 +29,7 @@ describe('Apollo Server', () => { }); const app = createApplication({ modules: [mod], + executionContext: false, }); const apollo = new ApolloServer({ typeDefs: app.typeDefs, @@ -112,6 +113,7 @@ describe('Apollo Server', () => { const app = createApplication({ modules: [mod], + executionContext: false, }); const apollo = new ApolloServer({ diff --git a/website/src/pages/docs/recipes/migration.mdx b/website/src/pages/docs/recipes/migration.mdx index be8196376b..b5c6170194 100644 --- a/website/src/pages/docs/recipes/migration.mdx +++ b/website/src/pages/docs/recipes/migration.mdx @@ -1,5 +1,44 @@ import { Tabs, Tab, Callout } from '@theguild/components' +# Migration from v2.X + +## ExecutionContext is now disabled by default + +In v2.X, the `ExecutionContext` was enabled by default, in v3.X, it is not. +If you want to use it, you need to enable it explicitly. + +The Execution Context uses `async_hooks` under the hood, which is only available on Node. +To make `graphql-modules` platform agnostic, we decided to make it opt-in. + + + +```ts +import { createApplication } from 'graphql-modules' +import { createHook, executionAsyncId } from 'async_hooks' + +const app = createApplication({ + modules: [/* modules */], + executionContext: { createHook, executionAsyncId }, +}) +``` +``` + + + +```ts +import { createApplication } from 'graphql-modules' + +const app = createApplication({ + modules: [/* modules */], +}) +``` + + + +# Migration from v1.X + +Check [graphql-tools/schema v8.0.0 release](https://github.com/ardatan/graphql-tools/releases/tag/%40graphql-tools%2Fschema%408.0.0) for possible breaking changes. + # Migration from v0.X Note: this page is still in progress! From aa2a0c42db091f67ef58c24e9b42f1eb586fb2ce Mon Sep 17 00:00:00 2001 From: Kamil Kisiela Date: Thu, 29 Sep 2022 07:55:37 +0000 Subject: [PATCH 4/5] fix build --- packages/graphql-modules/src/testing/test-module.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/graphql-modules/src/testing/test-module.ts b/packages/graphql-modules/src/testing/test-module.ts index b80d97e511..ff3568e845 100644 --- a/packages/graphql-modules/src/testing/test-module.ts +++ b/packages/graphql-modules/src/testing/test-module.ts @@ -71,6 +71,7 @@ export function testModule(testedModule: Module, config?: TestModuleConfig) { modules, providers: config?.providers, middlewares: config?.middlewares, + executionContext: false, }); } From 5f9105ed5a9ffbdea209b526544cd2ddb57c11b0 Mon Sep 17 00:00:00 2001 From: Kamil Kisiela Date: Thu, 29 Sep 2022 08:02:35 +0000 Subject: [PATCH 5/5] Update tests ad docs --- .github/workflows/tests.yml | 2 +- benchmark/basic.case.ts | 2 ++ examples/apollo-subscriptions/src/app/index.ts | 1 + examples/basic-with-dependency-injection/src/index.ts | 1 + examples/basic/src/index.ts | 1 + examples/graphql-yoga/src/app/index.ts | 1 + examples/subscriptions/src/app/index.ts | 1 + website/src/pages/docs/advanced/lifecycles.mdx | 3 ++- website/src/pages/docs/advanced/middlewares.mdx | 2 +- website/src/pages/docs/advanced/subscriptions.mdx | 6 ++++-- website/src/pages/docs/api.mdx | 3 ++- website/src/pages/docs/get-started.mdx | 3 ++- 12 files changed, 19 insertions(+), 7 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index c195154ad3..e6d9aaea78 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -39,7 +39,7 @@ jobs: strategy: matrix: os: [ubuntu-latest] # remove windows to speed up the tests - node-version: [12, 16, 18] + node-version: [16, 18] graphql_version: - 15 - 16 diff --git a/benchmark/basic.case.ts b/benchmark/basic.case.ts index eadee04a32..e0c9f400b7 100644 --- a/benchmark/basic.case.ts +++ b/benchmark/basic.case.ts @@ -41,6 +41,7 @@ const app = createApplication({ }, }), ], + executionContext: false, }); class Posts { @@ -76,6 +77,7 @@ const appWithDI = createApplication({ }, }), ], + executionContext: false, }); const pureSchema = makeExecutableSchema({ diff --git a/examples/apollo-subscriptions/src/app/index.ts b/examples/apollo-subscriptions/src/app/index.ts index 24d9ed5a3a..5cca9a7c4f 100644 --- a/examples/apollo-subscriptions/src/app/index.ts +++ b/examples/apollo-subscriptions/src/app/index.ts @@ -5,4 +5,5 @@ import { PostModule } from './post/post.module'; export const graphqlApplication = createApplication({ modules: [PostModule], providers: [PubSub], + executionContext: false, }); diff --git a/examples/basic-with-dependency-injection/src/index.ts b/examples/basic-with-dependency-injection/src/index.ts index 6ebd024b96..b353b1bf7d 100644 --- a/examples/basic-with-dependency-injection/src/index.ts +++ b/examples/basic-with-dependency-injection/src/index.ts @@ -7,6 +7,7 @@ import { graphqlHTTP } from 'express-graphql'; const app = createApplication({ modules: [BlogModule, UserModule], + executionContext: false, }); const server = express(); diff --git a/examples/basic/src/index.ts b/examples/basic/src/index.ts index 002e1a38c5..1940163e21 100644 --- a/examples/basic/src/index.ts +++ b/examples/basic/src/index.ts @@ -17,6 +17,7 @@ import { SocialNetworkModule } from './app/social-network/social-network.module' const server = express(); const app = createApplication({ modules: [UserModule, AuthModule, SocialNetworkModule], + executionContext: false, }); const execute = app.createExecution(); diff --git a/examples/graphql-yoga/src/app/index.ts b/examples/graphql-yoga/src/app/index.ts index 7b12a7f915..477bee059b 100644 --- a/examples/graphql-yoga/src/app/index.ts +++ b/examples/graphql-yoga/src/app/index.ts @@ -3,4 +3,5 @@ import { PostModule } from './post/post.module'; export const app = createApplication({ modules: [PostModule], + executionContext: false, }); diff --git a/examples/subscriptions/src/app/index.ts b/examples/subscriptions/src/app/index.ts index 1f256355e5..0754ec57cf 100644 --- a/examples/subscriptions/src/app/index.ts +++ b/examples/subscriptions/src/app/index.ts @@ -5,4 +5,5 @@ import { PostModule } from './post/post.module'; export const app = createApplication({ providers: [PubSub], modules: [PostModule], + executionContext: false, }); diff --git a/website/src/pages/docs/advanced/lifecycles.mdx b/website/src/pages/docs/advanced/lifecycles.mdx index 35bcd550f8..83fae1feac 100644 --- a/website/src/pages/docs/advanced/lifecycles.mdx +++ b/website/src/pages/docs/advanced/lifecycles.mdx @@ -60,7 +60,8 @@ const mod = createModule({ const app = createApplication({ modules: [mod], - providers: [Data] + providers: [Data], + executionContext: false, }) server.use('/graphql', (req, res) => { diff --git a/website/src/pages/docs/advanced/middlewares.mdx b/website/src/pages/docs/advanced/middlewares.mdx index 4fadfc46bb..3cac5c3fea 100644 --- a/website/src/pages/docs/advanced/middlewares.mdx +++ b/website/src/pages/docs/advanced/middlewares.mdx @@ -161,7 +161,7 @@ const application = createApplication({ Query: { me: [myMiddleware] } - } + }, }) ``` diff --git a/website/src/pages/docs/advanced/subscriptions.mdx b/website/src/pages/docs/advanced/subscriptions.mdx index ec732126ae..3b4a0669b7 100644 --- a/website/src/pages/docs/advanced/subscriptions.mdx +++ b/website/src/pages/docs/advanced/subscriptions.mdx @@ -16,7 +16,8 @@ import { createApplication } from 'graphql-modules' const application = createApplication({ modules: [ // ... - ] + ], + executionContext: false, }) const subscribe = application.createSubscription() @@ -48,7 +49,8 @@ const application = createApplication({ provide: PubSub, useValue: new PubSub() } - ] + ], + executionContext: false, }) ``` diff --git a/website/src/pages/docs/api.mdx b/website/src/pages/docs/api.mdx index 3869563061..fbca3c3913 100644 --- a/website/src/pages/docs/api.mdx +++ b/website/src/pages/docs/api.mdx @@ -73,7 +73,8 @@ import { postsModule } from './posts' import { commentsModule } from './comments' const app = createApplication({ - modules: [usersModule, postsModule, commentsModule] + modules: [usersModule, postsModule, commentsModule], + executionContext: false, }) ``` diff --git a/website/src/pages/docs/get-started.mdx b/website/src/pages/docs/get-started.mdx index 8ea73c5e9d..22c0674566 100644 --- a/website/src/pages/docs/get-started.mdx +++ b/website/src/pages/docs/get-started.mdx @@ -66,7 +66,8 @@ import { myModule } from './my-module' // This is your application, it contains your GraphQL schema and the implementation of it. const application = createApplication({ - modules: [myModule] + modules: [myModule], + executionContext: false, }) // This is your actual GraphQL schema