From 0d4e97bec019f00b8559f18f4867324e93582bc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20=C5=A0vanda?= <46406259+Papooch@users.noreply.github.com> Date: Mon, 29 Jan 2024 13:55:32 +0100 Subject: [PATCH] fix: symbol key access and explicit constructor error (#113) * When accessing symbol keys without context, an error is no longer thrown. * When creating ClsService without passing als, an error is thrown immediately. --- packages/core/src/lib/cls.service.spec.ts | 26 +++++++++++++++++++++++ packages/core/src/lib/cls.service.ts | 12 ++++++++--- 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/packages/core/src/lib/cls.service.spec.ts b/packages/core/src/lib/cls.service.spec.ts index 44213097..1fabc414 100644 --- a/packages/core/src/lib/cls.service.spec.ts +++ b/packages/core/src/lib/cls.service.spec.ts @@ -143,6 +143,14 @@ describe('ClsService', () => { expect(service.has(sym)).toEqual(true); }); }); + + it('checks key presence without context (string key)', () => { + expect(service.has('d')).toEqual(false); + }); + + it('checks key presence without context (symbol key)', () => { + expect(service.has(Symbol('sym'))).toEqual(false); + }); }); describe('edge cases', () => { @@ -153,6 +161,18 @@ describe('ClsService', () => { }); }); + it('returns undefined if trying to get a value without context (string key)', () => { + expect(service.get('key')).toBeUndefined(); + }); + + it('returns undefined if trying to get a value without context (symbol key)', () => { + expect(service.get(Symbol('xx'))).toBeUndefined(); + }); + + it('returns undefined if trying to get request ID without context (symbol key)', () => { + expect(service.getId()).toBeUndefined(); + }); + it('throws error if trying to set a value without context', () => { expect(() => service.set('key', 123)).toThrowError(); }); @@ -233,6 +253,12 @@ describe('ClsService', () => { }); }); + describe('manual creation', () => { + it('should fail with error', () => { + expect(() => new ClsService(undefined as any)).toThrowError(); + }); + }); + describe('typing', () => { interface IStore extends ClsStore { a: string; diff --git a/packages/core/src/lib/cls.service.ts b/packages/core/src/lib/cls.service.ts index 05564344..802da052 100644 --- a/packages/core/src/lib/cls.service.ts +++ b/packages/core/src/lib/cls.service.ts @@ -13,7 +13,13 @@ import { CLS_ID } from './cls.constants'; import { ClsContextOptions, ClsStore } from './cls.options'; export class ClsService { - constructor(private readonly als: AsyncLocalStorage) {} + constructor(private readonly als: AsyncLocalStorage) { + if (!als) { + throw new Error( + `Cannot create ClsService because no AsyncLocalStorage instance was provided.\nPlease make sure that ClsService is only provided by the ClsModule and not constructed manually or added to the providers array.`, + ); + } + } /** * Set (or overrides) a value on the CLS context. @@ -79,7 +85,7 @@ export class ClsService { const store = this.als.getStore(); if (!key) return store; if (typeof key === 'symbol') { - return store[key]; + return store?.[key]; } return getValueFromPath(store as S, key as any) as any; } @@ -95,7 +101,7 @@ export class ClsService { has(key: string | symbol): boolean { const store = this.als.getStore(); if (typeof key === 'symbol') { - return store[key] !== undefined; + return store?.[key] !== undefined; } return getValueFromPath(store as S, key as any) !== undefined; }