Skip to content

Commit

Permalink
Don't break context with deepmerge
Browse files Browse the repository at this point in the history
Signed-off-by: Arda TANRIKULU <[email protected]>
  • Loading branch information
ardatan committed Nov 16, 2018
1 parent 0c4e7e0 commit e1a6fbd
Show file tree
Hide file tree
Showing 2 changed files with 155 additions and 181 deletions.
21 changes: 0 additions & 21 deletions packages/core/src/di/inject-many.ts

This file was deleted.

315 changes: 155 additions & 160 deletions packages/core/src/graphql-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,9 +122,9 @@ export class GraphQLModule<Config = any, Request = any, Context = any> {
constructor(
private _options: GraphQLModuleOptions<Config, Request, Context> = {},
private _moduleConfig: Config = {} as Config,
) {
_options.name = _options.name || Math.floor(Math.random() * Math.floor(Number.MAX_SAFE_INTEGER)).toString();
}
) {
_options.name = _options.name || Math.floor(Math.random() * Math.floor(Number.MAX_SAFE_INTEGER)).toString();
}

/**
* Creates another instance of the module using a configuration
Expand Down Expand Up @@ -335,7 +335,7 @@ export class GraphQLModule<Config = any, Request = any, Context = any> {
importsTypeDefs.add(typeDef);
}
} else {
importsTypeDefs.add(typeDefs);
importsTypeDefs.add(typeDefs);
}
}
importsContextBuilders.add(contextBuilder);
Expand Down Expand Up @@ -423,7 +423,7 @@ export class GraphQLModule<Config = any, Request = any, Context = any> {
this._cache.contextBuilder = async networkRequest => {
const importsContextArr$ = [...importsContextBuilders].map(contextBuilder => contextBuilder(networkRequest));
const importsContextArr = await Promise.all(importsContextArr$);
const importsContext = deepmerge.all(importsContextArr as any[]) as any;
const importsContext = importsContextArr.reduce((acc, curr) => ({ ...acc, ...curr as any }), {});
const moduleContext = await (this._options.contextBuilder ? this._options.contextBuilder(networkRequest, importsContext, this._cache.injector) : async () => ({}));
const builtResult = {
...importsContext,
Expand Down Expand Up @@ -462,184 +462,179 @@ export class GraphQLModule<Config = any, Request = any, Context = any> {
*
* @param request - the network request from `connect`, `express`, etc...
*/
context = async (request: Request): Promise<ModuleContext<Context>> => {
const moduleContext = await this.contextBuilder(request);
return {
...moduleContext as any,
};
}
context = async (request: Request) => this.contextBuilder(request) as Promise<ModuleContext<Context>>;

get modulesMap() {
if (!this._cache.modulesMap) {
let modulesMap = this.createInitialModulesMap();
modulesMap = this.checkAndFixModulesMap(modulesMap);
this._cache.modulesMap = modulesMap;
}
return this._cache.modulesMap;
get modulesMap() {
if (!this._cache.modulesMap) {
let modulesMap = this.createInitialModulesMap();
modulesMap = this.checkAndFixModulesMap(modulesMap);
this._cache.modulesMap = modulesMap;
}
return this._cache.modulesMap;
}

private createInitialModulesMap() {
const modulesMap = new Map<string, GraphQLModule<any, Request, any>>();
const visitModule = (module: GraphQLModule<any, Request, any>) => {
if (!modulesMap.has(module.name)) {
modulesMap.set(module.name, module);
for (const subModule of module.selfImports) {
if (!subModule) {
throw new DependencyModuleUndefinedError(module.name);
}
if (typeof subModule !== 'string') {
visitModule(subModule);
}
private createInitialModulesMap() {
const modulesMap = new Map<string, GraphQLModule<any, Request, any>>();
const visitModule = (module: GraphQLModule<any, Request, any>) => {
if (!modulesMap.has(module.name)) {
modulesMap.set(module.name, module);
for (const subModule of module.selfImports) {
if (!subModule) {
throw new DependencyModuleUndefinedError(module.name);
}
if (typeof subModule !== 'string') {
visitModule(subModule);
}
}
};
visitModule(this);
return modulesMap;
}
}
};
visitModule(this);
return modulesMap;
}

private checkAndFixModulesMap(modulesMap: ModulesMap<Request>): Map<string, GraphQLModule<any, Request, any>> {
const graph = new DepGraph<GraphQLModule<any, Request, any>>();
private checkAndFixModulesMap(modulesMap: ModulesMap<Request>): Map<string, GraphQLModule<any, Request, any>> {
const graph = new DepGraph<GraphQLModule<any, Request, any>>();

modulesMap.forEach(module => {
const moduleName = module.name;
if (!graph.hasNode(moduleName)) {
graph.addNode(moduleName);
}
});
modulesMap.forEach(module => {
const moduleName = module.name;
if (!graph.hasNode(moduleName)) {
graph.addNode(moduleName);
}
});

const visitedModulesToAddDependency = new Set<string>();
const visitedModulesToAddDependency = new Set<string>();

const visitModuleToAddDependency = (module: GraphQLModule<any, Request, any>) => {
for (let subModule of module.selfImports) {
const subModuleOrigName = typeof subModule === 'string' ? subModule : subModule.name;
subModule = modulesMap.get(subModuleOrigName);
if (!subModule) {
throw new DependencyModuleNotFoundError(subModuleOrigName, module.name);
}
try {
graph.addDependency(
module.name,
subModule.name,
);
} catch (e) {
throw new DependencyModuleNotFoundError(subModuleOrigName, module.name);
}
// prevent infinite loop in case of circular dependency
if (!visitedModulesToAddDependency.has(subModule.name)) {
visitedModulesToAddDependency.add(subModule.name);
visitModuleToAddDependency(subModule);
}
const visitModuleToAddDependency = (module: GraphQLModule<any, Request, any>) => {
for (let subModule of module.selfImports) {
const subModuleOrigName = typeof subModule === 'string' ? subModule : subModule.name;
subModule = modulesMap.get(subModuleOrigName);
if (!subModule) {
throw new DependencyModuleNotFoundError(subModuleOrigName, module.name);
}
};
try {
graph.addDependency(
module.name,
subModule.name,
);
} catch (e) {
throw new DependencyModuleNotFoundError(subModuleOrigName, module.name);
}
// prevent infinite loop in case of circular dependency
if (!visitedModulesToAddDependency.has(subModule.name)) {
visitedModulesToAddDependency.add(subModule.name);
visitModuleToAddDependency(subModule);
}
}
};

visitModuleToAddDependency(modulesMap.get(this.name));

try {
graph.overallOrder();
return modulesMap;
} catch (e) {
const { message } = e as Error;
const currentPathStr = message.replace('Dependency Cycle Found: ', '');
const currentPath = currentPathStr.split(' -> ');
const moduleIndexMap = new Map<string, number>();
let start = 0;
let end = currentPath.length;
currentPath.forEach((moduleName, index) => {
if (moduleIndexMap.has(moduleName)) {
start = moduleIndexMap.get(moduleName);
end = index;
} else {
moduleIndexMap.set(moduleName, index);
}
});
const realPath = currentPath.slice(start, end);
const circularModules = Array.from(new Set(realPath)).map(moduleName => {
// if it is merged module, get one module, it will be enough to get merged one.
return modulesMap.get(moduleName);
});
const mergedModule = GraphQLModule.mergeModules<any, Request, any>(circularModules, modulesMap);
for (const moduleName of realPath) {
modulesMap.set(moduleName, mergedModule);
for (const subModuleName of moduleName.split('+')) {
if (modulesMap.has(subModuleName)) {
modulesMap.set(subModuleName, mergedModule);
}
visitModuleToAddDependency(modulesMap.get(this.name));

try {
graph.overallOrder();
return modulesMap;
} catch (e) {
const { message } = e as Error;
const currentPathStr = message.replace('Dependency Cycle Found: ', '');
const currentPath = currentPathStr.split(' -> ');
const moduleIndexMap = new Map<string, number>();
let start = 0;
let end = currentPath.length;
currentPath.forEach((moduleName, index) => {
if (moduleIndexMap.has(moduleName)) {
start = moduleIndexMap.get(moduleName);
end = index;
} else {
moduleIndexMap.set(moduleName, index);
}
});
const realPath = currentPath.slice(start, end);
const circularModules = Array.from(new Set(realPath)).map(moduleName => {
// if it is merged module, get one module, it will be enough to get merged one.
return modulesMap.get(moduleName);
});
const mergedModule = GraphQLModule.mergeModules<any, Request, any>(circularModules, modulesMap);
for (const moduleName of realPath) {
modulesMap.set(moduleName, mergedModule);
for (const subModuleName of moduleName.split('+')) {
if (modulesMap.has(subModuleName)) {
modulesMap.set(subModuleName, mergedModule);
}
}
modulesMap.set(mergedModule.name, mergedModule);
(mergedModule._options.imports as Array<ModuleDependency<any, Request, any>>)
= (mergedModule._options.imports as Array<ModuleDependency<any, Request, any>>).filter(
}
modulesMap.set(mergedModule.name, mergedModule);
(mergedModule._options.imports as Array<ModuleDependency<any, Request, any>>)
= (mergedModule._options.imports as Array<ModuleDependency<any, Request, any>>).filter(
module => {
const moduleName = typeof module === 'string' ? module : module.name;
module = modulesMap.get(moduleName);
return (module.name !== mergedModule.name);
},
);
return this.checkAndFixModulesMap(modulesMap);
}
return this.checkAndFixModulesMap(modulesMap);
}
}

static mergeModules<Config = any, Request = any, Context = any>(modules: Array<GraphQLModule<any, Request, any>>, modulesMap?: ModulesMap<Request>): GraphQLModule<Config, Request, Context> {
const nameSet = new Set();
const typeDefsSet = new Set();
const resolversSet = new Set<IResolvers>();
const contextBuilderSet = new Set<BuildContextFn<Request, Context>>();
const importsSet = new Set<ModuleDependency<any, Request, any>>();
const providersSet = new Set<Provider<any>>();
const resolversCompositionSet = new Set<IResolversComposerMapping>();
const schemaDirectivesSet = new Set<ISchemaDirectives>();
for (const module of modules) {
const subMergedModuleNames = module.name.split('+');
for (const subMergedModuleName of subMergedModuleNames) {
nameSet.add(subMergedModuleName);
}
if (Array.isArray(module.selfTypeDefs)) {
for (const typeDef of module.selfTypeDefs) {
typeDefsSet.add(typeDef);
}
} else {
typeDefsSet.add(module.selfTypeDefs);
}
resolversSet.add(module.selfResolvers);
contextBuilderSet.add(module._options.contextBuilder);
for (let importModule of module.selfImports) {
if (modulesMap) {
importModule = modulesMap.get(typeof importModule === 'string' ? importModule : importModule.name);
}
importsSet.add(importModule);
static mergeModules<Config = any, Request = any, Context = any>(modules: Array<GraphQLModule<any, Request, any>>, modulesMap?: ModulesMap<Request>): GraphQLModule<Config, Request, Context> {
const nameSet = new Set();
const typeDefsSet = new Set();
const resolversSet = new Set<IResolvers>();
const contextBuilderSet = new Set<BuildContextFn<Request, Context>>();
const importsSet = new Set<ModuleDependency<any, Request, any>>();
const providersSet = new Set<Provider<any>>();
const resolversCompositionSet = new Set<IResolversComposerMapping>();
const schemaDirectivesSet = new Set<ISchemaDirectives>();
for (const module of modules) {
const subMergedModuleNames = module.name.split('+');
for (const subMergedModuleName of subMergedModuleNames) {
nameSet.add(subMergedModuleName);
}
if (Array.isArray(module.selfTypeDefs)) {
for (const typeDef of module.selfTypeDefs) {
typeDefsSet.add(typeDef);
}
for (const provider of module.selfProviders) {
providersSet.add(provider);
} else {
typeDefsSet.add(module.selfTypeDefs);
}
resolversSet.add(module.selfResolvers);
contextBuilderSet.add(module._options.contextBuilder);
for (let importModule of module.selfImports) {
if (modulesMap) {
importModule = modulesMap.get(typeof importModule === 'string' ? importModule : importModule.name);
}
resolversCompositionSet.add(module.selfResolversComposition);
schemaDirectivesSet.add(module.selfSchemaDirectives);
importsSet.add(importModule);
}

const name = [...nameSet].join('+');
const typeDefs = [...typeDefsSet];
const resolvers = mergeResolvers([...resolversSet]);
const contextBuilder = [...contextBuilderSet].reduce(
(accContextBuilder, currentContextBuilder) => {
return async (networkRequest, currentContext, injector) => {
const accContext = await accContextBuilder(networkRequest, currentContext, injector);
const moduleContext = currentContextBuilder ? await currentContextBuilder(networkRequest, currentContext, injector) : {};
return Object.assign({}, accContext, moduleContext);
};
},
);
const imports = [...importsSet];
const providers = [...providersSet];
const resolversComposition = deepmerge.all([...resolversCompositionSet]);
const schemaDirectives = deepmerge.all([...schemaDirectivesSet]) as ISchemaDirectives;
return new GraphQLModule<Config, Request, Context>({
name,
typeDefs,
resolvers,
contextBuilder,
imports,
providers,
resolversComposition,
schemaDirectives,
});
for (const provider of module.selfProviders) {
providersSet.add(provider);
}
resolversCompositionSet.add(module.selfResolversComposition);
schemaDirectivesSet.add(module.selfSchemaDirectives);
}

const name = [...nameSet].join('+');
const typeDefs = [...typeDefsSet];
const resolvers = mergeResolvers([...resolversSet]);
const contextBuilder = [...contextBuilderSet].reduce(
(accContextBuilder, currentContextBuilder) => {
return async (networkRequest, currentContext, injector) => {
const accContext = await accContextBuilder(networkRequest, currentContext, injector);
const moduleContext = currentContextBuilder ? await currentContextBuilder(networkRequest, currentContext, injector) : {};
return Object.assign({}, accContext, moduleContext);
};
},
);
const imports = [...importsSet];
const providers = [...providersSet];
const resolversComposition = deepmerge.all([...resolversCompositionSet]);
const schemaDirectives = deepmerge.all([...schemaDirectivesSet]) as ISchemaDirectives;
return new GraphQLModule<Config, Request, Context>({
name,
typeDefs,
resolvers,
contextBuilder,
imports,
providers,
resolversComposition,
schemaDirectives,
});
}
}

0 comments on commit e1a6fbd

Please sign in to comment.