Skip to content

Commit 56a4773

Browse files
authored
Merge pull request #229 from Urigo/bugfix/datasource
Fix Apollo DataSources Integration
2 parents 69c5667 + 8a2a2e3 commit 56a4773

File tree

5 files changed

+137
-69
lines changed

5 files changed

+137
-69
lines changed

examples/basic/src/app/auth/auth.module.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export const AuthModule = new GraphQLModule({
2424
imports: [
2525
UserModule,
2626
],
27-
context: async (request: { req: Request }) => ({
27+
context: async (networkRequest: { req: Request }) => ({
2828
authenticatedUser: {
2929
_id: 1,
3030
username: 'me',

packages/core/src/graphql-module.ts

Lines changed: 66 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ export interface GraphQLModuleOptions<Config, Request, Context> {
6565
* Context builder method. Use this to add your own fields and data to the GraphQL `context`
6666
* of each execution of GraphQL.
6767
*/
68-
context?: BuildContextFn<Config, Request, Context> | Context;
68+
context?: BuildContextFn<Config, Request, Context> | Promise<Context> | Context;
6969
/**
7070
* The dependencies that this module need to run correctly, you can either provide the `GraphQLModule`,
7171
* or provide a string with the name of the other module.
@@ -111,7 +111,7 @@ export interface ModuleCache<Request, Context> {
111111
typeDefs: DocumentNode;
112112
resolvers: IResolvers<any, ModuleContext<Context>>;
113113
schemaDirectives: ISchemaDirectives;
114-
contextBuilder: (req: Request) => Promise<Context>;
114+
contextBuilder: (req: Request, excludeRequest?: boolean) => Promise<Context>;
115115
modulesMap: ModulesMap<Request>;
116116
extraSchemas: GraphQLSchema[];
117117
directiveResolvers: IDirectiveResolvers;
@@ -385,8 +385,8 @@ export class GraphQLModule<Config = any, Request = any, Context = any> {
385385
return directiveResolvers;
386386
}
387387

388-
private checkIfResolverCalledSafely(resolverPath: string, appContext: any, info: any) {
389-
if (!('request' in appContext)) {
388+
private checkIfResolverCalledSafely(resolverPath: string, request: any, info: any) {
389+
if (typeof request === 'undefined') {
390390
throw new IllegalResolverInvocationError(resolverPath, this.name, `Network Request hasn't been passed!`);
391391
}
392392
if (typeof info === 'undefined') {
@@ -399,29 +399,30 @@ export class GraphQLModule<Config = any, Request = any, Context = any> {
399399
// tslint:disable-next-line:forin
400400
for (const type in resolvers) {
401401
const typeResolvers = resolvers[type];
402-
if (typeResolvers instanceof GraphQLScalarType) {
403-
continue;
404-
}
405-
// tslint:disable-next-line:forin
406-
for (const prop in resolvers[type]) {
407-
const resolver = typeResolvers[prop];
408-
if (typeof resolver === 'function') {
409-
if (prop !== '__resolveType') {
410-
typeResolvers[prop] = async (root: any, args: any, appContext: any, info: any) => {
411-
this.checkIfResolverCalledSafely(`${type}.${prop}`, appContext, info);
412-
const { request } = appContext;
413-
const moduleContext = await this.context(request);
414-
info.schema = this._cache.schema;
415-
return resolver.call(typeResolvers, root, args, moduleContext, info);
416-
};
417-
} else {
418-
typeResolvers[prop] = async (root: any, appContext: any, info: any) => {
419-
this.checkIfResolverCalledSafely(`${type}.${prop}`, appContext, info);
420-
const { request } = appContext;
421-
const moduleContext = await this.context(request);
422-
info.schema = this._cache.schema;
423-
return resolver.call(typeResolvers, root, moduleContext as any, info);
424-
};
402+
if (!(typeResolvers instanceof GraphQLScalarType)) {
403+
// tslint:disable-next-line:forin
404+
for (const prop in resolvers[type]) {
405+
const resolver = typeResolvers[prop];
406+
if (typeof resolver === 'function') {
407+
if (prop !== '__resolveType') {
408+
typeResolvers[prop] = async (root: any, args: any, appContext: any, info: any) => {
409+
const request = info.request || appContext.request;
410+
info.request = request;
411+
this.checkIfResolverCalledSafely(`${type}.${prop}`, request, info);
412+
const moduleContext = await this.context(request, true);
413+
info.schema = this.schema;
414+
return resolver.call(typeResolvers, root, args, moduleContext, info);
415+
};
416+
} else {
417+
typeResolvers[prop] = async (root: any, appContext: any, info: any) => {
418+
const request = info.request || appContext.request;
419+
info.request = request;
420+
this.checkIfResolverCalledSafely(`${type}.${prop}`, request, info);
421+
const moduleContext = await this.context(request, true);
422+
info.schema = this.schema;
423+
return resolver.call(typeResolvers, root, moduleContext, info);
424+
};
425+
}
425426
}
426427
}
427428
}
@@ -436,10 +437,11 @@ export class GraphQLModule<Config = any, Request = any, Context = any> {
436437
const compositionArr = asArray(resolversComposition[path]);
437438
resolversComposition[path] = [
438439
(next: any) => async (root: any, args: any, appContext: any, info: any) => {
439-
this.checkIfResolverCalledSafely(path, appContext, info);
440-
const { request } = appContext;
441-
const moduleContext = await this.context(request);
442-
info.schema = this._cache.schema;
440+
const request = info.request || appContext.request;
441+
info.request = request;
442+
this.checkIfResolverCalledSafely(path, request, info);
443+
const moduleContext = await this.context(request, true);
444+
info.schema = this.schema;
443445
return next(root, args, moduleContext, info);
444446
},
445447
...compositionArr,
@@ -479,7 +481,7 @@ export class GraphQLModule<Config = any, Request = any, Context = any> {
479481
const importsTypeDefs = new Set<DocumentNode>();
480482
const importsResolvers = new Set<IResolvers<any, any>>();
481483
const importsInjectors = new Set<Injector>();
482-
const importsContextBuilders = new Set<(req: Request) => Promise<Context>>();
484+
const importsContextBuilders = new Set<(req: Request, excludeRequest?: boolean) => Promise<Context>>();
483485
const importsSchemaDirectives = new Set<ISchemaDirectives>();
484486
const importsExtraSchemas = new Set<GraphQLSchema>();
485487
const importsDirectiveResolvers = new Set<IDirectiveResolvers>();
@@ -610,40 +612,45 @@ export class GraphQLModule<Config = any, Request = any, Context = any> {
610612
}
611613
}
612614

613-
this._cache.contextBuilder = async request => {
614-
request['moduleNameContextMap'] = request['moduleNameContextMap'] || new Map();
615-
const moduleNameContextMap: Map<string, any> = request['moduleNameContextMap'];
615+
this._cache.contextBuilder = async (request, excludeRequest = false) => {
616+
const moduleNameContextMap = this.getModuleNameContextMap(request);
616617
if (!(moduleNameContextMap.has(this.name))) {
617-
const importsContextArr$ = [...importsContextBuilders].map(contextBuilder => contextBuilder(request));
618+
const importsContextArr$ = [...importsContextBuilders].map(contextBuilder => contextBuilder(request, true));
618619
const importsContextArr = await Promise.all(importsContextArr$);
619-
const importsContext = importsContextArr.reduce((acc, curr) => ({ ...acc, ...(curr as any) }), {});
620+
const importsContext = importsContextArr.reduce((acc, curr) => ({ ...acc, ...curr}), {} as any);
620621
const applicationInjector = this.injector;
621622
const sessionInjector = applicationInjector.getSessionInjector(request);
622-
const moduleSessionInfo = sessionInjector.has(ModuleSessionInfo) ? sessionInjector.get(ModuleSessionInfo) : new ModuleSessionInfo<Config, any, Context>(this, request, importsContext);
623+
const moduleSessionInfo = sessionInjector.has(ModuleSessionInfo) ? sessionInjector.get(ModuleSessionInfo) : new ModuleSessionInfo<Config, any, Context>(this, request);
623624
let moduleContext = {};
624625
const moduleContextDeclaration = this._options.context;
625626
if (moduleContextDeclaration) {
626-
if (typeof moduleContextDeclaration === 'function') {
627-
moduleContext = await (moduleContextDeclaration as any)(request, importsContext, moduleSessionInfo);
627+
if (moduleContextDeclaration instanceof Function) {
628+
moduleContext = await moduleContextDeclaration(request, { ...importsContext, injector }, moduleSessionInfo);
628629
} else {
629-
moduleContext = moduleContextDeclaration;
630+
moduleContext = await moduleContextDeclaration;
630631
}
631632
}
632-
const builtResult = {
633+
moduleNameContextMap.set(this.name, {
633634
...importsContext,
634-
...moduleContext as any,
635+
...moduleContext,
635636
injector: sessionInjector,
636-
request,
637-
};
637+
});
638638
const requestHooks$ = [
639639
...applicationInjector.scopeSet,
640640
...sessionInjector.scopeSet,
641641
].map(serviceIdentifier => moduleSessionInfo.callRequestHook(serviceIdentifier),
642642
);
643643
await Promise.all(requestHooks$);
644-
moduleNameContextMap.set(this.name, builtResult);
645644
}
646-
return moduleNameContextMap.get(this.name);
645+
const moduleContext = moduleNameContextMap.get(this.name);
646+
if (excludeRequest) {
647+
return moduleContext;
648+
} else {
649+
return {
650+
...moduleContext,
651+
request,
652+
};
653+
}
647654
};
648655

649656
if ('middleware' in this._options) {
@@ -652,6 +659,15 @@ export class GraphQLModule<Config = any, Request = any, Context = any> {
652659
}
653660
}
654661

662+
private static requestModuleNameContextMap = new WeakMap<any, Map<string, any>>();
663+
664+
getModuleNameContextMap(request: Request) {
665+
if (!GraphQLModule.requestModuleNameContextMap.has(request)) {
666+
GraphQLModule.requestModuleNameContextMap.set(request, new Map());
667+
}
668+
return GraphQLModule.requestModuleNameContextMap.get(request);
669+
}
670+
655671
/**
656672
* Build a GraphQL `context` object based on a network request.
657673
* It iterates over all modules by their dependency-based order, and executes
@@ -665,7 +681,7 @@ export class GraphQLModule<Config = any, Request = any, Context = any> {
665681
*
666682
* @param request - the network request from `connect`, `express`, etc...
667683
*/
668-
get context(): (request: Request) => Promise<ModuleContext<Context>> {
684+
get context(): (request: Request, excludeRequest?: boolean) => Promise<ModuleContext<Context>> {
669685
if (!this._cache.contextBuilder) {
670686
this.buildSchemaAndInjector();
671687
}
@@ -848,8 +864,8 @@ export class GraphQLModule<Config = any, Request = any, Context = any> {
848864
const accContext = await accContextBuilder(request, currentContext, injector);
849865
const moduleContext = typeof currentContextBuilder === 'function' ? await currentContextBuilder(request, currentContext, injector) : (currentContextBuilder || {});
850866
return {
851-
...accContext as any,
852-
...moduleContext as any,
867+
...accContext,
868+
...moduleContext,
853869
};
854870
};
855871
},

packages/core/src/module-session-info.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ export class ModuleSessionInfo<Config = any, Request = any, Context = any> {
66
constructor(
77
private _module: GraphQLModule<Config, Request, Context>,
88
private _request: Request,
9-
private _context: Context,
109
) {
1110
this.injector.provide({
1211
provide: ModuleSessionInfo,
@@ -23,10 +22,10 @@ export class ModuleSessionInfo<Config = any, Request = any, Context = any> {
2322
return this.module.cache;
2423
}
2524
get context() {
26-
return this._context;
25+
return this.module.getModuleNameContextMap(this.request).get(this.module.name);
2726
}
2827
get injector() {
29-
return this._module.injector.getSessionInjector(this.request);
28+
return this.module.injector.getSessionInjector(this.request);
3029
}
3130
public async callRequestHook<T extends OnRequest<Config, Request, Context>>(
3231
serviceIdentifier: ServiceIdentifier<T>,

packages/core/tests/graphql-module.spec.ts

Lines changed: 67 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ describe('GraphQLModule', () => {
6868
});
6969

7070
// C (with context building fn)
71-
const cContextBuilder = jest.fn(() => ({ user: { id: 1 } }));
71+
const cContextBuilder = () => ({ user: { id: 1 } });
7272
const typesC = [`type C { f: String}`, `type Query { c: C }`];
7373
const moduleC = new GraphQLModule({
7474
name: 'C',
@@ -142,6 +142,7 @@ describe('GraphQLModule', () => {
142142
document: testQuery,
143143
contextValue: context,
144144
});
145+
expect(result.errors).toBeFalsy();
145146
expect(result.data.b.f).toBe('1');
146147
});
147148

@@ -159,19 +160,6 @@ describe('GraphQLModule', () => {
159160
expect(injector.get(token)).toBe(provider);
160161
});
161162

162-
it('should inject implementation object into the context using the module name', async () => {
163-
const schema = app.schema;
164-
const context = await app.context({ req: {} });
165-
166-
const result = await execute({
167-
schema,
168-
document: testQuery,
169-
contextValue: context,
170-
});
171-
172-
expect(result.data.b.f).toBe('1');
173-
});
174-
175163
it('should put the correct providers to the injector', async () => {
176164

177165
expect(app.injector.get(ProviderA) instanceof ProviderA).toBe(true);
@@ -1047,4 +1035,69 @@ describe('GraphQLModule', () => {
10471035
expect(result.errors).toBeFalsy();
10481036
expect(result.data['today']).toBe(today.getTime());
10491037
});
1038+
describe('Apollo DataSources Intergration', () => {
1039+
it('Should pass props correctly to initialize method', async () => {
1040+
@Injectable({
1041+
scope: ProviderScope.Session,
1042+
})
1043+
class TestDataSourceAPI {
1044+
public initialize(initParams: ModuleSessionInfo) {
1045+
expect(initParams.context['myField']).toBe('some-value');
1046+
expect(initParams.module).toBe(moduleA);
1047+
expect(initParams.cache).toBe(moduleA.cache);
1048+
}
1049+
}
1050+
const testQuery = gql`
1051+
query {
1052+
a {
1053+
f
1054+
}
1055+
}
1056+
`;
1057+
const typesA = [`type A { f: String}`, `type Query { a: A }`];
1058+
const moduleA = new GraphQLModule({
1059+
name: 'A',
1060+
typeDefs: typesA,
1061+
resolvers: {
1062+
Query: { a: () => ({ f: 's' }) },
1063+
},
1064+
context: () => {
1065+
return {
1066+
myField: 'some-value',
1067+
};
1068+
},
1069+
providers: [TestDataSourceAPI],
1070+
});
1071+
const app = new GraphQLModule({ imports: [moduleA] });
1072+
await app.context({ req: {} });
1073+
});
1074+
});
1075+
it('should exclude network request', async () => {
1076+
const { schema, context } = new GraphQLModule({
1077+
context: {
1078+
request: { foo: 'BAR' },
1079+
// this request is not request that is internally passed by GraphQLModules
1080+
// this request must be passed instead of Network Request
1081+
},
1082+
typeDefs: gql`
1083+
type Query {
1084+
foo: String
1085+
}
1086+
`,
1087+
resolvers: {
1088+
Query: {
1089+
foo: (_, __, context) => {
1090+
return context.request.foo;
1091+
},
1092+
},
1093+
},
1094+
});
1095+
const result = await execute({
1096+
schema,
1097+
document: gql`query { foo }`,
1098+
contextValue: await context({ req: {} }),
1099+
});
1100+
expect(result.errors).toBeFalsy();
1101+
expect(result.data['foo']).toBe('BAR');
1102+
});
10501103
});

packages/di/src/injector.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ export class Injector {
145145
throw new ServiceIdentifierNotFoundError(serviceIdentifier, this._name);
146146
}
147147
}
148-
static sessionNameSessionInjectorMapMap = new WeakMap<any, Map<string, Injector>>();
148+
private static sessionNameSessionInjectorMapMap = new WeakMap<any, Map<string, Injector>>();
149149
public getSessionInjector<Session>(session: Session): Injector {
150150
if (!Injector.sessionNameSessionInjectorMapMap.has(session)) {
151151
Injector.sessionNameSessionInjectorMapMap.set(session, new Map());

0 commit comments

Comments
 (0)