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

Allow async initialization of data sources #3639

Merged
merged 10 commits into from Jan 22, 2020
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>;
}
96 changes: 96 additions & 0 deletions packages/apollo-server-core/src/__tests__/dataSources.test.ts
@@ -0,0 +1,96 @@
import { ApolloServerBase } from '../ApolloServer';
import gql from 'graphql-tag';

const typeDefs = gql`
type Query {
hello: String
}
`;

const resolvers = {
Query: {
hello() {
return 'world';
}
},
};

describe('ApolloServerBase dataSources', () => {
it('initializes synchronous datasources from a datasource creator function', async () => {
const initialize = jest.fn();

const server = new ApolloServerBase({
typeDefs,
resolvers,
dataSources: () => ({ x: { initialize }, y: { initialize } })
});

await server.executeOperation({ query: "query { hello }"});

expect(initialize).toHaveBeenCalledTimes(2);
});

it('initializes asynchronous datasources before calling resolvers', async () => {
const expectedMessage = 'success';
let maybeInitialized = 'failure';

const additionalInitializer = jest.fn();

const server = new ApolloServerBase({
typeDefs,
resolvers: {
Query: {
hello(_, __, context) {
return context.dataSources.x.getData();
}
},
},
dataSources: () => ({
x: {
initialize() {
return new Promise(res => { setTimeout(() => {
maybeInitialized = expectedMessage;
res();
}, 200) })
},
getData() { return maybeInitialized; }
},
y: {
initialize() {
return new Promise(res => { setTimeout(() => {
additionalInitializer();
res();
}, 400) })
}
}
})
});

lostfictions marked this conversation as resolved.
Show resolved Hide resolved
const res = await server.executeOperation({ query: "query { hello }"});

expect(res.data?.hello).toBe(expectedMessage);
expect(additionalInitializer).toHaveBeenCalled();
});

it('make 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);
});
});
6 changes: 3 additions & 3 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,15 +611,15 @@ 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();

for (const dataSource of Object.values(dataSources)) {
if (dataSource.initialize) {
dataSource.initialize({
await dataSource.initialize({
context,
cache: requestContext.cache,
});
Expand Down