From 9db540c55505a2678daf86af746b49ec4a66bd93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20=C5=A0vanda?= Date: Mon, 22 Jan 2024 14:25:17 +0100 Subject: [PATCH] Expose run options on decorator --- .../06_proxy-providers.md | 5 +- .../lib/cls-initializers/use-cls.decorator.ts | 2 +- packages/core/src/lib/cls.options.ts | 135 +++++++----------- packages/core/src/lib/cls.service.ts | 17 +-- 4 files changed, 59 insertions(+), 100 deletions(-) diff --git a/docs/docs/03_features-and-use-cases/06_proxy-providers.md b/docs/docs/03_features-and-use-cases/06_proxy-providers.md index 29b5eb85..17dcc2f3 100644 --- a/docs/docs/03_features-and-use-cases/06_proxy-providers.md +++ b/docs/docs/03_features-and-use-cases/06_proxy-providers.md @@ -226,7 +226,7 @@ export class CronController { #### With @UseCls() -The `resolveProxyProviders` is set to `false` by default on the `@UseCls` decorator. To achieve the same behavior using it, you must set it to `true`. +Since the `@UseCls()` decorator wraps the function body with `cls.run()` automatically, you can use the `setup` function to prepare the context. The Proxy Providers will be resolved after the `setup` phase. @@ -238,9 +238,8 @@ export class CronController { @Cron('45 * * * * *') @UseCls({ // highlight-start - resolveProxyProviders: true, setup: (cls) => { - this.cls.set('some-key', 'some-value'); + cls.set('some-key', 'some-value'); }, // highlight-end }) diff --git a/packages/core/src/lib/cls-initializers/use-cls.decorator.ts b/packages/core/src/lib/cls-initializers/use-cls.decorator.ts index 524c9d14..2ad8f1de 100644 --- a/packages/core/src/lib/cls-initializers/use-cls.decorator.ts +++ b/packages/core/src/lib/cls-initializers/use-cls.decorator.ts @@ -44,7 +44,7 @@ export function UseCls( ); } descriptor.value = function (...args: TArgs) { - return cls.run(async () => { + return cls.run(options.runOptions ?? {}, async () => { if (options.generateId) { const id = await options.idGenerator?.apply(this, args); cls.set(CLS_ID, id); diff --git a/packages/core/src/lib/cls.options.ts b/packages/core/src/lib/cls.options.ts index aebcf9a1..f0c7906c 100644 --- a/packages/core/src/lib/cls.options.ts +++ b/packages/core/src/lib/cls.options.ts @@ -63,28 +63,27 @@ export interface ClsModuleAsyncOptions extends Pick { plugins?: ClsPlugin[]; } -export class ClsMiddlewareOptions { +export class ClsContextOptions { /** - * whether to mount the middleware to every route + * Sets the behavior of nested CLS context creation. Has no effect if no parent context exists. + * + * `inherit` (default) - Run the callback with a shallow copy of the parent context. + * Assignments to top-level properties will not be reflected in the parent context. + * + * `reuse` - Reuse existing context without creating a new one. + * + * `override` - Run the callback with an new empty context. + * Warning: No values from the parent context will be accessible. */ - mount?: boolean; // default false + ifNested?: 'inherit' | 'reuse' | 'override' = 'inherit'; +} +export class ClsInitializerCommonOptions { /** * whether to automatically generate request ids */ generateId?: boolean; // default false - /** - * the function to generate request ids inside the middleware - */ - idGenerator?: (req: any) => string | Promise = getRandomString; - - /** - * Function that executes after the CLS context has been initialised. - * It can be used to put additional variables in the CLS context. - */ - setup?: (cls: ClsService, req: any, res: any) => void | Promise; - /** * Whether to resolve proxy providers as a part * of the CLS context registration @@ -100,6 +99,24 @@ export class ClsMiddlewareOptions { * Default: `true` */ initializePlugins? = true; +} + +export class ClsMiddlewareOptions extends ClsInitializerCommonOptions { + /** + * whether to mount the middleware to every route + */ + mount?: boolean; // default false + + /** + * the function to generate request ids for the CLS context + */ + idGenerator?: (req: any) => string | Promise = getRandomString; + + /** + * Function that executes after the CLS context has been initialized. + * It can be used to put additional variables in the CLS context. + */ + setup?: (cls: ClsService, req: any, res: any) => void | Promise; /** * Whether to store the Request object to the CLS @@ -124,17 +141,12 @@ export class ClsMiddlewareOptions { useEnterWith? = false; } -export class ClsGuardOptions { +export class ClsGuardOptions extends ClsInitializerCommonOptions { /** * whether to mount the guard globally */ mount?: boolean; // default false - /** - * whether to automatically generate request ids - */ - generateId?: boolean; // default false - /** * the function to generate request ids inside the guard */ @@ -149,35 +161,14 @@ export class ClsGuardOptions { cls: ClsService, context: ExecutionContext, ) => void | Promise; - - /** - * Whether to resolve proxy providers as a part - * of the CLS context registration - * - * Default: `true` - */ - resolveProxyProviders? = true; - - /** - * Whether to run the onClsInit hook for plugins as a part - * of the CLS context registration (runs before `resolveProxyProviders` just after `setup`) - * - * Default: `true` - */ - initializePlugins? = true; } -export class ClsInterceptorOptions { +export class ClsInterceptorOptions extends ClsInitializerCommonOptions { /** * whether to mount the interceptor globally */ mount?: boolean; // default false - /** - * whether to automatically generate request ids - */ - generateId?: boolean; // default false - /** * the function to generate request ids inside the interceptor */ @@ -192,64 +183,48 @@ export class ClsInterceptorOptions { cls: ClsService, context: ExecutionContext, ) => void | Promise; - - /** - * Whether to resolve proxy providers as a part - * of the CLS context registration - * - * Default: `true` - */ - resolveProxyProviders? = true; - - /** - * Whether to run the onClsInit hook for plugins as a part - * of the CLS context registration (runs before `resolveProxyProviders` just after `setup`) - * - * Default: `true` - */ - initializePlugins? = true; } -export class ClsDecoratorOptions { +export class ClsDecoratorOptions< + T extends any[], +> extends ClsInitializerCommonOptions { /** - * Whether to automatically generate request ids + * Additional options for the `ClsService#run` method. */ - generateId?: boolean; // default false + runOptions?: ClsContextOptions; /** * The function to generate request ids inside the interceptor. * * Takes the same parameters in the same order as the decorated function. * + * If you use a `function` expression, it will executed with the `this` context of the decorated class instance. + * to get type safety, use: + * + * `idGenerator: function (this: MyClass, ...args) { ... }` + * * Note: To avoid type errors, you must list all parameters, even if they're not used, - * or type the decorator as `@UseCls<[arg1: Type1, arg2: Type2]>()` + * or type the decorator as: + * + * `@UseCls<[arg1: Type1, arg2: Type2]>()` */ idGenerator?: (...args: T) => string | Promise = getRandomString; /** - * Function that executes after the CLS context has been initialised. + * Function that executes after the CLS context has been initialized. * Takes ClsService as the first parameter and then the same parameters in the same order as the decorated function. * - * Note: To avoid type errors, you must list all parameters, even if they're not used, - * or type the decorator as `@UseCls<[arg1: Type1, arg2: Type2]>()` - */ - setup?: (cls: ClsService, ...args: T) => void | Promise; - - /** - * Whether to resolve proxy providers as a part - * of the CLS context registration + * If you use a `function` expression, it will executed with the `this` context of the decorated class instance. + * to get type safety, use: * - * Default: `false` - */ - resolveProxyProviders? = false; - - /** - * Whether to run the onClsInit hook for plugins as a part - * of the CLS context registration (runs before `resolveProxyProviders` just after `setup`) + * `setup: function (this: MyClass, cls: ClsService, ...args) { ... }` * - * Default: `false` + * Note: To avoid type errors, you must list all parameters, even if they're not used, + * or type the decorator as: + * + * `@UseCls<[arg1: Type1, arg2: Type2]>()` */ - initializePlugins? = false; + setup?: (cls: ClsService, ...args: T) => void | Promise; } export interface ClsStore { diff --git a/packages/core/src/lib/cls.service.ts b/packages/core/src/lib/cls.service.ts index 77e2ead1..05564344 100644 --- a/packages/core/src/lib/cls.service.ts +++ b/packages/core/src/lib/cls.service.ts @@ -10,22 +10,7 @@ import { } from '../types/type-if-type.type'; import { getValueFromPath, setValueFromPath } from '../utils/value-from-path'; import { CLS_ID } from './cls.constants'; -import type { ClsStore } from './cls.options'; - -export class ClsContextOptions { - /** - * Sets the behavior of nested CLS context creation. Has no effect if no parent context exists. - * - * `inherit` (default) - Run the callback with a shallow copy of the parent context. - * Assignments to top-level properties will not be reflected in the parent context. - * - * `reuse` - Reuse existing context without creating a new one. - * - * `override` - Run the callback with an new empty context. - * Warning: No values from the parent context will be accessible. - */ - ifNested?: 'inherit' | 'reuse' | 'override' = 'inherit'; -} +import { ClsContextOptions, ClsStore } from './cls.options'; export class ClsService { constructor(private readonly als: AsyncLocalStorage) {}