graphql-modules
Version:
Create reusable, maintainable, testable and extendable GraphQL modules
2,321 lines (2,284 loc) • 83.4 kB
JavaScript
Object.defineProperty(exports, '__esModule', { value: true });
const schema = require('@graphql-tools/schema');
const graphql = require('graphql');
const wrap = require('@graphql-tools/wrap');
const ramda = require('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;
}
(function (Scope) {
Scope[Scope["Singleton"] = 0] = "Singleton";
Scope[Scope["Operation"] = 1] = "Operation";
})(exports.Scope || (exports.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) !== exports.Scope.Operation;
}
else {
return provider.scope !== exports.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) === exports.Scope.Operation;
}
else {
return provider.scope === exports.Scope.Operation;
}
});
}
function isClassProvider(provider) {
return typeof provider.useClass !== 'undefined';
}
function isFactoryProvider(provider) {
return typeof provider.useFactory !== 'undefined';
}
function ensureReflect() {
if (!(Reflect && Reflect.getOwnMetadata)) {
throw 'reflect-metadata shim is required when using class decorators';
}
}
function Injectable(options) {
return (target) => {
var _a;
ensureReflect();
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(value) {
try {
const result = await iterator.return(value);
return mapResult(result);
}
catch (error) {
doneCallback();
throw error;
}
},
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 graphql.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 === exports.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(function executionContextGetter() {
return appContext;
});
function createModuleExecutionContextGetter(moduleId) {
return function moduleExecutionContextGetter() {
return getModuleContext(moduleId, context);
};
}
modulesMap.forEach((mod, moduleId) => {
mod.injector.setExecutionContextGetter(createModuleExecutionContextGetter(moduleId));
});
// 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();
}
});
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) || graphql.execute;
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) || graphql.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 wrap.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(() => graphql.execute({
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: exports.Scope.Singleton,
});
const operationGlobalProvidersMap = createGlobalProvidersMap({
modules,
scope: exports.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$1 = (applicationConfig.schemaBuilder || schema.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: schema$1,
});
const createApolloExecutor = apolloExecutorCreator({
createExecution,
});
instantiateSingletonProviders({
appInjector,
modulesMap,
});
return {
typeDefs,
resolvers,
schema: schema$1,
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 === graphql.Kind.OBJECT_TYPE_DEFINITION) {
implemented[node.name.value].push('__isTypeOf');
}
if (node.kind === graphql.Kind.OBJECT_TYPE_DEFINITION) {
implemented[node.name.value].push('__resolveReference');
implemented[node.name.value].push('__resolveObject');
}
if (node.kind === graphql.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) {
graphql.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));
}
},
// Scalar
ScalarTypeDefinition(node) {
if (!implemented.__scalars) {
implemented.__scalars = [];
}
implemented.__scalars.push(node.name.value);
},
});
}
return {
id: config.id,
typeDefs,
implements: implemented,
extends: extended,
dirname: config.dirname,
};
}
function compose(middleware) {
if (!Array.isArray(middleware)) {
throw new TypeError('Middleware stack must be an array!');
}
for (const fn of middleware) {
if (typeof fn !== 'function') {
throw new TypeError('Middleware must be composed of functions!');
}
}
return function composed(context, next) {
// last called middleware
let index = -1;
function dispatch(i) {
if (i <= index) {
return Promise.reject(new Error('next() called multiple times'));
}
index = i;
const fn = i === middleware.length ? next : middleware[i];
if (!fn) {
return Promise.resolve();
}
try {
return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
}
catch (err) {
return Promise.reject(err);
}
}
return dispatch(0);
};
}
function createMiddleware(path, middlewareMap) {
const middlewares = middlewareMap ? pickMiddlewares(path, middlewareMap) : [];
return compose(middlewares);
}
function mergeMiddlewareMaps(app, mod) {
const merge = (left, right) => {
return ramda.mergeDeepWith((l, r) => {
if (Array.isArray(l)) {
return l.concat(r || []);
}
return merge(l, r);
}, left, right);
};
return merge(app, mod);
}
function pickMiddlewares(path, middlewareMap) {
var _a;
const middlewares = [];
const [type, field] = path;
if ((_a = middlewareMap['*']) === null || _a === void 0 ? void 0 : _a['*']) {
middlewares.push(...middlewareMap['*']['*']);
}
const typeMap = middlewareMap[type];
if (typeMap) {
if (typeMap['*']) {
middlewares.push(...typeMap['*']);
}
if (field && typeMap[field]) {
middlewares.push(...typeMap[field]);
}
}
return middlewares.filter(isDefined);
}
function validateMiddlewareMap(middlewareMap, metadata) {
const exists = checkExistence(metadata);
for (const typeName in middlewareMap.types) {
if (middlewareMap.types.hasOwnProperty(typeName)) {
const typeMiddlewareMap = middlewareMap[typeName];
if (!exists.type(typeName)) {
throw new ExtraMiddlewareError(`Cannot apply a middleware to non existing "${typeName}" type`, useLocation({ dirname: metadata.dirname, id: metadata.id }));
}
for (const fieldName in typeMiddlewareMap[typeName]) {
if (typeMiddlewareMap[typeName].hasOwnProperty(fieldName)) {
if (!exists.field(typeName, fieldName)) {
throw new ExtraMiddlewareError(`Cannot apply a middleware to non existing "${typeName}.${fieldName}" type.field`, useLocation({ dirname: metadata.dirname, id: metadata.id }));
}
}
}
}
}
}
/**
* Helps to make sure a middleware has a corresponding type/field definition.
* We don't want to pass a module-level middlewares that are not related to the module.
* Not because it's dangerous but to prevent unused middlewares.
*/
function checkExistence(metadata) {
return {
type(name) {
var _a, _b;
return isDefined(((_a = metadata.implements) === null || _a === void 0 ? void 0 : _a[name]) || ((_b = metadata.extends) === null || _b === void 0 ? void 0 : _b[name]));
},
field(type, name) {
var _a, _b, _c, _d;
return isDefined(((_b = (_a = metadata.implements) === null || _a === void 0 ? void 0 : _a[type]) === null || _b === void 0 ? void 0 : _b.includes(name)) ||
((_d = (_c = metadata.extends) === null || _c === void 0 ? void 0 : _c[type]) === null || _d === void 0 ? void 0 : _d.includes(name)));
},
};
}
const resolverMetadataProp = Symbol('metadata');
function createResolvers(config, metadata, app) {
const ensure = ensureImplements(metadata);
const normalizedModuleMiddlewareMap = config.middlewares || {};
const middlewareMap = mergeMiddlewareMaps(app.middlewareMap, normalizedModuleMiddlewareMap);
validateMiddlewareMap(normalizedModuleMiddlewareMap, metadata);
const resolvers = addDefaultResolvers(mergeResolvers(config), middlewareMap, config);
// Wrap resolvers
for (const typeName in resolvers) {
if (resolvers.hasOwnProperty(typeName)) {
const obj = resolvers[typeName];
if (isScalarResolver(obj)) {
continue;
}
else if (isEnumResolver(obj)) {
continue;
}
else if (obj && typeof obj === 'object') {
for (const fieldName in obj) {
if (obj.hasOwnProperty(fieldName)) {
ensure.type(typeName, fieldName);
const path = [typeName, fieldName];
// function
if (isResolveFn(obj[fieldName])) {
const resolver = wrapResolver({
config,
resolver: obj[fieldName],
middlewareMap,
path,
isTypeResolver: fieldName === '__isTypeOf' || fieldName === '__resolveType',
isReferenceResolver: fieldName === '__resolveReference',
isObjectResolver: fieldName === '__resolveObject',
});
resolvers[typeName][fieldName] = resolver;
}
else if (isResolveOptions(obj[fieldName])) {
// { resolve }
if (isDefined(obj[fieldName].resolve)) {
const resolver = wrapResolver({
config,
resolver: obj[fieldName].resolve,
middlewareMap,
path,
});
resolvers[typeName][fieldName].resolve = resolver;
}
// { subscribe }
if (isDefined(obj[fieldName].subscribe)) {
const resolver = wrapResolver({
config,
resolver: obj[fieldName].subscribe,
middlewareMap,
path,
});
resolvers[typeName][fieldName].subscribe = resolver;
}
}
}
}
}
}
}
return resolvers;
}
/**
* Wrap a resolver so we use module's context instead of app context.
* Use a middleware if available.
* Attach metadata to a resolver (we will see if it's helpful, probably in error handling)
*/
function wrapResolver({ resolver, config, path, middlewareMap, isTypeResolver, isReferenceResolver, isObjectResolver, }) {
if (isTypeResolver || isReferenceResolver) {
const wrappedResolver = (root, context, info) => {
const ctx = {
root,
context: isReferenceResolver
? context.ɵgetModuleContext(config.id, context)
: // We mark the context object as possibly undefined,
// because graphql-jit for some reason doesn't pass it for isTypeOf or resolveType methods
context === null || context === void 0 ? void 0 : context.ɵgetModuleContext(config.id, context),
info,
};
return resolver(ctx.root, ctx.context, ctx.info);
};
writeResolverMetadata(wrappedResolver, config);
return wrappedResolver;
}
if (isObjectResolver) {
const wrappedResolver = (root, fields, context, info) => {
const moduleContext = context.ɵgetModuleContext(config.id, context);
return resolver(root, fields, moduleContext, info);
};
writeResolverMetadata(wrappedResolver, config);
return wrappedResolver;
}
const middleware = createMiddleware(path, middlewareMap);
const wrappedResolver = (root, args, context, info) => {
const ctx = {
root,
args,
context: context.ɵgetModuleContext(config.id, context),
info,
};
return middleware(ctx, () => resolver(ctx.root, ctx.args, ctx.context, ctx.info));
};
writeResolverMetadata(wrappedResolver, config);
return wrappedResolver;
}
/**
* We iterate over every defined resolver and check if it's valid and not duplicated
*/
function mergeResolvers(config) {
if (!config.resolvers) {
return {};
}
const resolvers = Array.isArray(config.resolvers)
? config.resolvers
: [config.resolvers];
const container = {};
for (const currentResolvers of resolvers) {
for (const typeName in currentResolvers) {
if (currentResolvers.hasOwnProperty(typeName)) {
const value = currentResolvers[typeName];
if (isNil(value)) {
continue;
}
else if (isScalarResolver(value)) {
addScalar({ typeName, resolver: value, container, config });
}
else if (isEnumResolver(value)) {
addEnum({ typeName, resolver: value, container, config });
}
else if (value && typeof value === 'object') {
addObject({ typeName, fields: value, container, config });
}
else {
throw new ResolverInvalidError(`Resolver of "${typeName}" is invalid`, useLocation({ dirname: config.dirname, id: config.id }));
}
}
}
}
return container;
}
function addObject({ typeName, fields, container, config, }) {
if (!container[typeName]) {
container[typeName] = {};
}
for (const fieldName in fields) {
if (fields.hasOwnProperty(fieldName)) {
const resolver = fields[fieldName];
if (isResolveFn(resolver)) {
if (container[typeName][fieldName]) {
throw new ResolverDuplicatedError(`Duplicated resolver of "${typeName}.${fieldName}"`, useLocation({ dirname: config.dirname, id: config.id }));
}
writeResolverMetadata(resolver, config);
container[typeName][fieldName] = resolver;
}
else if (isResolveOptions(resolver)) {
if (!container[typeName][fieldName]) {
container[typeName][fieldName] = {};
}
// resolve
if (isDefined(resolver.resolve)) {
if (container[typeName][fieldName].resolve) {
throw new ResolverDuplicatedError(`Duplicated resolver of "${typeName}.${fieldName}" (resolve method)`, useLocation({ dirname: config.dirname, id: config.id }));
}
writeResolverMetadata(resolver.resolve, config);
container[typeName][fieldName].resolve = resolver.resolve;
}
// subscribe
if (isDefined(resolver.subscribe)) {
if (container[typeName][fieldName].subscribe) {
throw new ResolverDuplicatedError(`Duplicated resolver of "${typeName}.${fieldName}" (subscribe method)`, useLocation({ dirname: config.dirname, id: config.id }));
}
writeResolverMetadata(resolver.subscribe, config);
container[typeName][fieldName].subscribe = resolver.subscribe;
}
}
}
}
}
function addScalar({ typeName, resolver, container, config, }) {
if (container[typeName]) {
throw new ResolverDuplicatedError(`Duplicated resolver of scalar "${typeName}"`, useLocation({ dirname: config.dirname, id: config.id }));
}
writeResolverMetadata(resolver.parseLiteral, config);
writeResolverMetadata(resolver.parseValue, config);
writeResolverMetadata(resolver.serialize, config);
container[typeName] = resolver;
}
function addEnum({ typeName, resolver, container, config, }) {
if (!container[typeName]) {
container[typeName] = {};
}
for (const key in resolver) {
if (resolver.hasOwnProperty(key)) {
const value = resolver[key];
if (container[typeName][key]) {
throw new ResolverDuplicatedError(`Duplicated resolver of "${typeName}.${key}" enum value`, useLocation({ dirname: config.dirname, id: config.id }));
}
container[typeName][key] = value;
}
}
}
/**
* Helps to make sure a resolver has a corresponding type/field definition.
* We don't want to pass resolve function that are not related to the module.
*/
function ensureImplements(metadata) {
return {
type(name, field) {
var _a, _b;
const type = []
.concat((_a = metadata.implements) === null || _a === void 0 ? void 0 : _a[name], (_b = metadata.extends) === null || _b === void 0 ? void 0 : _b[name])
.filter(isDefined);
if (type === null || type === void 0 ? void 0 : type.includes(field)) {
return true;
}
const id = `"${name}.${field}"`;
throw new ExtraResolverError(`Resolver of "${id}" type cannot be implemented`, `${id} is not defined`, useLocation({ dirname: metadata.dirname, id: metadata.id }));
},
scalar(name) {
var _a;
if ((((_a = metadata.implements) === null || _a === void 0 ? void 0 : _a.__scalars) || []).includes(name)) {
return true;
}
throw new ExtraResolverError(`Resolver of "${name}" scalar cannot be implemented`, `${name} is not defined`, useLocation({ dirname: metadata.dirname, id: metadata.id }));
},
};
}
function writeResolverMetadata(resolver, config) {
if (!resolver) {
return;
}
resolver[resolverMetadataProp] = {
moduleId: config.id,
};
}
/**
* In order to use middlewares on fields
* that are defined in SDL but have no implemented resolvers,
* we would have to recreate GraphQLSchema and wrap resolve functions.
*
* Since we can't access GraphQLSchema on a module level
* and recreating GraphQLSchema seems unreasonable,
* we can create default resolvers instead.
*
* @example
*
* gql`
* type Query {
* me: User!
* }
*
* type User {
* name: String!
* }
* `
*
* The resolver of `Query.me` is implemented and resolver of `User.name` is not.
* In case where a middleware wants to intercept the resolver of `User.name`,
* we use a default field resolver from `graphql` package
* and put it next to other defined resolvers.
*
* This way our current logic of wrapping resolvers and running
* middleware functions stays untouched.
*/
function addDefaultResolvers(resolvers, middlewareMap, config) {
const container = resolvers;
const sdl = Array.isArray(config.typeDefs)
? graphql.concatAST(config.typeDefs)
: config.typeDefs;
function hasMiddleware(typeName, fieldName) {
var _a, _b, _c, _d, _e, _f;
return ((((_b = (_a = middlewareMap['*']) === null || _a === void 0 ? void 0 : _a['*']) === null || _b === void 0 ? void 0 : _b.length) ||
((_d = (_c = middlewareMap[typeName]) === null || _c === void 0 ? void 0 : _c['*']) === null || _d === void 0 ? void 0 : _d.length) ||
((_f = (_e = middlewareMap[typeName]) === null || _e === void 0 ? void 0 : _e[fieldName]) === null || _f === void 0 ? void 0 : _f.length)) > 0);
}
sdl.definitions.forEach((definition) => {
if (definition.kind === graphql.Kind.OBJECT_TYPE_DEFINITION ||
definition.kind === graphql.Kind.OBJECT_TYPE_EXTENSION) {
// Right now we only support Object type
if (definition.fields) {
const typeName = definition.name.value;
definition.fields.forEach((field) => {
var _a;
const fieldName = field.name.value;
if (!((_a = container[typeName]) === null || _a === void 0 ? void 0 : _a[fieldName]) &&
hasMiddleware(typeName, fieldName)) {
if (!container[typeName]) {
container[typeName] = {};
}
container[typeName][fieldName] = graphql.defaultFieldResolver;
}
});
}
}
});
return container;
}
//
// Resolver helpers
//
function isResolveFn(value) {
return typeof value === 'function';
}
function isResolveOptions(value) {
return isDefined(value.resolve) || isDefined(value.subscribe);
}
function isScalarResolver(obj) {
return obj instanceof graphql.GraphQLScalarType;
}
function isEnumResolver(obj) {
return Object.values(obj).every(isPrimitive);
}
/**
* Create a list of DocumentNode objects based on Module's config.
* Add a location, so we get richer errors.
*/
function createTypeDefs(config) {
const typeDefs = Array.isArray(config.typeDefs)
? config.typeDefs
: [config.typeDefs];
ensureDocumentNode(config, typeDefs);
return typeDefs;
}
function ensureDocumentNode(config, typeDefs) {
function ensureEach(doc, i) {
if ((doc === null || doc === void 0 ? void 0 : doc.kind) !== graphql.Kind.DOCUMENT) {
throw new NonDocumentNodeError(`Expected parsed document but received ${typeof doc} at index ${i} in typeDefs list`, useLocation(config));
}
}
typeDefs.forEach(ensureEach);
}
/**
* @api
* `MODULE_ID` is an InjectionToken representing module's ID
*
* @example
* ```typescript
* import { MODULE_ID, Inject, Injectable } from 'graphql-modules';
*
* (A)Injectable()
* export class Data {
* constructor((A)Inject(MODULE_ID) moduleId: string) {
* console.log(`Data used in ${moduleId} module`)
* }
* }
* ```
*/
const MODULE_ID = new InjectionToken('module-id');
function lazy(getter) {
let called = false;
let computedValue;
return {
get value() {
if (!called) {
called = true;
computedValue = getter();
}
return computedValue;
},
};
}
function moduleFactory(config) {
const typeDefs = createTypeDefs(config);
const metadata = metadataFactory(typeDefs, config);
const providers = lazy(() => typeof config.providers === 'function'
? config.providers()
: config.providers);
// Filter providers and keep them this way
// so we don't do this filtering multiple times.
// Providers don't change over time, so it's safe to do it.
const operationProviders = lazy(() => ReflectiveInjector.resolve(onlyOperationProviders(providers.value)));
const singletonProviders = lazy(() => ReflectiveInjector.resolve(onlySingletonProviders(providers.value)));
const mod = {
id: config.id,
config,
metadata,
typeDefs,
// Factory is called once on application creation,
// before we even handle GraphQL Operation
factory(app) {
const resolvedModule = mod;
resolvedModule.singletonProviders = singletonProviders.value;
resolvedModule.operationProviders = operationProviders.value;
// Create a module-level Singleton injector
const injector = ReflectiveInjector.createFromResolved({
name: `Module "${config.id}" (Singleton Scope)`,
providers: resolvedModule.singletonProviders.concat(resolveProviders([
{
// with module's id, useful in Logging and stuff
provide: MODULE_ID,
useValue: config.id,
},
])),
parent: app.injector,
});
// We attach injector property to existing `mod` object
// because we want to keep references
// that are later on used in testing utils
resolvedModule.injector = injector;
// Create resolvers object based on module's config
// It involves wrapping a resolver with middlewares
// and other things like validation
resolvedModule.resolvers = createResolvers(config, metadata, {
middlewareMap: app.middlewares,
});
return resolvedModule;
},
};
return mod;
}
/**
* @api
* Creates a Module, an element used by Application. Accepts `ModuleConfig`.
*
* @example
*
* ```typescript
* import { createModule, gql } from 'graphql-modules';
*
* export const usersModule = createModule({
* id: 'users',
* typeDefs: gql`
* // GraphQL SDL
* `,
* resolvers: {
* // ...
* }
* });
* ```
*/
function createModule(config) {
return moduleFactory(config);
}
function gql(literals) {
const result = typeof literals === 'string' ? literals : literals[0];
const parsed = graphql.parse(result);
if (!parsed || parsed.kind !== graphql.Kind.DOCUMENT) {
throw new Error('Not a valid GraphQL document.');
}
return parsed;
}
function mockApplication(app) {
function mockedFactory(newConfig) {
const sharedFactory = share(() => app.ɵfactory(newConfig));
return {
get typeDefs() {
return sharedFactory().typeDefs;
},
get resolvers() {
return sharedFactory().resolvers;
},
get schema() {
return sharedFactory().schema;
},
get injector() {
return sharedFactory().injector;
},
createOperationController(options) {
return sharedFactory().createOperationController(options);
},
createSubscription(options) {
return sharedFactory().createSubscription(options);
},
createExecution(options) {
return sharedFactory().createExecution(options);
},
createSchemaForApollo() {
return sharedFactory().createSchemaForApollo();
},
createApolloExecutor() {
return sharedFactory().createApolloExecutor();
},
get ɵfactory() {
return sharedFactory().ɵfactory;
},
get ɵconfig() {
return sharedFactory().ɵconfig;
},
replaceModule(newModule) {
const config = sharedFactory().ɵconfig;
return mockedFactory({
...config,
modules: config.modules.map((mod) => mod.id === newModule.ɵoriginalModule.id ? newModule : mod),
});
},
addProviders(newProviders) {
const config = sharedFactory().ɵconfig;
const existingProviders = typeof config.providers === 'function'
? config.providers()
: config.providers;
const providers = Array.isArray(existingProviders)
? existingProviders.concat(newProviders)
: newProviders;
return mockedFactory({
...config,
providers,
});
},
};
}
return mockedFactory();
}
function mockModule(testedModule, overrideConfig) {
const sourceProviders = typeof testedModule.config.providers === 'function'
? testedModule.config.providers()
: testedModule.config.providers;
const overrideProviders = typeof overrideConfig.providers === 'function'
? overrideConfig.providers()
: overrideConfig.providers;
const newModule = createModule({
...testedModule.config,
providers: [...(sourceProviders || []), ...(overrideProviders || [])],
});
newModule['ɵoriginalModule'] = testedModule;
return newModule;
}
function testModule(testedModule, config) {
var _a;
const mod = transformModule(testedModule, config);
const modules = [mod].concat((_a = config === null || config === void 0 ? void 0 : config.modules) !== null && _a !== void 0 ? _a : []);
return createApplication({
...(config || {}),
modules,
providers: config === null || config === void 0 ? void 0 : config.providers,
middlewares: config === null || config === void 0 ? void 0 : config.middlewares,
});
}
function transformModule(mod, config) {
const transforms = [];
if (config === null || config === void 0 ? void 0 : config.replaceExtensions) {
transforms.push((m) => moduleFactory({
...m.config,
typeDefs: replaceExtensions(m.typeDefs),
}));
}
if (config === null || config === void 0 ? void 0 : config.typeDefs) {
transforms.push((m) => moduleFactory({
...m.config,
typeDefs: m.typeDefs.concat(config.typeDefs),
}));
}
if (config === null || config === void 0 ? void 0 : config.inheritTypeDefs) {
transforms.push((m) => moduleFactory({
...m.config,
typeDefs: inheritTypeDefs(m.typeDefs, config.inheritTypeDefs),
}));
}
if (config === null || config === void 0 ? void 0 : config.resolvers) {
transforms.push((m) => {
const resolvers = m.config.resolvers
? Array.isArray(m.config.resolvers)
? m.config.resolvers
: [m.config.resolvers]
: [];
return moduleFactory({
...m.config,
resolvers: resolvers.concat(config.resolvers),
});
});
}
if (config === null || config === void 0 ? void 0 : config.providers) {
transforms.push((m) => {
const sourceProviders = typeof m.config.providers === 'function'
? m.config.providers()
: m.config.providers;
const overrideProviders = typeof config.providers === 'function'
? config.providers()
: config.providers;
return moduleFactory({
...m.config,
providers: [...(sourceProviders || []), ...(overrideProviders || [])],
});
});
}
if (transforms) {
return transforms.reduce((m, transform) => transform(m), mod);
}
return mod;
}
function inheritTypeDefs(originalTypeDefs, modules) {
const original = graphql.concatAST(originalTypeDefs);
const typeDefs = treeshakeTypesDefs(original, modules.reduce((typeDefs, externalMod) => typeDefs.concat(externalMod.typeDefs), []));
return typeDefs;
}
function replaceExtensions(typeDefs) {
const types = [];
const extensions = [];
// List all object types
typeDefs.forEach((doc) => {
graphql.visit(doc, {
ObjectTypeDefinition(node) {
types.push(node.name.value);
},
});
});
// turn object type extensions into object types
return typeDefs.map((doc) => {
return graphql.visit(doc, {
ObjectTypeExtension(node) {
// only if object type doesn't exist
if (extensions.includes(node.name.value) ||
types.includes(node.name.value)) {
return node;
}
return {
...node,
kind: graphql.Kind.OBJECT_TYPE_DEFINITION,
};
},
});
});
}
function treeshakeTypesDefs(originalSource, sources) {
const namedTypes = originalSource.definitions.filter(isNamedTypeDefinition);
const typesToVisit = namedTypes.map((def) => def.name.value);
const rootFields = namedTypes.reduce((acc, node) => {
const typeName = node.name.value;
if (isRootType(typeName) && hasFields(node)) {
if (!acc[typeName]) {
acc[typeName] = [];
}
node.fields.forEach((field) => {
acc[typeName].push(field.name.value);
});
}
return acc;
}, {});
const schema = graphql.concatAST([originalSource].concat(sources));
const involvedTypes = new Set(visitTypes(schema, typesToVisit, rootFields));
return {
kind: graphql.Kind.DOCUMENT,
definitions: schema.definitions.filter((def) => {
var _a, _b;
if (isNamedTypeDefinition(def)) {
const typeName = def.name.value;
if (!involvedTypes.has(def.name.value)) {
return false;
}
if ((_a = rootFields[typeName]) === null || _a === void 0 ? void 0 : _a.length) {
const rootType = def;
if ((_b = rootType.fields) === null || _b === void 0 ? void 0 : _b.every((field) => !rootFields[typeName].includes(field.name.value))) {
return false;
}
}
}
return true;
}),
};
}
function isNamedTypeDefinition(def) {
return (!!def &&
def.kind !== graphql.Kind.SCHEMA_DEFINITION &&
def.kind !== graphql.Kind.SCHEMA_EXTENSION);
}
function visitTypes(schema, types, rootFields) {
const visitedTypes = [];
const scalars = schema.definitions
.filter((def) => def.kind === graphql.Kind.SCALAR_TYPE_DEFINITION ||
def.kind === graphql.Kind.SCALAR_TYPE_EXTENSION)
.map((def) => def.name.value);
for (const typeName of types) {
collectType(typeName);
}
return visitedTypes;
function collectField(field, parentTypeName) {
var _a;
if (parentTypeName &&
isRootType(parentTypeName) &&
((_a = rootFields[parentTypeName]) === null || _a === void 0 ? void 0 : _a.length) &&
!rootFields[parentTypeName].includes(field.name.value)) {
return;
}
collectType(resolveType(field.type));
if (field.arguments) {
field.arguments.forEach((arg) => {
collectType(resolveType(arg.type));
});
}
if (field.directives) {
field.directives.forEach((directive) => {
collectType(directive.name.value);
});
}
}
function collectType(typeName) {
if (visitedTypes.includes(typeName)) {
return;
}
if (isScalar(typeName)) {
visitedTypes.push(typeName);
return;
}
const types = findTypes(typeName);
visitedTypes.push(typeName);
types.forEach((type) => {
if (hasFields(type)) {
type.fields.forEach((field) => {
collectField(field, typeName);
});
}
if (hasTypes(type)) {
type.types.forEach((t) => {
collectType(resolveType(t));
});
}
if (hasInterfaces(type)) {
type.interfaces.forEach((i) => {
collectType(resolveType(i));
});
}
});
}
function resolveType(type) {
if (type.kind === 'ListType') {
return resolveType(type.type);
}
if (type.kind === 'NonNullType') {
return resolveType(type.type);
}
return type.name.value;
}
function isScalar(name) {
return scalars
.concat(['String', 'Boolean', 'Int', 'ID', 'Float'])
.includes(name);
}
function findTypes(typeName) {
const types = schema.definitions.filter((def) => isNamedTypeDefinition(def) && def.name.value === typeName);
if (!types.length) {
throw new Error(`Missing type "${typeName}"`);
}
return types;
}
}
function hasInterfaces(def) {
return (hasPropValue(def, 'interfaces') &&
[
graphql.Kind.OBJECT_TYPE_DEFINITION,
graphql.Kind.OBJECT_TYPE_EXTENSION,
graphql.Kind.INTERFACE_TYPE_DEFINITION,
graphql.Kind.INTERFACE_TYPE_EXTENSION,
].includes(def.kind));
}
function hasTypes(def) {
return ([graphql.Kind.UNION_TYPE_DEFINITION, graphql.Kind.UNION_TYPE_EXTENSION].includes(def.kind) && hasPropValue(def, 'types'));
}
function hasFields(def) {
return ([
graphql.Kind.OBJECT_TYPE_DEFINITION,
graphql.Kind.OBJECT_TYPE_EXTENSION,
graphql.Kind.INTERFACE_TYPE_DEFINITION,
graphql.Kind.INTERFACE_TYPE_EXTENSION,
graphql.Kind.INPUT_OBJECT_TYPE_DEFINITION,
graphql.Kind.INPUT_OBJECT_TYPE_EXTENSION,
].includes(def.kind) && hasPropValue(def, 'fields'));
}
function hasPropValue(obj, prop) {
return Object.prototype.hasOwnProperty.call(obj, prop) && obj[prop];
}
function isRootType(typeName) {
return (typeName === 'Query' ||
typeName === 'Mutation' ||
typeName === 'Subscription');
}
function testInjector(providers) {
const resolvedProviders = ReflectiveInjector.resolve([
{ provide: CONTEXT, useValue: {} },
...providers,
]);
const injector = ReflectiveInjector.createFromResolved({
name: 'test',
providers: resolvedProviders,
});
injector.instantiateAll();
return injector;
}
function readProviderOptions(provider) {
return readInjectableMetadata(provider, true).options;
}
function execute(app, inputs, options) {
const executor = app.createExecution(options);
return executor({
schema: app.schema,
...inputs,
});
}
function provideEmpty(token) {
return {
provide: token,
useValue: {},
};
}
const testkit = {
mockApplication,
mockModule,
testModule,
testInjector,
readProviderOptions,
provideEmpty,
execute,
};
exports.CONTEXT = CONTEXT;
exports.ExecutionContext = ExecutionContext;
exports.Inject = Inject;
exports.Injectable = Injectable;
exports.InjectionToken = InjectionToken;
exports.Injector = Injector;
exports.MODULE_ID = MODULE_ID;
exports.Optional = Optional;
exports.createApplication = createApplication;
exports.createModule = createModule;
exports.forwardRef = forwardRef;
exports.gql = gql;
exports.metadataFactory = metadataFactory;
exports.testkit = testkit;
;