UNPKG

graphql-modules

Version:

Create reusable, maintainable, testable and extendable GraphQL modules

1,434 lines (1,412 loc) 84.5 kB
import { makeExecutableSchema } from '@graphql-tools/schema'; import { createHook, executionAsyncId } from 'async_hooks'; import { GraphQLSchema, execute as execute$1, subscribe, visit, Kind, GraphQLScalarType, concatAST, defaultFieldResolver, parse } from 'graphql'; import { wrapSchema } from '@graphql-tools/wrap'; import { mergeDeepWith } from 'ramda'; const ERROR_ORIGINAL_ERROR = 'diOriginalError'; function getOriginalError(error) { return error[ERROR_ORIGINAL_ERROR]; } function wrappedError(message, originalError) { const msg = `${message} caused by: ${originalError instanceof Error ? originalError.message : originalError}`; const error = Error(msg); error[ERROR_ORIGINAL_ERROR] = originalError; return error; } function stringify(token) { if (typeof token === 'string') { return token; } if (token == null) { return '' + token; } if (token.name) { return `${token.name}`; } const res = token.toString(); const newLineIndex = res.indexOf('\n'); return newLineIndex === -1 ? res : res.substring(0, newLineIndex); } function invalidProviderError(provider) { return Error(`Invalid provider - only instances of Provider and Type are allowed, got: ${provider}`); } function noInjectableError(type) { return Error(`Missing @Injectable decorator for '${stringify(type)}'`); } function noAnnotationError(typeOrFunc, params) { const signature = []; for (let i = 0, len = params.length; i < len; i++) { const parameter = params[i]; if (!parameter.type) { signature.push('?'); } else { signature.push(stringify(parameter.type)); } } return Error("Cannot resolve all parameters for '" + stringify(typeOrFunc) + "'(" + signature.join(', ') + '). ' + "Make sure that all the parameters are decorated with Inject or have valid type annotations and that '" + stringify(typeOrFunc) + "' is decorated with Injectable."); } function cyclicDependencyError(injector, key) { return injectionError(injector, key, function () { return `Cannot instantiate cyclic dependency!${constructResolvingPath(this.keys)}`; }); } function noProviderError(injector, key) { return injectionError(injector, key, function () { const first = stringify(this.keys[0].token); return `No provider for ${first}!${constructResolvingPath(this.keys)}`; }); } function instantiationError(injector, originalException, key) { return injectionError(injector, key, function () { const first = stringify(this.keys[0].token); return `Error during instantiation of ${first}: ${getOriginalError(this).message}${constructResolvingPath(this.keys)}`; }, originalException); } function injectionError(injector, key, constructResolvingMessage, originalError) { const error = (originalError ? wrappedError('', originalError) : Error()); error.addKey = addKey; error.keys = [key]; error.constructResolvingMessage = function wrappedConstructResolvingMessage() { return (constructResolvingMessage.call(this) + ` - in ${injector.displayName}`); }; error.message = error.constructResolvingMessage(); error[ERROR_ORIGINAL_ERROR] = originalError; return error; } function constructResolvingPath(keys) { if (keys.length > 1) { const reversed = findFirstClosedCycle(keys.slice().reverse()); const tokenStrs = reversed.map((k) => stringify(k.token)); return ' (' + tokenStrs.join(' -> ') + ')'; } return ''; } function findFirstClosedCycle(keys) { const res = []; for (let i = 0; i < keys.length; ++i) { if (res.indexOf(keys[i]) > -1) { res.push(keys[i]); return res; } res.push(keys[i]); } return res; } function addKey(key) { this.keys.push(key); this.message = this.constructResolvingMessage(); } const INJECTABLE = Symbol('di:injectable'); function readInjectableMetadata(type, throwOnMissing) { const meta = type[INJECTABLE]; if (!meta && throwOnMissing) { throw noInjectableError(type); } return meta; } function ensureInjectableMetadata(type) { if (!readInjectableMetadata(type)) { const meta = { params: [], }; type[INJECTABLE] = meta; } } const Type = Function; /// @ts-ignore class InjectionToken { constructor(_desc) { this._desc = _desc; } toString() { return `InjectionToken ${this._desc}`; } } function isType(v) { return typeof v === 'function' && v !== Object; } var Scope; (function (Scope) { Scope[Scope["Singleton"] = 0] = "Singleton"; Scope[Scope["Operation"] = 1] = "Operation"; })(Scope || (Scope = {})); function onlySingletonProviders(providers = []) { return providers.filter((provider) => { if (isType(provider)) { const { options } = readInjectableMetadata(provider, true); return (options === null || options === void 0 ? void 0 : options.scope) !== Scope.Operation; } else { return provider.scope !== Scope.Operation; } }); } function onlyOperationProviders(providers = []) { return providers.filter((provider) => { if (isType(provider)) { const { options } = readInjectableMetadata(provider, true); return (options === null || options === void 0 ? void 0 : options.scope) === Scope.Operation; } else { return provider.scope === Scope.Operation; } }); } function isClassProvider(provider) { return typeof provider.useClass !== 'undefined'; } function isFactoryProvider(provider) { return typeof provider.useFactory !== 'undefined'; } const executionContextStore = new Map(); const executionContextDependencyStore = new Map(); const executionContextHook = createHook({ init(asyncId, _, triggerAsyncId) { var _a; // Store same context data for child async resources const ctx = executionContextStore.get(triggerAsyncId); if (ctx) { const dependencies = (_a = executionContextDependencyStore.get(triggerAsyncId)) !== null && _a !== void 0 ? _a : executionContextDependencyStore .set(triggerAsyncId, new Set()) .get(triggerAsyncId); dependencies.add(asyncId); executionContextStore.set(asyncId, ctx); } }, destroy(asyncId) { if (executionContextStore.has(asyncId)) { executionContextStore.delete(asyncId); } }, }); function destroyContextAndItsChildren(id) { if (executionContextStore.has(id)) { executionContextStore.delete(id); } const deps = executionContextDependencyStore.get(id); if (deps) { for (const dep of deps) { destroyContextAndItsChildren(dep); } executionContextDependencyStore.delete(id); } } const executionContext = { create(picker) { const id = executionAsyncId(); executionContextStore.set(id, picker); return function destroyContext() { destroyContextAndItsChildren(id); }; }, getModuleContext(moduleId) { const picker = executionContextStore.get(executionAsyncId()); return picker.getModuleContext(moduleId); }, getApplicationContext() { const picker = executionContextStore.get(executionAsyncId()); return picker.getApplicationContext(); }, }; function enableExecutionContext() { { executionContextHook.enable(); } } function ensureReflect() { if (!(Reflect && Reflect.getOwnMetadata)) { throw 'reflect-metadata shim is required when using class decorators'; } } function Injectable(options) { return (target) => { var _a; ensureReflect(); enableExecutionContext(); const params = (Reflect.getMetadata('design:paramtypes', target) || []).map((param) => (isType(param) ? param : null)); const existingMeta = readInjectableMetadata(target); const meta = { params: ((_a = existingMeta === null || existingMeta === void 0 ? void 0 : existingMeta.params) === null || _a === void 0 ? void 0 : _a.length) > 0 && params.length === 0 ? existingMeta === null || existingMeta === void 0 ? void 0 : existingMeta.params : params.map((param, i) => { var _a; const existingParam = (_a = existingMeta === null || existingMeta === void 0 ? void 0 : existingMeta.params) === null || _a === void 0 ? void 0 : _a[i]; return { type: (existingParam === null || existingParam === void 0 ? void 0 : existingParam.type) || param, optional: typeof (existingParam === null || existingParam === void 0 ? void 0 : existingParam.optional) === 'boolean' ? existingParam.optional : false, }; }), options: { ...((existingMeta === null || existingMeta === void 0 ? void 0 : existingMeta.options) || {}), ...(options || {}), }, }; target[INJECTABLE] = meta; return target; }; } function Optional() { return (target, _, index) => { ensureReflect(); ensureInjectableMetadata(target); const meta = readInjectableMetadata(target); meta.params[index] = { ...meta.params[index], optional: true, }; }; } function Inject(type) { return (target, _, index) => { ensureReflect(); ensureInjectableMetadata(target); const meta = readInjectableMetadata(target); meta.params[index] = { type, optional: false, }; }; } function ExecutionContext() { return (obj, propertyKey) => { ensureReflect(); const target = obj.constructor; ensureInjectableMetadata(target); const meta = readInjectableMetadata(target); if (!meta.options) { meta.options = {}; } if (!meta.options.executionContextIn) { meta.options.executionContextIn = []; } meta.options.executionContextIn.push(propertyKey); }; } const forwardRefSymbol = Symbol('__forward_ref__'); /** * Useful in "circular dependencies of modules" situation */ function forwardRef(forwardRefFn) { forwardRefFn[forwardRefSymbol] = forwardRef; forwardRefFn.toString = function () { return stringify(this()); }; return forwardRefFn; } function resolveForwardRef(type) { if (typeof type === 'function' && type.hasOwnProperty(forwardRefSymbol) && type[forwardRefSymbol] === forwardRef) { return type(); } else { return type; } } class Key { constructor(token, id) { this.token = token; this.id = id; if (!token) { throw new Error('Token must be defined!'); } } /** * Returns a stringified token. */ get displayName() { return stringify(this.token); } static get(token) { return _globalKeyRegistry.get(resolveForwardRef(token)); } } class GlobalKeyRegistry { constructor() { this._allKeys = new Map(); } get(token) { if (token instanceof Key) { return token; } if (this._allKeys.has(token)) { return this._allKeys.get(token); } const newKey = new Key(token, _globalKeyRegistry.numberOfKeys); this._allKeys.set(token, newKey); return newKey; } get numberOfKeys() { return this._allKeys.size; } } const _globalKeyRegistry = new GlobalKeyRegistry(); const _EMPTY_LIST = []; class ResolvedProvider { constructor(key, factory) { this.key = key; this.factory = factory; } } class ResolvedFactory { constructor( /** * Factory function which can return an instance of an object represented by a key. */ factory, /** * Arguments (dependencies) to the `factory` function. */ dependencies, /** * Methods invoked within ExecutionContext. */ executionContextIn, /** * Has onDestroy hook */ hasOnDestroyHook, /** * Is Global */ isGlobal) { this.factory = factory; this.dependencies = dependencies; this.executionContextIn = executionContextIn; this.hasOnDestroyHook = hasOnDestroyHook; this.isGlobal = isGlobal; } } class Dependency { constructor(key, optional) { this.key = key; this.optional = optional; } static fromKey(key) { return new Dependency(key, false); } } function resolveProviders(providers) { const normalized = normalizeProviders(providers, []); const resolved = normalized.map(resolveProvider); const resolvedProviderMap = mergeResolvedProviders(resolved, new Map()); return Array.from(resolvedProviderMap.values()); } function resolveProvider(provider) { return new ResolvedProvider(Key.get(provider.provide), resolveFactory(provider)); } function mergeResolvedProviders(providers, normalizedProvidersMap) { for (let i = 0; i < providers.length; i++) { const provider = providers[i]; normalizedProvidersMap.set(provider.key.id, provider); } return normalizedProvidersMap; } function normalizeProviders(providers, res) { providers.forEach((token) => { if (token instanceof Type) { res.push({ provide: token, useClass: token }); } else if (token && typeof token === 'object' && token.provide !== undefined) { res.push(token); } else if (token instanceof Array) { normalizeProviders(token, res); } else { throw invalidProviderError(token); } }); return res; } function resolveFactory(provider) { let factoryFn; let resolvedDeps = _EMPTY_LIST; let executionContextIn = _EMPTY_LIST; let hasOnDestroyHook = false; let isGlobal; if (isClassProvider(provider)) { const useClass = resolveForwardRef(provider.useClass); factoryFn = makeFactory(useClass); resolvedDeps = dependenciesFor(useClass); executionContextIn = executionContextInFor(useClass); isGlobal = globalFor(useClass); hasOnDestroyHook = typeof useClass.prototype.onDestroy === 'function'; } else if (isFactoryProvider(provider)) { factoryFn = provider.useFactory; resolvedDeps = constructDependencies(provider.useFactory, provider.deps || []); isGlobal = provider.global; if (provider.executionContextIn) { executionContextIn = provider.executionContextIn; } } else { factoryFn = () => provider.useValue; resolvedDeps = _EMPTY_LIST; isGlobal = provider.global; } return new ResolvedFactory(factoryFn, resolvedDeps, executionContextIn, hasOnDestroyHook, isGlobal !== null && isGlobal !== void 0 ? isGlobal : false); } function dependenciesFor(type) { const { params } = readInjectableMetadata(type, true); if (!params) { return []; } if (params.some((p) => p.type == null)) { throw noAnnotationError(type, params); } return params.map((p) => extractToken(p, params)); } function executionContextInFor(type) { const { options } = readInjectableMetadata(type, true); if ((options === null || options === void 0 ? void 0 : options.executionContextIn) && options.executionContextIn !== _EMPTY_LIST) { return options === null || options === void 0 ? void 0 : options.executionContextIn; } return []; } function globalFor(type) { var _a; const { options } = readInjectableMetadata(type); return (_a = options === null || options === void 0 ? void 0 : options.global) !== null && _a !== void 0 ? _a : false; } function constructDependencies(typeOrFunc, dependencies) { if (!dependencies) { return dependenciesFor(typeOrFunc); } else { const params = dependencies.map((d) => ({ type: d, optional: false })); return params.map((t) => extractToken(t, params)); } } function extractToken(param, params) { const token = resolveForwardRef(param.type); if (token) { return createDependency(token, param.optional); } throw noAnnotationError(param.type, params); } function createDependency(token, optional) { return new Dependency(Key.get(token), optional); } function makeFactory(t) { return (...args) => new t(...args); } const _THROW_IF_NOT_FOUND = new Object(); const UNDEFINED = new Object(); const NOT_FOUND = new Object(); function notInExecutionContext() { throw new Error('Not in execution context'); } // Publicly available Injector. // We use ReflectiveInjector everywhere // but we don't want to leak its API to everyone class Injector { } class ReflectiveInjector { constructor({ name, providers, parent, fallbackParent, globalProvidersMap = new Map(), }) { this._constructionCounter = 0; this._executionContextGetter = notInExecutionContext; this.displayName = name; this._parent = parent || null; this._fallbackParent = fallbackParent || null; this._providers = providers; this._globalProvidersMap = globalProvidersMap; const len = this._providers.length; this._keyIds = new Array(len); this._objs = new Array(len); for (let i = 0; i < len; i++) { this._keyIds[i] = this._providers[i].key.id; this._objs[i] = UNDEFINED; } } static createFromResolved({ name, providers, parent, fallbackParent, globalProvidersMap, }) { return new ReflectiveInjector({ name, providers, parent, fallbackParent, globalProvidersMap, }); } static resolve(providers) { return resolveProviders(providers); } get parent() { return this._parent; } get fallbackParent() { return this._fallbackParent; } get(token, notFoundValue = _THROW_IF_NOT_FOUND) { return this._getByKey(Key.get(token), notFoundValue); } setExecutionContextGetter(getter) { this._executionContextGetter = getter; } _getByKey(key, notFoundValue) { let inj = this; function getObj() { while (inj instanceof ReflectiveInjector) { const inj_ = inj; const obj = inj_._getObjByKeyId(key.id); if (obj !== UNDEFINED) { return obj; } inj = inj_._parent; } return NOT_FOUND; } const resolvedValue = getObj(); if (resolvedValue !== NOT_FOUND) { return resolvedValue; } // search in fallback Injector if (this._fallbackParent) { inj = this._fallbackParent; const resolvedFallbackValue = getObj(); if (resolvedFallbackValue !== NOT_FOUND) { return resolvedFallbackValue; } } if (inj !== null) { return inj.get(key.token, notFoundValue); } return this._throwOrNull(key, notFoundValue); } _isObjectDefinedByKeyId(keyId) { for (let i = 0; i < this._keyIds.length; i++) { if (this._keyIds[i] === keyId) { return this._objs[i] !== UNDEFINED; } } return false; } _getObjByKeyId(keyId) { var _a, _b; if ((_a = this._globalProvidersMap) === null || _a === void 0 ? void 0 : _a.has(keyId)) { return (_b = this._globalProvidersMap.get(keyId)) === null || _b === void 0 ? void 0 : _b._getObjByKeyId(keyId); } for (let i = 0; i < this._keyIds.length; i++) { if (this._keyIds[i] === keyId) { if (this._objs[i] === UNDEFINED) { this._objs[i] = this._new(this._providers[i]); } return this._objs[i]; } } return UNDEFINED; } _throwOrNull(key, notFoundValue) { if (notFoundValue !== _THROW_IF_NOT_FOUND) { return notFoundValue; } else { throw noProviderError(this, key); } } instantiateAll() { this._providers.forEach((provider) => { this._getByKey(provider.key, _THROW_IF_NOT_FOUND); }); } _instantiateProvider(provider) { const factory = provider.factory.factory; let deps; try { deps = provider.factory.dependencies.map((dep) => this._getByDependency(dep)); } catch (e) { if (e.addKey) { e.addKey(provider.key); } throw e; } let obj; try { obj = factory(...deps); // attach execution context getter if (provider.factory.executionContextIn.length > 0) { for (const prop of provider.factory.executionContextIn) { Object.defineProperty(obj, prop, { get: () => { return this._executionContextGetter(); }, }); } } } catch (e) { throw instantiationError(this, e, provider.key); } return obj; } _getByDependency(dep) { return this._getByKey(dep.key, dep.optional ? null : _THROW_IF_NOT_FOUND); } _new(provider) { if (this._constructionCounter++ > this._getMaxNumberOfObjects()) { throw cyclicDependencyError(this, provider.key); } return this._instantiateProvider(provider); } _getMaxNumberOfObjects() { return this._objs.length; } toString() { return this.displayName; } } class ModuleNonUniqueIdError extends ExtendableBuiltin(Error) { constructor(message, ...rest) { super(composeMessage(message, ...rest)); this.name = this.constructor.name; this.message = composeMessage(message, ...rest); } } class ModuleDuplicatedError extends ExtendableBuiltin(Error) { constructor(message, ...rest) { super(composeMessage(message, ...rest)); this.name = this.constructor.name; this.message = composeMessage(message, ...rest); } } class ExtraResolverError extends ExtendableBuiltin(Error) { constructor(message, ...rest) { super(composeMessage(message, ...rest)); this.name = this.constructor.name; this.message = composeMessage(message, ...rest); } } class ExtraMiddlewareError extends ExtendableBuiltin(Error) { constructor(message, ...rest) { super(composeMessage(message, ...rest)); this.name = this.constructor.name; this.message = composeMessage(message, ...rest); } } class ResolverDuplicatedError extends ExtendableBuiltin(Error) { constructor(message, ...rest) { super(composeMessage(message, ...rest)); this.name = this.constructor.name; this.message = composeMessage(message, ...rest); } } class ResolverInvalidError extends ExtendableBuiltin(Error) { constructor(message, ...rest) { super(composeMessage(message, ...rest)); this.name = this.constructor.name; this.message = composeMessage(message, ...rest); } } class NonDocumentNodeError extends ExtendableBuiltin(Error) { constructor(message, ...rest) { super(composeMessage(message, ...rest)); this.name = this.constructor.name; this.message = composeMessage(message, ...rest); } } // helpers function useLocation({ dirname, id }) { return dirname ? `Module "${id}" located at ${dirname}` : [ `Module "${id}"`, `Hint: pass __dirname to "dirname" option of your modules to get more insightful errors`, ].join('\n'); } function ExtendableBuiltin(cls) { function ExtendableBuiltin() { cls.apply(this, arguments); } ExtendableBuiltin.prototype = Object.create(cls.prototype); Object.setPrototypeOf(ExtendableBuiltin, cls); return ExtendableBuiltin; } function composeMessage(...lines) { return lines.join('\n'); } function flatten(arr) { return Array.prototype.concat(...arr); } function isDefined(val) { return !isNil(val); } function isNil(val) { return val === null || typeof val === 'undefined'; } function isPrimitive(val) { return ['number', 'string', 'boolean', 'symbol', 'bigint'].includes(typeof val); } function isAsyncIterable(obj) { return obj && typeof obj[Symbol.asyncIterator] === 'function'; } function tapAsyncIterator(iterable, doneCallback) { const iteratorMethod = iterable[Symbol.asyncIterator]; const iterator = iteratorMethod.call(iterable); function mapResult(result) { if (result.done) { doneCallback(); } return result; } return { async next() { try { let result = await iterator.next(); return mapResult(result); } catch (error) { doneCallback(); throw error; } }, async return() { try { const result = await iterator.return(); return mapResult(result); } catch (error) { doneCallback(); throw error; } }, async throw(error) { doneCallback(); return iterator.throw(error); }, [Symbol.asyncIterator]() { return this; }, }; } function once(cb) { let called = false; return () => { if (!called) { called = true; cb(); } }; } function share(factory) { let cached = null; return (arg) => { if (!cached) { cached = factory(arg); } return cached; }; } function uniqueId(isNotUsed) { let id; while (!isNotUsed((id = Math.random().toString(16).substr(2)))) { } return id; } function isNotSchema(obj) { return obj instanceof GraphQLSchema === false; } function merge(source, target) { const result = { ...source, ...target, }; function attachSymbols(obj) { const symbols = Object.getOwnPropertySymbols(obj); for (const symbol of symbols) { result[symbol] = obj[symbol]; } } if (source) { attachSymbols(source); } attachSymbols(target); return result; } function instantiateSingletonProviders({ appInjector, modulesMap, }) { appInjector.instantiateAll(); modulesMap.forEach((mod) => { mod.injector.instantiateAll(); }); } function createGlobalProvidersMap({ modules, scope, }) { const globalProvidersMap = {}; const propType = scope === Scope.Singleton ? 'singletonProviders' : 'operationProviders'; modules.forEach((mod) => { mod[propType].forEach((provider) => { if (provider.factory.isGlobal) { const key = provider.key.id; if (globalProvidersMap[key]) { throw duplicatedGlobalTokenError(provider, [ mod.id, globalProvidersMap[key], ]); } globalProvidersMap[key] = mod.id; } }); }); return globalProvidersMap; } function attachGlobalProvidersMap({ injector, globalProvidersMap, moduleInjectorGetter, }) { injector._globalProvidersMap = { has(key) { return typeof globalProvidersMap[key] === 'string'; }, get(key) { return moduleInjectorGetter(globalProvidersMap[key]); }, }; } function duplicatedGlobalTokenError(provider, modules) { return Error([ `Failed to define '${provider.key.displayName}' token as global.`, `Token provided by two modules: '${modules.join("', '")}'`, ].join(' ')); } /** * @api * `CONTEXT` is an InjectionToken representing the provided `GraphQLModules.GlobalContext` * * @example * * ```typescript * import { CONTEXT, Inject, Injectable } from 'graphql-modules'; * * (A)Injectable() * export class Data { * constructor((A)Inject(CONTEXT) private context: GraphQLModules.GlobalContext) {} * } * ``` */ const CONTEXT = new InjectionToken('context'); function createContextBuilder({ appInjector, modulesMap, appLevelOperationProviders, singletonGlobalProvidersMap, operationGlobalProvidersMap, }) { // This is very critical. It creates an execution context. // It has to run on every operation. const contextBuilder = (context) => { // Cache for context per module let contextCache = {}; // A list of providers with OnDestroy hooks // It's a tuple because we want to know which Injector controls the provider // and we want to know if the provider was even instantiated. let providersToDestroy = []; function registerProvidersToDestroy(injector) { injector._providers.forEach((provider) => { if (provider.factory.hasOnDestroyHook) { // keep provider key's id (it doesn't change over time) // and related injector providersToDestroy.push([injector, provider.key.id]); } }); } let appContext; attachGlobalProvidersMap({ injector: appInjector, globalProvidersMap: singletonGlobalProvidersMap, moduleInjectorGetter(moduleId) { return modulesMap.get(moduleId).injector; }, }); appInjector.setExecutionContextGetter(executionContext.getApplicationContext); function createModuleExecutionContextGetter(moduleId) { return function moduleExecutionContextGetter() { return executionContext.getModuleContext(moduleId); }; } modulesMap.forEach((mod, moduleId) => { mod.injector.setExecutionContextGetter(createModuleExecutionContextGetter(moduleId)); }); const executionContextPicker = { getApplicationContext() { return appContext; }, getModuleContext(moduleId) { return getModuleContext(moduleId, context); }, }; const destroyExecutionContext = executionContext.create(executionContextPicker); // As the name of the Injector says, it's an Operation scoped Injector // Application level // Operation scoped - means it's created and destroyed on every GraphQL Operation const operationAppInjector = ReflectiveInjector.createFromResolved({ name: 'App (Operation Scope)', providers: appLevelOperationProviders.concat(ReflectiveInjector.resolve([ { provide: CONTEXT, useValue: context, }, ])), parent: appInjector, }); // Create a context for application-level ExecutionContext appContext = merge(context, { injector: operationAppInjector, }); // Track Providers with OnDestroy hooks registerProvidersToDestroy(operationAppInjector); function getModuleContext(moduleId, ctx) { var _a; // Reuse a context or create if not available if (!contextCache[moduleId]) { // We're interested in operation-scoped providers only const providers = (_a = modulesMap.get(moduleId)) === null || _a === void 0 ? void 0 : _a.operationProviders; // Create module-level Operation-scoped Injector const operationModuleInjector = ReflectiveInjector.createFromResolved({ name: `Module "${moduleId}" (Operation Scope)`, providers: providers.concat(ReflectiveInjector.resolve([ { provide: CONTEXT, useFactory() { return contextCache[moduleId]; }, }, ])), // This injector has a priority parent: modulesMap.get(moduleId).injector, // over this one fallbackParent: operationAppInjector, }); // Same as on application level, we need to collect providers with OnDestroy hooks registerProvidersToDestroy(operationModuleInjector); contextCache[moduleId] = merge(ctx, { injector: operationModuleInjector, moduleId, }); } return contextCache[moduleId]; } const sharedContext = merge( // We want to pass the received context context || {}, { // Here's something very crutial // It's a function that is used in module's context creation ɵgetModuleContext: getModuleContext, }); attachGlobalProvidersMap({ injector: operationAppInjector, globalProvidersMap: operationGlobalProvidersMap, moduleInjectorGetter(moduleId) { return getModuleContext(moduleId, sharedContext).injector; }, }); return { ɵdestroy: once(() => { providersToDestroy.forEach(([injector, keyId]) => { // If provider was instantiated if (injector._isObjectDefinedByKeyId(keyId)) { // call its OnDestroy hook injector._getObjByKeyId(keyId).onDestroy(); } }); destroyExecutionContext(); contextCache = {}; }), ɵinjector: operationAppInjector, context: sharedContext, }; }; return contextBuilder; } function executionCreator({ contextBuilder, }) { const createExecution = (options) => { // Custom or original execute function const executeFn = (options === null || options === void 0 ? void 0 : options.execute) || execute$1; return (argsOrSchema, document, rootValue, contextValue, variableValues, operationName, fieldResolver, typeResolver) => { var _a; // Create an execution context const { context, ɵdestroy: destroy } = (_a = options === null || options === void 0 ? void 0 : options.controller) !== null && _a !== void 0 ? _a : contextBuilder(isNotSchema(argsOrSchema) ? argsOrSchema.contextValue : contextValue); const executionArgs = isNotSchema(argsOrSchema) ? { ...argsOrSchema, contextValue: context, } : { schema: argsOrSchema, document: document, rootValue, contextValue: context, variableValues, operationName, fieldResolver, typeResolver, }; // It's important to wrap the executeFn within a promise // so we can easily control the end of execution (with finally) return Promise.resolve() .then(() => executeFn(executionArgs)) .finally(destroy); }; }; return createExecution; } function subscriptionCreator({ contextBuilder, }) { const createSubscription = (options) => { // Custom or original subscribe function const subscribeFn = (options === null || options === void 0 ? void 0 : options.subscribe) || subscribe; return (argsOrSchema, document, rootValue, contextValue, variableValues, operationName, fieldResolver, subscribeFieldResolver) => { var _a; // Create an subscription context const { context, ɵdestroy: destroy } = (_a = options === null || options === void 0 ? void 0 : options.controller) !== null && _a !== void 0 ? _a : contextBuilder(isNotSchema(argsOrSchema) ? argsOrSchema.contextValue : contextValue); const subscriptionArgs = isNotSchema(argsOrSchema) ? { ...argsOrSchema, contextValue: context, } : { schema: argsOrSchema, document: document, rootValue, contextValue: context, variableValues, operationName, fieldResolver, subscribeFieldResolver, }; let isIterable = false; // It's important to wrap the subscribeFn within a promise // so we can easily control the end of subscription (with finally) return Promise.resolve() .then(() => subscribeFn(subscriptionArgs)) .then((sub) => { if (isAsyncIterable(sub)) { isIterable = true; return tapAsyncIterator(sub, destroy); } return sub; }) .finally(() => { if (!isIterable) { destroy(); } }); }; }; return createSubscription; } const CONTEXT_ID = Symbol.for('context-id'); function apolloExecutorCreator({ createExecution, }) { return function createApolloExecutor(options) { const executor = createExecution(options); return async function executorAdapter(requestContext) { return executor({ schema: requestContext.schema, document: requestContext.document, operationName: requestContext.operationName, variableValues: requestContext.request.variables, contextValue: requestContext.context, }); }; }; } function apolloSchemaCreator({ createSubscription, contextBuilder, schema, }) { const createApolloSchema = () => { const sessions = {}; const subscription = createSubscription(); function getSession(ctx) { if (!ctx[CONTEXT_ID]) { ctx[CONTEXT_ID] = uniqueId((id) => !sessions[id]); const { context, ɵdestroy: destroy } = contextBuilder(ctx); sessions[ctx[CONTEXT_ID]] = { count: 0, session: { context, destroy() { if (--sessions[ctx[CONTEXT_ID]].count === 0) { destroy(); delete sessions[ctx[CONTEXT_ID]]; delete ctx[CONTEXT_ID]; } }, }, }; } sessions[ctx[CONTEXT_ID]].count++; return sessions[ctx[CONTEXT_ID]].session; } return wrapSchema({ schema, batch: true, executor(input) { if (input.operationType === 'subscription') { return subscription({ schema, document: input.document, variableValues: input.variables, contextValue: input.context, rootValue: input.rootValue, operationName: input.operationName, }); } // Create an execution context const { context, destroy } = getSession(input.context); // It's important to wrap the executeFn within a promise // so we can easily control the end of execution (with finally) return Promise.resolve() .then(() => execute$1({ schema, document: input.document, contextValue: context, variableValues: input.variables, rootValue: input.rootValue, operationName: input.operationName, })) .finally(destroy); }, }); }; return createApolloSchema; } function operationControllerCreator(options) { const { contextBuilder } = options; return (input) => { const operation = contextBuilder(input.context); const ɵdestroy = input.autoDestroy ? operation.ɵdestroy : () => { }; return { context: operation.context, injector: operation.ɵinjector, destroy: operation.ɵdestroy, ɵdestroy, }; }; } /** * @api * Creates Application out of Modules. Accepts `ApplicationConfig`. * * @example * * ```typescript * import { createApplication } from 'graphql-modules'; * import { usersModule } from './users'; * import { postsModule } from './posts'; * import { commentsModule } from './comments'; * * const app = createApplication({ * modules: [ * usersModule, * postsModule, * commentsModule * ] * }) * ``` */ function createApplication(applicationConfig) { function applicationFactory(cfg) { const config = cfg || applicationConfig; const providers = config.providers && typeof config.providers === 'function' ? config.providers() : config.providers; // Creates an Injector with singleton classes at application level const appSingletonProviders = ReflectiveInjector.resolve(onlySingletonProviders(providers)); const appInjector = ReflectiveInjector.createFromResolved({ name: 'App (Singleton Scope)', providers: appSingletonProviders, }); // Filter Operation-scoped providers, and keep it here // so we don't do it over and over again const appOperationProviders = ReflectiveInjector.resolve(onlyOperationProviders(providers)); const middlewareMap = config.middlewares || {}; // Validations ensureModuleUniqueIds(config.modules); // Create all modules const modules = config.modules.map((mod) => mod.factory({ injector: appInjector, middlewares: middlewareMap, })); const modulesMap = createModulesMap(modules); const singletonGlobalProvidersMap = createGlobalProvidersMap({ modules, scope: Scope.Singleton, }); const operationGlobalProvidersMap = createGlobalProvidersMap({ modules, scope: Scope.Operation, }); attachGlobalProvidersMap({ injector: appInjector, globalProvidersMap: singletonGlobalProvidersMap, moduleInjectorGetter(moduleId) { return modulesMap.get(moduleId).injector; }, }); // Creating a schema, flattening the typedefs and resolvers // is not expensive since it happens only once const typeDefs = flatten(modules.map((mod) => mod.typeDefs)); const resolvers = modules.map((mod) => mod.resolvers).filter(isDefined); const schema = (applicationConfig.schemaBuilder || makeExecutableSchema)({ typeDefs, resolvers, }); const contextBuilder = createContextBuilder({ appInjector, appLevelOperationProviders: appOperationProviders, modulesMap: modulesMap, singletonGlobalProvidersMap, operationGlobalProvidersMap, }); const createOperationController = operationControllerCreator({ contextBuilder, }); const createSubscription = subscriptionCreator({ contextBuilder }); const createExecution = executionCreator({ contextBuilder }); const createSchemaForApollo = apolloSchemaCreator({ createSubscription, contextBuilder, schema, }); const createApolloExecutor = apolloExecutorCreator({ createExecution, }); instantiateSingletonProviders({ appInjector, modulesMap, }); return { typeDefs, resolvers, schema, injector: appInjector, createOperationController, createSubscription, createExecution, createSchemaForApollo, createApolloExecutor, ɵfactory: applicationFactory, ɵconfig: config, }; } return applicationFactory(); } function createModulesMap(modules) { var _a; const modulesMap = new Map(); for (const module of modules) { if (modulesMap.has(module.id)) { const location = module.metadata.dirname; const existingLocation = (_a = modulesMap.get(module.id)) === null || _a === void 0 ? void 0 : _a.metadata.dirname; const info = []; if (existingLocation) { info.push(`Already registered module located at: ${existingLocation}`); } if (location) { info.push(`Duplicated module located at: ${location}`); } throw new ModuleDuplicatedError(`Module "${module.id}" already exists`, ...info); } modulesMap.set(module.id, module); } return modulesMap; } function ensureModuleUniqueIds(modules) { const collisions = modules .filter((mod, i, all) => i !== all.findIndex((m) => m.id === mod.id)) .map((m) => m.id); if (collisions.length) { throw new ModuleNonUniqueIdError(`Modules with non-unique ids: ${collisions.join(', ')}`, `All modules should have unique ids, please locate and fix them.`); } } function metadataFactory(typeDefs, config) { const implemented = {}; const extended = {}; function collectObjectDefinition(node) { if (!implemented[node.name.value]) { implemented[node.name.value] = []; } if (node.fields && node.fields.length > 0) { implemented[node.name.value].push(...node.fields.map((field) => field.name.value)); } if (node.kind === Kind.OBJECT_TYPE_DEFINITION) { implemented[node.name.value].push('__isTypeOf'); } if (node.kind === Kind.OBJECT_TYPE_DEFINITION) { implemented[node.name.value].push('__resolveReference'); implemented[node.name.value].push('__resolveObject'); } if (node.kind === Kind.INTERFACE_TYPE_DEFINITION) { implemented[node.name.value].push('__resolveType'); } } function collectObjectExtension(node) { if (node.fields) { if (!extended[node.name.value]) { extended[node.name.value] = []; } node.fields.forEach((field) => { extended[node.name.value].push(field.name.value); }); } } for (const doc of typeDefs) { visit(doc, { // Object ObjectTypeDefinition(node) { collectObjectDefinition(node); }, ObjectTypeExtension(node) { collectObjectExtension(node); }, // Interface InterfaceTypeDefinition(node) { collectObjectDefinition(node); }, InterfaceTypeExtension(node) { collectObjectExtension(node); }, // Union UnionTypeDefinition(node) { if (!implemented[node.name.value]) { implemented[node.name.value] = []; } if (node.types) { implemented[node.name.value].push(...node.types.map((type) => type.name.value)); } implemented[node.name.value].push('__resolveType'); }, UnionTypeExtension(node) { if (node.types) { if (!extended[node.name.value]) { extended[node.name.value] = []; } extended[node.name.value].push(...node.types.map((type) => type.name.value)); } }, // Input InputObjectTypeDefinition(node) { collectObjectDefinition(node); }, InputObjectTypeExtension(node) { collectObjectExtension(node); }, // Enum EnumTypeDefinition(node) { if (node.values) { if (!implemented[node.name.value]) { implemented[node.name.value] = []; } implemented[node.name.value].push(...node.values.map((value) => value.name.value)); } }, EnumTypeExtension(node) { if (node.values) { if (!extended[node.name.value]) { extended[node.name.value] = []; } extended[node.name.value].push(...node.values.map((value) => value.name.value));