From d49c16a5ff031540e64d9460d4016f97ab077206 Mon Sep 17 00:00:00 2001 From: Hugo Richard Date: Tue, 23 Jun 2026 20:30:55 +0100 Subject: [PATCH 1/6] fix(elysia): polyfill enterWith for Cloudflare Workers Cloudflare Workers omit native AsyncLocalStorage.enterWith(), which the Elysia integration relies on across lifecycle hooks. --- .../fix-elysia-workers-asynclocalstorage.md | 9 ++++ packages/evlog/src/elysia/index.ts | 13 ++++-- .../evlog/src/shared/asyncStorageScope.ts | 42 +++++++++++++++++++ packages/evlog/test/frameworks/elysia.test.ts | 32 ++++++++++++++ .../test/shared/asyncStorageScope.test.ts | 40 ++++++++++++++++++ 5 files changed, 132 insertions(+), 4 deletions(-) create mode 100644 .changeset/fix-elysia-workers-asynclocalstorage.md create mode 100644 packages/evlog/src/shared/asyncStorageScope.ts create mode 100644 packages/evlog/test/shared/asyncStorageScope.test.ts diff --git a/.changeset/fix-elysia-workers-asynclocalstorage.md b/.changeset/fix-elysia-workers-asynclocalstorage.md new file mode 100644 index 00000000..34f617c5 --- /dev/null +++ b/.changeset/fix-elysia-workers-asynclocalstorage.md @@ -0,0 +1,9 @@ +--- +"evlog": patch +--- + +# fix(elysia): support Cloudflare Workers without AsyncLocalStorage.enterWith + +Cloudflare Workers omit native `AsyncLocalStorage.enterWith()`. The Elysia integration now installs a small polyfill on load so `useLogger()` and `{ log }` keep working under `wrangler dev`. + +Closes #394 diff --git a/packages/evlog/src/elysia/index.ts b/packages/evlog/src/elysia/index.ts index 5008e776..333e76e1 100644 --- a/packages/evlog/src/elysia/index.ts +++ b/packages/evlog/src/elysia/index.ts @@ -1,10 +1,13 @@ import { AsyncLocalStorage } from 'node:async_hooks' import { Elysia } from 'elysia' import type { AuditableLogger } from '../audit' +import { installAsyncLocalStorageEnterWithPolyfill } from '../shared/asyncStorageScope' import { defineFrameworkIntegration } from '../shared/integration' import type { BaseEvlogOptions } from '../shared/middleware' import { attachForkToLogger } from '../shared/fork' +installAsyncLocalStorageEnterWithPolyfill() + const storage = new AsyncLocalStorage() const activeLoggers = new WeakSet() @@ -15,9 +18,9 @@ export type EvlogElysiaOptions = BaseEvlogOptions * Get the request-scoped logger from anywhere in the call stack. * Must be called inside a request handled by the `evlog()` plugin. * - * Unlike other frameworks, Elysia uses `storage.enterWith()` which persists - * beyond the request lifecycle. This accessor additionally checks `activeLoggers` - * to ensure the logger belongs to an in-flight request. + * Elysia uses `storage.enterWith()` so the logger stays available across async + * boundaries between lifecycle hooks. On Cloudflare Workers, a small polyfill + * provides the same API because the runtime omits native `enterWith()`. * * @example * ```ts @@ -110,7 +113,9 @@ export function evlog(options: EvlogElysiaOptions = {}) { const headers = (request.headers).toJSON?.() ?? Object.fromEntries(request.headers.entries()) const ctx: ElysiaContext = { request, path: url.pathname, headers } const { logger, finish, skipped } = integration.start(ctx, options) - storage.enterWith(logger) + if (!skipped) { + storage.enterWith(logger) + } requestState.set(request, { finish, skipped, logger }) }) .derive({ as: 'global' }, ({ request }) => { diff --git a/packages/evlog/src/shared/asyncStorageScope.ts b/packages/evlog/src/shared/asyncStorageScope.ts new file mode 100644 index 00000000..add0e390 --- /dev/null +++ b/packages/evlog/src/shared/asyncStorageScope.ts @@ -0,0 +1,42 @@ +import { AsyncLocalStorage } from 'node:async_hooks' + +/** + * Polyfill `AsyncLocalStorage.enterWith()` on runtimes that omit it (Cloudflare Workers). + * + * Elysia's lifecycle is split across hooks (`onRequest` → handler → `onAfterResponse`), so + * the integration relies on `enterWith()` to keep `useLogger()` working across `await` + * boundaries. Workers only expose `run()` / `getStore()`, which this maps onto a fallback + * store when no `run()` frame is active. + * + * Same concurrency semantics as native `enterWith()` — one store per ALS instance between + * paired `enterWith(logger)` / `enterWith(undefined)` calls on a single request. + */ +export function installAsyncLocalStorageEnterWithPolyfill(): void { + if (typeof AsyncLocalStorage.prototype.enterWith === 'function') return + + const fallbackStores = new WeakMap, unknown>() + const runDepth = new WeakMap, number>() + const originalGetStore = AsyncLocalStorage.prototype.getStore + const originalRun = AsyncLocalStorage.prototype.run + + AsyncLocalStorage.prototype.enterWith = function enterWith(store: unknown) { + fallbackStores.set(this, store) + } + + AsyncLocalStorage.prototype.run = function run(store, callback, ...args) { + runDepth.set(this, (runDepth.get(this) ?? 0) + 1) + try { + return originalRun.call(this, store, callback, ...args) + } finally { + const depth = (runDepth.get(this) ?? 1) - 1 + if (depth <= 0) runDepth.delete(this) + else runDepth.set(this, depth) + } + } + + AsyncLocalStorage.prototype.getStore = function getStore() { + const active = originalGetStore.call(this) + if ((runDepth.get(this) ?? 0) > 0) return active + return active !== undefined ? active : fallbackStores.get(this) + } +} diff --git a/packages/evlog/test/frameworks/elysia.test.ts b/packages/evlog/test/frameworks/elysia.test.ts index aa9f545b..1f6329ae 100644 --- a/packages/evlog/test/frameworks/elysia.test.ts +++ b/packages/evlog/test/frameworks/elysia.test.ts @@ -1,3 +1,4 @@ +import { AsyncLocalStorage } from 'node:async_hooks' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { Elysia } from 'elysia' import { initLogger } from '../../src/logger' @@ -387,5 +388,36 @@ describe('evlog/elysia', () => { expect(findEventViaDrain(drain, e => e.fromService === true)).toBeDefined() }) + + it('works when AsyncLocalStorage.enterWith is unavailable (#394)', async () => { + const { drain } = createPipelineSpies() + const { enterWith } = AsyncLocalStorage.prototype + Object.defineProperty(AsyncLocalStorage.prototype, 'enterWith', { + configurable: true, + value: undefined, + }) + + const { installAsyncLocalStorageEnterWithPolyfill } = await import('../../src/shared/asyncStorageScope') + installAsyncLocalStorageEnterWithPolyfill() + + try { + const app = new Elysia() + .use(evlog({ drain })) + .get('/api/test', () => { + useLogger().set({ workerSafe: true }) + return { ok: true } + }) + + await request(app, '/api/test') + await waitForDrainCalls(drain) + + expect(findEventViaDrain(drain, e => e.workerSafe === true)).toBeDefined() + } finally { + Object.defineProperty(AsyncLocalStorage.prototype, 'enterWith', { + configurable: true, + value: enterWith, + }) + } + }) }) }) diff --git a/packages/evlog/test/shared/asyncStorageScope.test.ts b/packages/evlog/test/shared/asyncStorageScope.test.ts new file mode 100644 index 00000000..af801375 --- /dev/null +++ b/packages/evlog/test/shared/asyncStorageScope.test.ts @@ -0,0 +1,40 @@ +import { AsyncLocalStorage } from 'node:async_hooks' +import { afterEach, describe, expect, it } from 'vitest' +import { installAsyncLocalStorageEnterWithPolyfill } from '../../src/shared/asyncStorageScope' + +const nativeEnterWith = AsyncLocalStorage.prototype.enterWith +const nativeGetStore = AsyncLocalStorage.prototype.getStore +const nativeRun = AsyncLocalStorage.prototype.run + +describe('installAsyncLocalStorageEnterWithPolyfill', () => { + afterEach(() => { + AsyncLocalStorage.prototype.enterWith = nativeEnterWith + AsyncLocalStorage.prototype.getStore = nativeGetStore + AsyncLocalStorage.prototype.run = nativeRun + }) + + it('is a no-op when enterWith already exists', () => { + installAsyncLocalStorageEnterWithPolyfill() + + expect(AsyncLocalStorage.prototype.enterWith).toBe(nativeEnterWith) + }) + + it('polyfills enterWith for runtimes that omit it', async () => { + const storage = new AsyncLocalStorage() + Object.defineProperty(AsyncLocalStorage.prototype, 'enterWith', { + configurable: true, + value: undefined, + }) + + installAsyncLocalStorageEnterWithPolyfill() + + storage.enterWith('request-logger') + expect(storage.getStore()).toBe('request-logger') + + await Promise.resolve() + expect(storage.getStore()).toBe('request-logger') + + storage.enterWith(undefined as unknown as string) + expect(storage.getStore()).toBeUndefined() + }) +}) From 9b2bd110e559a723a0f688251298f1fb7ed08dab Mon Sep 17 00:00:00 2001 From: Hugo Richard Date: Tue, 23 Jun 2026 20:38:18 +0100 Subject: [PATCH 2/6] fix(elysia): isolate enterWith polyfill tests from global ALS The Workers regression test mutated AsyncLocalStorage.prototype and flaked under CI sharding; cover the pattern via a subclass-based unit test instead. --- .../evlog/src/shared/asyncStorageScope.ts | 79 ++++++++++++------- packages/evlog/test/frameworks/elysia.test.ts | 32 -------- .../test/shared/asyncStorageScope.test.ts | 69 +++++++++++----- 3 files changed, 101 insertions(+), 79 deletions(-) diff --git a/packages/evlog/src/shared/asyncStorageScope.ts b/packages/evlog/src/shared/asyncStorageScope.ts index add0e390..bbfe9eff 100644 --- a/packages/evlog/src/shared/asyncStorageScope.ts +++ b/packages/evlog/src/shared/asyncStorageScope.ts @@ -1,5 +1,56 @@ import { AsyncLocalStorage } from 'node:async_hooks' +type AsyncLocalStoragePrototype = Pick, 'getStore' | 'run'> & { + enterWith?: (store: unknown) => void +} + +/** + * Polyfill `enterWith()` on a single AsyncLocalStorage prototype. Used by + * {@link installAsyncLocalStorageEnterWithPolyfill} and unit tests via a subclass + * so global `AsyncLocalStorage` is never mutated in parallel test workers. + */ +export function patchAsyncLocalStorageEnterWith(prototype: AsyncLocalStoragePrototype): void { + if (typeof prototype.enterWith === 'function') return + + const fallbackStores = new WeakMap, unknown>() + const runDepth = new WeakMap, number>() + const originalGetStore = prototype.getStore + const originalRun = prototype.run + + Object.defineProperty(prototype, 'enterWith', { + configurable: true, + writable: true, + value: function enterWith(this: AsyncLocalStorage, store: unknown) { + fallbackStores.set(this, store) + }, + }) + + Object.defineProperty(prototype, 'run', { + configurable: true, + writable: true, + value: function run(this: AsyncLocalStorage, store, callback, ...args) { + runDepth.set(this, (runDepth.get(this) ?? 0) + 1) + try { + return originalRun.call(this, store, callback, ...args) + } finally { + const depth = (runDepth.get(this) ?? 1) - 1 + if (depth <= 0) runDepth.delete(this) + else runDepth.set(this, depth) + } + }, + }) + + Object.defineProperty(prototype, 'getStore', { + configurable: true, + writable: true, + value: function getStore(this: AsyncLocalStorage) { + const active = originalGetStore.call(this) + if ((runDepth.get(this) ?? 0) > 0) return active + return active !== undefined ? active : fallbackStores.get(this) + }, + }) +} + /** * Polyfill `AsyncLocalStorage.enterWith()` on runtimes that omit it (Cloudflare Workers). * @@ -12,31 +63,5 @@ import { AsyncLocalStorage } from 'node:async_hooks' * paired `enterWith(logger)` / `enterWith(undefined)` calls on a single request. */ export function installAsyncLocalStorageEnterWithPolyfill(): void { - if (typeof AsyncLocalStorage.prototype.enterWith === 'function') return - - const fallbackStores = new WeakMap, unknown>() - const runDepth = new WeakMap, number>() - const originalGetStore = AsyncLocalStorage.prototype.getStore - const originalRun = AsyncLocalStorage.prototype.run - - AsyncLocalStorage.prototype.enterWith = function enterWith(store: unknown) { - fallbackStores.set(this, store) - } - - AsyncLocalStorage.prototype.run = function run(store, callback, ...args) { - runDepth.set(this, (runDepth.get(this) ?? 0) + 1) - try { - return originalRun.call(this, store, callback, ...args) - } finally { - const depth = (runDepth.get(this) ?? 1) - 1 - if (depth <= 0) runDepth.delete(this) - else runDepth.set(this, depth) - } - } - - AsyncLocalStorage.prototype.getStore = function getStore() { - const active = originalGetStore.call(this) - if ((runDepth.get(this) ?? 0) > 0) return active - return active !== undefined ? active : fallbackStores.get(this) - } + patchAsyncLocalStorageEnterWith(AsyncLocalStorage.prototype) } diff --git a/packages/evlog/test/frameworks/elysia.test.ts b/packages/evlog/test/frameworks/elysia.test.ts index 1f6329ae..aa9f545b 100644 --- a/packages/evlog/test/frameworks/elysia.test.ts +++ b/packages/evlog/test/frameworks/elysia.test.ts @@ -1,4 +1,3 @@ -import { AsyncLocalStorage } from 'node:async_hooks' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { Elysia } from 'elysia' import { initLogger } from '../../src/logger' @@ -388,36 +387,5 @@ describe('evlog/elysia', () => { expect(findEventViaDrain(drain, e => e.fromService === true)).toBeDefined() }) - - it('works when AsyncLocalStorage.enterWith is unavailable (#394)', async () => { - const { drain } = createPipelineSpies() - const { enterWith } = AsyncLocalStorage.prototype - Object.defineProperty(AsyncLocalStorage.prototype, 'enterWith', { - configurable: true, - value: undefined, - }) - - const { installAsyncLocalStorageEnterWithPolyfill } = await import('../../src/shared/asyncStorageScope') - installAsyncLocalStorageEnterWithPolyfill() - - try { - const app = new Elysia() - .use(evlog({ drain })) - .get('/api/test', () => { - useLogger().set({ workerSafe: true }) - return { ok: true } - }) - - await request(app, '/api/test') - await waitForDrainCalls(drain) - - expect(findEventViaDrain(drain, e => e.workerSafe === true)).toBeDefined() - } finally { - Object.defineProperty(AsyncLocalStorage.prototype, 'enterWith', { - configurable: true, - value: enterWith, - }) - } - }) }) }) diff --git a/packages/evlog/test/shared/asyncStorageScope.test.ts b/packages/evlog/test/shared/asyncStorageScope.test.ts index af801375..6346d3b7 100644 --- a/packages/evlog/test/shared/asyncStorageScope.test.ts +++ b/packages/evlog/test/shared/asyncStorageScope.test.ts @@ -1,32 +1,33 @@ import { AsyncLocalStorage } from 'node:async_hooks' -import { afterEach, describe, expect, it } from 'vitest' -import { installAsyncLocalStorageEnterWithPolyfill } from '../../src/shared/asyncStorageScope' - -const nativeEnterWith = AsyncLocalStorage.prototype.enterWith -const nativeGetStore = AsyncLocalStorage.prototype.getStore -const nativeRun = AsyncLocalStorage.prototype.run - -describe('installAsyncLocalStorageEnterWithPolyfill', () => { - afterEach(() => { - AsyncLocalStorage.prototype.enterWith = nativeEnterWith - AsyncLocalStorage.prototype.getStore = nativeGetStore - AsyncLocalStorage.prototype.run = nativeRun +import { describe, expect, it } from 'vitest' +import { + installAsyncLocalStorageEnterWithPolyfill, + patchAsyncLocalStorageEnterWith, +} from '../../src/shared/asyncStorageScope' + +function createWorkersLikeStorage(): AsyncLocalStorage { + class LocalAsyncLocalStorage extends AsyncLocalStorage {} + Object.defineProperty(LocalAsyncLocalStorage.prototype, 'enterWith', { + configurable: true, + writable: true, + value: undefined, }) + patchAsyncLocalStorageEnterWith(LocalAsyncLocalStorage.prototype) + return new LocalAsyncLocalStorage() +} +describe('patchAsyncLocalStorageEnterWith', () => { it('is a no-op when enterWith already exists', () => { + const before = AsyncLocalStorage.prototype.getStore + installAsyncLocalStorageEnterWithPolyfill() - expect(AsyncLocalStorage.prototype.enterWith).toBe(nativeEnterWith) + expect(typeof AsyncLocalStorage.prototype.enterWith).toBe('function') + expect(AsyncLocalStorage.prototype.getStore).toBe(before) }) it('polyfills enterWith for runtimes that omit it', async () => { - const storage = new AsyncLocalStorage() - Object.defineProperty(AsyncLocalStorage.prototype, 'enterWith', { - configurable: true, - value: undefined, - }) - - installAsyncLocalStorageEnterWithPolyfill() + const storage = createWorkersLikeStorage() storage.enterWith('request-logger') expect(storage.getStore()).toBe('request-logger') @@ -37,4 +38,32 @@ describe('installAsyncLocalStorageEnterWithPolyfill', () => { storage.enterWith(undefined as unknown as string) expect(storage.getStore()).toBeUndefined() }) + + it('supports elysia-style request scope usage (#394)', async () => { + const storage = createWorkersLikeStorage() + const activeLoggers = new WeakSet() + + function bindRequestLogger(logger: object) { + storage.enterWith(logger) + activeLoggers.add(logger) + } + + function useLogger() { + const logger = storage.getStore() + if (!logger || !activeLoggers.has(logger)) { + throw new Error('[evlog] useLogger() was called outside of an evlog plugin context.') + } + return logger + } + + const requestLogger = { id: 'request-logger' } + bindRequestLogger(requestLogger) + expect(useLogger()).toBe(requestLogger) + await Promise.resolve() + expect(useLogger()).toBe(requestLogger) + + storage.enterWith(undefined as unknown as object) + activeLoggers.delete(requestLogger) + expect(() => useLogger()).toThrow('[evlog] useLogger()') + }) }) From 700415c7fad69a49ae7e3769095a5448917e5296 Mon Sep 17 00:00:00 2001 From: Hugo Richard Date: Tue, 23 Jun 2026 20:39:21 +0100 Subject: [PATCH 3/6] fix(elysia): tighten asyncStorageScope polyfill typings Use object-typed WeakMap keys and explicit run callback signatures so the polyfill passes strict TypeScript and eslint checks. --- .../evlog/src/shared/asyncStorageScope.ts | 43 ++++++++++++++----- 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/packages/evlog/src/shared/asyncStorageScope.ts b/packages/evlog/src/shared/asyncStorageScope.ts index bbfe9eff..6481589b 100644 --- a/packages/evlog/src/shared/asyncStorageScope.ts +++ b/packages/evlog/src/shared/asyncStorageScope.ts @@ -1,26 +1,42 @@ import { AsyncLocalStorage } from 'node:async_hooks' -type AsyncLocalStoragePrototype = Pick, 'getStore' | 'run'> & { - enterWith?: (store: unknown) => void +type AsyncLocalStorageLike = { + getStore(): unknown + run( + store: unknown, + callback: (...args: unknown[]) => unknown, + ...args: unknown[] + ): unknown + enterWith?(store: unknown): void } +type AsyncLocalStorageInstance = object + +type BoundGetStore = (this: AsyncLocalStorageInstance) => unknown +type BoundRun = ( + this: AsyncLocalStorageInstance, + store: unknown, + callback: (...args: unknown[]) => unknown, + ...args: unknown[] +) => unknown + /** * Polyfill `enterWith()` on a single AsyncLocalStorage prototype. Used by * {@link installAsyncLocalStorageEnterWithPolyfill} and unit tests via a subclass * so global `AsyncLocalStorage` is never mutated in parallel test workers. */ -export function patchAsyncLocalStorageEnterWith(prototype: AsyncLocalStoragePrototype): void { +export function patchAsyncLocalStorageEnterWith(prototype: AsyncLocalStorageLike): void { if (typeof prototype.enterWith === 'function') return - const fallbackStores = new WeakMap, unknown>() - const runDepth = new WeakMap, number>() - const originalGetStore = prototype.getStore - const originalRun = prototype.run + const fallbackStores = new WeakMap() + const runDepth = new WeakMap() + const originalGetStore = prototype.getStore as BoundGetStore + const originalRun = prototype.run as BoundRun Object.defineProperty(prototype, 'enterWith', { configurable: true, writable: true, - value: function enterWith(this: AsyncLocalStorage, store: unknown) { + value(this: AsyncLocalStorageInstance, store: unknown): void { fallbackStores.set(this, store) }, }) @@ -28,7 +44,12 @@ export function patchAsyncLocalStorageEnterWith(prototype: AsyncLocalStorageProt Object.defineProperty(prototype, 'run', { configurable: true, writable: true, - value: function run(this: AsyncLocalStorage, store, callback, ...args) { + value( + this: AsyncLocalStorageInstance, + store: unknown, + callback: (...args: unknown[]) => unknown, + ...args: unknown[] + ): unknown { runDepth.set(this, (runDepth.get(this) ?? 0) + 1) try { return originalRun.call(this, store, callback, ...args) @@ -43,7 +64,7 @@ export function patchAsyncLocalStorageEnterWith(prototype: AsyncLocalStorageProt Object.defineProperty(prototype, 'getStore', { configurable: true, writable: true, - value: function getStore(this: AsyncLocalStorage) { + value(this: AsyncLocalStorageInstance): unknown { const active = originalGetStore.call(this) if ((runDepth.get(this) ?? 0) > 0) return active return active !== undefined ? active : fallbackStores.get(this) @@ -63,5 +84,5 @@ export function patchAsyncLocalStorageEnterWith(prototype: AsyncLocalStorageProt * paired `enterWith(logger)` / `enterWith(undefined)` calls on a single request. */ export function installAsyncLocalStorageEnterWithPolyfill(): void { - patchAsyncLocalStorageEnterWith(AsyncLocalStorage.prototype) + patchAsyncLocalStorageEnterWith(AsyncLocalStorage.prototype as AsyncLocalStorageLike) } From bfc29f1df8258947ecba866c039d0997bcca683c Mon Sep 17 00:00:00 2001 From: Hugo Richard Date: Tue, 23 Jun 2026 20:45:50 +0100 Subject: [PATCH 4/6] refactor(elysia): simplify enterWith polyfill and address review Use native AsyncLocalStorage helpers, document Workers concurrency limits honestly, and add a derive-based concurrency regression test. --- .../fix-elysia-workers-asynclocalstorage.md | 2 +- packages/evlog/src/elysia/index.ts | 19 ++-- .../evlog/src/shared/asyncStorageScope.ts | 93 ++++++++++++------- .../test/shared/asyncStorageScope.test.ts | 83 +++++++++++++++-- 4 files changed, 146 insertions(+), 51 deletions(-) diff --git a/.changeset/fix-elysia-workers-asynclocalstorage.md b/.changeset/fix-elysia-workers-asynclocalstorage.md index 34f617c5..2fef6f7e 100644 --- a/.changeset/fix-elysia-workers-asynclocalstorage.md +++ b/.changeset/fix-elysia-workers-asynclocalstorage.md @@ -4,6 +4,6 @@ # fix(elysia): support Cloudflare Workers without AsyncLocalStorage.enterWith -Cloudflare Workers omit native `AsyncLocalStorage.enterWith()`. The Elysia integration now installs a small polyfill on load so `useLogger()` and `{ log }` keep working under `wrangler dev`. +Cloudflare Workers omit native `AsyncLocalStorage.enterWith()`. The Elysia integration now installs a small polyfill on load so `useLogger()` keeps working in typical `wrangler dev` flows. `{ log }` from derive remains the safest option when multiple requests may interleave in the same isolate. Closes #394 diff --git a/packages/evlog/src/elysia/index.ts b/packages/evlog/src/elysia/index.ts index 333e76e1..a7ba438d 100644 --- a/packages/evlog/src/elysia/index.ts +++ b/packages/evlog/src/elysia/index.ts @@ -1,7 +1,11 @@ import { AsyncLocalStorage } from 'node:async_hooks' import { Elysia } from 'elysia' import type { AuditableLogger } from '../audit' -import { installAsyncLocalStorageEnterWithPolyfill } from '../shared/asyncStorageScope' +import { + bindAsyncLocalStorage, + clearAsyncLocalStorage, + installAsyncLocalStorageEnterWithPolyfill, +} from '../shared/asyncStorageScope' import { defineFrameworkIntegration } from '../shared/integration' import type { BaseEvlogOptions } from '../shared/middleware' import { attachForkToLogger } from '../shared/fork' @@ -18,9 +22,10 @@ export type EvlogElysiaOptions = BaseEvlogOptions * Get the request-scoped logger from anywhere in the call stack. * Must be called inside a request handled by the `evlog()` plugin. * - * Elysia uses `storage.enterWith()` so the logger stays available across async - * boundaries between lifecycle hooks. On Cloudflare Workers, a small polyfill - * provides the same API because the runtime omits native `enterWith()`. + * Elysia binds the logger with `enterWith()` because its lifecycle hooks are + * separate from the route handler. On Cloudflare Workers, a small polyfill + * provides `enterWith()` when the runtime omits it. Prefer `{ log }` from + * derive when multiple requests may interleave in the same isolate. * * @example * ```ts @@ -114,7 +119,7 @@ export function evlog(options: EvlogElysiaOptions = {}) { const ctx: ElysiaContext = { request, path: url.pathname, headers } const { logger, finish, skipped } = integration.start(ctx, options) if (!skipped) { - storage.enterWith(logger) + bindAsyncLocalStorage(storage, logger) } requestState.set(request, { finish, skipped, logger }) }) @@ -127,7 +132,7 @@ export function evlog(options: EvlogElysiaOptions = {}) { emitted.add(request) await state.finish({ status: set.status as number || 200 }) activeLoggers.delete(state.logger) - storage.enterWith(undefined as unknown as AuditableLogger) + clearAsyncLocalStorage(storage) }) .onError({ as: 'global' }, async ({ request, error }) => { const state = requestState.get(request) @@ -137,6 +142,6 @@ export function evlog(options: EvlogElysiaOptions = {}) { state.logger.error(err) await state.finish({ error: err }) activeLoggers.delete(state.logger) - storage.enterWith(undefined as unknown as AuditableLogger) + clearAsyncLocalStorage(storage) }) } diff --git a/packages/evlog/src/shared/asyncStorageScope.ts b/packages/evlog/src/shared/asyncStorageScope.ts index 6481589b..5a331118 100644 --- a/packages/evlog/src/shared/asyncStorageScope.ts +++ b/packages/evlog/src/shared/asyncStorageScope.ts @@ -1,42 +1,61 @@ import { AsyncLocalStorage } from 'node:async_hooks' -type AsyncLocalStorageLike = { - getStore(): unknown - run( - store: unknown, - callback: (...args: unknown[]) => unknown, - ...args: unknown[] - ): unknown - enterWith?(store: unknown): void +/** Prototype surface patched by {@link patchAsyncLocalStorageEnterWith}. */ +type AsyncLocalStoragePrototype = Pick< + AsyncLocalStorage, + 'getStore' | 'run' +> & { + enterWith?: unknown } -type AsyncLocalStorageInstance = object +/** + * Whether this runtime implements native `AsyncLocalStorage.enterWith()`. + * Cloudflare Workers omit it; Node.js and Bun provide it. + */ +export function supportsAsyncLocalStorageEnterWith( + storage: { enterWith?: unknown }, +): boolean { + return typeof storage.enterWith === 'function' +} -type BoundGetStore = (this: AsyncLocalStorageInstance) => unknown -type BoundRun = ( - this: AsyncLocalStorageInstance, - store: unknown, - callback: (...args: unknown[]) => unknown, - ...args: unknown[] -) => unknown +/** + * Bind `value` to `storage` for the current async execution context. + * Uses native `enterWith()` when available; otherwise relies on the polyfill + * installed by {@link installAsyncLocalStorageEnterWithPolyfill}. + */ +export function bindAsyncLocalStorage( + storage: AsyncLocalStorage, + value: T, +): void { + storage.enterWith(value) +} + +/** Clear a value previously bound with {@link bindAsyncLocalStorage}. */ +export function clearAsyncLocalStorage(storage: AsyncLocalStorage): void { + storage.enterWith(undefined as unknown as T) +} /** - * Polyfill `enterWith()` on a single AsyncLocalStorage prototype. Used by - * {@link installAsyncLocalStorageEnterWithPolyfill} and unit tests via a subclass - * so global `AsyncLocalStorage` is never mutated in parallel test workers. + * Polyfill `enterWith()` on a single `AsyncLocalStorage` prototype. + * + * Used by {@link installAsyncLocalStorageEnterWithPolyfill} and by unit tests + * through a dedicated subclass so the global prototype is never mutated in + * parallel Vitest workers. */ -export function patchAsyncLocalStorageEnterWith(prototype: AsyncLocalStorageLike): void { - if (typeof prototype.enterWith === 'function') return +export function patchAsyncLocalStorageEnterWith( + prototype: AsyncLocalStoragePrototype, +): void { + if (supportsAsyncLocalStorageEnterWith(prototype)) return - const fallbackStores = new WeakMap() - const runDepth = new WeakMap() - const originalGetStore = prototype.getStore as BoundGetStore - const originalRun = prototype.run as BoundRun + const fallbackStores = new WeakMap() + const runDepth = new WeakMap() + const originalGetStore = prototype.getStore + const originalRun = prototype.run Object.defineProperty(prototype, 'enterWith', { configurable: true, writable: true, - value(this: AsyncLocalStorageInstance, store: unknown): void { + value(this: object, store: unknown): void { fallbackStores.set(this, store) }, }) @@ -45,7 +64,7 @@ export function patchAsyncLocalStorageEnterWith(prototype: AsyncLocalStorageLike configurable: true, writable: true, value( - this: AsyncLocalStorageInstance, + this: object, store: unknown, callback: (...args: unknown[]) => unknown, ...args: unknown[] @@ -64,7 +83,7 @@ export function patchAsyncLocalStorageEnterWith(prototype: AsyncLocalStorageLike Object.defineProperty(prototype, 'getStore', { configurable: true, writable: true, - value(this: AsyncLocalStorageInstance): unknown { + value(this: object): unknown { const active = originalGetStore.call(this) if ((runDepth.get(this) ?? 0) > 0) return active return active !== undefined ? active : fallbackStores.get(this) @@ -73,16 +92,18 @@ export function patchAsyncLocalStorageEnterWith(prototype: AsyncLocalStorageLike } /** - * Polyfill `AsyncLocalStorage.enterWith()` on runtimes that omit it (Cloudflare Workers). + * Install an `enterWith()` polyfill when the runtime omits it (Cloudflare Workers). * - * Elysia's lifecycle is split across hooks (`onRequest` → handler → `onAfterResponse`), so - * the integration relies on `enterWith()` to keep `useLogger()` working across `await` - * boundaries. Workers only expose `run()` / `getStore()`, which this maps onto a fallback - * store when no `run()` frame is active. + * Elysia's lifecycle is split across hooks (`onRequest` → handler → `onAfterResponse`). + * Unlike Express or Fastify, there is no single `next()` boundary to wrap in + * `storage.run()`, so the integration binds the logger with `enterWith()`. * - * Same concurrency semantics as native `enterWith()` — one store per ALS instance between - * paired `enterWith(logger)` / `enterWith(undefined)` calls on a single request. + * The polyfill stores the value on the ALS instance when no native `run()` frame + * is active. That matches single-request `wrangler dev` flows and async work + * spawned from a handler, but it does **not** replicate native per-async-context + * isolation when multiple requests interleave in the same isolate. Prefer `{ log }` + * from derive for concurrent Workers handlers. */ export function installAsyncLocalStorageEnterWithPolyfill(): void { - patchAsyncLocalStorageEnterWith(AsyncLocalStorage.prototype as AsyncLocalStorageLike) + patchAsyncLocalStorageEnterWith(AsyncLocalStorage.prototype) } diff --git a/packages/evlog/test/shared/asyncStorageScope.test.ts b/packages/evlog/test/shared/asyncStorageScope.test.ts index 6346d3b7..0ecc70a6 100644 --- a/packages/evlog/test/shared/asyncStorageScope.test.ts +++ b/packages/evlog/test/shared/asyncStorageScope.test.ts @@ -1,22 +1,49 @@ import { AsyncLocalStorage } from 'node:async_hooks' -import { describe, expect, it } from 'vitest' +import { beforeEach, describe, expect, it } from 'vitest' +import { Elysia } from 'elysia' +import { initLogger } from '../../src/logger' import { installAsyncLocalStorageEnterWithPolyfill, patchAsyncLocalStorageEnterWith, + supportsAsyncLocalStorageEnterWith, } from '../../src/shared/asyncStorageScope' +import { evlog } from '../../src/elysia/index' +import { + createPipelineSpies, + findEventViaDrain, + waitForDrainCalls, +} from '../helpers/framework' -function createWorkersLikeStorage(): AsyncLocalStorage { - class LocalAsyncLocalStorage extends AsyncLocalStorage {} +function createWorkersLikeStorage(): AsyncLocalStorage { + class LocalAsyncLocalStorage extends AsyncLocalStorage {} Object.defineProperty(LocalAsyncLocalStorage.prototype, 'enterWith', { configurable: true, writable: true, value: undefined, }) - patchAsyncLocalStorageEnterWith(LocalAsyncLocalStorage.prototype) + patchAsyncLocalStorageEnterWith( + LocalAsyncLocalStorage.prototype as Parameters[0], + ) return new LocalAsyncLocalStorage() } -describe('patchAsyncLocalStorageEnterWith', () => { +function delay(ms = 1) { + return new Promise((resolve) => { + setImmediate(resolve) + }) +} + +async function request(app: { handle: (req: Request) => Promise }, path: string) { + const response = await app.handle(new Request(`http://localhost${path}`)) + await delay() + return response +} + +describe('asyncStorageScope', () => { + it('detects native enterWith support', () => { + expect(supportsAsyncLocalStorageEnterWith(new AsyncLocalStorage())).toBe(true) + }) + it('is a no-op when enterWith already exists', () => { const before = AsyncLocalStorage.prototype.getStore @@ -27,7 +54,7 @@ describe('patchAsyncLocalStorageEnterWith', () => { }) it('polyfills enterWith for runtimes that omit it', async () => { - const storage = createWorkersLikeStorage() + const storage = createWorkersLikeStorage() storage.enterWith('request-logger') expect(storage.getStore()).toBe('request-logger') @@ -40,7 +67,7 @@ describe('patchAsyncLocalStorageEnterWith', () => { }) it('supports elysia-style request scope usage (#394)', async () => { - const storage = createWorkersLikeStorage() + const storage = createWorkersLikeStorage() const activeLoggers = new WeakSet() function bindRequestLogger(logger: object) { @@ -67,3 +94,45 @@ describe('patchAsyncLocalStorageEnterWith', () => { expect(() => useLogger()).toThrow('[evlog] useLogger()') }) }) + +describe('evlog/elysia workers concurrency', () => { + beforeEach(() => { + initLogger({ + env: { service: 'elysia-test' }, + pretty: false, + }) + }) + + it('keeps derive log isolated across interleaved requests without enterWith (#394)', async () => { + const { drain } = createPipelineSpies() + const contexts: Record = { + a: undefined, + b: undefined, + } + + const app = new Elysia() + .use(evlog({ drain })) + .get('/api/a', async ({ log }) => { + log.set({ route: 'a' }) + await Promise.resolve() + contexts.a = log.getContext().route as string | undefined + return { ok: true } + }) + .get('/api/b', ({ log }) => { + log.set({ route: 'b' }) + contexts.b = log.getContext().route as string | undefined + return { ok: true } + }) + + await Promise.all([ + request(app, '/api/a'), + request(app, '/api/b'), + ]) + await waitForDrainCalls(drain, 2) + + expect(contexts.a).toBe('a') + expect(contexts.b).toBe('b') + expect(findEventViaDrain(drain, event => event.route === 'a')).toBeDefined() + expect(findEventViaDrain(drain, event => event.route === 'b')).toBeDefined() + }) +}) From efb8eedfb91a9557be5ff3d62331fc59edf85525 Mon Sep 17 00:00:00 2001 From: Hugo Richard Date: Tue, 23 Jun 2026 21:02:14 +0100 Subject: [PATCH 5/6] fix(elysia): patch only the evlog AsyncLocalStorage instance Scope the enterWith polyfill to evlog's storage object instead of mutating AsyncLocalStorage.prototype globally. --- packages/evlog/src/elysia/index.ts | 5 +- .../evlog/src/shared/asyncStorageScope.ts | 92 +++++++------------ .../test/shared/asyncStorageScope.test.ts | 36 ++++++-- 3 files changed, 64 insertions(+), 69 deletions(-) diff --git a/packages/evlog/src/elysia/index.ts b/packages/evlog/src/elysia/index.ts index a7ba438d..2202319a 100644 --- a/packages/evlog/src/elysia/index.ts +++ b/packages/evlog/src/elysia/index.ts @@ -4,15 +4,14 @@ import type { AuditableLogger } from '../audit' import { bindAsyncLocalStorage, clearAsyncLocalStorage, - installAsyncLocalStorageEnterWithPolyfill, + patchAsyncLocalStorageEnterWith, } from '../shared/asyncStorageScope' import { defineFrameworkIntegration } from '../shared/integration' import type { BaseEvlogOptions } from '../shared/middleware' import { attachForkToLogger } from '../shared/fork' -installAsyncLocalStorageEnterWithPolyfill() - const storage = new AsyncLocalStorage() +patchAsyncLocalStorageEnterWith(storage) const activeLoggers = new WeakSet() diff --git a/packages/evlog/src/shared/asyncStorageScope.ts b/packages/evlog/src/shared/asyncStorageScope.ts index 5a331118..62018633 100644 --- a/packages/evlog/src/shared/asyncStorageScope.ts +++ b/packages/evlog/src/shared/asyncStorageScope.ts @@ -1,13 +1,5 @@ import { AsyncLocalStorage } from 'node:async_hooks' -/** Prototype surface patched by {@link patchAsyncLocalStorageEnterWith}. */ -type AsyncLocalStoragePrototype = Pick< - AsyncLocalStorage, - 'getStore' | 'run' -> & { - enterWith?: unknown -} - /** * Whether this runtime implements native `AsyncLocalStorage.enterWith()`. * Cloudflare Workers omit it; Node.js and Bun provide it. @@ -20,8 +12,8 @@ export function supportsAsyncLocalStorageEnterWith( /** * Bind `value` to `storage` for the current async execution context. - * Uses native `enterWith()` when available; otherwise relies on the polyfill - * installed by {@link installAsyncLocalStorageEnterWithPolyfill}. + * Uses native `enterWith()` when available; otherwise relies on + * {@link patchAsyncLocalStorageEnterWith}. */ export function bindAsyncLocalStorage( storage: AsyncLocalStorage, @@ -36,74 +28,60 @@ export function clearAsyncLocalStorage(storage: AsyncLocalStorage): void { } /** - * Polyfill `enterWith()` on a single `AsyncLocalStorage` prototype. + * Polyfill `enterWith()` on a single `AsyncLocalStorage` instance. * - * Used by {@link installAsyncLocalStorageEnterWithPolyfill} and by unit tests - * through a dedicated subclass so the global prototype is never mutated in - * parallel Vitest workers. + * Elysia's lifecycle is split across hooks (`onRequest` → handler → `onAfterResponse`). + * Unlike Express or Fastify, there is no single `next()` boundary to wrap in + * `storage.run()`, so the integration binds the logger with `enterWith()`. + * + * The polyfill stores the value on the ALS instance when no native `run()` frame + * is active. That matches single-request `wrangler dev` flows and async work + * spawned from a handler, but it does **not** replicate native per-async-context + * isolation when multiple requests interleave in the same isolate. Prefer `{ log }` + * from derive for concurrent Workers handlers. */ -export function patchAsyncLocalStorageEnterWith( - prototype: AsyncLocalStoragePrototype, +export function patchAsyncLocalStorageEnterWith( + storage: AsyncLocalStorage, ): void { - if (supportsAsyncLocalStorageEnterWith(prototype)) return + if (supportsAsyncLocalStorageEnterWith(storage)) return - const fallbackStores = new WeakMap() - const runDepth = new WeakMap() - const originalGetStore = prototype.getStore - const originalRun = prototype.run + let fallbackStore: T | undefined + let runDepth = 0 + const originalGetStore = storage.getStore.bind(storage) + const originalRun = storage.run.bind(storage) - Object.defineProperty(prototype, 'enterWith', { + Object.defineProperty(storage, 'enterWith', { configurable: true, writable: true, - value(this: object, store: unknown): void { - fallbackStores.set(this, store) + value(store: T): void { + fallbackStore = store }, }) - Object.defineProperty(prototype, 'run', { + Object.defineProperty(storage, 'run', { configurable: true, writable: true, - value( - this: object, - store: unknown, - callback: (...args: unknown[]) => unknown, + value( + store: T, + callback: (...args: unknown[]) => TReturn, ...args: unknown[] - ): unknown { - runDepth.set(this, (runDepth.get(this) ?? 0) + 1) + ): TReturn { + runDepth++ try { - return originalRun.call(this, store, callback, ...args) + return originalRun(store, callback, ...args) } finally { - const depth = (runDepth.get(this) ?? 1) - 1 - if (depth <= 0) runDepth.delete(this) - else runDepth.set(this, depth) + runDepth-- } }, }) - Object.defineProperty(prototype, 'getStore', { + Object.defineProperty(storage, 'getStore', { configurable: true, writable: true, - value(this: object): unknown { - const active = originalGetStore.call(this) - if ((runDepth.get(this) ?? 0) > 0) return active - return active !== undefined ? active : fallbackStores.get(this) + value(): T | undefined { + if (runDepth > 0) return originalGetStore() + const active = originalGetStore() + return active !== undefined ? active : fallbackStore }, }) } - -/** - * Install an `enterWith()` polyfill when the runtime omits it (Cloudflare Workers). - * - * Elysia's lifecycle is split across hooks (`onRequest` → handler → `onAfterResponse`). - * Unlike Express or Fastify, there is no single `next()` boundary to wrap in - * `storage.run()`, so the integration binds the logger with `enterWith()`. - * - * The polyfill stores the value on the ALS instance when no native `run()` frame - * is active. That matches single-request `wrangler dev` flows and async work - * spawned from a handler, but it does **not** replicate native per-async-context - * isolation when multiple requests interleave in the same isolate. Prefer `{ log }` - * from derive for concurrent Workers handlers. - */ -export function installAsyncLocalStorageEnterWithPolyfill(): void { - patchAsyncLocalStorageEnterWith(AsyncLocalStorage.prototype) -} diff --git a/packages/evlog/test/shared/asyncStorageScope.test.ts b/packages/evlog/test/shared/asyncStorageScope.test.ts index 0ecc70a6..29c007ff 100644 --- a/packages/evlog/test/shared/asyncStorageScope.test.ts +++ b/packages/evlog/test/shared/asyncStorageScope.test.ts @@ -3,7 +3,6 @@ import { beforeEach, describe, expect, it } from 'vitest' import { Elysia } from 'elysia' import { initLogger } from '../../src/logger' import { - installAsyncLocalStorageEnterWithPolyfill, patchAsyncLocalStorageEnterWith, supportsAsyncLocalStorageEnterWith, } from '../../src/shared/asyncStorageScope' @@ -21,10 +20,9 @@ function createWorkersLikeStorage(): AsyncLocalStorage { writable: true, value: undefined, }) - patchAsyncLocalStorageEnterWith( - LocalAsyncLocalStorage.prototype as Parameters[0], - ) - return new LocalAsyncLocalStorage() + const storage = new LocalAsyncLocalStorage() + patchAsyncLocalStorageEnterWith(storage) + return storage } function delay(ms = 1) { @@ -45,12 +43,32 @@ describe('asyncStorageScope', () => { }) it('is a no-op when enterWith already exists', () => { - const before = AsyncLocalStorage.prototype.getStore + const storage = new AsyncLocalStorage() + const beforeGetStore = AsyncLocalStorage.prototype.getStore - installAsyncLocalStorageEnterWithPolyfill() + patchAsyncLocalStorageEnterWith(storage) - expect(typeof AsyncLocalStorage.prototype.enterWith).toBe('function') - expect(AsyncLocalStorage.prototype.getStore).toBe(before) + storage.enterWith('request-logger') + expect(storage.getStore()).toBe('request-logger') + expect(AsyncLocalStorage.prototype.getStore).toBe(beforeGetStore) + }) + + it('patches only the provided storage instance', () => { + class LocalAsyncLocalStorage extends AsyncLocalStorage {} + Object.defineProperty(LocalAsyncLocalStorage.prototype, 'enterWith', { + configurable: true, + writable: true, + value: undefined, + }) + + const patched = new LocalAsyncLocalStorage() + const untouched = new LocalAsyncLocalStorage() + + patchAsyncLocalStorageEnterWith(patched) + + patched.enterWith('evlog') + expect(patched.getStore()).toBe('evlog') + expect(untouched.enterWith).toBeUndefined() }) it('polyfills enterWith for runtimes that omit it', async () => { From 8ce0538c9013f6c54271b2b9d40ebf77c7211dab Mon Sep 17 00:00:00 2001 From: Hugo Richard Date: Tue, 23 Jun 2026 21:13:51 +0100 Subject: [PATCH 6/6] fix(elysia): probe enterWith support instead of typeof check Cloudflare Workers expose enterWith but throw when called; detect that at probe time so the polyfill still applies on those runtimes. --- .../evlog/src/shared/asyncStorageScope.ts | 14 +++++-- .../test/shared/asyncStorageScope.test.ts | 37 +++++++++++++++++++ 2 files changed, 48 insertions(+), 3 deletions(-) diff --git a/packages/evlog/src/shared/asyncStorageScope.ts b/packages/evlog/src/shared/asyncStorageScope.ts index 62018633..2e80d336 100644 --- a/packages/evlog/src/shared/asyncStorageScope.ts +++ b/packages/evlog/src/shared/asyncStorageScope.ts @@ -1,13 +1,21 @@ import { AsyncLocalStorage } from 'node:async_hooks' /** - * Whether this runtime implements native `AsyncLocalStorage.enterWith()`. - * Cloudflare Workers omit it; Node.js and Bun provide it. + * Whether this runtime provides a working native `AsyncLocalStorage.enterWith()`. + * + * Cloudflare Workers expose `enterWith` on the prototype but throw when it is + * called, so a `typeof` check alone is not enough — we probe with a call. */ export function supportsAsyncLocalStorageEnterWith( storage: { enterWith?: unknown }, ): boolean { - return typeof storage.enterWith === 'function' + if (typeof storage.enterWith !== 'function') return false + try { + storage.enterWith(undefined) + return true + } catch { + return false + } } /** diff --git a/packages/evlog/test/shared/asyncStorageScope.test.ts b/packages/evlog/test/shared/asyncStorageScope.test.ts index 29c007ff..68f40155 100644 --- a/packages/evlog/test/shared/asyncStorageScope.test.ts +++ b/packages/evlog/test/shared/asyncStorageScope.test.ts @@ -25,6 +25,20 @@ function createWorkersLikeStorage(): AsyncLocalStorage { return storage } +function createThrowingEnterWithStorage(): AsyncLocalStorage { + class LocalAsyncLocalStorage extends AsyncLocalStorage {} + Object.defineProperty(LocalAsyncLocalStorage.prototype, 'enterWith', { + configurable: true, + writable: true, + value() { + throw new Error('asyncLocalStorage.enterWith() is not implemented') + }, + }) + const storage = new LocalAsyncLocalStorage() + patchAsyncLocalStorageEnterWith(storage) + return storage +} + function delay(ms = 1) { return new Promise((resolve) => { setImmediate(resolve) @@ -42,6 +56,19 @@ describe('asyncStorageScope', () => { expect(supportsAsyncLocalStorageEnterWith(new AsyncLocalStorage())).toBe(true) }) + it('detects throwing enterWith as unsupported', () => { + class LocalAsyncLocalStorage extends AsyncLocalStorage {} + Object.defineProperty(LocalAsyncLocalStorage.prototype, 'enterWith', { + configurable: true, + writable: true, + value() { + throw new Error('asyncLocalStorage.enterWith() is not implemented') + }, + }) + + expect(supportsAsyncLocalStorageEnterWith(new LocalAsyncLocalStorage())).toBe(false) + }) + it('is a no-op when enterWith already exists', () => { const storage = new AsyncLocalStorage() const beforeGetStore = AsyncLocalStorage.prototype.getStore @@ -84,6 +111,16 @@ describe('asyncStorageScope', () => { expect(storage.getStore()).toBeUndefined() }) + it('polyfills when enterWith exists but throws at runtime', async () => { + const storage = createThrowingEnterWithStorage() + + storage.enterWith('request-logger') + expect(storage.getStore()).toBe('request-logger') + + await Promise.resolve() + expect(storage.getStore()).toBe('request-logger') + }) + it('supports elysia-style request scope usage (#394)', async () => { const storage = createWorkersLikeStorage() const activeLoggers = new WeakSet()