diff --git a/docs/docs/04_api/01_service-interface.md b/docs/docs/04_api/01_service-interface.md index f2529aa7..68fa5d45 100644 --- a/docs/docs/04_api/01_service-interface.md +++ b/docs/docs/04_api/01_service-interface.md @@ -44,9 +44,19 @@ The `S` type parameter is used as the type of custom `ClsStore`. - **_`isActive`_**`(): boolean` Whether the current code runs within an active CLS context. +The following methods only apply to the [Proxy](../03_features-and-use-cases/06_proxy-providers.md) feature: + +- **_`getProxy`_**`(proxyToken: any): any` + Retrieve a Proxy provider from the CLS context based on its injection token. + +- **_`setProxy`_**`(proxyToken: any, value? any): any` + Replace an instance of a Proxy provider in the CLS context based on its injection token. + - **_`resolveProxyProviders`_**`(): Promise` Manually trigger resolution of Proxy Providers. +The following methods involve the [plugin lifecycle](../06_plugins/02_plugin-api.md): + - **_`initializePlugins`_**`(): Promise` Manually trigger `onClsInit` hooks of registered plugins. diff --git a/packages/core/src/lib/cls.service.ts b/packages/core/src/lib/cls.service.ts index 130ab068..ef251e65 100644 --- a/packages/core/src/lib/cls.service.ts +++ b/packages/core/src/lib/cls.service.ts @@ -11,6 +11,7 @@ import { import { getValueFromPath, setValueFromPath } from '../utils/value-from-path'; import { CLS_ID } from './cls.constants'; import { ClsContextOptions, ClsStore } from './cls.options'; +import { getProxyProviderSymbol } from './proxy-provider/get-proxy-provider-symbol'; export class ClsService { constructor(private readonly als: AsyncLocalStorage) { @@ -196,6 +197,26 @@ export class ClsService { return !!this.als.getStore(); } + /** + * Retrieve a Proxy provider from the CLS context + * based on its injection token. + */ + getProxy(proxyToken: string | symbol): T; + getProxy(proxyToken: new (...args: any) => T): T; + getProxy(proxyToken: any) { + return this.get(getProxyProviderSymbol(proxyToken)); + } + + /** + * Replace an instance of a Proxy provider in the CLS context + * based on its injection token. + */ + setProxy(proxyToken: string | symbol, value: T): void; + setProxy(proxyToken: new (...args: any) => T, value: T): void; + setProxy(proxyToken: any, value: any) { + return this.set(getProxyProviderSymbol(proxyToken), value); + } + /** * Use to manually trigger resolution of Proxy Providers * in case `resolveProxyProviders` is not enabled in the enhancer. diff --git a/packages/core/src/lib/proxy-provider/get-proxy-provider-symbol.ts b/packages/core/src/lib/proxy-provider/get-proxy-provider-symbol.ts new file mode 100644 index 00000000..84d302a2 --- /dev/null +++ b/packages/core/src/lib/proxy-provider/get-proxy-provider-symbol.ts @@ -0,0 +1,11 @@ +/** + * Returns a symbol under which the given proxy provider is stored in the CLS. + */ +export function getProxyProviderSymbol( + proxyToken: symbol | string | { name: string }, +) { + if (typeof proxyToken === 'symbol') return proxyToken; + if (typeof proxyToken === 'string') return Symbol.for(proxyToken); + if (proxyToken.name) return Symbol.for(proxyToken.name); + return Symbol.for(proxyToken.toString()); +} diff --git a/packages/core/src/lib/proxy-provider/proxy-provider-manager.ts b/packages/core/src/lib/proxy-provider/proxy-provider-manager.ts index 161a7b50..ebf1f446 100644 --- a/packages/core/src/lib/proxy-provider/proxy-provider-manager.ts +++ b/packages/core/src/lib/proxy-provider/proxy-provider-manager.ts @@ -2,6 +2,7 @@ import { FactoryProvider, Type, ValueProvider } from '@nestjs/common'; import { ModuleRef } from '@nestjs/core'; import { UnknownDependenciesException } from '@nestjs/core/errors/exceptions/unknown-dependencies.exception'; import { globalClsService } from '../cls-service.globals'; +import { getProxyProviderSymbol } from './get-proxy-provider-symbol'; import { CLS_PROXY_METADATA_KEY } from './proxy-provider.constants'; import { ProxyProviderNotDecoratedException, @@ -26,13 +27,14 @@ export class ProxyProviderManager { private static proxyProviderMap = new Map(); static createProxyProvider(options: ClsModuleProxyProviderOptions) { - const providerSymbol = this.getProxyProviderSymbol(options); + const providerToken = this.getProxyProviderToken(options); + const providerSymbol = getProxyProviderSymbol(providerToken); const proxy = this.createProxy( providerSymbol, (options as ClsModuleProxyFactoryProviderOptions).type ?? 'object', ); const proxyProvider: FactoryProvider = { - provide: this.getProxyProviderToken(options), + provide: providerToken, inject: [ ModuleRef, ...((options as ClsModuleProxyFactoryProviderOptions).inject ?? @@ -70,21 +72,6 @@ export class ProxyProviderManager { return proxyProvider; } - private static getProxyProviderSymbol( - options: ClsModuleProxyProviderOptions, - ) { - const maybeExistingSymbol = - typeof options.provide == 'symbol' ? options.provide : undefined; - return ( - maybeExistingSymbol ?? - Symbol.for( - options.provide?.toString() ?? - (options as ClsModuleProxyClassProviderOptions).useClass - .name, - ) - ); - } - private static getProxyProviderToken( options: ClsModuleProxyProviderOptions, ) { diff --git a/packages/core/test/proxy-providers/proxy-providers.spec.ts b/packages/core/test/proxy-providers/proxy-providers.spec.ts index b8f3d8a4..9621b28b 100644 --- a/packages/core/test/proxy-providers/proxy-providers.spec.ts +++ b/packages/core/test/proxy-providers/proxy-providers.spec.ts @@ -11,6 +11,7 @@ import { ClsModule, ClsModuleOptions, ClsService, + CLS_ID, InjectableProxy, } from '../../src'; import { @@ -44,12 +45,16 @@ class RequestScopedClass { } const FACTORY_PROVIDER = 'FACTORY_PROVIDER'; + function requestScopedFactory(injected: InjectedClass, cls: ClsService) { return { id: cls.getId(), getInjected: () => injected.property, }; } +type RequestScopedFactoryResult = Awaited< + ReturnType +>; function randomString() { return Math.random().toString(36).slice(-10); @@ -60,7 +65,7 @@ class TestController { constructor( private readonly rsc: RequestScopedClass, @Inject(FACTORY_PROVIDER) - private readonly rsf: Awaited>, + private readonly rsf: RequestScopedFactoryResult, ) {} @Get('/hello') @@ -120,7 +125,7 @@ async function getTestApp(forRoorOptions: ClsModuleOptions) { return app; } -describe('Proxy providers', () => { +describe('Injecting Proxy providers', () => { let app: INestApplication; it('works with middleware', async () => { @@ -145,6 +150,46 @@ describe('Proxy providers', () => { }); }); +describe('Proxy providers from CLS', () => { + let app: INestApplication; + + it('allows getting a Class Proxy provider from CLS', async () => { + app = await getTestApp({}); + const cls = app.get(ClsService); + const id = randomString(); + await cls.runWith({ [CLS_ID]: id }, async () => { + await cls.resolveProxyProviders(); + const rsc = cls.getProxy(RequestScopedClass); + expect(rsc.id).toEqual(id); + }); + }); + + it('allows getting a factory Proxy provider from CLS', async () => { + app = await getTestApp({}); + const cls = app.get(ClsService); + const id = randomString(); + await cls.runWith({ [CLS_ID]: id }, async () => { + await cls.resolveProxyProviders(); + const rsc = + cls.getProxy(FACTORY_PROVIDER); + expect(rsc.id).toEqual(id); + }); + }); + + it('allows setting a Class Proxy provider in CLS', async () => { + app = await getTestApp({}); + const cls = app.get(ClsService); + const id = randomString(); + await cls.runWith({ [CLS_ID]: id }, async () => { + await cls.resolveProxyProviders(); + const rsc = new RequestScopedClass(cls, new InjectedClass()); + cls.setProxy(RequestScopedClass, rsc); + const rscFromCls = cls.getProxy(RequestScopedClass); + expect(rscFromCls).toEqual(rsc); + }); + }); +}); + describe('Edge cases', () => { it('proxy should allow setting falsy value', async () => { const clsService = globalClsService;