UNPKG

graphql-modules

Version:

Create reusable, maintainable, testable and extendable GraphQL modules

2,481 lines 92.1 kB
'use strict';

Object.defineProperty(exports, '__esModule', { value: true });

const schema = require('@graphql-tools/schema');
const graphql = require('graphql');
const async_context = require('#async-context');
const wrap = require('@graphql-tools/wrap');
const ramda = require('ramda');

function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }

const async_context__default = /*#__PURE__*/_interopDefaultLegacy(async_context);

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;
}
/**
 * Preserves provider inference for separately declared provider arrays.
 */
function defineProviders(providers) {
    return providers;
}
exports.Scope = void 0;
(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');

/**
 * Installs a getter-based view of `refs.context` onto `target` — every
 * string/symbol-keyed property of `source` (captured at call time) becomes
 * an accessor on `target` that delegates to `refs.context` dynamically.
 *
 * The view therefore holds no user data of its own; once `refs.context`
 * is nulled (in `ɵdestroy`), every accessor returns `undefined` and the
 * original user-context object is no longer reachable from the view.
 *
 * The helper lives at module scope (rather than inside `contextBuilder`)
 * so it doesn't close over the per-operation `context` parameter — that
 * keeps the operation's V8 scope free of any captured reference to the
 * user context other than `refs`, which is what makes the leak fix work.
 */
function defineUserContextAccessors(target, source, refs) {
    if (source === undefined || source === null)
        return;
    const define = (key) => {
        Object.defineProperty(target, key, {
            enumerable: true,
            configurable: true,
            get() {
                return refs.context === undefined
                    ? undefined
                    : refs.context[key];
            },
            set(value) {
                if (refs.context !== undefined) {
                    refs.context[key] = value;
                }
            },
        });
    };
    for (const key of Object.keys(source))
        define(key);
    for (const sym of Object.getOwnPropertySymbols(source))
        define(sym);
}
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]);
                }
            });
        }
        // See https://github.com/graphql-hive/graphql-modules/pull/2681
        //
        // Heavy per-operation values (the user-supplied `context`, and the
        // `appContext` we derive from it) are reachable via this function's
        // closure scope, which is shared by every closure created below of it —
        // including the one used by AsyncLocalStorae frame.
        //
        // Since https://github.com/nodejs/node/pull/48528, any async resource
        // scheduled while we're inside that `AsyncLocalStorage.run` (like global setTimeout,
        // an telemetry exporter timer, a deferred promise reaction, …)
        // snapshots and captures the current `AsyncContextFrame`.
        //
        // If that resource outlives the operation (=defined globally, or just have a longer lifetime),
        // then the snapshot keeps the AsyncLocalStorage-stored object alive, which keeps this scope alive — which
        // pins `context` forever.
        //
        // Routing the heavy values through a mutable holder lets `ɵdestroy`
        // detach them from the (still pinned) scope by nulling the holder's
        // properties; the closures continue to exist but no longer reach
        // anything that matters.
        const refs = {
            context,
            appContext: undefined,
        };
        attachGlobalProvidersMap({
            injector: appInjector,
            globalProvidersMap: singletonGlobalProvidersMap,
            moduleInjectorGetter(moduleId) {
                return modulesMap.get(moduleId).injector;
            },
        });
        appInjector.setExecutionContextGetter(function executionContextGetter() {
            var _a;
            return (((_a = async_context__default["default"].getAsyncContext()) === null || _a === void 0 ? void 0 : _a.getApplicationContext()) ||
                refs.appContext);
        });
        function createModuleExecutionContextGetter(moduleId) {
            return function moduleExecutionContextGetter() {
                var _a;
                return (((_a = async_context__default["default"].getAsyncContext()) === null || _a === void 0 ? void 0 : _a.getModuleContext(moduleId)) ||
                    (refs.context ? getModuleContext(moduleId, refs.context) : undefined));
            };
        }
        modulesMap.forEach((mod, moduleId) => {
            mod.injector.setExecutionContextGetter(createModuleExecutionContextGetter(moduleId));
        });
        // The cached `CONTEXT` injection value. `ReflectiveInjector` caches
        // the resolved instance forever in `_objs[i]`, so if we returned the
        // raw `refs.context` here that cache would pin the user's context
        // object even after `ɵdestroy` nulls `refs.context`. Returning a
        // view that *reads through* `refs.context` keeps the cached object
        // payload-free. See `defineUserContextAccessors` at module scope.
        const contextView = Object.create(null);
        defineUserContextAccessors(contextView, context, refs);
        // 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,
                    useFactory: () => contextView,
                    deps: [],
                },
            ])),
            parent: appInjector,
        });
        // Create a context for application-level ExecutionContext
        refs.appContext = merge(refs.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];
        }
        // sharedContext — exposed publicly as `env.context`. Same shape as a
        // `merge(context, { ɵgetModuleContext })` would produce, but its
        // user-context fields are getter accessors over `refs.context`
        // (see `defineUserContextAccessors` at module scope) rather than
        // independent shallow copies. Once `refs.context` is nulled the user
        // payload is unreachable from `sharedContext` too, without us having
        // to mutate the object's keys in `ɵdestroy`.
        const sharedContext = {
            // It's a function that is used in module's context creation
            ɵgetModuleContext: getModuleContext,
        };
        defineUserContextAccessors(sharedContext, context, refs);
        attachGlobalProvidersMap({
            injector: operationAppInjector,
            globalProvidersMap: operationGlobalProvidersMap,
            moduleInjectorGetter(moduleId) {
                return getModuleContext(moduleId, sharedContext).injector;
            },
        });
        const env = {
            ɵdestroy: once(() => {
                providersToDestroy.forEach(([injector, keyId]) => {
                    // If provider was instantiated
                    if (injector._isObjectDefinedByKeyId(keyId)) {
                        // call its OnDestroy hook
                        injector._getObjByKeyId(keyId).onDestroy();
                    }
                });
                contextCache = {};
                providersToDestroy = [];
                // All retention paths from this closure scope to the
                // user-supplied `context` route through `refs`:
                //   - `sharedContext` (= env.context) and the cached CONTEXT
                //     injector value are getter-based views over `refs.context`,
                //   - `appInjector._executionContextGetter` reads through
                //     `refs.appContext`.
                // Nulling the holder slots is the only cleanup required —
                // public-facing identities (`sharedContext`, `ɵinjector`) keep
                // working for any post-destroy reads, but the heavy user
                // payload becomes unreachable from this scope.
                refs.context = undefined;
                refs.appContext = undefined;
                // The function parameter `context` is itself a captured binding
                // in this lexical scope. Even though no surviving closure reads
                // it directly (everything goes through `refs`), V8's scope info
                // may keep the binding alive for as long as any closure in this
                // scope is reachable. Reassigning it here makes that binding
                // empty too — without this, the original user `context` object
                // remains pinned by the scope itself.
                context = undefined;
            }),
            ɵinjector: operationAppInjector,
            context: sharedContext,
        };
        return {
            ...env,
            runWithContext(cb) {
                return async_context__default["default"].runWithAsyncContext({
                    getApplicationContext() {
                        return refs.appContext;
                    },
                    getModuleContext(moduleId) {
                        return refs.context
                            ? getModuleContext(moduleId, refs.context)
                            : undefined;
                    },
                }, cb, env);
            },
        };
    };
    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) => {
            function perform({ context, ɵdestroy: destroy, }) {
                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);
            }
            if (options === null || options === void 0 ? void 0 : options.controller) {
                // Wrap `perform` in the controller's own ALS frame so that
                // `@ExecutionContext()` reads inside the resolver — including
                // after an `await`.
                const controller = options.controller;
                return controller.ɵrunWithContext(() => perform(controller));
            }
            return contextBuilder(isNotSchema(argsOrSchema)
                ? argsOrSchema.contextValue
                : contextValue).runWithContext(perform);
        };
    };
    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) => {
            function perform({ context, ɵdestroy: destroy, }) {
                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();
                    }
                });
            }
            if (options === null || options === void 0 ? void 0 : options.controller) {
                // Wrap `perform` in the controller's own ALS frame so that
                // `@ExecutionContext()` reads inside the resolver — including
                // after an `await`.
                const controller = options.controller;
                return controller.ɵrunWithContext(() => perform(controller));
            }
            return contextBuilder(isNotSchema(argsOrSchema)
                ? argsOrSchema.contextValue
                : contextValue).runWithContext(perform);
        };
    };
    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, { context, ɵdestroy: destroy }) {
            if (!ctx[CONTEXT_ID]) {
                ctx[CONTEXT_ID] = uniqueId((id) => !sessions[id]);
                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 and run within it
                return contextBuilder(input.context).runWithContext((env) => {
                    const { context, destroy } = getSession(input.context, env);
                    // 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,
            ɵrunWithContext: operation.runWithContext,
        };
    };
}

/**
 * @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;
                            }
                            if (isDefined(obj[fieldName].extensions)) {
                                // Do NOT add a resolve if one is not specified, it will cause
                                // change in behavior in systems like `grafast`
                                resolvers[typeName][fieldName].extensions =
                                    obj[fieldName].extensions;
                            }
                        }
                    }
                }
            }
        }
    }
    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;
                }
                // extensions
                if (isDefined(resolver.extensions)) {
                    if (container[typeName][fieldName].extensions) {
                        throw new ResolverDuplicatedError(`Duplicated resolver of "${typeName}.${fieldName}" (extensions object)`, useLocation({ dirname: config.dirname, id: config.id }));
                    }
                    writeResolverMetadata(resolver.extensions, config);
                    container[typeName][fieldName].extensions = resolver.extensions;
                }
            }
        }
    }
}
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) ||
        isDefined(value.extensions));
}
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.defineProviders = defineProviders;
exports.forwardRef = forwardRef;
exports.gql = gql;
exports.metadataFactory = metadataFactory;
exports.testkit = testkit;