|
| 1 | +import { AsyncLocalStorage } from 'node:async_hooks'; |
| 2 | +import { describe, expect, it } from 'vitest'; |
| 3 | + |
| 4 | +const CONTEXT_STORAGE_SYMBOL = Symbol.for('WORKFLOW_STEP_CONTEXT_STORAGE'); |
| 5 | + |
| 6 | +describe('contextStorage singleton', () => { |
| 7 | + it('returns the same AsyncLocalStorage instance across multiple imports', async () => { |
| 8 | + // Import the module twice (simulating what bundlers might do) |
| 9 | + const mod1 = await import('./context-storage.js'); |
| 10 | + const mod2 = await import('./context-storage.js'); |
| 11 | + |
| 12 | + expect(mod1.contextStorage).toBe(mod2.contextStorage); |
| 13 | + }); |
| 14 | + |
| 15 | + it('shares the same instance stored on globalThis via Symbol.for()', async () => { |
| 16 | + const { contextStorage } = await import('./context-storage.js'); |
| 17 | + const globalInstance = (globalThis as any)[CONTEXT_STORAGE_SYMBOL]; |
| 18 | + |
| 19 | + expect(globalInstance).toBe(contextStorage); |
| 20 | + expect(globalInstance).toBeInstanceOf(AsyncLocalStorage); |
| 21 | + }); |
| 22 | + |
| 23 | + it('preserves context from run() when getStore() is called from a separately-constructed reference', async () => { |
| 24 | + // This simulates the dual-module-instance problem: |
| 25 | + // step-handler sets context via one reference, user code reads via another. |
| 26 | + // With the Symbol.for() singleton fix, both references point to the same instance. |
| 27 | + const { contextStorage } = await import('./context-storage.js'); |
| 28 | + const globalInstance = (globalThis as any)[ |
| 29 | + CONTEXT_STORAGE_SYMBOL |
| 30 | + ] as AsyncLocalStorage<{ value: string }>; |
| 31 | + |
| 32 | + let storeFromGlobal: { value: string } | undefined; |
| 33 | + |
| 34 | + globalInstance.run({ value: 'test-context' }, () => { |
| 35 | + storeFromGlobal = contextStorage.getStore() as any; |
| 36 | + }); |
| 37 | + |
| 38 | + expect(storeFromGlobal).toEqual({ value: 'test-context' }); |
| 39 | + }); |
| 40 | +}); |
0 commit comments