graphql-modules
Version:
Create reusable, maintainable, testable and extendable GraphQL modules
1,434 lines (1,412 loc) • 84.5 kB
JavaScript
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));