From afbb15038d02f753f7b4a98a40f5028a1f97b2d6 Mon Sep 17 00:00:00 2001 From: s Date: Wed, 22 Jan 2020 13:40:30 -0500 Subject: [PATCH] Allow async initialization of data sources (#3639) * Allow async initialization of dataSources * Update return type of DataSource.initialize * Add dataSource integration tests --- CHANGELOG.md | 1 + packages/apollo-datasource/src/index.ts | 2 +- .../src/__tests__/dataSources.test.ts | 105 ++++++++++++++++++ .../apollo-server-core/src/requestPipeline.ts | 17 ++- 4 files changed, 118 insertions(+), 7 deletions(-) create mode 100644 packages/apollo-server-core/src/__tests__/dataSources.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 136bf0998e1..9c4c1a587c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ The version headers in this history reflect the versions of Apollo Server itself > The changes noted within this `vNEXT` section have not been released yet. New PRs and commits which introduce changes should include an entry in this `vNEXT` section as part of their development. When a release is being prepared, a new header will be (manually) created below and the the appropriate changes within that release will be moved into the new section. +- `apollo-server-core`: Allow asynchronous initialization of datasources: the `initialize` method on datasources may now return a Promise, which will be settled before any resolvers are called. [#3639](https://github.com/apollographql/apollo-server/pull/3639) - `apollo-server-core`: Update apollo-tooling dependencies, resolve TS build error (missing types for node-fetch) [#3662](https://github.com/apollographql/apollo-server/pull/3662) ### v2.9.15 diff --git a/packages/apollo-datasource/src/index.ts b/packages/apollo-datasource/src/index.ts index b8195d8c682..9e82de93133 100644 --- a/packages/apollo-datasource/src/index.ts +++ b/packages/apollo-datasource/src/index.ts @@ -6,5 +6,5 @@ export interface DataSourceConfig { } export abstract class DataSource { - initialize?(config: DataSourceConfig): void; + initialize?(config: DataSourceConfig): void | Promise; } diff --git a/packages/apollo-server-core/src/__tests__/dataSources.test.ts b/packages/apollo-server-core/src/__tests__/dataSources.test.ts new file mode 100644 index 00000000000..995ef2ee497 --- /dev/null +++ b/packages/apollo-server-core/src/__tests__/dataSources.test.ts @@ -0,0 +1,105 @@ +import { ApolloServerBase } from '../ApolloServer'; +import gql from 'graphql-tag'; + +const typeDefs = gql` + type Query { + hello: String + } +`; + +describe('ApolloServerBase dataSources', () => { + it('initializes synchronous datasources from a datasource creator function', async () => { + const initialize = jest.fn(); + + const server = new ApolloServerBase({ + typeDefs, + resolvers: { + Query: { + hello() { + return 'world'; + } + } + }, + dataSources: () => ({ x: { initialize }, y: { initialize } }) + }); + + await server.executeOperation({ query: "query { hello }"}); + + expect(initialize).toHaveBeenCalledTimes(2); + }); + + it('initializes all async and sync datasources before calling resolvers', async () => { + const INITIALIZE = "datasource initializer call"; + const METHOD_CALL = "datasource method call"; + + const expectedCallOrder = [ + INITIALIZE, + INITIALIZE, + INITIALIZE, + METHOD_CALL + ]; + + const actualCallOrder: string[] = []; + + const server = new ApolloServerBase({ + typeDefs, + resolvers: { + Query: { + hello(_, __, context) { + context.dataSources.x.getData(); + return "world"; + } + }, + }, + dataSources: () => ({ + x: { + initialize() { + return Promise.resolve().then( + () => { actualCallOrder.push(INITIALIZE); } + ); + }, + getData() { actualCallOrder.push(METHOD_CALL); } + }, + y: { + initialize() { + return new Promise(res => { + setTimeout(() => { + actualCallOrder.push(INITIALIZE); + res(); + }, 0); + }); + }, + }, + z: { + initialize() { actualCallOrder.push(INITIALIZE); } + } + }) + }); + + await server.executeOperation({ query: "query { hello }"}); + + expect(actualCallOrder).toEqual(expectedCallOrder); + }); + + it('makes datasources available on resolver contexts', async () => { + const message = 'hi from dataSource'; + const getData = jest.fn(() => message); + + const server = new ApolloServerBase({ + typeDefs, + resolvers: { + Query: { + hello(_, __, context) { + return context.dataSources.x.getData(); + } + }, + }, + dataSources: () => ({ x: { initialize() {}, getData } }) + }); + + const res = await server.executeOperation({ query: "query { hello }"}); + + expect(getData).toHaveBeenCalled(); + expect(res.data?.hello).toBe(message); + }); +}); diff --git a/packages/apollo-server-core/src/requestPipeline.ts b/packages/apollo-server-core/src/requestPipeline.ts index b40f8bdfdde..786ade95b37 100644 --- a/packages/apollo-server-core/src/requestPipeline.ts +++ b/packages/apollo-server-core/src/requestPipeline.ts @@ -114,7 +114,7 @@ export async function processGraphQLRequest( const dispatcher = initializeRequestListenerDispatcher(); - initializeDataSources(); + await initializeDataSources(); const metrics = requestContext.metrics || Object.create(null); if (!requestContext.metrics) { @@ -611,21 +611,26 @@ export async function processGraphQLRequest( return new GraphQLExtensionStack(extensions); } - function initializeDataSources() { + async function initializeDataSources() { if (config.dataSources) { const context = requestContext.context; const dataSources = config.dataSources(); + const initializers: any[] = []; for (const dataSource of Object.values(dataSources)) { if (dataSource.initialize) { - dataSource.initialize({ - context, - cache: requestContext.cache, - }); + initializers.push( + dataSource.initialize({ + context, + cache: requestContext.cache, + }) + ); } } + await Promise.all(initializers); + if ('dataSources' in context) { throw new Error( 'Please use the dataSources config option instead of putting dataSources on the context yourself.',