diff --git a/.changeset/safe-context-probe.md b/.changeset/safe-context-probe.md new file mode 100644 index 000000000..47943f24c --- /dev/null +++ b/.changeset/safe-context-probe.md @@ -0,0 +1,5 @@ +--- +"evlog": patch +--- + +Prevent the Elysia integration from stalling `bun:test` lifecycle hooks by avoiding the side-effecting `AsyncLocalStorage.enterWith()` capability probe on Bun. diff --git a/packages/evlog/src/shared/asyncStorageScope.ts b/packages/evlog/src/shared/asyncStorageScope.ts index ad7159b93..9eec695b2 100644 --- a/packages/evlog/src/shared/asyncStorageScope.ts +++ b/packages/evlog/src/shared/asyncStorageScope.ts @@ -19,12 +19,15 @@ export interface AsyncLocalStorageLike { * 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. + * called, so a `typeof` check alone is not enough. Bun implements it natively, + * but probing changes `bun:test`'s active async context, so Bun is detected + * without invoking the method. */ export function supportsAsyncLocalStorageEnterWith( storage: { enterWith?: unknown }, ): boolean { if (typeof storage.enterWith !== 'function') return false + if ('Bun' in globalThis) return true try { // Must call as a method — unbound enterWith loses `this` and throws on Node. const probe = storage as { enterWith: (store: undefined) => void } diff --git a/packages/evlog/test/shared/asyncStorageScope.test.ts b/packages/evlog/test/shared/asyncStorageScope.test.ts index 68f401553..ef67be64b 100644 --- a/packages/evlog/test/shared/asyncStorageScope.test.ts +++ b/packages/evlog/test/shared/asyncStorageScope.test.ts @@ -1,5 +1,5 @@ import { AsyncLocalStorage } from 'node:async_hooks' -import { beforeEach, describe, expect, it } from 'vitest' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { Elysia } from 'elysia' import { initLogger } from '../../src/logger' import { @@ -52,6 +52,18 @@ async function request(app: { handle: (req: Request) => Promise }, pat } describe('asyncStorageScope', () => { + afterEach(() => { + vi.unstubAllGlobals() + }) + + it('does not probe enterWith on Bun (#496)', () => { + vi.stubGlobal('Bun', {}) + const enterWith = vi.fn() + + expect(supportsAsyncLocalStorageEnterWith({ enterWith })).toBe(true) + expect(enterWith).not.toHaveBeenCalled() + }) + it('detects native enterWith support', () => { expect(supportsAsyncLocalStorageEnterWith(new AsyncLocalStorage())).toBe(true) })