Skip to content

Commit

Permalink
Allow async initialization of data sources (#3639)
Browse files Browse the repository at this point in the history
* Allow async initialization of dataSources
* Update return type of DataSource.initialize
* Add dataSource integration tests
  • Loading branch information
lostfictions authored and trevor-scheer committed Jan 22, 2020
1 parent a86b6a0 commit afbb150
Show file tree
Hide file tree
Showing 4 changed files with 118 additions and 7 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion packages/apollo-datasource/src/index.ts
Expand Up @@ -6,5 +6,5 @@ export interface DataSourceConfig<TContext> {
}

export abstract class DataSource<TContext = any> {
initialize?(config: DataSourceConfig<TContext>): void;
initialize?(config: DataSourceConfig<TContext>): void | Promise<void>;
}
105 changes: 105 additions & 0 deletions 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);
});
});
17 changes: 11 additions & 6 deletions packages/apollo-server-core/src/requestPipeline.ts
Expand Up @@ -114,7 +114,7 @@ export async function processGraphQLRequest<TContext>(

const dispatcher = initializeRequestListenerDispatcher();

initializeDataSources();
await initializeDataSources();

const metrics = requestContext.metrics || Object.create(null);
if (!requestContext.metrics) {
Expand Down Expand Up @@ -611,21 +611,26 @@ export async function processGraphQLRequest<TContext>(
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.',
Expand Down

0 comments on commit afbb150

Please sign in to comment.