UNPKG

nestjs-cls

Version:

A continuation-local storage module compatible with NestJS's dependency injection.

185 lines (184 loc) 7.79 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ProxyProviderManager = void 0; const core_1 = require("@nestjs/core"); const cls_service_globals_1 = require("../cls-service.globals"); const get_proxy_provider_symbol_1 = require("./get-proxy-provider-symbol"); const proxy_provider_resolver_1 = require("./proxy-provider-resolver"); const proxy_provider_constants_1 = require("./proxy-provider.constants"); const proxy_provider_exceptions_1 = require("./proxy-provider.exceptions"); const proxy_provider_functions_1 = require("./proxy-provider.functions"); const proxy_provider_utils_1 = require("./proxy-provider.utils"); class ProxyProviderManager { /** * Reset method called by the ClsModule#forRoot/Async before the init method is called. * * It ensures that the internal state is reset to support testing multiple setups in the same process. * * Otherwise the proxy provider map would leak from one test to the next. * * FUTURE: * A better approach would be to store the proxy provider map in a local instance, instead of a global static variable. */ static reset() { this.proxyProviderMap.clear(); } /** * Init method called by the ClsRootModule#onModuleInit after it is certain * that all Proxy Providers have been registered. */ static init() { // If there are no proxy providers, there is no need to set up the resolver. if (this.proxyProviderMap.size === 0) { return; } this.proxyProviderResolver = new proxy_provider_resolver_1.ProxyProvidersResolver(this.clsService, this.proxyProviderMap); } static createProxyProvider(options) { const providerToken = this.getProxyProviderToken(options); const providerSymbol = (0, get_proxy_provider_symbol_1.getProxyProviderSymbol)(providerToken); let strict = undefined; if ((0, proxy_provider_functions_1.isProxyClassProviderOptions)(options)) { const metadata = this.getInjectableProxyMetadata(options.useClass); strict = metadata.strict; } strict = options.strict ?? strict ?? false; const proxy = this.createProxy(providerSymbol, { strict, type: options.type, }); let proxyProvider; if ((0, proxy_provider_functions_1.isProxyClassProviderOptions)(options)) { const dependencies = (0, proxy_provider_utils_1.reflectAllClassDependencies)(options.useClass); proxyProvider = { provide: providerToken, inject: [core_1.ModuleRef], useFactory: (moduleRef) => { const providerOptions = { symbol: providerSymbol, moduleRef, dependencies, provide: options.provide, useClass: options.useClass, }; this.proxyProviderMap.set(providerSymbol, providerOptions); return proxy; }, }; } else { const dependencies = options.inject ?? []; proxyProvider = { provide: providerToken, inject: options.inject ?? [], useFactory: (...injected) => { const providerOptions = { symbol: providerSymbol, injected, dependencies, provide: options.provide, useFactory: options.useFactory, }; this.proxyProviderMap.set(providerSymbol, providerOptions); return proxy; }, }; } return proxyProvider; } static createProxyProviderFromExistingKey(providerKey, options) { const proxy = this.createProxy(providerKey, options); const proxyProvider = { provide: providerKey, useValue: proxy, }; return proxyProvider; } static getInjectableProxyMetadata(Provider) { const metadata = Reflect.getMetadata(proxy_provider_constants_1.CLS_PROXY_METADATA_KEY, Provider); if (!metadata) { throw proxy_provider_exceptions_1.ProxyProviderNotDecoratedException.create(Provider); } return metadata; } static getProxyProviderToken(options) { return (options.provide ?? options.useClass); } static createProxy(providerKey, { type = 'object', strict = false } = {}) { const checkAccess = getPropertyAccessChecker(providerKey, strict); const getProvider = () => this.clsService.get()?.[providerKey]; const getProviderOrEmpty = () => getProvider() ?? {}; const baseType = type === 'function' ? () => null : {}; return new Proxy(baseType, { apply(_, __, argArray) { const provider = getProvider() ?? checkAccess(); return provider.apply(provider, argArray); }, get(_, propName) { const provider = getProvider() ?? checkAccess(propName); const prop = provider[propName]; if (typeof prop === 'function') { return Object.assign(prop.bind(provider), prop); } else { return prop; } }, set(_, propName, value) { const provider = getProvider() ?? checkAccess(propName); return Reflect.set(provider, propName, value); }, ownKeys() { return Reflect.ownKeys(getProviderOrEmpty()); }, getPrototypeOf() { return Reflect.getPrototypeOf(getProviderOrEmpty()); }, getOwnPropertyDescriptor(_, prop) { return Reflect.getOwnPropertyDescriptor(getProviderOrEmpty(), prop); }, has(_, prop) { return Reflect.has(getProviderOrEmpty(), prop); }, }); } static async resolveProxyProviders(providerSymbols) { await this.proxyProviderResolver?.resolve(providerSymbols); } } exports.ProxyProviderManager = ProxyProviderManager; ProxyProviderManager.clsService = cls_service_globals_1.globalClsService; ProxyProviderManager.proxyProviderMap = new Map(); const allowedPropertyAccess = new Set([ // Allow all default properties of Object.prototype // as they're often used by other libraries for duck typing ...Object.getOwnPropertyNames(Object.prototype), // used by Nest to check for async providers 'then', // checked by Nest to trigger lifecycle hooks 'onModuleInit', 'onApplicationBootstrap', 'onModuleDestroy', 'beforeApplicationShutdown', 'onApplicationShutdown', ]); /** * To enable strict mode, which means throwing an error when a property on an unresolved proxy provider is accessed, * we still need to allow access to some properties in order for Nest to bootstrap properly. * * This is because Nest checks for the presence of some properties of all providers in the bootstrap process, * but at that time, all Proxy providers are still undefined. */ function getPropertyAccessChecker(providerKey, strict) { const empty = {}; if (!strict) { return () => empty; } return function checkAllowedPropertyAccess(propName) { if (!propName || !allowedPropertyAccess.has(propName.toString())) { throw proxy_provider_exceptions_1.ProxyProviderNotResolvedException.create(providerKey, propName?.toString()); } return empty; }; }