nestjs-cls
Version:
A continuation-local storage module compatible with NestJS's dependency injection.
185 lines (184 loc) • 7.77 kB
JavaScript
;
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 prop.bind(provider);
}
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;
};
}