From 543448046811c637183f6b8e2ef58b4141cc522c Mon Sep 17 00:00:00 2001 From: "drosca@totalsoft.ro" Date: Fri, 20 Mar 2026 11:54:52 +0200 Subject: [PATCH 1/5] added local store to not have a bad closure --- .../correlation/__tests__/correlation.test.ts | 22 +++++++++++++ .../correlation/src/correlationManager.ts | 11 +++---- .../__tests__/tenant-context-accessor.text.ts | 33 +++++++++++++++++++ .../src/tenantContextAccessor.ts | 10 +++--- 4 files changed, 63 insertions(+), 13 deletions(-) diff --git a/packages/correlation/__tests__/correlation.test.ts b/packages/correlation/__tests__/correlation.test.ts index db8fca3..922eb61 100644 --- a/packages/correlation/__tests__/correlation.test.ts +++ b/packages/correlation/__tests__/correlation.test.ts @@ -46,4 +46,26 @@ describe('correlation tests:', () => { //assert expect(correlationId).toHaveLength(36) }) + + it('does not mix correlation ids between concurrent requests', async () => { + //arrange + let correlationId1: string | undefined + let correlationId2: string | undefined + + //act + await Promise.all([ + correlationManager.useCorrelationId('request-1', async () => { + await new Promise(resolve => setTimeout(resolve, 10)) + correlationId1 = correlationManager.getCorrelationId() + }), + correlationManager.useCorrelationId('request-2', async () => { + await new Promise(resolve => setTimeout(resolve, 5)) + correlationId2 = correlationManager.getCorrelationId() + }) + ]) + + //assert + expect(correlationId1).toBe('request-1') + expect(correlationId2).toBe('request-2') + }) }) diff --git a/packages/correlation/src/correlationManager.ts b/packages/correlation/src/correlationManager.ts index 44f6798..a5c9210 100644 --- a/packages/correlation/src/correlationManager.ts +++ b/packages/correlation/src/correlationManager.ts @@ -4,18 +4,15 @@ import { AsyncLocalStorage } from 'async_hooks' import { v4 } from 'uuid' -const asyncLocalStorage = new AsyncLocalStorage>() -const store = new Map() -const correlationIdKey = 'correlationId' +const asyncLocalStorage = new AsyncLocalStorage() const getCorrelationId = () => { - const correlationIdStore = asyncLocalStorage.getStore() - return correlationIdStore?.get(correlationIdKey) + return asyncLocalStorage.getStore() } async function useCorrelationId(correlationId: string | null, next: () => Promise) { - return asyncLocalStorage.run(store, async () => { - store.set(correlationIdKey, correlationId || v4()) + const correlationIdToUse = correlationId || v4() + return asyncLocalStorage.run(correlationIdToUse, async () => { return await next() }) } diff --git a/packages/multitenancy-core/__tests__/tenant-context-accessor.text.ts b/packages/multitenancy-core/__tests__/tenant-context-accessor.text.ts index fb404dc..8a7e152 100644 --- a/packages/multitenancy-core/__tests__/tenant-context-accessor.text.ts +++ b/packages/multitenancy-core/__tests__/tenant-context-accessor.text.ts @@ -35,4 +35,37 @@ describe('tenant context accessor tests:', () => { expect(tenantContext).not.toBe(undefined) expect(tenantContext).not.toHaveProperty('tenant') }) + + it('does not share tenant context between concurrent requests', async () => { + //arrange + const tenant1 = { id: 'tenant1', code: 'tenant1-code', enabled: true } + const tenant2 = { id: 'tenant2', code: 'tenant2-code', enabled: true } + let capturedContext1: ReturnType = {} as ReturnType + let capturedContext2: ReturnType = {} as ReturnType + + const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)) + + async function request1() { + await tenantContextAccessor.useTenantContext({ tenant: tenant1 }, async () => { + await delay(10) + capturedContext1 = tenantContextAccessor.getTenantContext() + await delay(10) + }) + } + + async function request2() { + await tenantContextAccessor.useTenantContext({ tenant: tenant2 }, async () => { + await delay(5) + capturedContext2 = tenantContextAccessor.getTenantContext() + await delay(15) + }) + } + + //act + await Promise.all([request1(), request2()]) + + //assert + expect(capturedContext1).toHaveProperty('tenant', tenant1) + expect(capturedContext2).toHaveProperty('tenant', tenant2) + }) }) diff --git a/packages/multitenancy-core/src/tenantContextAccessor.ts b/packages/multitenancy-core/src/tenantContextAccessor.ts index f133ef8..841d61e 100644 --- a/packages/multitenancy-core/src/tenantContextAccessor.ts +++ b/packages/multitenancy-core/src/tenantContextAccessor.ts @@ -4,16 +4,15 @@ import { AsyncLocalStorage } from 'async_hooks' import { TenantContext } from './types' -const asyncLocalStorage = new AsyncLocalStorage>() -const store = new Map() +const asyncLocalStorage = new AsyncLocalStorage() /** * Access the current tenant context in scope * @returns - the tenant context */ const getTenantContext = (): TenantContext => { - const tenantStore = asyncLocalStorage.getStore() - return tenantStore?.get('tenantContext') ?? {} + const tenantContext = asyncLocalStorage.getStore() + return tenantContext ?? {} } /** @@ -23,8 +22,7 @@ const getTenantContext = (): TenantContext => { * @returns the result of the next function */ async function useTenantContext(tenantContext: TenantContext, next: () => Promise) { - return asyncLocalStorage.run(store, async () => { - store.set('tenantContext', tenantContext) + return asyncLocalStorage.run(tenantContext, async () => { return await next() }) } From 34dd35f38439af974f6739ac98891550ab465de9 Mon Sep 17 00:00:00 2001 From: dragos rosca <26022019+dragos-rosca@users.noreply.github.com> Date: Fri, 20 Mar 2026 11:58:48 +0200 Subject: [PATCH 2/5] Fix correlation ID and Tenant handling in local store Fixed issue with correlation ID and Tenant separation in concurrent calls. --- .changeset/hip-teachers-do.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/hip-teachers-do.md diff --git a/.changeset/hip-teachers-do.md b/.changeset/hip-teachers-do.md new file mode 100644 index 0000000..85a2eeb --- /dev/null +++ b/.changeset/hip-teachers-do.md @@ -0,0 +1,6 @@ +--- +"@totalsoft/correlation": patch +"@totalsoft/multitenancy-core": patch +--- + +FIX: correlation ID and Tenant are separated between concurent calls. From 7391c83e49ee8991b1aea30b9fca338d22da7bcb Mon Sep 17 00:00:00 2001 From: "drosca@totalsoft.ro" Date: Fri, 20 Mar 2026 12:03:02 +0200 Subject: [PATCH 3/5] code review changes --- ...context-accessor.text.ts => tenant-context-accessor.test.ts} | 0 packages/multitenancy-core/src/types.ts | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename packages/multitenancy-core/__tests__/{tenant-context-accessor.text.ts => tenant-context-accessor.test.ts} (100%) diff --git a/packages/multitenancy-core/__tests__/tenant-context-accessor.text.ts b/packages/multitenancy-core/__tests__/tenant-context-accessor.test.ts similarity index 100% rename from packages/multitenancy-core/__tests__/tenant-context-accessor.text.ts rename to packages/multitenancy-core/__tests__/tenant-context-accessor.test.ts diff --git a/packages/multitenancy-core/src/types.ts b/packages/multitenancy-core/src/types.ts index 60b1b40..1cb805e 100644 --- a/packages/multitenancy-core/src/types.ts +++ b/packages/multitenancy-core/src/types.ts @@ -31,5 +31,5 @@ export interface TenantSection { } export interface TenantContext { - tenant: Tenant; + tenant?: Tenant; } From 3d8c6306567b00178002f23b5f98f66fb78bbde0 Mon Sep 17 00:00:00 2001 From: dragos rosca <26022019+dragos-rosca@users.noreply.github.com> Date: Fri, 20 Mar 2026 12:09:50 +0200 Subject: [PATCH 4/5] Update .changeset/hip-teachers-do.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .changeset/hip-teachers-do.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/hip-teachers-do.md b/.changeset/hip-teachers-do.md index 85a2eeb..d8eff27 100644 --- a/.changeset/hip-teachers-do.md +++ b/.changeset/hip-teachers-do.md @@ -3,4 +3,4 @@ "@totalsoft/multitenancy-core": patch --- -FIX: correlation ID and Tenant are separated between concurent calls. +Fix: Ensure correlation ID and tenant context are kept separate between concurrent calls. From a94f841f6ffccd741114e65eeb2e72db9a00dda8 Mon Sep 17 00:00:00 2001 From: "drosca@totalsoft.ro" Date: Fri, 20 Mar 2026 12:10:11 +0200 Subject: [PATCH 5/5] small fixed from code review --- .../multitenancy-core/__tests__/tenant-context-accessor.test.ts | 2 +- packages/multitenancy-core/src/types.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/multitenancy-core/__tests__/tenant-context-accessor.test.ts b/packages/multitenancy-core/__tests__/tenant-context-accessor.test.ts index 8a7e152..965c60b 100644 --- a/packages/multitenancy-core/__tests__/tenant-context-accessor.test.ts +++ b/packages/multitenancy-core/__tests__/tenant-context-accessor.test.ts @@ -13,7 +13,7 @@ describe('tenant context accessor tests:', () => { } //act - tenantContextAccessor.useTenantContext({ tenant }, async () => { + await tenantContextAccessor.useTenantContext({ tenant }, async () => { await inner() }) diff --git a/packages/multitenancy-core/src/types.ts b/packages/multitenancy-core/src/types.ts index 1cb805e..20c008c 100644 --- a/packages/multitenancy-core/src/types.ts +++ b/packages/multitenancy-core/src/types.ts @@ -31,5 +31,5 @@ export interface TenantSection { } export interface TenantContext { - tenant?: Tenant; + tenant?: Tenant }