Skip to content

Commit

Permalink
Refactor and remove config from factory functions
Browse files Browse the repository at this point in the history
  • Loading branch information
ardatan committed Nov 12, 2018
1 parent 03b7fd0 commit ae947bf
Show file tree
Hide file tree
Showing 13 changed files with 127 additions and 101 deletions.
1 change: 1 addition & 0 deletions packages/core/src/di/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ export { Inject } from './inject';
export { Injectable } from './injectable';
export { Injector } from './injector';
export * from './types';
export * from './utils';
21 changes: 21 additions & 0 deletions packages/core/src/di/inject-many.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { ServiceIdentifier } from './types';

import { DESIGN_PARAM_TYPES } from './utils';

declare var Reflect: any;

export function InjectMany<Dependency>(serviceIdentifiers: Array<ServiceIdentifier<Dependency>>) {
return (target: any, _targetKey: any, index: number) => {
let dependencies = Reflect.getMetadata(DESIGN_PARAM_TYPES, target) || [];
if (!dependencies) {
throw new Error('You must decorate the provider class with @Injectable()');
}
if (typeof index === 'number') {
dependencies[index] = serviceIdentifiers;
} else {
dependencies = serviceIdentifiers;
}
Reflect.defineMetadata(DESIGN_PARAM_TYPES, dependencies, target);
return target;
};
}
2 changes: 1 addition & 1 deletion packages/core/src/di/inject.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ServiceIdentifier } from './types';

import { DESIGN_PARAM_TYPES } from '../utils';
import { DESIGN_PARAM_TYPES } from './utils';

declare var Reflect: any;

Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/di/injectable.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { DESIGN_PARAM_TYPES } from '../utils';
import { DESIGN_PARAM_TYPES } from './utils';

declare var Reflect: any;

Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/di/injector.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Provider, ServiceIdentifier, Factory, OnRequest } from './types';
import { isType, DESIGN_PARAM_TYPES, isValueProvider, isClassProvider, isFactoryProvider, isTypeProvider } from '../utils';
import { isType, DESIGN_PARAM_TYPES, isValueProvider, isClassProvider, isFactoryProvider, isTypeProvider } from './utils';
import { GraphQLModule } from '../graphql-module';
import { ServiceIdentifierNotFoundError, DependencyProviderNotFoundError, ProviderNotValidError } from '../errors';

Expand Down
31 changes: 31 additions & 0 deletions packages/core/src/di/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { ServiceIdentifier, Provider, Type, ValueProvider, ClassProvider, FactoryProvider, TypeProvider } from './types';

export const DESIGN_PARAM_TYPES = 'design:paramtypes';

export function getServiceIdentifierName<T>(serviceIdentifier: ServiceIdentifier<T>) {
if (typeof serviceIdentifier === 'function' && isType<T>(serviceIdentifier)) {
return serviceIdentifier.name;
} else {
return serviceIdentifier.toString();
}
}

export function isType<T>(v: any): v is Type<T> {
return typeof v === 'function' && 'prototype' in v;
}

export function isTypeProvider<T>(v: Provider<T>): v is TypeProvider<T> {
return isType<T>(v);
}

export function isValueProvider<T>(v: Provider<T>): v is ValueProvider<T> {
return 'useValue' in v;
}

export function isClassProvider<T>(v: Provider<T>): v is ClassProvider<T> {
return 'useClass' in v && isType(v.useClass);
}

export function isFactoryProvider<T>(v: Provider<T>): v is FactoryProvider<T> {
return 'useFactory' in v && typeof v.useFactory === 'function';
}
4 changes: 1 addition & 3 deletions packages/core/src/errors/dependency-provider-not-found.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import { ServiceIdentifier } from '../di';

import { getServiceIdentifierName } from '../utils';
import { ServiceIdentifier, getServiceIdentifierName } from '../di';

export class DependencyProviderNotFoundError<Dependency, Dependent> extends Error {
constructor(private _dependency: ServiceIdentifier<Dependency>, private _dependent: ServiceIdentifier<Dependent>, private _moduleName: string) {
Expand Down
3 changes: 1 addition & 2 deletions packages/core/src/errors/provider-already-defined.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { ServiceIdentifier } from '../di/types';
import { getServiceIdentifierName } from '../utils';
import { ServiceIdentifier, getServiceIdentifierName } from '../di';

export class ProviderAlreadyDefinedError<T> extends Error {
constructor(private _moduleName: string, private _serviceIdentifier: ServiceIdentifier<T>) {
Expand Down
3 changes: 1 addition & 2 deletions packages/core/src/errors/provider-not-valid.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { ServiceIdentifier } from '../di/types';
import { getServiceIdentifierName } from '../utils';
import { ServiceIdentifier, getServiceIdentifierName } from '../di';

export class ProviderNotValidError<T> extends Error {
constructor(private _moduleName: string, private _serviceIdentifier: ServiceIdentifier<T>) {
Expand Down
3 changes: 1 addition & 2 deletions packages/core/src/errors/service-identifier-not-found.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { getServiceIdentifierName } from '../utils';
import { ServiceIdentifier } from '../di';
import { ServiceIdentifier, getServiceIdentifierName } from '../di';

export class ServiceIdentifierNotFoundError<T> extends Error {
constructor(protected _serviceIdentifier: ServiceIdentifier<T>, private _dependent: string) {
Expand Down
81 changes: 24 additions & 57 deletions packages/core/src/graphql-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ import { IResolvers, makeExecutableSchema, SchemaDirectiveVisitor } from 'graphq
import { mergeGraphQLSchemas, mergeResolvers } from '@graphql-modules/epoxy';
import { Provider, ModuleContext, Injector } from './di';
import { DocumentNode, print, GraphQLSchema } from 'graphql';
import { IResolversComposerMapping, composeResolvers, asArray } from './resolvers-composition';
import { IResolversComposerMapping, composeResolvers } from './resolvers-composition';
import { DepGraph } from 'dependency-graph';
import { DependencyModuleNotFoundError, SchemaNotValidError, DependencyModuleUndefinedError, TypeDefNotFoundError } from './errors';
import deepmerge = require('deepmerge');
import { addInjectorToResolversContext, addInjectorToResolversCompositionContext } from './utils';

/**
* A context builder method signature for `contextBuilder`.
Expand All @@ -27,6 +28,8 @@ export type ModulesMap<Request> = Map<string, GraphQLModule<any, Request, any>>;
*/
export type ModuleDependency<Config, Request, Context> = GraphQLModule<Config, Request, Context> | string;

export type GraphQLModuleOption<Option, Config, Request, Context> = Option | ((module: GraphQLModule<Config, Request, Context>) => Option);

/**
* Defined the structure of GraphQL module options object.
*/
Expand All @@ -42,12 +45,12 @@ export interface GraphQLModuleOptions<Config, Request, Context> {
* You can also pass a function that will get the module's config as argument, and should return
* the type definitions.
*/
typeDefs?: string | string[] | DocumentNode | DocumentNode[] | ((config: Config, module: GraphQLModule<Config, Request, Context>) => string | string[] | DocumentNode | DocumentNode[]);
typeDefs?: GraphQLModuleOption<string | string[] | DocumentNode | DocumentNode[], Config, Request, Context>;
/**
* Resolvers object, or a function will get the module's config as argument, and should
* return the resolvers object.
*/
resolvers?: IResolvers | ((config: Config, module: GraphQLModule<Config, Request, Context>) => IResolvers);
resolvers?: GraphQLModuleOption<IResolvers, Config, Request, Context>;
/**
* Context builder method. Use this to add your own fields and data to the GraphQL `context`
* of each execution of GraphQL.
Expand All @@ -59,17 +62,17 @@ export interface GraphQLModuleOptions<Config, Request, Context> {
* Adding a dependency will effect the order of the type definition building, resolvers building and context
* building.
*/
imports?: ((config: Config, module: GraphQLModule<Config, Request, Context>) => Array<ModuleDependency<any, Request, Context>> | string[]) | string[] | Array<ModuleDependency<any, Request, Context>>;
imports?: GraphQLModuleOption<Array<ModuleDependency<any, Request, Context>>, Config, Request, Context>;
/**
* A list of `Providers` to load into the GraphQL module.
* It could be either a `class` or a value/class instance.
* All loaded class will be loaded as Singletons, and the instance will be
* shared across all GraphQL executions.
*/
providers?: Provider[] | ((config: Config, module: GraphQLModule<Config, Request, Context>) => Provider[]);
providers?: GraphQLModuleOption<Provider[], Config, Request, Context>;
/** Object map between `Type.field` to a function(s) that will wrap the resolver of the field */
resolversComposition?: IResolversComposerMapping | ((config: Config, module: GraphQLModule<Config, Request, Context>) => IResolversComposerMapping);
schemaDirectives?: ISchemaDirectives | ((config: Config, module: GraphQLModule<Config, Request, Context>) => ISchemaDirectives);
resolversComposition?: GraphQLModuleOption<IResolversComposerMapping, Config, Request, Context>;
schemaDirectives?: GraphQLModuleOption<ISchemaDirectives, Config, Request, Context>;
}

/**
Expand Down Expand Up @@ -117,10 +120,9 @@ export class GraphQLModule<Config = any, Request = any, Context = any> {
* @param options - module configuration
*/
constructor(
private _options: GraphQLModuleOptions<Config, Request, Context>,
private _options: GraphQLModuleOptions<Config, Request, Context> = {},
private _moduleConfig: Config = {} as Config,
) {
_options = _options || {};
_options.name = _options.name || Math.floor(Math.random() * Math.floor(Number.MAX_SAFE_INTEGER)).toString();
}

Expand Down Expand Up @@ -217,7 +219,7 @@ export class GraphQLModule<Config = any, Request = any, Context = any> {
const typeDefsDefinitions = this._options.typeDefs;
if (typeDefsDefinitions) {
if (typeof typeDefsDefinitions === 'function') {
typeDefs = typeDefsDefinitions(this._moduleConfig, this);
typeDefs = typeDefsDefinitions(this);
} else if (Array.isArray(typeDefsDefinitions)) {
typeDefs = mergeGraphQLSchemas(typeDefsDefinitions);
} else if (typeof typeDefsDefinitions === 'string') {
Expand All @@ -234,7 +236,7 @@ export class GraphQLModule<Config = any, Request = any, Context = any> {
const resolversDefinitions = this._options.resolvers;
if (resolversDefinitions) {
if (typeof resolversDefinitions === 'function') {
resolvers = resolversDefinitions(this._moduleConfig, this);
resolvers = resolversDefinitions(this);
} else {
resolvers = resolversDefinitions;
}
Expand All @@ -246,7 +248,7 @@ export class GraphQLModule<Config = any, Request = any, Context = any> {
let imports = new Array<ModuleDependency<any, Request, any>>();
if (this._options.imports) {
if (typeof this._options.imports === 'function') {
imports = this._options.imports(this._moduleConfig, this);
imports = this._options.imports(this);
} else {
imports = this._options.imports;
}
Expand All @@ -259,7 +261,7 @@ export class GraphQLModule<Config = any, Request = any, Context = any> {
const providersDefinitions = this._options.providers;
if (providersDefinitions) {
if (typeof providersDefinitions === 'function') {
providers = providersDefinitions(this._moduleConfig, this);
providers = providersDefinitions(this);
} else {
providers = providersDefinitions;
}
Expand All @@ -278,35 +280,20 @@ export class GraphQLModule<Config = any, Request = any, Context = any> {
const resolversCompositionDefinitions = this._options.resolversComposition;
if (resolversCompositionDefinitions) {
if (typeof resolversCompositionDefinitions === 'function') {
resolversComposition = (resolversCompositionDefinitions as any)(this._moduleConfig);
resolversComposition = (resolversCompositionDefinitions as any)(this);
} else {
resolversComposition = resolversCompositionDefinitions;
}
}
return resolversComposition;
}

private wrapResolversComposition(resolversComposition: IResolversComposerMapping) {
// tslint:disable-next-line:forin
for (const path in resolversComposition) {
const compositionArr = asArray(resolversComposition[path]);
resolversComposition[path] = [
(next: any) => (root: any, args: any, context: any, info: any) => next(root, args, {
...context,
injector: this._cache.injector,
}, info),
...compositionArr,
];
}
return resolversComposition;
}

get selfSchemaDirectives(): ISchemaDirectives {
let schemaDirectives: ISchemaDirectives = {};
const schemaDirectivesDefinitions = this._options.schemaDirectives;
if (schemaDirectivesDefinitions) {
if (typeof schemaDirectivesDefinitions === 'function') {
schemaDirectives = schemaDirectivesDefinitions(this._moduleConfig, this);
schemaDirectives = schemaDirectivesDefinitions(this);
} else {
schemaDirectives = schemaDirectivesDefinitions;
}
Expand All @@ -330,12 +317,14 @@ export class GraphQLModule<Config = any, Request = any, Context = any> {
module.buildSchemaAndInjector(modulesMap);
}

const typeDefs = module._cache.typeDefs;
const resolvers = module._cache.resolvers;
const injector = module._cache.injector;
const resolvers = module._cache.resolvers;
const typeDefs = module._cache.typeDefs;
const contextBuilder = module._cache.contextBuilder;
const schemaDirectives = module._cache.schemaDirectives;

importsInjectors.add(injector);
importsResolvers.add(resolvers);
if (typeDefs && typeDefs.length) {
if (Array.isArray(typeDefs)) {
for (const typeDef of typeDefs) {
Expand All @@ -345,9 +334,6 @@ export class GraphQLModule<Config = any, Request = any, Context = any> {
importsTypeDefs.add(typeDefs);
}
}

importsResolvers.add(resolvers);
importsInjectors.add(injector);
importsContextBuilders.add(contextBuilder);
importsSchemaDirectives.add(schemaDirectives);
}
Expand All @@ -366,28 +352,9 @@ export class GraphQLModule<Config = any, Request = any, Context = any> {

this._cache.injector = injector;

const resolvers = this.selfResolvers;
// tslint:disable-next-line:forin
for ( const type in resolvers ) {
const typeResolvers = resolvers[type];
// tslint:disable-next-line:forin
for (const prop in resolvers[type]) {
const resolver = typeResolvers[prop];
if (typeof resolver === 'function') {
if (prop !== '__resolveType') {
typeResolvers[prop] = (root: any, args: any, context: any, info: any) => {
return resolver.call(typeResolvers, root, args, { injector, ...context }, info);
};
} else {
typeResolvers[prop] = (root: any, context: any, info: any) => {
return resolver.call(typeResolvers, root, { injector, ...context }, info);
};
}
}
}
}
const resolvers = addInjectorToResolversContext(this.selfResolvers, injector);

const resolversComposition = this.wrapResolversComposition(this.selfResolversComposition);
const resolversComposition = addInjectorToResolversCompositionContext(this.selfResolversComposition, injector);

const resolversToBeComposed = new Set(importsResolvers);
resolversToBeComposed.add(resolvers);
Expand Down Expand Up @@ -586,7 +553,7 @@ export class GraphQLModule<Config = any, Request = any, Context = any> {
// if it is merged module, get one module, it will be enough to get merged one.
return modulesMap.get(moduleName);
});
const mergedModule = GraphQLModule.mergeModules(circularModules, modulesMap);
const mergedModule = GraphQLModule.mergeModules<any, Request, any>(circularModules, modulesMap);
for (const moduleName of realPath) {
modulesMap.set(moduleName, mergedModule);
for (const subModuleName of moduleName.split('+')) {
Expand Down
11 changes: 1 addition & 10 deletions packages/core/src/resolvers-composition.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { get, set } from 'lodash';
import { IResolvers } from 'graphql-tools';
import { chainFunctions, asArray } from './utils';

export interface IResolversComposerMapping {
[resolverPath: string]: any | any[];
Expand All @@ -25,16 +26,6 @@ function resolveRelevantMappings(resolvers: IResolvers, path: string, allMapping
return result;
}

export const asArray = <T>(fns: T | T[]) => (Array.isArray(fns) ? fns : [fns]);

export function chainFunctions(funcs: any[]) {
if (funcs.length === 1) {
return funcs[0];
}

return funcs.reduce((a, b) => (...args: any[]) => a(b(...args)));
}

/**
* Wraps the resolvers object with the resolvers composition objects.
* Implemented as a simple and basic middleware mechanism.
Expand Down
Loading

0 comments on commit ae947bf

Please sign in to comment.