diff --git a/packages/core/package.json b/packages/core/package.json index 2e6ebe09bb..87dbdebfdf 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -25,6 +25,12 @@ "./async_hooks": { "dev-source": "./src/async_hooks/index.ts", "types": "./dist/async_hooks/index.d.mts", + "node": "./dist/async_hooks/index.mjs", + "deno": "./dist/async_hooks/index.mjs", + "bun": "./dist/async_hooks/index.mjs", + "edge": "./dist/async_hooks/pure.index.mjs", + "workerd": "./dist/async_hooks/index.mjs", + "browser": "./dist/async_hooks/pure.index.mjs", "default": "./dist/async_hooks/index.mjs" }, "./context": { diff --git a/packages/core/src/async_hooks/convex.spec.ts b/packages/core/src/async_hooks/convex.spec.ts deleted file mode 100644 index 25d5cb3627..0000000000 --- a/packages/core/src/async_hooks/convex.spec.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { expect, test, vi } from "vitest"; - -vi.mock(import("node:async_hooks"), () => { - throw new Error("Doesn't work with convex"); -}); - -test("should work with convex", async () => { - vi.stubEnv("CONVEX_CLOUD_URL", "https://convex.com"); - vi.stubEnv("CONVEX_SITE_URL", "http://test.com"); - const { getAsyncLocalStorage } = await import("."); - await expect(getAsyncLocalStorage()).to.resolves.toBeDefined(); -}); diff --git a/packages/core/src/async_hooks/index.ts b/packages/core/src/async_hooks/index.ts index c5bc8f4235..94c432a6ee 100644 --- a/packages/core/src/async_hooks/index.ts +++ b/packages/core/src/async_hooks/index.ts @@ -1,34 +1,7 @@ import type { AsyncLocalStorage } from "node:async_hooks"; -import { env } from "../env"; export type { AsyncLocalStorage }; -/** - * Due to the lack of AsyncLocalStorage in some environments (like Convex), - * - * We assume serverless functions are short-lived and single-threaded, so we can use a simple polyfill. - */ -class AsyncLocalStoragePolyfill { - #current: T | undefined = undefined; - - run(store: T, fn: () => unknown): unknown { - const prev = this.#current; - this.#current = store; - const result = fn(); - if (result instanceof Promise) { - return result.finally(() => { - this.#current = prev; - }); - } - this.#current = prev; - return result; - } - - getStore(): T | undefined { - return this.#current; - } -} - const AsyncLocalStoragePromise: Promise = import( /* @vite-ignore */ @@ -43,9 +16,6 @@ const AsyncLocalStoragePromise: Promise = if (typeof window !== "undefined") { return null; } - if (env["CONVEX_CLOUD_URL"] || env["CONVEX_SITE_URL"]) { - return AsyncLocalStoragePolyfill; - } console.warn( "[better-auth] Warning: AsyncLocalStorage is not available in this environment. Some features may not work as expected.", ); diff --git a/packages/core/src/async_hooks/pure.index.ts b/packages/core/src/async_hooks/pure.index.ts new file mode 100644 index 0000000000..db37a27787 --- /dev/null +++ b/packages/core/src/async_hooks/pure.index.ts @@ -0,0 +1,46 @@ +import type { AsyncLocalStorage } from "node:async_hooks"; + +/** + * Due to the lack of AsyncLocalStorage in some environments (like Convex), + * + * We assume serverless functions are short-lived and single-threaded, so we can use a simple polyfill. + */ +class AsyncLocalStoragePolyfill { + #current: T | undefined = undefined; + + run(store: T, fn: () => unknown): unknown { + const prev = this.#current; + this.#current = store; + const result = fn(); + if (result instanceof Promise) { + return result.finally(() => { + this.#current = prev; + }); + } + this.#current = prev; + return result; + } + + getStore(): T | undefined { + return this.#current; + } +} + +const AsyncLocalStoragePromise: Promise = + Promise.resolve().then(() => { + if ("AsyncLocalStorage" in globalThis) { + return (globalThis as any).AsyncLocalStorage; + } + return AsyncLocalStoragePolyfill; + }); + +export async function getAsyncLocalStorage(): Promise< + typeof AsyncLocalStorage +> { + const mod = await AsyncLocalStoragePromise; + if (mod === null) { + throw new Error("getAsyncLocalStorage is only available in server code"); + } else { + return mod; + } +} diff --git a/packages/core/src/context/endpoint-context.ts b/packages/core/src/context/endpoint-context.ts index 6c91b4bcc1..58f32426fc 100644 --- a/packages/core/src/context/endpoint-context.ts +++ b/packages/core/src/context/endpoint-context.ts @@ -1,6 +1,6 @@ +import type { AsyncLocalStorage } from "@better-auth/core/async_hooks"; +import { getAsyncLocalStorage } from "@better-auth/core/async_hooks"; import type { EndpointContext, InputContext } from "better-call"; -import type { AsyncLocalStorage } from "../async_hooks"; -import { getAsyncLocalStorage } from "../async_hooks"; import type { AuthContext } from "../types"; export type AuthEndpointContext = Partial< diff --git a/packages/core/src/context/request-state.ts b/packages/core/src/context/request-state.ts index 5a2e53b433..6bb9b3fa7f 100644 --- a/packages/core/src/context/request-state.ts +++ b/packages/core/src/context/request-state.ts @@ -1,5 +1,5 @@ -import type { AsyncLocalStorage } from "../async_hooks"; -import { getAsyncLocalStorage } from "../async_hooks"; +import type { AsyncLocalStorage } from "@better-auth/core/async_hooks"; +import { getAsyncLocalStorage } from "@better-auth/core/async_hooks"; export type RequestStateWeakMap = WeakMap; diff --git a/packages/core/src/context/transaction.ts b/packages/core/src/context/transaction.ts index 8f6a75cde4..a086ee2de0 100644 --- a/packages/core/src/context/transaction.ts +++ b/packages/core/src/context/transaction.ts @@ -1,5 +1,5 @@ -import type { AsyncLocalStorage } from "../async_hooks"; -import { getAsyncLocalStorage } from "../async_hooks"; +import type { AsyncLocalStorage } from "@better-auth/core/async_hooks"; +import { getAsyncLocalStorage } from "@better-auth/core/async_hooks"; import type { DBAdapter, DBTransactionAdapter } from "../db/adapter"; let currentAdapterAsyncStorage: AsyncLocalStorage | null = diff --git a/packages/core/tsdown.config.ts b/packages/core/tsdown.config.ts index 62131667bb..6337f28f36 100644 --- a/packages/core/tsdown.config.ts +++ b/packages/core/tsdown.config.ts @@ -8,6 +8,7 @@ export default defineConfig({ "./src/db/index.ts", "./src/db/adapter/index.ts", "./src/async_hooks/index.ts", + "./src/async_hooks/pure.index.ts", "./src/context/index.ts", "./src/env/index.ts", "./src/oauth2/index.ts", @@ -16,5 +17,6 @@ export default defineConfig({ "./src/utils/index.ts", "./src/error/index.ts", ], + external: ["@better-auth/core/async_hooks"], clean: true, });