From 2ae491eac3ece50839a0eb2d4f868c4deedac67b Mon Sep 17 00:00:00 2001 From: Taesu <166604494+bytaesu@users.noreply.github.com> Date: Fri, 7 Aug 2026 20:33:52 +0900 Subject: [PATCH 1/6] fix(types): align auth endpoints with `better-call` (#10657) --- .changeset/calm-routes-align.md | 6 + .../src/api/routes/session-api.test.ts | 3 +- .../better-auth/src/api/routes/session.ts | 8 +- packages/better-auth/src/types/types.test.ts | 8 +- packages/core/src/api/index.ts | 118 +++++++++--------- packages/scim/src/routes.ts | 4 +- pnpm-lock.yaml | 53 ++++---- pnpm-workspace.yaml | 2 +- 8 files changed, 109 insertions(+), 93 deletions(-) create mode 100644 .changeset/calm-routes-align.md diff --git a/.changeset/calm-routes-align.md b/.changeset/calm-routes-align.md new file mode 100644 index 0000000000..0211af20b1 --- /dev/null +++ b/.changeset/calm-routes-align.md @@ -0,0 +1,6 @@ +--- +"@better-auth/core": patch +"better-auth": patch +--- + +Aligned endpoint and middleware context types with runtime route parameters, and preserved response headers when resolving sessions from endpoint contexts. diff --git a/packages/better-auth/src/api/routes/session-api.test.ts b/packages/better-auth/src/api/routes/session-api.test.ts index 393a9152ed..867387d944 100644 --- a/packages/better-auth/src/api/routes/session-api.test.ts +++ b/packages/better-auth/src/api/routes/session-api.test.ts @@ -1444,6 +1444,7 @@ describe("cookie cache refreshCache", async () => { context: ctx, headers, query: {}, + responseHeaders: new Headers(), } as unknown as GenericEndpointContext; await runWithRequestState(new WeakMap(), async () => { @@ -1452,7 +1453,7 @@ describe("cookie cache refreshCache", async () => { expect(session?.user.email).toBe(testUser.email); const parsed = parseSetCookieHeader( - endpointCtx.context.responseHeaders?.get("set-cookie") || "", + endpointCtx.responseHeaders.get("set-cookie") || "", ); const forwardedSessionDataCookie = Array.from(parsed.keys()).some( (name) => diff --git a/packages/better-auth/src/api/routes/session.ts b/packages/better-auth/src/api/routes/session.ts index 1134250ac7..50ce11ee4d 100644 --- a/packages/better-auth/src/api/routes/session.ts +++ b/packages/better-auth/src/api/routes/session.ts @@ -593,12 +593,10 @@ export const getSessionFromCtx = async < if (lowerKey === "cache-control" || lowerKey === "pragma") { return; } - if (!ctx.context.responseHeaders) { - ctx.context.responseHeaders = new Headers({ [key]: value }); - } else if (lowerKey === "set-cookie") { - ctx.context.responseHeaders.append(key, value); + if (lowerKey === "set-cookie") { + ctx.responseHeaders.append(key, value); } else { - ctx.context.responseHeaders.set(key, value); + ctx.responseHeaders.set(key, value); } }); } diff --git a/packages/better-auth/src/types/types.test.ts b/packages/better-auth/src/types/types.test.ts index b97f1c7fa1..7176f27409 100644 --- a/packages/better-auth/src/types/types.test.ts +++ b/packages/better-auth/src/types/types.test.ts @@ -115,7 +115,13 @@ describe("general types", async () => { { method: "GET", }, - async () => "ok", + async (context) => { + expectTypeOf(context.path).toEqualTypeOf(); + expectTypeOf(context.params).toEqualTypeOf< + Record | undefined + >(); + return "ok"; + }, ), testServerScoped: createAuthEndpoint( "/test-server-scoped", diff --git a/packages/core/src/api/index.ts b/packages/core/src/api/index.ts index 7acf9f9ff5..a123a4613c 100644 --- a/packages/core/src/api/index.ts +++ b/packages/core/src/api/index.ts @@ -1,6 +1,7 @@ import type { - EndpointContext, + EndpointHandler, EndpointOptions, + MiddlewareHandler, StrictEndpoint, } from "better-call"; import { @@ -56,13 +57,37 @@ export const createAuthMiddleware = createMiddleware.create({ ], }); -const use = [optionsMiddleware]; +const createEndpointWithAuthContext = createEndpoint.create({ + use: [optionsMiddleware], +}); -type EndpointHandler< +type AuthEndpointHandler< Path extends string, Options extends EndpointOptions, R, -> = (context: EndpointContext) => Promise; +> = EndpointHandler; + +type PathlessAuthEndpointHandler< + Options extends EndpointOptions, + R, +> = AuthEndpointHandler; + +function wrapEndpointHandler< + Path extends string, + Options extends EndpointOptions, + R, +>( + handler: AuthEndpointHandler, +): AuthEndpointHandler { + return async (context) => { + try { + return await runWithEndpointContext(context, () => handler(context)); + } catch (error) { + attachResponseHeadersToAPIError(context.responseHeaders, error); + throw error; + } + }; +} export function createAuthEndpoint< Path extends string, @@ -71,65 +96,42 @@ export function createAuthEndpoint< >( path: Path, options: Options, - handler: EndpointHandler, + handler: AuthEndpointHandler, ): StrictEndpoint; +export function createAuthEndpoint< + InferredPath extends string, + Options extends EndpointOptions, + R, +>( + options: Options, + handler: PathlessAuthEndpointHandler, +): StrictEndpoint; + export function createAuthEndpoint< Path extends string, Options extends EndpointOptions, R, >( - options: Options, - handler: EndpointHandler, -): StrictEndpoint; - -export function createAuthEndpoint< - Path extends string, - Opts extends EndpointOptions, - R, ->( - pathOrOptions: Path | Opts, - handlerOrOptions: EndpointHandler | Opts, - handlerOrNever?: any, + ...args: + | [ + path: Path, + options: Options, + handler: AuthEndpointHandler, + ] + | [options: Options, handler: PathlessAuthEndpointHandler] ) { - const path: Path | undefined = - typeof pathOrOptions === "string" ? pathOrOptions : undefined; - const options: Opts = - typeof handlerOrOptions === "object" - ? handlerOrOptions - : (pathOrOptions as Opts); - const handler: EndpointHandler = - typeof handlerOrOptions === "function" ? handlerOrOptions : handlerOrNever; - - // todo: prettify the code, we want to call `runWithEndpointContext` to top level - const wrapped: EndpointHandler = async (ctx) => { - const runtimeCtx = ctx as unknown as { responseHeaders?: Headers }; - try { - return await runWithEndpointContext(ctx as any, () => handler(ctx)); - } catch (e) { - attachResponseHeadersToAPIError(runtimeCtx.responseHeaders, e); - throw e; - } - }; - - if (path) { - return createEndpoint( + if (args.length === 3) { + const [path, options, handler] = args; + return createEndpointWithAuthContext( path, - { - ...options, - use: [...(options?.use || []), ...use], - }, - wrapped, + options, + wrapEndpointHandler(handler), ); } - return createEndpoint( - { - ...options, - use: [...(options?.use || []), ...use], - }, - wrapped, - ); + const [options, handler] = args; + return createEndpointWithAuthContext(options, wrapEndpointHandler(handler)); } /** @@ -168,18 +170,22 @@ function withServerOnly( * ``` */ createAuthEndpoint.serverOnly = < - Path extends string, + InferredPath extends string, Options extends EndpointOptions, R, >( options: Options, - handler: EndpointHandler, -): StrictEndpoint => - createAuthEndpoint(withServerOnly(options), handler); + handler: PathlessAuthEndpointHandler, +): StrictEndpoint => + createAuthEndpoint( + withServerOnly(options), + handler, + ); export type AuthEndpoint< Path extends string, Opts extends EndpointOptions, R, > = ReturnType>; -export type AuthMiddleware = ReturnType; + +export type AuthMiddleware = MiddlewareHandler; diff --git a/packages/scim/src/routes.ts b/packages/scim/src/routes.ts index e7eb29946e..1f3163c9c1 100644 --- a/packages/scim/src/routes.ts +++ b/packages/scim/src/routes.ts @@ -170,6 +170,7 @@ async function findOrganizationMember( */ async function canLinkExistingUser( ctx: GenericEndpointContext, + provider: Pick, opts: SCIMOptions, existingUser: User, email: string, @@ -178,7 +179,7 @@ async function canLinkExistingUser( if (!policy) return false; if (policy === true) return true; - const { organizationId, providerId } = ctx.context.scimProvider; + const { organizationId, providerId } = provider; // An empty policy object must not silently allow linking — require at least // one positive constraint to be configured. @@ -834,6 +835,7 @@ export const createSCIMUser = ( // Require an explicit, configured policy to allow it. const allowLink = await canLinkExistingUser( ctx, + ctx.context.scimProvider, opts, existingUser, email, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2defc30a45..756cf72a2b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -13,8 +13,8 @@ catalogs: specifier: 1.3.1 version: 1.3.1 better-call: - specifier: 1.3.7 - version: 1.3.7 + specifier: 1.4.0 + version: 1.4.0 kysely: specifier: ^0.28.17 || ^0.29.0 version: 0.29.2 @@ -859,7 +859,7 @@ importers: version: 0.4.2 better-call: specifier: 'catalog:' - version: 1.3.7(zod@4.3.6) + version: 1.4.0(zod@4.3.6) zod: specifier: ^4.3.6 version: 4.3.6 @@ -914,7 +914,7 @@ importers: version: 7.4.1(prisma@7.4.1(@types/react@19.2.14)(better-sqlite3@12.6.2)(magicast@0.5.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@6.0.3))(typescript@6.0.3) better-call: specifier: 'catalog:' - version: 1.3.7(zod@4.3.6) + version: 1.4.0(zod@4.3.6) better-sqlite3: specifier: ^12.0.0 version: 12.6.2 @@ -1163,7 +1163,7 @@ importers: version: 2.10.0(@opentelemetry/api@1.9.0) better-call: specifier: 'catalog:' - version: 1.3.7(zod@4.3.6) + version: 1.4.0(zod@4.3.6) jose: specifier: ^6.1.3 version: 6.1.3 @@ -1205,7 +1205,7 @@ importers: version: 1.3.1 better-call: specifier: 'catalog:' - version: 1.3.7(zod@4.3.6) + version: 1.4.0(zod@4.3.6) conf: specifier: ^15.0.2 version: 15.1.0 @@ -1239,7 +1239,7 @@ importers: version: 1.3.1 better-call: specifier: 'catalog:' - version: 1.3.7(zod@4.3.6) + version: 1.4.0(zod@4.3.6) zod: specifier: ^4.3.6 version: 4.3.6 @@ -1345,7 +1345,7 @@ importers: version: 1.3.1 better-call: specifier: 'catalog:' - version: 1.3.7(zod@4.3.6) + version: 1.4.0(zod@4.3.6) jose: specifier: ^6.1.3 version: 6.1.3 @@ -1385,7 +1385,7 @@ importers: version: 13.2.3 better-call: specifier: 'catalog:' - version: 1.3.7(zod@4.3.6) + version: 1.4.0(zod@4.3.6) nanostores: specifier: ^1.0.1 version: 1.1.1 @@ -1456,7 +1456,7 @@ importers: version: link:../better-auth better-call: specifier: 'catalog:' - version: 1.3.7(zod@4.3.6) + version: 1.4.0(zod@4.3.6) zod: specifier: ^4.3.6 version: 4.3.6 @@ -1509,7 +1509,7 @@ importers: version: link:../better-auth better-call: specifier: 'catalog:' - version: 1.3.7(zod@4.3.6) + version: 1.4.0(zod@4.3.6) body-parser: specifier: ^2.3.0 version: 2.3.0 @@ -1540,7 +1540,7 @@ importers: version: link:../better-auth better-call: specifier: 'catalog:' - version: 1.3.7(zod@4.3.6) + version: 1.4.0(zod@4.3.6) stripe: specifier: ^22.0.1 version: 22.0.1(@types/node@25.9.5) @@ -2444,6 +2444,9 @@ packages: '@better-auth/utils@0.4.2': resolution: {integrity: sha512-AUxrvu+HaaODsUyzDxFgwd/8RZ1yZaYo42LXKSrU2oGgR38pS1ij8nqQKNgtTWoYGpNevNXtCfgTy6loHveW9A==} + '@better-auth/utils@0.5.0': + resolution: {integrity: sha512-BL8W4EfIZFwlu0r54m3v1ztjDhu6dDe/amLTm0xybmbZaNgYUqhD3SjpAsnq0q8YD6/ki4iwIgxJNLP/N3TxiA==} + '@better-fetch/fetch@1.3.1': resolution: {integrity: sha512-ABkD1WhyfPZprKRQI3bhATjeiFuNWC9PXhfGWqL+sg/gKrM977oFrYkdb4msM3hgUGonr7KlOsOFT5TU2rht9g==} @@ -7427,8 +7430,8 @@ packages: peerDependencies: better-auth: ^1.0.3 - better-call@1.3.7: - resolution: {integrity: sha512-Al51/hjp2SSp6CRTa3F2ptcx4yQVS1xWKoY6jcVXqNYOap6mHFP2jUBn5EwIL4iIed1/Sq4hlQ+Umm6EflZG+w==} + better-call@1.4.0: + resolution: {integrity: sha512-bBKOT4vv1kZLDgxVePdilk/Jwkn+dtRRsmi3DzHcDP+WnswyVl6dR59l2HEeP/0cB+bDoopASAesWDPIdd/zZA==} peerDependencies: zod: ^4.0.0 peerDependenciesMeta: @@ -12316,9 +12319,6 @@ packages: engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true - rou3@0.7.12: - resolution: {integrity: sha512-iFE4hLDuloSWcD7mjdCDhx2bKcIsYbtOTpfH5MHHLSKMOUyjqQXTeZVa289uuwEGEKFoE/BAPbhaU4B774nceg==} - rou3@0.9.1: resolution: {integrity: sha512-z/sSmzvtwMDDnxsPVhfWMuG6F6mbmhFDXoVqLmMfbpDD9qfV3GDmSQpf0+W296/ZDIpW2wcMmBfpVFzcnOi/nA==} @@ -12444,9 +12444,6 @@ packages: set-blocking@2.0.0: resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} - set-cookie-parser@3.1.0: - resolution: {integrity: sha512-kjnC1DXBHcxaOaOXBHBeRtltsDG2nUiUni+jP92M9gYdW12rsmx92UsfpH7o5tDRs7I1ZZPSQJQGv3UaRfCiuw==} - set-cookie-parser@3.1.2: resolution: {integrity: sha512-5/r/lTwbJ3zQ+qwdUFZYeRNqda7P5HD8zQKqlSjdGt1/S0cjLAphHusj4Y58ahDtWn/g32xrIS58/ikOvwl0Lw==} @@ -15238,6 +15235,10 @@ snapshots: dependencies: '@noble/hashes': 2.0.1 + '@better-auth/utils@0.5.0': + dependencies: + '@noble/hashes': 2.0.1 + '@better-fetch/fetch@1.3.1': {} '@biomejs/biome@2.4.4': @@ -20840,12 +20841,12 @@ snapshots: mailchecker: 6.0.19 validator: 13.15.26 - better-call@1.3.7(zod@4.3.6): + better-call@1.4.0(zod@4.3.6): dependencies: - '@better-auth/utils': 0.4.2 + '@better-auth/utils': 0.5.0 '@better-fetch/fetch': 1.3.1 - rou3: 0.7.12 - set-cookie-parser: 3.1.0 + rou3: 0.9.1 + set-cookie-parser: 3.1.2 optionalDependencies: zod: 4.3.6 @@ -26951,8 +26952,6 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.60.4 fsevents: 2.3.3 - rou3@0.7.12: {} - rou3@0.9.1: {} roughjs@4.6.6: @@ -27111,8 +27110,6 @@ snapshots: set-blocking@2.0.0: {} - set-cookie-parser@3.1.0: {} - set-cookie-parser@3.1.2: {} setprototypeof@1.2.0: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 08fc89ab0c..3a1435b765 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -61,7 +61,7 @@ overrides: catalog: '@better-auth/utils': 0.4.2 '@better-fetch/fetch': 1.3.1 - better-call: 1.3.7 + better-call: 1.4.0 kysely: ^0.28.17 || ^0.29.0 tsdown: 0.22.7 typescript: ^6.0.3 From 95e248eb88a2819ae95da2e6632e2958e0771805 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 7 Aug 2026 21:30:18 +0900 Subject: [PATCH 2/6] chore(deps): bump mermaid from 11.15.0 to 11.16.1 (#10715) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- docs/package.json | 2 +- pnpm-lock.yaml | 363 ++++++++++++++++++++++++++-------------------- 2 files changed, 203 insertions(+), 162 deletions(-) diff --git a/docs/package.json b/docs/package.json index 596e76cd26..86cf7773a4 100644 --- a/docs/package.json +++ b/docs/package.json @@ -78,7 +78,7 @@ "js-beautify": "^1.15.4", "lottie-react": "^2.4.1", "lucide-react": "^1.7.0", - "mermaid": "^11.15.0", + "mermaid": "^11.16.1", "next": "16.2.11", "next-mdx-remote": "^6.0.0", "next-themes": "^0.4.6", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 756cf72a2b..da1c44e07d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -166,7 +166,7 @@ importers: version: 6.0.3 vitest: specifier: catalog:vitest - version: 4.1.10(@opentelemetry/api@1.9.0)(@types/node@25.3.2)(@vitest/coverage-istanbul@4.1.10)(@vitest/ui@4.1.10)(happy-dom@20.8.9)(msw@2.12.10(@types/node@25.3.2)(typescript@6.0.3))(vite@8.1.5(@types/node@25.3.2)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.0)(@types/node@25.3.2)(@vitest/coverage-istanbul@4.1.10)(@vitest/ui@4.1.10)(happy-dom@20.8.9)(msw@2.12.10(@types/node@25.3.2)(typescript@6.0.3))(vite@8.1.5(@types/node@25.3.2)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0)) docs: dependencies: @@ -286,7 +286,7 @@ importers: version: 1.36.4 '@vercel/analytics': specifier: ^1.6.1 - version: 1.6.1(@sveltejs/kit@2.70.1(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.55.7)(vite@6.4.3(@types/node@25.5.0)(jiti@2.7.0)(lightningcss@1.33.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.55.7)(typescript@6.0.3)(vite@6.4.3(@types/node@25.5.0)(jiti@2.7.0)(lightningcss@1.33.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0)))(next@16.2.11(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(@types/node@25.5.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4)(svelte@5.55.7)(vue@3.5.29(typescript@6.0.3)) + version: 1.6.1(@sveltejs/kit@2.70.1(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.55.7)(vite@6.4.3(@types/node@25.5.0)(jiti@2.7.0)(lightningcss@1.33.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.55.7)(typescript@6.0.3)(vite@6.4.3(@types/node@25.5.0)(jiti@2.7.0)(lightningcss@1.33.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0)))(next@16.2.11(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(@types/node@25.5.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4)(svelte@5.55.7)(vue@3.5.29(typescript@6.0.3)) '@vercel/og': specifier: ^0.10.1 version: 0.10.1(@types/node@25.5.0) @@ -319,7 +319,7 @@ importers: version: 16.12.0(@mdx-js/mdx@3.1.1)(@tanstack/react-router@1.170.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.14)(lucide-react@1.7.0(react@19.2.4))(next@16.2.11(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(@types/node@25.5.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(zod@4.3.6) fumadocs-mdx: specifier: ^14.2.8 - version: 14.2.9(@types/mdast@4.0.4)(@types/mdx@2.0.13)(@types/react@19.2.14)(fumadocs-core@16.12.0(@mdx-js/mdx@3.1.1)(@tanstack/react-router@1.170.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.14)(lucide-react@1.7.0(react@19.2.4))(next@16.2.11(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(@types/node@25.5.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(zod@4.3.6))(mdast-util-directive@3.1.0)(next@16.2.11(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(@types/node@25.5.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4)(vite@6.4.3(@types/node@25.5.0)(jiti@2.7.0)(lightningcss@1.33.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0)) + version: 14.2.9(@types/mdast@4.0.4)(@types/mdx@2.0.13)(@types/react@19.2.14)(fumadocs-core@16.12.0(@mdx-js/mdx@3.1.1)(@tanstack/react-router@1.170.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.14)(lucide-react@1.7.0(react@19.2.4))(next@16.2.11(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(@types/node@25.5.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(zod@4.3.6))(mdast-util-directive@3.1.0)(next@16.2.11(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(@types/node@25.5.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4)(vite@6.4.3(@types/node@25.5.0)(jiti@2.7.0)(lightningcss@1.33.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0)) fumadocs-typescript: specifier: ^5.2.1 version: 5.2.1(c87c276dbf4e0a39723c80519b3ea5bb) @@ -348,8 +348,8 @@ importers: specifier: ^1.7.0 version: 1.7.0(react@19.2.4) mermaid: - specifier: ^11.15.0 - version: 11.15.0 + specifier: ^11.16.1 + version: 11.16.1 next: specifier: 16.2.11 version: 16.2.11(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(@types/node@25.5.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) @@ -658,7 +658,7 @@ importers: version: 0.15.4(solid-js@1.9.11) '@solidjs/start': specifier: ^1.3.2 - version: 1.3.2(solid-js@1.9.11)(vinxi@0.5.11(45e8bfaeb23901a471de4e31ceb75bd8))(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0)) + version: 1.3.2(solid-js@1.9.11)(vinxi@0.5.11(fda321adb0ec1a60575cb6b8181efeb3))(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0)) better-auth: specifier: workspace:* version: link:../../../packages/better-auth @@ -667,7 +667,7 @@ importers: version: 1.9.11 vinxi: specifier: ^0.5.11 - version: 0.5.11(45e8bfaeb23901a471de4e31ceb75bd8) + version: 0.5.11(fda321adb0ec1a60575cb6b8181efeb3) devDependencies: '@better-auth-test/test-utils': specifier: workspace:* @@ -702,7 +702,7 @@ importers: version: link:../test-utils vite: specifier: ^8.1.5 - version: 8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0) + version: 8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0) e2e/smoke: dependencies: @@ -850,7 +850,7 @@ importers: devDependencies: vite: specifier: ^8.1.5 - version: 8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0) + version: 8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0) packages/api-key: dependencies: @@ -969,13 +969,13 @@ importers: version: 2.10.0(@opentelemetry/api@1.9.0) '@sveltejs/kit': specifier: ^2.70.1 - version: 2.70.1(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.55.7)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.55.7)(typescript@6.0.3)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0)) + version: 2.70.1(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.55.7)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.55.7)(typescript@6.0.3)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0)) '@tanstack/react-start': specifier: ^1.168.4 - version: 1.168.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(vite-plugin-solid@2.11.10(solid-js@1.9.11)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0)))(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0)) + version: 1.168.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(vite-plugin-solid@2.11.10(solid-js@1.9.11)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0)))(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0)) '@tanstack/solid-start': specifier: ^1.168.4 - version: 1.168.4(@tanstack/react-router@1.170.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(solid-js@1.9.11)(vite-plugin-solid@2.11.10(solid-js@1.9.11)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0)))(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0)) + version: 1.168.4(@tanstack/react-router@1.170.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(solid-js@1.9.11)(vite-plugin-solid@2.11.10(solid-js@1.9.11)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0)))(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0)) '@types/bun': specifier: ^1.3.9 version: 1.3.9 @@ -1026,10 +1026,10 @@ importers: version: 6.0.3 vite: specifier: ^8.1.5 - version: 8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0) + version: 8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0) vitest: specifier: catalog:vitest - version: 4.1.10(@opentelemetry/api@1.9.0)(@types/node@25.9.5)(@vitest/coverage-istanbul@4.1.10)(@vitest/ui@4.1.10)(happy-dom@20.8.9)(msw@2.12.10(@types/node@25.9.5)(typescript@6.0.3))(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.0)(@types/node@25.9.5)(@vitest/coverage-istanbul@4.1.10)(@vitest/ui@4.1.10)(happy-dom@20.8.9)(msw@2.12.10(@types/node@25.9.5)(typescript@6.0.3))(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0)) vue: specifier: ^3.5.29 version: 3.5.29(typescript@6.0.3) @@ -1444,7 +1444,7 @@ importers: version: 6.0.3 vitest: specifier: catalog:vitest - version: 4.1.10(@opentelemetry/api@1.9.0)(@types/node@25.3.2)(@vitest/coverage-istanbul@4.1.10)(@vitest/ui@4.1.10)(happy-dom@20.8.9)(msw@2.12.10(@types/node@25.3.2)(typescript@6.0.3))(vite@8.1.5(@types/node@25.3.2)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.0)(@types/node@25.3.2)(@vitest/coverage-istanbul@4.1.10)(@vitest/ui@4.1.10)(happy-dom@20.8.9)(msw@2.12.10(@types/node@25.3.2)(typescript@6.0.3))(vite@8.1.5(@types/node@25.3.2)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0)) packages/scim: dependencies: @@ -1580,7 +1580,7 @@ importers: version: 0.22.7(@arethetypeswrong/core@0.18.2)(oxc-resolver@11.19.0)(publint@0.3.17)(tsx@4.22.4)(typescript@6.0.3) vitest: specifier: catalog:vitest - version: 4.1.10(@opentelemetry/api@1.9.0)(@types/node@25.9.5)(@vitest/coverage-istanbul@4.1.10)(@vitest/ui@4.1.10)(happy-dom@20.8.9)(msw@2.12.10(@types/node@25.9.5)(typescript@6.0.3))(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.0)(@types/node@25.9.5)(@vitest/coverage-istanbul@4.1.10)(@vitest/ui@4.1.10)(happy-dom@20.8.9)(msw@2.12.10(@types/node@25.9.5)(typescript@6.0.3))(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0)) test: devDependencies: @@ -1601,7 +1601,7 @@ importers: version: 7.29.0 vitest: specifier: catalog:vitest - version: 4.1.10(@opentelemetry/api@1.9.0)(@types/node@25.9.5)(@vitest/coverage-istanbul@4.1.10)(@vitest/ui@4.1.10)(happy-dom@20.8.9)(msw@2.12.10(@types/node@25.9.5)(typescript@6.0.3))(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.0)(@types/node@25.9.5)(@vitest/coverage-istanbul@4.1.10)(@vitest/ui@4.1.10)(happy-dom@20.8.9)(msw@2.12.10(@types/node@25.9.5)(typescript@6.0.3))(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0)) packages: @@ -3349,8 +3349,8 @@ packages: '@iconify/types@2.0.0': resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==} - '@iconify/utils@3.1.3': - resolution: {integrity: sha512-LPKOXPn/zV+zis1oOfGWogaXVpqUybF3ZS6SCZIsz8vg0ivVp9+fVqyYB7xq0aiST/VhUQYGO1qo6uoYSiEJqw==} + '@iconify/utils@3.1.4': + resolution: {integrity: sha512-b1S7B1k9ohZ+iNTi2ATxbRYG9fTrJmUT0rc46bvVnNxqNRGW7dyo/vRREwyniI5IRN2RSJHDcm+s3BjWrSAjHw==} '@img/colour@1.1.0': resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==} @@ -3791,8 +3791,8 @@ packages: '@medv/finder@4.0.2': resolution: {integrity: sha512-RraNY9SCcx4KZV0Dh6BEW6XEW2swkqYca74pkFFRw6hHItSHiy+O/xMnpbofjYbzXj0tSpBGthUF1hHTsr3vIQ==} - '@mermaid-js/parser@1.1.1': - resolution: {integrity: sha512-VuHdsYMK1bT6X2JbcAaWAhugTRvRBRyuZgd+c22swUeI9g/ntaxF7CY7dYarhZovofCbUNO0G7JesfmNtjYOCw==} + '@mermaid-js/parser@1.2.0': + resolution: {integrity: sha512-oYPyv8A4As1yH5Bx+04iQEQxXuIQDe0GKCNSRgao6z8AM9jixXIfP0vsppRLvGf+nKIOb9/LdpWA4YuJiVvESA==} '@modelcontextprotocol/sdk@1.27.1': resolution: {integrity: sha512-sr6GbP+4edBwFndLbM60gf07z0FQ79gaExpnsjMGePXqFcSSb7t6iscpjk9DhFhwd+mTEQrzNafGP8/iGGFYaA==} @@ -6561,8 +6561,8 @@ packages: '@types/d3-format@3.0.4': resolution: {integrity: sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==} - '@types/d3-geo@3.1.0': - resolution: {integrity: sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==} + '@types/d3-geo@3.1.1': + resolution: {integrity: sha512-65Emv9fQiQQqphLlRkuQ5ypPsOmWPhtBGCMv61JDPEPMvsx+gzhGf74yw1a78xFKPj6zw4AgQICJoQv0vK9M2w==} '@types/d3-hierarchy@3.1.7': resolution: {integrity: sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==} @@ -6579,8 +6579,8 @@ packages: '@types/d3-quadtree@3.0.6': resolution: {integrity: sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==} - '@types/d3-random@3.0.3': - resolution: {integrity: sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==} + '@types/d3-random@3.0.4': + resolution: {integrity: sha512-UHYId5WTCx4L4YNel7NU00XUXXgvgpgZOvp10PuvsQENjMDXhh2RyFc0KBjO7B45ne4Ha1yVH7ii0vnzKkuzWA==} '@types/d3-scale-chromatic@3.1.0': resolution: {integrity: sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==} @@ -7118,6 +7118,11 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + acorn@8.18.0: + resolution: {integrity: sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==} + engines: {node: '>=0.4.0'} + hasBin: true + agent-base@6.0.2: resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} engines: {node: '>= 6.0.0'} @@ -7940,8 +7945,9 @@ packages: resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} engines: {node: '>=18'} - core-js-compat@3.49.0: - resolution: {integrity: sha512-VQXt1jr9cBz03b331DFDCCP90b3fanciLkgiOoy8SBHy06gNf+vQ1A3WFLqG7I8TipYIKeYK9wxd0tUrvHcOZA==} + core-js-compat@3.50.0: + resolution: {integrity: sha512-XGpFGbMLHwSt74YLTKho7Ib242qi6O8MSX+sRokV4oz7iKXvQWGYZthjIhjRGMxjzVkAubBO512dKGYcefmX3Q==} + engines: {node: '>=6.4.0'} core-util-is@1.0.3: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} @@ -8054,8 +8060,8 @@ packages: peerDependencies: cytoscape: ^3.2.0 - cytoscape@3.33.3: - resolution: {integrity: sha512-Gej7U+OKR+LZ8kvX7rb2HhCYJ0IhvEFsnkud4SB1PR+BUY/TsSO0dmOW59WEVLu51b1Rm+gQRKoz4bLYxGSZ2g==} + cytoscape@3.34.0: + resolution: {integrity: sha512-62rNSrioXw93uliKFBwjukeQyeWwH2PqDrTac31r2P6464u3AUvTk0xS4LVvT251g7IgkFunrI48ZEZGjywSOg==} engines: {node: '>=0.10'} d3-array@2.12.1: @@ -8213,8 +8219,8 @@ packages: resolution: {integrity: sha512-uULa1sSIHgXKGCqJ/pA0zsnzbHlVnuq7g8O2fkHokWFNwEGIhh5lAJlxZa1POG5En5ba7AU4KcBAvGQWMMf8rg==} deprecated: This package has moved to simply be 'dax' instead of 'dax-sh' - dayjs@1.11.20: - resolution: {integrity: sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==} + dayjs@1.11.21: + resolution: {integrity: sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA==} db0@0.3.4: resolution: {integrity: sha512-RiXXi4WaNzPTHEOu8UPQKMooIbqOEyqA1t7Z6MsdxSCeb8iUC9ko3LcmsLmeUt2SM5bctfArZKkRQggKZz7JNw==} @@ -8381,8 +8387,8 @@ packages: resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} engines: {node: '>= 4'} - dompurify@3.4.12: - resolution: {integrity: sha512-zQvGet8Z2sWbQhCmfFz/T5QWH2oBmjnqK3qvOjaqaNLrLEF912WamU+ohnTp0TCep/MFVHpdJuCZEdFOdTnEFg==} + dompurify@3.4.13: + resolution: {integrity: sha512-2vmYIoqjze2d+kakP8S/nS5shfsl587kzwEjcGlTdiksUVgFHnFCsLYDVj/JNqJVOQZGSYBTmuycv0PodwmnMQ==} domutils@3.2.2: resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} @@ -8679,8 +8685,8 @@ packages: es-toolkit@1.44.0: resolution: {integrity: sha512-6penXeZalaV88MM3cGkFZZfOoLGWshWWfdy0tWw/RlVVyhvMaWSBTOvXNeiW3e5FwdS5ePW0LGEu17zT139ktg==} - es-toolkit@1.46.1: - resolution: {integrity: sha512-5eNtXOs3tbfxXOj04tjjseeWkRWaoCjdEI+96DgwzZoe6c9juL49pXlzAFTI72aWC9Y8p7168g6XIKjh7k6pyQ==} + es-toolkit@1.50.0: + resolution: {integrity: sha512-OyZKhUVvEep9ITEiwHn8GKnMRQIVqoSIX7WnRbkWgJkllCujilqP2rD0u979tkl8wqyc8ICwlc1UBVv/Sl1G6w==} es6-error@4.1.1: resolution: {integrity: sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==} @@ -10045,8 +10051,8 @@ packages: jws@4.0.1: resolution: {integrity: sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==} - katex@0.16.45: - resolution: {integrity: sha512-pQpZbdBu7wCTmQUh7ufPmLr0pFoObnGUoL/yhtwJDgmmQpbkg/0HSVti25Fu4rmd1oCR6NGWe9vqTWuWv3GcNA==} + katex@0.16.47: + resolution: {integrity: sha512-Eeo8Ys1doU1z+x8AZsPpQu+p/QcZBI5PeOo7QGQdy2x2m0MU/hYagBbGOmXwr5KVbEfVuWv9LpnQWeehogurjg==} hasBin: true khroma@2.1.0: @@ -10613,8 +10619,8 @@ packages: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} - mermaid@11.15.0: - resolution: {integrity: sha512-pTMbcf3rWdtLiYGpmoTjHEpeY8seiy6sR+9nD7LOs8KfUbHE4lOUAprTRqRAcWSQ6MQpdX+YEsxShtGsINtPtw==} + mermaid@11.16.1: + resolution: {integrity: sha512-TQsq6u22fAn3rek5VOubrhKPo1g5hwC3FXUN9hiyupTckcYiGuuKGkNQrKYwGJkXUxZdojwRG46gsSCFZMDp4g==} metro-babel-transformer@0.83.3: resolution: {integrity: sha512-1vxlvj2yY24ES1O5RsSIvg4a4WeL7PFXgKOHvXTXiW0deLvQr28ExXj6LjwCCDZ4YZLhq6HddLpZnX4dEdSq5g==} @@ -11369,6 +11375,9 @@ packages: package-manager-detector@1.6.0: resolution: {integrity: sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==} + package-manager-detector@1.8.0: + resolution: {integrity: sha512-yQA4H19AmPEoMUeavPMDIe1higySl/gH/yaQrkT/s07Qp+7pp2hYz30N3z2l5BkjVkF9Ow6o0wjJamm2y7Sn0A==} + pako@0.2.9: resolution: {integrity: sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==} @@ -11602,6 +11611,10 @@ packages: resolution: {integrity: sha512-DTPx3RWSSnWyzLxQnlH0rJP+EW5ekl16ZU4/psbIhA0e53kJfdgaN5vKM+xP7yJtXVu+nfdVFmlgFDEKAe4Pyw==} engines: {node: ^10 || ^12 || >=14} + postcss@8.5.26: + resolution: {integrity: sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==} + engines: {node: ^10 || ^12 || >=14} + postgres-array@2.0.0: resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==} engines: {node: '>=4'} @@ -12942,6 +12955,11 @@ packages: engines: {node: '>=10'} hasBin: true + terser@5.49.2: + resolution: {integrity: sha512-rGbJiKeQ4WDe3EXlDAIaQcwftVfv2Q8o1awFNfvXolJYKkb1AuZY1RTOmqx4LJXZENbWZA7eIsYGHuEzHsi1nQ==} + engines: {node: '>=10'} + hasBin: true + test-exclude@7.0.2: resolution: {integrity: sha512-u9E6A+ZDYdp7a4WnarkXPZOx8Ilz46+kby6p1yZ8zsGTz9gYa6FIS7lj2oezzNKmtdyyJNNmmXDppga5GB7kSw==} engines: {node: '>=18'} @@ -13000,6 +13018,10 @@ packages: resolution: {integrity: sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==} engines: {node: '>=18'} + tinyexec@1.3.0: + resolution: {integrity: sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==} + engines: {node: '>=18'} + tinyglobby@0.2.15: resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} engines: {node: '>=12.0.0'} @@ -13068,8 +13090,8 @@ packages: trough@2.2.0: resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} - ts-dedent@2.2.0: - resolution: {integrity: sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==} + ts-dedent@2.3.0: + resolution: {integrity: sha512-JfJeIHke7y2egdGGgRAvpCwYFUsHlM2gPcrVOxFkznt/4uzQ7HFmvE63iFHVLBJNDuyDOQgijDK/tXH/f6Msjg==} engines: {node: '>=6.10'} ts-interface-checker@0.1.13: @@ -14216,8 +14238,8 @@ snapshots: '@antfu/install-pkg@1.1.0': dependencies: - package-manager-detector: 1.6.0 - tinyexec: 1.2.4 + package-manager-detector: 1.8.0 + tinyexec: 1.3.0 '@arethetypeswrong/cli@0.18.2': dependencies: @@ -15488,7 +15510,7 @@ snapshots: cjs-module-lexer: 1.4.3 esbuild: 0.28.1 miniflare: 4.20260305.0(@types/node@25.9.5) - vitest: 4.1.10(@opentelemetry/api@1.9.0)(@types/node@25.9.5)(@vitest/coverage-istanbul@4.1.10)(@vitest/ui@4.1.10)(happy-dom@20.8.9)(msw@2.12.10(@types/node@25.9.5)(typescript@6.0.3))(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0)) + vitest: 4.1.10(@opentelemetry/api@1.9.0)(@types/node@25.9.5)(@vitest/coverage-istanbul@4.1.10)(@vitest/ui@4.1.10)(happy-dom@20.8.9)(msw@2.12.10(@types/node@25.9.5)(typescript@6.0.3))(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0)) wrangler: 4.69.0(@cloudflare/workers-types@4.20260226.1)(@types/node@25.9.5) transitivePeerDependencies: - '@cloudflare/workers-types' @@ -16175,7 +16197,7 @@ snapshots: jsc-safe-url: 0.2.4 lightningcss: 1.33.0 minimatch: 10.2.6 - postcss: 8.5.25 + postcss: 8.5.26 resolve-from: 5.0.0 optionalDependencies: expo: 54.0.33(@babel/core@7.29.7)(graphql@16.13.0)(react-native@0.84.1(@babel/core@7.29.7)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4)(typescript@6.0.3) @@ -16334,7 +16356,7 @@ snapshots: '@iconify/types@2.0.0': {} - '@iconify/utils@3.1.3': + '@iconify/utils@3.1.4': dependencies: '@antfu/install-pkg': 1.1.0 '@iconify/types': 2.0.0 @@ -16826,7 +16848,7 @@ snapshots: '@medv/finder@4.0.2': {} - '@mermaid-js/parser@1.1.1': + '@mermaid-js/parser@1.2.0': dependencies: '@chevrotain/types': 11.1.2 @@ -19242,11 +19264,11 @@ snapshots: dependencies: solid-js: 1.9.11 - '@solidjs/start@1.3.2(solid-js@1.9.11)(vinxi@0.5.11(45e8bfaeb23901a471de4e31ceb75bd8))(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0))': + '@solidjs/start@1.3.2(solid-js@1.9.11)(vinxi@0.5.11(fda321adb0ec1a60575cb6b8181efeb3))(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0))': dependencies: - '@tanstack/server-functions-plugin': 1.121.21(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0)) - '@vinxi/plugin-directives': 0.5.1(vinxi@0.5.11(45e8bfaeb23901a471de4e31ceb75bd8)) - '@vinxi/server-components': 0.5.1(vinxi@0.5.11(45e8bfaeb23901a471de4e31ceb75bd8)) + '@tanstack/server-functions-plugin': 1.121.21(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0)) + '@vinxi/plugin-directives': 0.5.1(vinxi@0.5.11(fda321adb0ec1a60575cb6b8181efeb3)) + '@vinxi/server-components': 0.5.1(vinxi@0.5.11(fda321adb0ec1a60575cb6b8181efeb3)) cookie-es: 2.0.0 defu: 6.1.7 error-stack-parser: 2.1.4 @@ -19258,8 +19280,8 @@ snapshots: source-map-js: 1.2.1 terracotta: 1.1.0(solid-js@1.9.11) tinyglobby: 0.2.15 - vinxi: 0.5.11(45e8bfaeb23901a471de4e31ceb75bd8) - vite-plugin-solid: 2.11.10(solid-js@1.9.11)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0)) + vinxi: 0.5.11(fda321adb0ec1a60575cb6b8181efeb3) + vite-plugin-solid: 2.11.10(solid-js@1.9.11)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0)) transitivePeerDependencies: - '@testing-library/jest-dom' - solid-js @@ -19278,11 +19300,11 @@ snapshots: dependencies: acorn: 8.17.0 - '@sveltejs/kit@2.70.1(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.55.7)(vite@6.4.3(@types/node@25.5.0)(jiti@2.7.0)(lightningcss@1.33.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.55.7)(typescript@6.0.3)(vite@6.4.3(@types/node@25.5.0)(jiti@2.7.0)(lightningcss@1.33.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0))': + '@sveltejs/kit@2.70.1(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.55.7)(vite@6.4.3(@types/node@25.5.0)(jiti@2.7.0)(lightningcss@1.33.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.55.7)(typescript@6.0.3)(vite@6.4.3(@types/node@25.5.0)(jiti@2.7.0)(lightningcss@1.33.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0))': dependencies: '@standard-schema/spec': 1.1.0 '@sveltejs/acorn-typescript': 1.0.11(acorn@8.17.0) - '@sveltejs/vite-plugin-svelte': 6.2.1(svelte@5.55.7)(vite@6.4.3(@types/node@25.5.0)(jiti@2.7.0)(lightningcss@1.33.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0)) + '@sveltejs/vite-plugin-svelte': 6.2.1(svelte@5.55.7)(vite@6.4.3(@types/node@25.5.0)(jiti@2.7.0)(lightningcss@1.33.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0)) '@types/cookie': 0.6.0 acorn: 8.17.0 cookie: 0.7.2 @@ -19294,17 +19316,17 @@ snapshots: set-cookie-parser: 3.1.2 sirv: 3.0.2 svelte: 5.55.7 - vite: 6.4.3(@types/node@25.5.0)(jiti@2.7.0)(lightningcss@1.33.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0) + vite: 6.4.3(@types/node@25.5.0)(jiti@2.7.0)(lightningcss@1.33.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0) optionalDependencies: '@opentelemetry/api': 1.9.0 typescript: 6.0.3 optional: true - '@sveltejs/kit@2.70.1(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.55.7)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.55.7)(typescript@6.0.3)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0))': + '@sveltejs/kit@2.70.1(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.55.7)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.55.7)(typescript@6.0.3)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0))': dependencies: '@standard-schema/spec': 1.1.0 '@sveltejs/acorn-typescript': 1.0.11(acorn@8.17.0) - '@sveltejs/vite-plugin-svelte': 6.2.1(svelte@5.55.7)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0)) + '@sveltejs/vite-plugin-svelte': 6.2.1(svelte@5.55.7)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0)) '@types/cookie': 0.6.0 acorn: 8.17.0 cookie: 0.7.2 @@ -19316,48 +19338,48 @@ snapshots: set-cookie-parser: 3.1.2 sirv: 3.0.2 svelte: 5.55.7 - vite: 8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0) + vite: 8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0) optionalDependencies: '@opentelemetry/api': 1.9.0 typescript: 6.0.3 - '@sveltejs/vite-plugin-svelte-inspector@5.0.2(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.55.7)(vite@6.4.3(@types/node@25.5.0)(jiti@2.7.0)(lightningcss@1.33.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.55.7)(vite@6.4.3(@types/node@25.5.0)(jiti@2.7.0)(lightningcss@1.33.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0))': + '@sveltejs/vite-plugin-svelte-inspector@5.0.2(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.55.7)(vite@6.4.3(@types/node@25.5.0)(jiti@2.7.0)(lightningcss@1.33.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.55.7)(vite@6.4.3(@types/node@25.5.0)(jiti@2.7.0)(lightningcss@1.33.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0))': dependencies: - '@sveltejs/vite-plugin-svelte': 6.2.1(svelte@5.55.7)(vite@6.4.3(@types/node@25.5.0)(jiti@2.7.0)(lightningcss@1.33.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0)) + '@sveltejs/vite-plugin-svelte': 6.2.1(svelte@5.55.7)(vite@6.4.3(@types/node@25.5.0)(jiti@2.7.0)(lightningcss@1.33.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0)) obug: 2.1.4 svelte: 5.55.7 - vite: 6.4.3(@types/node@25.5.0)(jiti@2.7.0)(lightningcss@1.33.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0) + vite: 6.4.3(@types/node@25.5.0)(jiti@2.7.0)(lightningcss@1.33.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0) optional: true - '@sveltejs/vite-plugin-svelte-inspector@5.0.2(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.55.7)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.55.7)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0))': + '@sveltejs/vite-plugin-svelte-inspector@5.0.2(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.55.7)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.55.7)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0))': dependencies: - '@sveltejs/vite-plugin-svelte': 6.2.1(svelte@5.55.7)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0)) + '@sveltejs/vite-plugin-svelte': 6.2.1(svelte@5.55.7)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0)) obug: 2.1.4 svelte: 5.55.7 - vite: 8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0) + vite: 8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0) - '@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.55.7)(vite@6.4.3(@types/node@25.5.0)(jiti@2.7.0)(lightningcss@1.33.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0))': + '@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.55.7)(vite@6.4.3(@types/node@25.5.0)(jiti@2.7.0)(lightningcss@1.33.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0))': dependencies: - '@sveltejs/vite-plugin-svelte-inspector': 5.0.2(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.55.7)(vite@6.4.3(@types/node@25.5.0)(jiti@2.7.0)(lightningcss@1.33.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.55.7)(vite@6.4.3(@types/node@25.5.0)(jiti@2.7.0)(lightningcss@1.33.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0)) + '@sveltejs/vite-plugin-svelte-inspector': 5.0.2(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.55.7)(vite@6.4.3(@types/node@25.5.0)(jiti@2.7.0)(lightningcss@1.33.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.55.7)(vite@6.4.3(@types/node@25.5.0)(jiti@2.7.0)(lightningcss@1.33.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0)) debug: 4.4.3 deepmerge: 4.3.1 magic-string: 0.30.21 svelte: 5.55.7 - vite: 6.4.3(@types/node@25.5.0)(jiti@2.7.0)(lightningcss@1.33.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0) - vitefu: 1.1.3(vite@6.4.3(@types/node@25.5.0)(jiti@2.7.0)(lightningcss@1.33.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0)) + vite: 6.4.3(@types/node@25.5.0)(jiti@2.7.0)(lightningcss@1.33.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0) + vitefu: 1.1.3(vite@6.4.3(@types/node@25.5.0)(jiti@2.7.0)(lightningcss@1.33.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0)) transitivePeerDependencies: - supports-color optional: true - '@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.55.7)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0))': + '@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.55.7)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0))': dependencies: - '@sveltejs/vite-plugin-svelte-inspector': 5.0.2(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.55.7)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.55.7)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0)) + '@sveltejs/vite-plugin-svelte-inspector': 5.0.2(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.55.7)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.55.7)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0)) debug: 4.4.3 deepmerge: 4.3.1 magic-string: 0.30.21 svelte: 5.55.7 - vite: 8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0) - vitefu: 1.1.3(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0)) + vite: 8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0) + vitefu: 1.1.3(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0)) transitivePeerDependencies: - supports-color @@ -19434,7 +19456,7 @@ snapshots: postcss: 8.5.23 tailwindcss: 4.2.1 - '@tanstack/directive-functions-plugin@1.121.21(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0))': + '@tanstack/directive-functions-plugin@1.121.21(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0))': dependencies: '@babel/code-frame': 7.26.2 '@babel/core': 7.29.7 @@ -19443,7 +19465,7 @@ snapshots: '@tanstack/router-utils': 1.161.4 babel-dead-code-elimination: 1.0.12 tiny-invariant: 1.3.3 - vite: 8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0) + vite: 8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0) transitivePeerDependencies: - supports-color @@ -19466,7 +19488,7 @@ snapshots: react: 19.2.4 react-dom: 19.2.4(react@19.2.4) - '@tanstack/react-start-rsc@0.1.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(vite-plugin-solid@2.11.10(solid-js@1.9.11)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0)))(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0))': + '@tanstack/react-start-rsc@0.1.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(vite-plugin-solid@2.11.10(solid-js@1.9.11)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0)))(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0))': dependencies: '@tanstack/react-router': 1.170.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@tanstack/react-start-server': 1.167.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4) @@ -19474,7 +19496,7 @@ snapshots: '@tanstack/router-utils': 1.162.0 '@tanstack/start-client-core': 1.169.3 '@tanstack/start-fn-stubs': 1.162.0 - '@tanstack/start-plugin-core': 1.170.4(@tanstack/react-router@1.170.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite-plugin-solid@2.11.10(solid-js@1.9.11)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0)))(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0)) + '@tanstack/start-plugin-core': 1.170.4(@tanstack/react-router@1.170.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite-plugin-solid@2.11.10(solid-js@1.9.11)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0)))(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0)) '@tanstack/start-server-core': 1.168.3 '@tanstack/start-storage-context': 1.167.3 pathe: 2.0.3 @@ -19500,21 +19522,21 @@ snapshots: transitivePeerDependencies: - crossws - '@tanstack/react-start@1.168.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(vite-plugin-solid@2.11.10(solid-js@1.9.11)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0)))(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0))': + '@tanstack/react-start@1.168.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(vite-plugin-solid@2.11.10(solid-js@1.9.11)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0)))(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0))': dependencies: '@tanstack/react-router': 1.170.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@tanstack/react-start-client': 1.167.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@tanstack/react-start-rsc': 0.1.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(vite-plugin-solid@2.11.10(solid-js@1.9.11)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0)))(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0)) + '@tanstack/react-start-rsc': 0.1.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(vite-plugin-solid@2.11.10(solid-js@1.9.11)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0)))(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0)) '@tanstack/react-start-server': 1.167.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@tanstack/router-utils': 1.162.0 '@tanstack/start-client-core': 1.169.3 - '@tanstack/start-plugin-core': 1.170.4(@tanstack/react-router@1.170.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite-plugin-solid@2.11.10(solid-js@1.9.11)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0)))(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0)) + '@tanstack/start-plugin-core': 1.170.4(@tanstack/react-router@1.170.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite-plugin-solid@2.11.10(solid-js@1.9.11)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0)))(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0)) '@tanstack/start-server-core': 1.168.3 pathe: 2.0.3 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) optionalDependencies: - vite: 8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0) + vite: 8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0) transitivePeerDependencies: - '@rspack/core' - crossws @@ -19556,7 +19578,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@tanstack/router-plugin@1.168.4(@tanstack/react-router@1.170.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite-plugin-solid@2.11.10(solid-js@1.9.11)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0)))(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0))': + '@tanstack/router-plugin@1.168.4(@tanstack/react-router@1.170.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite-plugin-solid@2.11.10(solid-js@1.9.11)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0)))(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0))': dependencies: '@babel/core': 7.29.7 '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.7) @@ -19573,8 +19595,8 @@ snapshots: zod: 3.25.76 optionalDependencies: '@tanstack/react-router': 1.170.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - vite: 8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0) - vite-plugin-solid: 2.11.10(solid-js@1.9.11)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0)) + vite: 8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0) + vite-plugin-solid: 2.11.10(solid-js@1.9.11)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0)) transitivePeerDependencies: - supports-color @@ -19606,7 +19628,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@tanstack/server-functions-plugin@1.121.21(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0))': + '@tanstack/server-functions-plugin@1.121.21(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0))': dependencies: '@babel/code-frame': 7.26.2 '@babel/core': 7.29.7 @@ -19615,7 +19637,7 @@ snapshots: '@babel/template': 7.28.6 '@babel/traverse': 7.29.0 '@babel/types': 7.29.0 - '@tanstack/directive-functions-plugin': 1.121.21(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0)) + '@tanstack/directive-functions-plugin': 1.121.21(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0)) babel-dead-code-elimination: 1.0.12 tiny-invariant: 1.3.3 transitivePeerDependencies: @@ -19651,18 +19673,18 @@ snapshots: transitivePeerDependencies: - crossws - '@tanstack/solid-start@1.168.4(@tanstack/react-router@1.170.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(solid-js@1.9.11)(vite-plugin-solid@2.11.10(solid-js@1.9.11)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0)))(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0))': + '@tanstack/solid-start@1.168.4(@tanstack/react-router@1.170.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(solid-js@1.9.11)(vite-plugin-solid@2.11.10(solid-js@1.9.11)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0)))(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0))': dependencies: '@tanstack/solid-router': 1.170.3(solid-js@1.9.11) '@tanstack/solid-start-client': 1.167.3(solid-js@1.9.11) '@tanstack/solid-start-server': 1.167.3(solid-js@1.9.11) '@tanstack/start-client-core': 1.169.3 - '@tanstack/start-plugin-core': 1.170.4(@tanstack/react-router@1.170.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite-plugin-solid@2.11.10(solid-js@1.9.11)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0)))(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0)) + '@tanstack/start-plugin-core': 1.170.4(@tanstack/react-router@1.170.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite-plugin-solid@2.11.10(solid-js@1.9.11)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0)))(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0)) '@tanstack/start-server-core': 1.168.3 pathe: 2.0.3 solid-js: 1.9.11 optionalDependencies: - vite: 8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0) + vite: 8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0) transitivePeerDependencies: - '@tanstack/react-router' - crossws @@ -19679,7 +19701,7 @@ snapshots: '@tanstack/start-fn-stubs@1.162.0': {} - '@tanstack/start-plugin-core@1.170.4(@tanstack/react-router@1.170.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite-plugin-solid@2.11.10(solid-js@1.9.11)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0)))(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0))': + '@tanstack/start-plugin-core@1.170.4(@tanstack/react-router@1.170.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite-plugin-solid@2.11.10(solid-js@1.9.11)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0)))(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0))': dependencies: '@babel/code-frame': 7.27.1 '@babel/core': 7.29.7 @@ -19687,7 +19709,7 @@ snapshots: '@rolldown/pluginutils': 1.0.0-beta.40 '@tanstack/router-core': 1.171.1 '@tanstack/router-generator': 1.167.4 - '@tanstack/router-plugin': 1.168.4(@tanstack/react-router@1.170.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite-plugin-solid@2.11.10(solid-js@1.9.11)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0)))(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0)) + '@tanstack/router-plugin': 1.168.4(@tanstack/react-router@1.170.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite-plugin-solid@2.11.10(solid-js@1.9.11)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0)))(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0)) '@tanstack/router-utils': 1.162.0 '@tanstack/start-client-core': 1.169.3 '@tanstack/start-server-core': 1.168.3 @@ -19701,11 +19723,11 @@ snapshots: srvx: 0.11.15 tinyglobby: 0.2.17 ufo: 1.6.3 - vitefu: 1.1.3(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0)) + vitefu: 1.1.3(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0)) xmlbuilder2: 4.0.3 zod: 3.25.76 optionalDependencies: - vite: 8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0) + vite: 8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0) transitivePeerDependencies: - '@tanstack/react-router' - crossws @@ -19859,7 +19881,7 @@ snapshots: '@types/d3-format@3.0.4': {} - '@types/d3-geo@3.1.0': + '@types/d3-geo@3.1.1': dependencies: '@types/geojson': 7946.0.16 @@ -19875,7 +19897,7 @@ snapshots: '@types/d3-quadtree@3.0.6': {} - '@types/d3-random@3.0.3': {} + '@types/d3-random@3.0.4': {} '@types/d3-scale-chromatic@3.1.0': {} @@ -19920,13 +19942,13 @@ snapshots: '@types/d3-fetch': 3.0.7 '@types/d3-force': 3.0.10 '@types/d3-format': 3.0.4 - '@types/d3-geo': 3.1.0 + '@types/d3-geo': 3.1.1 '@types/d3-hierarchy': 3.1.7 '@types/d3-interpolate': 3.0.4 '@types/d3-path': 3.1.1 '@types/d3-polygon': 3.0.2 '@types/d3-quadtree': 3.0.6 - '@types/d3-random': 3.0.3 + '@types/d3-random': 3.0.4 '@types/d3-scale': 4.0.9 '@types/d3-scale-chromatic': 3.1.0 '@types/d3-selection': 3.0.11 @@ -20154,9 +20176,9 @@ snapshots: '@urql/core': 5.2.0(graphql@16.13.0) wonka: 6.3.6 - '@vercel/analytics@1.6.1(@sveltejs/kit@2.70.1(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.55.7)(vite@6.4.3(@types/node@25.5.0)(jiti@2.7.0)(lightningcss@1.33.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.55.7)(typescript@6.0.3)(vite@6.4.3(@types/node@25.5.0)(jiti@2.7.0)(lightningcss@1.33.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0)))(next@16.2.11(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(@types/node@25.5.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4)(svelte@5.55.7)(vue@3.5.29(typescript@6.0.3))': + '@vercel/analytics@1.6.1(@sveltejs/kit@2.70.1(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.55.7)(vite@6.4.3(@types/node@25.5.0)(jiti@2.7.0)(lightningcss@1.33.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.55.7)(typescript@6.0.3)(vite@6.4.3(@types/node@25.5.0)(jiti@2.7.0)(lightningcss@1.33.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0)))(next@16.2.11(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(@types/node@25.5.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4)(svelte@5.55.7)(vue@3.5.29(typescript@6.0.3))': optionalDependencies: - '@sveltejs/kit': 2.70.1(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.55.7)(vite@6.4.3(@types/node@25.5.0)(jiti@2.7.0)(lightningcss@1.33.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.55.7)(typescript@6.0.3)(vite@6.4.3(@types/node@25.5.0)(jiti@2.7.0)(lightningcss@1.33.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0)) + '@sveltejs/kit': 2.70.1(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.55.7)(vite@6.4.3(@types/node@25.5.0)(jiti@2.7.0)(lightningcss@1.33.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.55.7)(typescript@6.0.3)(vite@6.4.3(@types/node@25.5.0)(jiti@2.7.0)(lightningcss@1.33.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0)) next: 16.2.11(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(@types/node@25.5.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) react: 19.2.4 svelte: 5.55.7 @@ -20212,7 +20234,7 @@ snapshots: untun: 0.1.3 uqr: 0.1.2 - '@vinxi/plugin-directives@0.5.1(vinxi@0.5.11(45e8bfaeb23901a471de4e31ceb75bd8))': + '@vinxi/plugin-directives@0.5.1(vinxi@0.5.11(fda321adb0ec1a60575cb6b8181efeb3))': dependencies: '@babel/parser': 7.29.0 acorn: 8.16.0 @@ -20223,18 +20245,18 @@ snapshots: magicast: 0.2.11 recast: 0.23.11 tslib: 2.8.1 - vinxi: 0.5.11(45e8bfaeb23901a471de4e31ceb75bd8) + vinxi: 0.5.11(fda321adb0ec1a60575cb6b8181efeb3) - '@vinxi/server-components@0.5.1(vinxi@0.5.11(45e8bfaeb23901a471de4e31ceb75bd8))': + '@vinxi/server-components@0.5.1(vinxi@0.5.11(fda321adb0ec1a60575cb6b8181efeb3))': dependencies: - '@vinxi/plugin-directives': 0.5.1(vinxi@0.5.11(45e8bfaeb23901a471de4e31ceb75bd8)) + '@vinxi/plugin-directives': 0.5.1(vinxi@0.5.11(fda321adb0ec1a60575cb6b8181efeb3)) acorn: 8.16.0 acorn-loose: 8.5.2 acorn-typescript: 1.4.13(acorn@8.16.0) astring: 1.9.0 magicast: 0.2.11 recast: 0.23.11 - vinxi: 0.5.11(45e8bfaeb23901a471de4e31ceb75bd8) + vinxi: 0.5.11(fda321adb0ec1a60575cb6b8181efeb3) '@vitest/coverage-istanbul@4.1.10(vitest@4.1.10)': dependencies: @@ -20248,7 +20270,7 @@ snapshots: magicast: 0.5.2 obug: 2.1.4 tinyrainbow: 3.1.0 - vitest: 4.1.10(@opentelemetry/api@1.9.0)(@types/node@25.3.2)(@vitest/coverage-istanbul@4.1.10)(@vitest/ui@4.1.10)(happy-dom@20.8.9)(msw@2.12.10(@types/node@25.3.2)(typescript@6.0.3))(vite@8.1.5(@types/node@25.3.2)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0)) + vitest: 4.1.10(@opentelemetry/api@1.9.0)(@types/node@25.3.2)(@vitest/coverage-istanbul@4.1.10)(@vitest/ui@4.1.10)(happy-dom@20.8.9)(msw@2.12.10(@types/node@25.3.2)(typescript@6.0.3))(vite@8.1.5(@types/node@25.3.2)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0)) transitivePeerDependencies: - supports-color @@ -20261,23 +20283,23 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.1.0 - '@vitest/mocker@4.1.10(msw@2.12.10(@types/node@25.3.2)(typescript@6.0.3))(vite@8.1.5(@types/node@25.3.2)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0))': + '@vitest/mocker@4.1.10(msw@2.12.10(@types/node@25.3.2)(typescript@6.0.3))(vite@8.1.5(@types/node@25.3.2)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0))': dependencies: '@vitest/spy': 4.1.10 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: msw: 2.12.10(@types/node@25.3.2)(typescript@6.0.3) - vite: 8.1.5(@types/node@25.3.2)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0) + vite: 8.1.5(@types/node@25.3.2)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0) - '@vitest/mocker@4.1.10(msw@2.12.10(@types/node@25.9.5)(typescript@6.0.3))(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0))': + '@vitest/mocker@4.1.10(msw@2.12.10(@types/node@25.9.5)(typescript@6.0.3))(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0))': dependencies: '@vitest/spy': 4.1.10 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: msw: 2.12.10(@types/node@25.9.5)(typescript@6.0.3) - vite: 8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0) + vite: 8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0) '@vitest/pretty-format@4.1.10': dependencies: @@ -20306,7 +20328,7 @@ snapshots: sirv: 3.0.2 tinyglobby: 0.2.17 tinyrainbow: 3.1.0 - vitest: 4.1.10(@opentelemetry/api@1.9.0)(@types/node@25.3.2)(@vitest/coverage-istanbul@4.1.10)(@vitest/ui@4.1.10)(happy-dom@20.8.9)(msw@2.12.10(@types/node@25.3.2)(typescript@6.0.3))(vite@8.1.5(@types/node@25.3.2)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0)) + vitest: 4.1.10(@opentelemetry/api@1.9.0)(@types/node@25.3.2)(@vitest/coverage-istanbul@4.1.10)(@vitest/ui@4.1.10)(happy-dom@20.8.9)(msw@2.12.10(@types/node@25.3.2)(typescript@6.0.3))(vite@8.1.5(@types/node@25.3.2)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0)) '@vitest/utils@4.1.10': dependencies: @@ -20478,6 +20500,8 @@ snapshots: acorn@8.17.0: {} + acorn@8.18.0: {} + agent-base@6.0.2: dependencies: debug: 4.4.3 @@ -20723,7 +20747,7 @@ snapshots: dependencies: '@babel/core': 7.29.7 '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.7) - core-js-compat: 3.49.0 + core-js-compat: 3.50.0 transitivePeerDependencies: - supports-color @@ -21418,7 +21442,7 @@ snapshots: cookie@1.1.1: {} - core-js-compat@3.49.0: + core-js-compat@3.50.0: dependencies: browserslist: 4.28.7 @@ -21573,17 +21597,17 @@ snapshots: csstype@3.2.3: {} - cytoscape-cose-bilkent@4.1.0(cytoscape@3.33.3): + cytoscape-cose-bilkent@4.1.0(cytoscape@3.34.0): dependencies: cose-base: 1.0.3 - cytoscape: 3.33.3 + cytoscape: 3.34.0 - cytoscape-fcose@2.2.0(cytoscape@3.33.3): + cytoscape-fcose@2.2.0(cytoscape@3.34.0): dependencies: cose-base: 2.2.0 - cytoscape: 3.33.3 + cytoscape: 3.34.0 - cytoscape@3.33.3: {} + cytoscape@3.34.0: {} d3-array@2.12.1: dependencies: @@ -21768,7 +21792,7 @@ snapshots: '@deno/shim-deno': 0.19.2 undici-types: 5.28.4 - dayjs@1.11.20: {} + dayjs@1.11.21: {} db0@0.3.4(@electric-sql/pglite@0.3.15)(better-sqlite3@12.6.2)(drizzle-orm@0.45.2(@cloudflare/workers-types@4.20260226.1)(@electric-sql/pglite@0.3.15)(@opentelemetry/api@1.9.0)(@prisma/client@7.4.1(prisma@7.4.1(@types/react@19.2.14)(better-sqlite3@12.6.2)(magicast@0.5.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@6.0.3))(typescript@6.0.3))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@upstash/redis@1.36.4)(better-sqlite3@12.6.2)(bun-types@1.3.9)(kysely@0.29.2)(mysql2@3.18.2(@types/node@25.9.5))(pg@8.19.0)(postgres@3.4.8)(prisma@7.4.1(@types/react@19.2.14)(better-sqlite3@12.6.2)(magicast@0.5.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@6.0.3)))(mysql2@3.18.2(@types/node@25.9.5)): optionalDependencies: @@ -21878,7 +21902,7 @@ snapshots: dependencies: domelementtype: 2.3.0 - dompurify@3.4.12: + dompurify@3.4.13: optionalDependencies: '@types/trusted-types': 2.0.7 @@ -22079,7 +22103,7 @@ snapshots: es-toolkit@1.44.0: {} - es-toolkit@1.46.1: {} + es-toolkit@1.50.0: {} es6-error@4.1.1: {} @@ -22671,7 +22695,7 @@ snapshots: transitivePeerDependencies: - supports-color - fumadocs-mdx@14.2.9(@types/mdast@4.0.4)(@types/mdx@2.0.13)(@types/react@19.2.14)(fumadocs-core@16.12.0(@mdx-js/mdx@3.1.1)(@tanstack/react-router@1.170.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.14)(lucide-react@1.7.0(react@19.2.4))(next@16.2.11(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(@types/node@25.5.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(zod@4.3.6))(mdast-util-directive@3.1.0)(next@16.2.11(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(@types/node@25.5.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4)(vite@6.4.3(@types/node@25.5.0)(jiti@2.7.0)(lightningcss@1.33.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0)): + fumadocs-mdx@14.2.9(@types/mdast@4.0.4)(@types/mdx@2.0.13)(@types/react@19.2.14)(fumadocs-core@16.12.0(@mdx-js/mdx@3.1.1)(@tanstack/react-router@1.170.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.14)(lucide-react@1.7.0(react@19.2.4))(next@16.2.11(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(@types/node@25.5.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(zod@4.3.6))(mdast-util-directive@3.1.0)(next@16.2.11(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(@types/node@25.5.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4)(vite@6.4.3(@types/node@25.5.0)(jiti@2.7.0)(lightningcss@1.33.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0)): dependencies: '@mdx-js/mdx': 3.1.1 '@standard-schema/spec': 1.1.0 @@ -22698,7 +22722,7 @@ snapshots: mdast-util-directive: 3.1.0 next: 16.2.11(@opentelemetry/api@1.9.0)(@playwright/test@1.58.2)(@types/node@25.5.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) react: 19.2.4 - vite: 6.4.3(@types/node@25.5.0)(jiti@2.7.0)(lightningcss@1.33.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0) + vite: 6.4.3(@types/node@25.5.0)(jiti@2.7.0)(lightningcss@1.33.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0) transitivePeerDependencies: - supports-color @@ -23612,7 +23636,7 @@ snapshots: jwa: 2.0.1 safe-buffer: 5.2.1 - katex@0.16.45: + katex@0.16.47: dependencies: commander: 8.3.0 @@ -24262,28 +24286,28 @@ snapshots: merge2@1.4.1: {} - mermaid@11.15.0: + mermaid@11.16.1: dependencies: '@braintree/sanitize-url': 7.1.2 - '@iconify/utils': 3.1.3 - '@mermaid-js/parser': 1.1.1 + '@iconify/utils': 3.1.4 + '@mermaid-js/parser': 1.2.0 '@types/d3': 7.4.3 '@upsetjs/venn.js': 2.0.0 - cytoscape: 3.33.3 - cytoscape-cose-bilkent: 4.1.0(cytoscape@3.33.3) - cytoscape-fcose: 2.2.0(cytoscape@3.33.3) + cytoscape: 3.34.0 + cytoscape-cose-bilkent: 4.1.0(cytoscape@3.34.0) + cytoscape-fcose: 2.2.0(cytoscape@3.34.0) d3: 7.9.0 d3-sankey: 0.12.3 dagre-d3-es: 7.0.14 - dayjs: 1.11.20 - dompurify: 3.4.12 - es-toolkit: 1.46.1 - katex: 0.16.45 + dayjs: 1.11.21 + dompurify: 3.4.13 + es-toolkit: 1.50.0 + katex: 0.16.47 khroma: 2.1.0 marked: 16.4.2 roughjs: 4.6.6 stylis: 4.4.0 - ts-dedent: 2.2.0 + ts-dedent: 2.3.0 uuid: 11.1.1 metro-babel-transformer@0.83.3: @@ -24403,7 +24427,7 @@ snapshots: metro-minify-terser@0.83.3: dependencies: flow-enums-runtime: 0.0.6 - terser: 5.47.1 + terser: 5.49.2 metro-minify-terser@0.83.4: dependencies: @@ -25629,6 +25653,8 @@ snapshots: package-manager-detector@1.6.0: {} + package-manager-detector@1.8.0: {} + pako@0.2.9: {} parent-module@1.0.1: @@ -25851,6 +25877,12 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 + postcss@8.5.26: + dependencies: + nanoid: 3.3.17 + picocolors: 1.1.1 + source-map-js: 1.2.1 + postgres-array@2.0.0: {} postgres-array@3.0.4: {} @@ -27713,6 +27745,13 @@ snapshots: commander: 2.20.3 source-map-support: 0.5.21 + terser@5.49.2: + dependencies: + '@jridgewell/source-map': 0.3.11 + acorn: 8.18.0 + commander: 2.20.3 + source-map-support: 0.5.21 + test-exclude@7.0.2: dependencies: '@istanbuljs/schema': 0.1.3 @@ -27763,6 +27802,8 @@ snapshots: tinyexec@1.2.4: {} + tinyexec@1.3.0: {} + tinyglobby@0.2.15: dependencies: fdir: 6.5.0(picomatch@4.0.4) @@ -27817,7 +27858,7 @@ snapshots: trough@2.2.0: {} - ts-dedent@2.2.0: {} + ts-dedent@2.3.0: {} ts-interface-checker@0.1.13: {} @@ -28349,7 +28390,7 @@ snapshots: d3-time: 3.1.0 d3-timer: 3.0.1 - vinxi@0.5.11(45e8bfaeb23901a471de4e31ceb75bd8): + vinxi@0.5.11(fda321adb0ec1a60575cb6b8181efeb3): dependencies: '@babel/core': 7.29.7 '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.7) @@ -28383,7 +28424,7 @@ snapshots: unctx: 2.5.0 unenv: 1.10.0 unstorage: 1.17.4(@azure/identity@4.13.0)(@upstash/redis@1.36.4)(db0@0.3.4(@electric-sql/pglite@0.3.15)(better-sqlite3@12.6.2)(drizzle-orm@0.45.2(@cloudflare/workers-types@4.20260226.1)(@electric-sql/pglite@0.3.15)(@opentelemetry/api@1.9.0)(@prisma/client@7.4.1(prisma@7.4.1(@types/react@19.2.14)(better-sqlite3@12.6.2)(magicast@0.5.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@6.0.3))(typescript@6.0.3))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@upstash/redis@1.36.4)(better-sqlite3@12.6.2)(bun-types@1.3.9)(kysely@0.29.2)(mysql2@3.18.2(@types/node@25.9.5))(pg@8.19.0)(postgres@3.4.8)(prisma@7.4.1(@types/react@19.2.14)(better-sqlite3@12.6.2)(magicast@0.5.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@6.0.3)))(mysql2@3.18.2(@types/node@25.9.5)))(ioredis@5.10.1) - vite: 6.4.3(@types/node@25.9.5)(jiti@2.7.0)(lightningcss@1.33.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0) + vite: 6.4.3(@types/node@25.9.5)(jiti@2.7.0)(lightningcss@1.33.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0) zod: 4.3.6 transitivePeerDependencies: - '@azure/app-configuration' @@ -28431,7 +28472,7 @@ snapshots: - xml2js - yaml - vite-plugin-solid@2.11.10(solid-js@1.9.11)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0)): + vite-plugin-solid@2.11.10(solid-js@1.9.11)(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0)): dependencies: '@babel/core': 7.29.7 '@types/babel__core': 7.20.5 @@ -28439,12 +28480,12 @@ snapshots: merge-anything: 5.1.7 solid-js: 1.9.11 solid-refresh: 0.6.3(solid-js@1.9.11) - vite: 8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0) - vitefu: 1.1.2(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0)) + vite: 8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0) + vitefu: 1.1.2(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0)) transitivePeerDependencies: - supports-color - vite@6.4.3(@types/node@25.5.0)(jiti@2.7.0)(lightningcss@1.33.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0): + vite@6.4.3(@types/node@25.5.0)(jiti@2.7.0)(lightningcss@1.33.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0): dependencies: esbuild: 0.28.1 fdir: 6.5.0(picomatch@4.0.4) @@ -28457,12 +28498,12 @@ snapshots: fsevents: 2.3.3 jiti: 2.7.0 lightningcss: 1.33.0 - terser: 5.47.1 + terser: 5.49.2 tsx: 4.22.4 yaml: 2.9.0 optional: true - vite@6.4.3(@types/node@25.9.5)(jiti@2.7.0)(lightningcss@1.33.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0): + vite@6.4.3(@types/node@25.9.5)(jiti@2.7.0)(lightningcss@1.33.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0): dependencies: esbuild: 0.28.1 fdir: 6.5.0(picomatch@4.0.4) @@ -28475,11 +28516,11 @@ snapshots: fsevents: 2.3.3 jiti: 2.7.0 lightningcss: 1.33.0 - terser: 5.47.1 + terser: 5.49.2 tsx: 4.22.4 yaml: 2.9.0 - vite@8.1.5(@types/node@25.3.2)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0): + vite@8.1.5(@types/node@25.3.2)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0): dependencies: lightningcss: 1.33.0 picomatch: 4.0.5 @@ -28491,11 +28532,11 @@ snapshots: esbuild: 0.28.1 fsevents: 2.3.3 jiti: 2.7.0 - terser: 5.47.1 + terser: 5.49.2 tsx: 4.22.4 yaml: 2.9.0 - vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0): + vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0): dependencies: lightningcss: 1.33.0 picomatch: 4.0.5 @@ -28507,27 +28548,27 @@ snapshots: esbuild: 0.28.1 fsevents: 2.3.3 jiti: 2.7.0 - terser: 5.47.1 + terser: 5.49.2 tsx: 4.22.4 yaml: 2.9.0 - vitefu@1.1.2(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0)): + vitefu@1.1.2(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0)): optionalDependencies: - vite: 8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0) + vite: 8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0) - vitefu@1.1.3(vite@6.4.3(@types/node@25.5.0)(jiti@2.7.0)(lightningcss@1.33.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0)): + vitefu@1.1.3(vite@6.4.3(@types/node@25.5.0)(jiti@2.7.0)(lightningcss@1.33.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0)): optionalDependencies: - vite: 6.4.3(@types/node@25.5.0)(jiti@2.7.0)(lightningcss@1.33.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0) + vite: 6.4.3(@types/node@25.5.0)(jiti@2.7.0)(lightningcss@1.33.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0) optional: true - vitefu@1.1.3(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0)): + vitefu@1.1.3(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0)): optionalDependencies: - vite: 8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0) + vite: 8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0) - vitest@4.1.10(@opentelemetry/api@1.9.0)(@types/node@25.3.2)(@vitest/coverage-istanbul@4.1.10)(@vitest/ui@4.1.10)(happy-dom@20.8.9)(msw@2.12.10(@types/node@25.3.2)(typescript@6.0.3))(vite@8.1.5(@types/node@25.3.2)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0)): + vitest@4.1.10(@opentelemetry/api@1.9.0)(@types/node@25.3.2)(@vitest/coverage-istanbul@4.1.10)(@vitest/ui@4.1.10)(happy-dom@20.8.9)(msw@2.12.10(@types/node@25.3.2)(typescript@6.0.3))(vite@8.1.5(@types/node@25.3.2)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0)): dependencies: '@vitest/expect': 4.1.10 - '@vitest/mocker': 4.1.10(msw@2.12.10(@types/node@25.3.2)(typescript@6.0.3))(vite@8.1.5(@types/node@25.3.2)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0)) + '@vitest/mocker': 4.1.10(msw@2.12.10(@types/node@25.3.2)(typescript@6.0.3))(vite@8.1.5(@types/node@25.3.2)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0)) '@vitest/pretty-format': 4.1.10 '@vitest/runner': 4.1.10 '@vitest/snapshot': 4.1.10 @@ -28544,7 +28585,7 @@ snapshots: tinyexec: 1.2.4 tinyglobby: 0.2.17 tinyrainbow: 3.1.0 - vite: 8.1.5(@types/node@25.3.2)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0) + vite: 8.1.5(@types/node@25.3.2)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0) why-is-node-running: 2.3.0 optionalDependencies: '@opentelemetry/api': 1.9.0 @@ -28555,10 +28596,10 @@ snapshots: transitivePeerDependencies: - msw - vitest@4.1.10(@opentelemetry/api@1.9.0)(@types/node@25.9.5)(@vitest/coverage-istanbul@4.1.10)(@vitest/ui@4.1.10)(happy-dom@20.8.9)(msw@2.12.10(@types/node@25.9.5)(typescript@6.0.3))(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0)): + vitest@4.1.10(@opentelemetry/api@1.9.0)(@types/node@25.9.5)(@vitest/coverage-istanbul@4.1.10)(@vitest/ui@4.1.10)(happy-dom@20.8.9)(msw@2.12.10(@types/node@25.9.5)(typescript@6.0.3))(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0)): dependencies: '@vitest/expect': 4.1.10 - '@vitest/mocker': 4.1.10(msw@2.12.10(@types/node@25.9.5)(typescript@6.0.3))(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0)) + '@vitest/mocker': 4.1.10(msw@2.12.10(@types/node@25.9.5)(typescript@6.0.3))(vite@8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0)) '@vitest/pretty-format': 4.1.10 '@vitest/runner': 4.1.10 '@vitest/snapshot': 4.1.10 @@ -28575,7 +28616,7 @@ snapshots: tinyexec: 1.2.4 tinyglobby: 0.2.17 tinyrainbow: 3.1.0 - vite: 8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.4)(yaml@2.9.0) + vite: 8.1.5(@types/node@25.9.5)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.2)(tsx@4.22.4)(yaml@2.9.0) why-is-node-running: 2.3.0 optionalDependencies: '@opentelemetry/api': 1.9.0 From 8a3fa826e1ec71a2250e5f64e279a5c95e80b494 Mon Sep 17 00:00:00 2001 From: Maxwell <145994855+ping-maxwell@users.noreply.github.com> Date: Tue, 11 Aug 2026 01:11:39 +1000 Subject: [PATCH 3/6] docs: make AI chat failures recoverable (#10737) Co-authored-by: Cursor --- docs/components/ai-chat.tsx | 52 +++++++++++++++++++++++++----- docs/lib/ai-chat/error.test.ts | 58 ++++++++++++++++++++++++++++++++++ docs/lib/ai-chat/error.ts | 22 +++++++++++++ docs/lib/ai-chat/route.ts | 10 +++++- docs/lib/ai-chat/step.test.ts | 14 ++++++++ docs/lib/ai-chat/step.ts | 7 ++++ docs/vitest.config.ts | 7 ++++ 7 files changed, 161 insertions(+), 9 deletions(-) create mode 100644 docs/lib/ai-chat/error.test.ts create mode 100644 docs/lib/ai-chat/error.ts create mode 100644 docs/lib/ai-chat/step.test.ts create mode 100644 docs/lib/ai-chat/step.ts create mode 100644 docs/vitest.config.ts diff --git a/docs/components/ai-chat.tsx b/docs/components/ai-chat.tsx index 7e1c7ea9cb..cd7aa06486 100644 --- a/docs/components/ai-chat.tsx +++ b/docs/components/ai-chat.tsx @@ -15,6 +15,7 @@ import { useRef, useState, } from "react"; +import { getChatErrorMessage } from "@/lib/ai-chat/error"; import { cn } from "@/lib/utils"; import { Markdown } from "./markdown"; import { MessageFeedback } from "./message-feedback"; @@ -396,11 +397,20 @@ function PanelMessages({ className, ...props }: ComponentProps<"div">) { // ─── Panel Input ───────────────────────────────────────────────────────────── function PanelInput({ autoFocus = false }: { autoFocus?: boolean }) { - const { status, sendMessage, stop, setMessages, messages, regenerate } = - useChatContext(); + const { + status, + error, + sendMessage, + stop, + setMessages, + messages, + regenerate, + clearError, + } = useChatContext(); const [input, setInput] = useState(""); const textareaRef = useRef(null); const isLoading = status === "streaming" || status === "submitted"; + const hasError = error != null; const inputDragRef = useRef<{ startY: number; startHeight: number } | null>( null, ); @@ -420,7 +430,7 @@ function PanelInput({ autoFocus = false }: { autoFocus?: boolean }) { const onSubmit = (e?: SyntheticEvent) => { e?.preventDefault(); - if (!input.trim() || isLoading) return; + if (!input.trim() || isLoading || hasError) return; void sendMessage({ text: input }); setInput(""); requestAnimationFrame(adjustHeight); @@ -487,8 +497,14 @@ function PanelInput({ autoFocus = false }: { autoFocus?: boolean }) { onSubmit(); } }} - disabled={isLoading} - placeholder={isLoading ? "AI is answering..." : "Ask a question"} + disabled={isLoading || hasError} + placeholder={ + isLoading + ? "AI is answering..." + : hasError + ? "Retry or clear the chat to continue" + : "Ask a question" + } autoFocus={autoFocus} rows={1} style={{ @@ -525,8 +541,25 @@ function PanelInput({ autoFocus = false }: { autoFocus?: boolean }) { -
- {!isLoading && +
+ {hasError && ( +
+ {getChatErrorMessage(error)} + +
+ )} + {!hasError && + !isLoading && messages.length > 0 && messages.at(-1)?.role === "assistant" && ( diff --git a/docs/lib/ai-chat/error.test.ts b/docs/lib/ai-chat/error.test.ts new file mode 100644 index 0000000000..3a62773b26 --- /dev/null +++ b/docs/lib/ai-chat/error.test.ts @@ -0,0 +1,58 @@ +import { describe, expect, it } from "vitest"; +import { + CHAT_ERROR_MESSAGE, + CHAT_RATE_LIMIT_MESSAGE, + getChatErrorMessage, +} from "./error"; + +describe("getChatErrorMessage", () => { + it("returns a recoverable message without exposing internal errors", () => { + expect( + getChatErrorMessage(new Error("Gateway request failed: secret")), + ).toBe(CHAT_ERROR_MESSAGE); + }); + + it("explains when the request was rate limited", () => { + const errors = [ + "Rate limit exceeded", + "rate_limit_error", + "Too Many Requests", + "RESOURCE_EXCEEDED", + "RESOURCE_EXHAUSTED", + "Quota exceeded", + "quota_exhausted", + "Request failed with status 429", + "HTTP 429", + ]; + + for (const error of errors) { + expect(getChatErrorMessage(new Error(error))).toBe( + CHAT_RATE_LIMIT_MESSAGE, + ); + } + }); + + it("does not mistake unrelated quota or numeric details for rate limits", () => { + const errors = [ + "Moderation blocked due to output quota policy", + "Unexpected value 429 in response", + ]; + + for (const error of errors) { + expect(getChatErrorMessage(new Error(error))).toBe(CHAT_ERROR_MESSAGE); + } + }); + + it("preserves sanitized errors received from the stream", () => { + expect(getChatErrorMessage(new Error(CHAT_ERROR_MESSAGE))).toBe( + CHAT_ERROR_MESSAGE, + ); + expect(getChatErrorMessage(new Error(CHAT_RATE_LIMIT_MESSAGE))).toBe( + CHAT_RATE_LIMIT_MESSAGE, + ); + }); + + it("handles non-Error values", () => { + expect(getChatErrorMessage(undefined)).toBe(CHAT_ERROR_MESSAGE); + }); +}); diff --git a/docs/lib/ai-chat/error.ts b/docs/lib/ai-chat/error.ts new file mode 100644 index 0000000000..bf5ebdf780 --- /dev/null +++ b/docs/lib/ai-chat/error.ts @@ -0,0 +1,22 @@ +export const CHAT_ERROR_MESSAGE = + "The response was interrupted. Please try again."; + +export const CHAT_RATE_LIMIT_MESSAGE = + "Too many requests. Please wait a moment and try again."; + +const RATE_LIMIT_PATTERN = + /rate[_ -]?limit|too many requests|resource_(?:exceeded|exhausted)|quota[_ -]?(?:exceeded|exhausted)|(?:http(?: status)?|status(?: code)?)\s*:?\s*429/i; + +export function getChatErrorMessage(error: unknown): string { + const message = error instanceof Error ? error.message : String(error); + + if (message === CHAT_ERROR_MESSAGE || message === CHAT_RATE_LIMIT_MESSAGE) { + return message; + } + + if (RATE_LIMIT_PATTERN.test(message)) { + return CHAT_RATE_LIMIT_MESSAGE; + } + + return CHAT_ERROR_MESSAGE; +} diff --git a/docs/lib/ai-chat/route.ts b/docs/lib/ai-chat/route.ts index d71ce8e14e..0b61ec18ce 100644 --- a/docs/lib/ai-chat/route.ts +++ b/docs/lib/ai-chat/route.ts @@ -1,5 +1,7 @@ import { convertToModelMessages, stepCountIs, streamText, tool } from "ai"; import * as z from "zod"; +import { getChatErrorMessage } from "@/lib/ai-chat/error"; +import { prepareChatStep } from "@/lib/ai-chat/step"; import { getLLMText } from "@/lib/llm-text"; import { source } from "@/lib/source"; import { checkRateLimit, getClientIP } from "./rate-limit"; @@ -400,10 +402,16 @@ export async function POST(req: Request) { }, }), }, + prepareStep: prepareChatStep, stopWhen: stepCountIs(8), }); - return result.toUIMessageStreamResponse(); + return result.toUIMessageStreamResponse({ + onError: (error) => { + console.error(`[ai-chat] stream error (${errorId})`, error); + return getChatErrorMessage(error); + }, + }); } catch (err) { console.error(`[ai-chat] unhandled error (${errorId})`, err); return new Response( diff --git a/docs/lib/ai-chat/step.test.ts b/docs/lib/ai-chat/step.test.ts new file mode 100644 index 0000000000..9b709346e9 --- /dev/null +++ b/docs/lib/ai-chat/step.test.ts @@ -0,0 +1,14 @@ +import { describe, expect, it } from "vitest"; +import { prepareChatStep } from "./step"; + +describe("prepareChatStep", () => { + it("requires a tool call before the assistant can answer", () => { + expect(prepareChatStep({ stepNumber: 0 })).toEqual({ + toolChoice: "required", + }); + }); + + it("lets the assistant answer after using a tool", () => { + expect(prepareChatStep({ stepNumber: 1 })).toEqual({}); + }); +}); diff --git a/docs/lib/ai-chat/step.ts b/docs/lib/ai-chat/step.ts new file mode 100644 index 0000000000..68a0c7e0a0 --- /dev/null +++ b/docs/lib/ai-chat/step.ts @@ -0,0 +1,7 @@ +export function prepareChatStep({ stepNumber }: { stepNumber: number }) { + if (stepNumber === 0) { + return { toolChoice: "required" as const }; + } + + return {}; +} diff --git a/docs/vitest.config.ts b/docs/vitest.config.ts new file mode 100644 index 0000000000..e92f387bef --- /dev/null +++ b/docs/vitest.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + environment: "node", + }, +}); From b8077b74ef9a80a7757220b72834349bd8de05c0 Mon Sep 17 00:00:00 2001 From: Taesu <166604494+bytaesu@users.noreply.github.com> Date: Tue, 11 Aug 2026 01:47:54 +0900 Subject: [PATCH 4/6] fix(cli): align packages with running CLI version (#10743) --- .changeset/align-cli-upgrades.md | 5 ++ docs/content/docs/concepts/cli.mdx | 13 +++ packages/cli/src/commands/init/index.ts | 2 +- packages/cli/src/commands/upgrade.ts | 34 +++----- packages/cli/src/index.ts | 12 +-- .../cli/src/utils/fetch-latest-version.ts | 19 ----- packages/cli/src/version.ts | 3 + packages/cli/test/init.test.ts | 3 +- packages/cli/test/upgrade.test.ts | 85 +++++++++++++++++++ packages/cli/tsconfig.json | 3 +- 10 files changed, 123 insertions(+), 56 deletions(-) create mode 100644 .changeset/align-cli-upgrades.md delete mode 100644 packages/cli/src/utils/fetch-latest-version.ts create mode 100644 packages/cli/src/version.ts create mode 100644 packages/cli/test/upgrade.test.ts diff --git a/.changeset/align-cli-upgrades.md b/.changeset/align-cli-upgrades.md new file mode 100644 index 0000000000..7a85fbaa00 --- /dev/null +++ b/.changeset/align-cli-upgrades.md @@ -0,0 +1,5 @@ +--- +"auth": patch +--- + +The upgrade command now targets the running CLI version, including prereleases, instead of the npm `latest` tag. diff --git a/docs/content/docs/concepts/cli.mdx b/docs/content/docs/concepts/cli.mdx index cac55cbf37..ceb215c0ca 100644 --- a/docs/content/docs/concepts/cli.mdx +++ b/docs/content/docs/concepts/cli.mdx @@ -54,6 +54,19 @@ npx auth@latest init * `--database` - The database you want to use. Currently, the only supported database is `SQLite`. * `--package-manager` - The package manager you want to use. Currently, the only supported package managers are `npm`, `pnpm`, `yarn`, `bun` (defaults to the manager you used to initialize the CLI). +## Upgrade + +The `upgrade` command updates older `better-auth` and official `@better-auth/*` dependencies to the version of the CLI being run. Dependencies at the same or a newer version are left unchanged. + +```package-install title="Terminal" +npx auth@latest upgrade +``` + +### Options + +* `--cwd` - The project directory containing the `package.json` to update. Defaults to the current directory. +* `--yes` - Skip the confirmation prompt and install the updates directly. + ## Info The `info` command provides diagnostic information about your Better Auth setup and environment. Useful for debugging and sharing when seeking support. diff --git a/packages/cli/src/commands/init/index.ts b/packages/cli/src/commands/init/index.ts index 988543e202..ff00372693 100644 --- a/packages/cli/src/commands/init/index.ts +++ b/packages/cli/src/commands/init/index.ts @@ -7,7 +7,6 @@ import open from "open"; import prompts from "prompts"; import yoctoSpinner from "yocto-spinner"; import * as z from "zod"; -import { cliVersion } from "../.."; import { generateDrizzleSchema } from "../../generators/drizzle"; import { generatePrismaSchema } from "../../generators/prisma"; import { @@ -22,6 +21,7 @@ import { import { getPackageInfo, hasDependency } from "../../utils/get-package-info"; import { generateSecretHash, tryCatch } from "../../utils/helper"; import { installDependencies } from "../../utils/install-dependencies"; +import { cliVersion } from "../../version"; import type { DatabaseAdapter } from "./configs/databases.config"; import type { Framework } from "./configs/frameworks.config"; import { FRAMEWORKS } from "./configs/frameworks.config"; diff --git a/packages/cli/src/commands/upgrade.ts b/packages/cli/src/commands/upgrade.ts index 9b34048bf6..cc388ad5ba 100644 --- a/packages/cli/src/commands/upgrade.ts +++ b/packages/cli/src/commands/upgrade.ts @@ -7,9 +7,9 @@ import * as semver from "semver"; import yoctoSpinner from "yocto-spinner"; import * as z from "zod"; import { detectPackageManager } from "../utils/check-package-managers"; -import { fetchLatestVersion } from "../utils/fetch-latest-version"; import { getPackageInfo } from "../utils/get-package-info"; import { installDependencies } from "../utils/install-dependencies"; +import { cliVersion } from "../version"; function isBetterAuthPackage(name: string): boolean { return name === "better-auth" || name.startsWith("@better-auth/"); @@ -18,11 +18,12 @@ function isBetterAuthPackage(name: string): boolean { interface UpgradeEntry { name: string; current: string; - latest: string; + target: string; depType: "prod" | "dev"; } -async function upgradeAction(opts: unknown) { +/** @internal */ +export async function upgradeAction(opts: unknown) { const options = z .object({ cwd: z.string(), @@ -73,22 +74,11 @@ async function upgradeAction(opts: unknown) { const spinner = yoctoSpinner({ text: "checking for updates..." }).start(); - const results = await Promise.allSettled( - candidates.map(async (c) => { - const latest = await fetchLatestVersion(c.name); - return { ...c, latest }; - }), - ); - const upgrades: UpgradeEntry[] = []; - for (const result of results) { - if (result.status !== "fulfilled" || !result.value.latest) { - continue; - } - const { name, current, latest, depType } = result.value; - const coerced = semver.coerce(current); - if (coerced && semver.lt(coerced, latest)) { - upgrades.push({ name, current, latest, depType }); + for (const { name, current, depType } of candidates) { + const currentVersion = semver.minVersion(current); + if (currentVersion && semver.lt(currentVersion, cliVersion)) { + upgrades.push({ name, current, target: cliVersion, depType }); } } @@ -102,7 +92,7 @@ async function upgradeAction(opts: unknown) { console.log(`\nThe following packages can be upgraded:\n`); for (const u of upgrades) { console.log( - ` ${chalk.cyan(u.name)} ${chalk.gray(u.current)} ${chalk.white("→")} ${chalk.green(u.latest)}`, + ` ${chalk.cyan(u.name)} ${chalk.gray(u.current)} ${chalk.white("→")} ${chalk.green(u.target)}`, ); } console.log(); @@ -127,10 +117,10 @@ async function upgradeAction(opts: unknown) { const prodUpgrades = upgrades .filter((u) => u.depType === "prod") - .map((u) => `${u.name}@${u.latest}`); + .map((u) => `${u.name}@${u.target}`); const devUpgrades = upgrades .filter((u) => u.depType === "dev") - .map((u) => `${u.name}@${u.latest}`); + .map((u) => `${u.name}@${u.target}`); const installSpinner = yoctoSpinner({ text: "installing updates...", @@ -163,7 +153,7 @@ async function upgradeAction(opts: unknown) { } export const upgrade = new Command("upgrade") - .description("Upgrade better-auth packages to their latest versions") + .description("Upgrade better-auth packages to the running CLI version") .option( "-c, --cwd ", "the working directory. defaults to the current directory.", diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index e19a08440a..a66f243893 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -10,7 +10,7 @@ import { mcp } from "./commands/mcp"; import { migrate } from "./commands/migrate"; import { generateSecret } from "./commands/secret"; import { upgrade } from "./commands/upgrade"; -import { getPackageInfo } from "./utils/get-package-info"; +import { cliVersion } from "./version"; import "dotenv/config"; @@ -18,18 +18,8 @@ import "dotenv/config"; process.on("SIGINT", () => process.exit(0)); process.on("SIGTERM", () => process.exit(0)); -export let cliVersion = "1.1.2"; - async function main() { const program = new Command("better-auth"); - - let packageInfo: Record = {}; - try { - packageInfo = await getPackageInfo(); - cliVersion = packageInfo.version || "1.1.2"; - } catch { - // it doesn't matter if we can't read the package.json file, we'll just use an empty object - } program .addCommand(ai) .addCommand(init) diff --git a/packages/cli/src/utils/fetch-latest-version.ts b/packages/cli/src/utils/fetch-latest-version.ts deleted file mode 100644 index 81a9d11433..0000000000 --- a/packages/cli/src/utils/fetch-latest-version.ts +++ /dev/null @@ -1,19 +0,0 @@ -export async function fetchLatestVersion( - packageName: string, -): Promise { - const encoded = packageName.startsWith("@") - ? `@${encodeURIComponent(packageName.slice(1))}` - : encodeURIComponent(packageName); - try { - const response = await fetch( - `https://registry.npmjs.org/${encoded}/latest`, - ); - if (!response.ok) { - return null; - } - const data = (await response.json()) as { version?: string }; - return data.version ?? null; - } catch { - return null; - } -} diff --git a/packages/cli/src/version.ts b/packages/cli/src/version.ts new file mode 100644 index 0000000000..9ebad23f88 --- /dev/null +++ b/packages/cli/src/version.ts @@ -0,0 +1,3 @@ +import packageJson from "../package.json" with { type: "json" }; + +export const cliVersion = packageJson.version; diff --git a/packages/cli/test/init.test.ts b/packages/cli/test/init.test.ts index ab4801cb99..b72b5dcac5 100644 --- a/packages/cli/test/init.test.ts +++ b/packages/cli/test/init.test.ts @@ -11,8 +11,7 @@ import { getPackageInfo, hasDependency } from "../src/utils/get-package-info"; import { installDependencies } from "../src/utils/install-dependencies"; import { testWithTmpDir } from "./test-utils"; -// Mock src/index.ts to prevent it from executing during tests -vi.mock("../src/index.ts", () => ({ +vi.mock(import("../src/version"), () => ({ cliVersion: "1.0.0", })); diff --git a/packages/cli/test/upgrade.test.ts b/packages/cli/test/upgrade.test.ts new file mode 100644 index 0000000000..f9caef10c1 --- /dev/null +++ b/packages/cli/test/upgrade.test.ts @@ -0,0 +1,85 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; +import type { Spinner } from "yocto-spinner"; +import { upgradeAction } from "../src/commands/upgrade"; +import { detectPackageManager } from "../src/utils/check-package-managers"; +import { getPackageInfo } from "../src/utils/get-package-info"; +import { installDependencies } from "../src/utils/install-dependencies"; + +vi.mock(import("../src/version"), () => ({ + cliVersion: "1.7.0-rc.4", +})); +vi.mock(import("../src/utils/check-package-managers"), { spy: true }); +vi.mock(import("../src/utils/get-package-info"), { spy: true }); +vi.mock(import("../src/utils/install-dependencies"), { spy: true }); +vi.mock(import("yocto-spinner"), () => ({ + default: vi.fn(() => { + const spinner: Spinner = { + text: "", + color: "cyan", + start: vi.fn(() => spinner), + stop: vi.fn(() => spinner), + success: vi.fn(() => spinner), + error: vi.fn(() => spinner), + warning: vi.fn(() => spinner), + info: vi.fn(() => spinner), + clear: vi.fn(() => spinner), + get isSpinning() { + return false; + }, + }; + return spinner; + }), +})); + +const mockDetectPackageManager = vi.mocked(detectPackageManager); +const mockGetPackageInfo = vi.mocked(getPackageInfo); +const mockInstallDependencies = vi.mocked(installDependencies); + +describe("upgradeAction", () => { + beforeEach(() => { + vi.spyOn(console, "log").mockImplementation(() => {}); + mockGetPackageInfo.mockReturnValue({ + dependencies: { + "better-auth": "^1.6.26", + "@better-auth/core": "1.7.0-beta.10", + }, + devDependencies: { + "@better-auth/passkey": "~1.6.26", + }, + }); + mockDetectPackageManager.mockResolvedValue({ packageManager: "pnpm" }); + mockInstallDependencies.mockResolvedValue(true); + }); + + it("upgrades Better Auth packages to the running CLI version", async () => { + await upgradeAction({ cwd: process.cwd(), yes: true }); + + expect(mockInstallDependencies).toHaveBeenCalledTimes(2); + expect(mockInstallDependencies).toHaveBeenCalledWith({ + dependencies: ["better-auth@1.7.0-rc.4", "@better-auth/core@1.7.0-rc.4"], + packageManager: "pnpm", + cwd: process.cwd(), + type: "prod", + }); + expect(mockInstallDependencies).toHaveBeenCalledWith({ + dependencies: ["@better-auth/passkey@1.7.0-rc.4"], + packageManager: "pnpm", + cwd: process.cwd(), + type: "dev", + }); + }); + + it("does not downgrade packages newer than the running CLI", async () => { + mockGetPackageInfo.mockReturnValue({ + dependencies: { + "better-auth": "^1.7.0", + "@better-auth/core": "^1.7.0-rc.4", + }, + }); + + await upgradeAction({ cwd: process.cwd(), yes: true }); + + expect(mockDetectPackageManager).not.toHaveBeenCalled(); + expect(mockInstallDependencies).not.toHaveBeenCalled(); + }); +}); diff --git a/packages/cli/tsconfig.json b/packages/cli/tsconfig.json index 998bb6379b..49489f1ff3 100644 --- a/packages/cli/tsconfig.json +++ b/packages/cli/tsconfig.json @@ -10,5 +10,6 @@ { "path": "../core/tsconfig.json" } - ] + ], + "include": ["**/*.ts", "package.json"] } From 999acbd41d4d6bad81b24ce9edef063632ad22bf Mon Sep 17 00:00:00 2001 From: Taesu <166604494+bytaesu@users.noreply.github.com> Date: Wed, 12 Aug 2026 02:50:45 +0900 Subject: [PATCH 5/6] Merge commit from fork * fix(sso): bind domain verification to provider state Complete domain verification with an atomic guarded transition keyed by the provider row, exact domain snapshot, and unverified state. Concurrent provider mutations now return a retryable conflict instead of applying stale DNS proof. Document the conflict behavior and cover provider mutation, replacement, deletion, simultaneous verification, memory-adapter, and downstream trust cases. * fix(sso): allow domain verification when the stored bit is null * test(sso): assert the resolved DNS hostname outside the mock callback * fix(sso): require verified domains for organization assignment Use verified persisted provider domains and canonical verified user emails for domain-derived routing and organization membership. Keep explicit organization-bound SSO provisioning separate from domain trust. * fix(sso): scope domain verification to organization assignment * fix(sso): keep verified provider lookup internal --------- Co-authored-by: Gustavo Valverde --- .changeset/guard-sso-domain-verification.md | 5 + .changeset/secure-sso-domain-provisioning.md | 5 + docs/content/docs/plugins/sso.mdx | 30 +- .../domain-verification-concurrency.test.ts | 395 +++++++++++++++ packages/sso/src/domain-verification.test.ts | 153 ++++++ .../linking/org-assignment.security.test.ts | 286 +++++++++++ .../sso/src/linking/org-assignment.test.ts | 455 ++++++++++++++---- packages/sso/src/linking/org-assignment.ts | 298 +++++++----- .../sso/src/routes/domain-verification.ts | 42 +- 9 files changed, 1438 insertions(+), 231 deletions(-) create mode 100644 .changeset/guard-sso-domain-verification.md create mode 100644 .changeset/secure-sso-domain-provisioning.md create mode 100644 packages/sso/src/domain-verification-concurrency.test.ts create mode 100644 packages/sso/src/linking/org-assignment.security.test.ts diff --git a/.changeset/guard-sso-domain-verification.md b/.changeset/guard-sso-domain-verification.md new file mode 100644 index 0000000000..c9a52624c1 --- /dev/null +++ b/.changeset/guard-sso-domain-verification.md @@ -0,0 +1,5 @@ +--- +"@better-auth/sso": patch +--- + +Domain verification now applies only to the domains the provider held when the request started. If the provider changes while the DNS check is still running, the request returns `409` with the `SSO_PROVIDER_CHANGED` code instead of recording a result, so callers can reload the provider and retry. diff --git a/.changeset/secure-sso-domain-provisioning.md b/.changeset/secure-sso-domain-provisioning.md new file mode 100644 index 0000000000..6119239506 --- /dev/null +++ b/.changeset/secure-sso-domain-provisioning.md @@ -0,0 +1,5 @@ +--- +"@better-auth/sso": patch +--- + +Automatic organization assignment from an email domain now requires a verified provider domain and a verified stored user email, so a social sign-in no longer joins an organization whose SSO provider only claims that domain. Explicit organization-bound OIDC and SAML provisioning is unchanged. diff --git a/docs/content/docs/plugins/sso.mdx b/docs/content/docs/plugins/sso.mdx index 9aa928a476..d858a8c941 100644 --- a/docs/content/docs/plugins/sso.mdx +++ b/docs/content/docs/plugins/sso.mdx @@ -786,7 +786,17 @@ await auth.api.registerSSOProvider({ }); ``` -Now when users from `acmecorp.com` sign in through this provider, they'll automatically be added to the "Acme Corp" organization with the appropriate role. +Users who sign in through this provider are automatically added to the "Acme +Corp" organization because the provider is explicitly linked to that +organization. Domain-derived assignment from another sign-in method requires +domain verification, a verified stored user email, and one unambiguous +organization match. + + + Automatic SSO provisioning does not accept or cancel pending invitations. An + invited user must complete the existing invitation flow before Better Auth + creates an automatic membership for the same organization. + #### Self-Service SSO Dashboard @@ -1287,11 +1297,16 @@ The provider ID is stored in the OAuth state so the callback can identify which ## Domain verification -Domain verification allows your application to automatically trust a new SSO provider -by automatically validating ownership via the associated domain. +Domain verification allows your application to trust a new SSO provider after +validating ownership of every associated domain. When a provider's domain is verified, it is also trusted for **automatic account linking**. This means that if a user signs in with an SSO provider (OIDC or SAML) and an existing account with the same email exists, the accounts will be linked automatically — as long as the user's email domain matches the provider's verified domain. +Domain verification is also required for automatic organization assignment +after a user signs in through another identity provider, such as a social +provider. Better Auth uses the stored user's verified email and assigns an +organization only when the verified domain maps to exactly one. + ```ts title="auth-client.ts" @@ -1343,6 +1358,13 @@ Once enabled, make sure you migrate the database schema (again). See the [Schema](#if-you-have-enabled-domain-verification) section to add the fields manually. + + With domain verification disabled, Better Auth does not create organization + memberships from email-domain matches. Signing in through a provider that is + explicitly linked to an organization still provisions normally, and + `signIn.sso` still resolves providers by email or domain. + + ### Verify your domain When domain verification is enabled, every new SSO provider will be untrusted at first. @@ -1384,6 +1406,8 @@ Once you have configured your domain, you can use your `auth` instance to submit This request will either result in a rejection (could not prove your ownership over the domain) or if the verification is successful, your SSO provider domain will be marked as verified. +Verification applies to the provider's exact domain configuration when the request starts. If the provider is updated or deleted while DNS verification is in progress, the request returns a `409` response with the `SSO_PROVIDER_CHANGED` code. Reload the provider, confirm its current domains, and retry verification. Request a new token first if the current provider does not have an active verification token. + ```ts type verifyDomain = { diff --git a/packages/sso/src/domain-verification-concurrency.test.ts b/packages/sso/src/domain-verification-concurrency.test.ts new file mode 100644 index 0000000000..1f029ec77c --- /dev/null +++ b/packages/sso/src/domain-verification-concurrency.test.ts @@ -0,0 +1,395 @@ +import { getTestInstance } from "better-auth/test"; +import { afterEach, describe, expect, it, vi } from "vitest"; +import { sso } from "."; +import { ssoClient } from "./client"; + +const dnsMock = vi.hoisted(() => ({ + resolveTxt: vi.fn(), +})); + +vi.mock("node:dns/promises", () => ({ + ...dnsMock, + default: dnsMock, +})); + +function createDeferred() { + let resolve!: (value: T) => void; + const promise = new Promise((resolvePromise) => { + resolve = resolvePromise; + }); + return { promise, resolve }; +} + +async function createDomainVerificationHarness() { + const { auth, signInWithTestUser } = await getTestInstance( + { + plugins: [ + sso({ + domainVerification: { enabled: true }, + }), + ], + }, + { + clientOptions: { + plugins: [ + ssoClient({ + domainVerification: { enabled: true }, + }), + ], + }, + }, + ); + const { headers: sessionHeaders } = await signInWithTestUser(); + const request = ( + path: string, + options: { + method?: "GET" | "POST"; + body?: Record; + } = {}, + ) => { + const headers = new Headers(sessionHeaders); + if (options.body) { + headers.set("content-type", "application/json"); + } + return auth.handler( + new Request(`http://localhost:3000/api/auth${path}`, { + method: options.method ?? "GET", + headers, + body: options.body ? JSON.stringify(options.body) : undefined, + }), + ); + }; + + const registerProvider = async ( + providerId: string, + domain: string, + options: { + audience?: string; + issuer?: string; + } = {}, + ) => { + const issuer = options.issuer ?? "https://idp.example"; + const response = await request("/sso/register", { + method: "POST", + body: { + providerId, + issuer, + domain, + samlConfig: { + entryPoint: `${issuer}/sso`, + cert: "test-certificate", + callbackUrl: `http://localhost:3000/api/auth/sso/saml2/sp/acs/${providerId}`, + audience: options.audience, + idpMetadata: { + entityID: issuer, + }, + spMetadata: {}, + }, + }, + }); + expect(response.status).toBe(200); + return (await response.json()) as { + domain: string; + domainVerificationToken: string; + domainVerified: boolean; + providerId: string; + samlConfig: { + audience?: string; + }; + }; + }; + + const getProvider = async (providerId: string) => { + const response = await request( + `/sso/get-provider?providerId=${providerId}`, + ); + const provider = + response.status === 200 + ? ((await response.json()) as { + domain: string; + domainVerified: boolean; + samlConfig: { + audience?: string; + }; + }) + : null; + return { response, provider }; + }; + + return { getProvider, registerProvider, request }; +} + +const staleProviderError = { + code: "SSO_PROVIDER_CHANGED", + message: + "SSO provider changed while domain verification was in progress. Reload the provider and try again", +}; + +describe("domain verification concurrency", () => { + afterEach(() => { + dnsMock.resolveTxt.mockReset(); + }); + + /** + * @see https://github.com/better-auth/better-auth/security/advisories/GHSA-8c5h-wx78-2cfg + */ + it("rejects a DNS proof after the provider domain changes", async () => { + const { getProvider, registerProvider, request } = + await createDomainVerificationHarness(); + const providerId = "domain-update-race"; + const attackerDomain = "attacker.example"; + const victimDomain = "victim.example"; + const provider = await registerProvider(providerId, attackerDomain); + + const dnsStarted = createDeferred(); + const dnsCompletion = createDeferred(); + dnsMock.resolveTxt.mockImplementation(async () => { + dnsStarted.resolve(); + return dnsCompletion.promise; + }); + + const verificationPromise = request("/sso/verify-domain", { + method: "POST", + body: { providerId }, + }); + await dnsStarted.promise; + + const updateResponse = await request("/sso/update-provider", { + method: "POST", + body: { + providerId, + domain: victimDomain, + }, + }); + expect(updateResponse.status).toBe(200); + + dnsCompletion.resolve([[provider.domainVerificationToken]]); + const verificationResponse = await verificationPromise; + + expect(verificationResponse.status).toBe(409); + expect(await verificationResponse.json()).toEqual(staleProviderError); + // The proof was collected for the domain held at request start, not the one + // swapped in. + expect(dnsMock.resolveTxt).toHaveBeenCalledWith( + `_better-auth-token-${providerId}.${attackerDomain}`, + ); + const { provider: persistedProvider } = await getProvider(providerId); + expect(persistedProvider).toMatchObject({ + domain: victimDomain, + domainVerified: false, + }); + }); + + /** + * @see https://github.com/better-auth/better-auth/security/advisories/GHSA-8c5h-wx78-2cfg + */ + it("keeps a later domain update unverified after verification completes", async () => { + const { getProvider, registerProvider, request } = + await createDomainVerificationHarness(); + const providerId = "verify-before-update"; + const originalDomain = "original.example"; + const changedDomain = "changed.example"; + const provider = await registerProvider(providerId, originalDomain); + dnsMock.resolveTxt.mockResolvedValue([[provider.domainVerificationToken]]); + + const verificationResponse = await request("/sso/verify-domain", { + method: "POST", + body: { providerId }, + }); + expect(verificationResponse.status).toBe(204); + + const updateResponse = await request("/sso/update-provider", { + method: "POST", + body: { + providerId, + domain: changedDomain, + }, + }); + expect(updateResponse.status).toBe(200); + + const { provider: persistedProvider } = await getProvider(providerId); + expect(persistedProvider).toMatchObject({ + domain: changedDomain, + domainVerified: false, + }); + }); + + /** + * @see https://github.com/better-auth/better-auth/security/advisories/GHSA-8c5h-wx78-2cfg + */ + it("rejects a DNS proof after the provider is deleted", async () => { + const { getProvider, registerProvider, request } = + await createDomainVerificationHarness(); + const providerId = "delete-race"; + const domain = "delete.example"; + const provider = await registerProvider(providerId, domain); + + const dnsStarted = createDeferred(); + const dnsCompletion = createDeferred(); + dnsMock.resolveTxt.mockImplementation(async () => { + dnsStarted.resolve(); + return dnsCompletion.promise; + }); + + const verificationPromise = request("/sso/verify-domain", { + method: "POST", + body: { providerId }, + }); + await dnsStarted.promise; + + const deleteResponse = await request("/sso/delete-provider", { + method: "POST", + body: { providerId }, + }); + expect(deleteResponse.status).toBe(200); + + dnsCompletion.resolve([[provider.domainVerificationToken]]); + const verificationResponse = await verificationPromise; + expect(verificationResponse.status).toBe(409); + expect(await verificationResponse.json()).toEqual(staleProviderError); + + const { response: getProviderResponse } = await getProvider(providerId); + expect(getProviderResponse.status).toBe(404); + }); + + /** + * @see https://github.com/better-auth/better-auth/security/advisories/GHSA-8c5h-wx78-2cfg + */ + it("does not apply an old proof to a replacement with the same provider ID", async () => { + const { getProvider, registerProvider, request } = + await createDomainVerificationHarness(); + const providerId = "replacement-race"; + const originalDomain = "original.example"; + const replacementDomain = "replacement.example"; + const originalProvider = await registerProvider(providerId, originalDomain); + + const dnsStarted = createDeferred(); + const dnsCompletion = createDeferred(); + dnsMock.resolveTxt.mockImplementation(async () => { + dnsStarted.resolve(); + return dnsCompletion.promise; + }); + + const verificationPromise = request("/sso/verify-domain", { + method: "POST", + body: { providerId }, + }); + await dnsStarted.promise; + + const deleteResponse = await request("/sso/delete-provider", { + method: "POST", + body: { providerId }, + }); + expect(deleteResponse.status).toBe(200); + const replacement = await registerProvider(providerId, replacementDomain, { + issuer: "https://replacement-idp.example", + }); + expect(replacement.domainVerified).toBe(false); + + dnsCompletion.resolve([[originalProvider.domainVerificationToken]]); + const verificationResponse = await verificationPromise; + expect(verificationResponse.status).toBe(409); + expect(await verificationResponse.json()).toEqual(staleProviderError); + + const { provider: persistedProvider } = await getProvider(providerId); + expect(persistedProvider).toMatchObject({ + domain: replacementDomain, + domainVerified: false, + }); + }); + + /** + * @see https://github.com/better-auth/better-auth/security/advisories/GHSA-8c5h-wx78-2cfg + */ + it("verifies the domain when two simultaneous requests prove the same domain", async () => { + const { getProvider, registerProvider, request } = + await createDomainVerificationHarness(); + const providerId = "simultaneous-verification"; + const domain = "simultaneous.example"; + const provider = await registerProvider(providerId, domain); + + let startedCount = 0; + const bothStarted = createDeferred(); + const dnsCompletion = createDeferred(); + dnsMock.resolveTxt.mockImplementation(async () => { + startedCount += 1; + if (startedCount === 2) { + bothStarted.resolve(); + } + return dnsCompletion.promise; + }); + + const firstVerification = request("/sso/verify-domain", { + method: "POST", + body: { providerId }, + }); + const secondVerification = request("/sso/verify-domain", { + method: "POST", + body: { providerId }, + }); + await bothStarted.promise; + dnsCompletion.resolve([[provider.domainVerificationToken]]); + + // Both requests proved the domain that is still stored, so neither is + // stale. Reporting a conflict here would tell a retrying client the + // provider changed when nothing did. + const responses = await Promise.all([ + firstVerification, + secondVerification, + ]); + expect(responses.map((response) => response.status)).toEqual([204, 204]); + + const { provider: persistedProvider } = await getProvider(providerId); + expect(persistedProvider).toMatchObject({ domain, domainVerified: true }); + }); + + /** + * @see https://github.com/better-auth/better-auth/security/advisories/GHSA-8c5h-wx78-2cfg + */ + it("allows an unrelated provider update while DNS verification is in flight", async () => { + const { getProvider, registerProvider, request } = + await createDomainVerificationHarness(); + const providerId = "unrelated-update"; + const domain = "unchanged.example"; + const provider = await registerProvider(providerId, domain, { + audience: "original-audience", + }); + + const dnsStarted = createDeferred(); + const dnsCompletion = createDeferred(); + dnsMock.resolveTxt.mockImplementation(async () => { + dnsStarted.resolve(); + return dnsCompletion.promise; + }); + + const verificationPromise = request("/sso/verify-domain", { + method: "POST", + body: { providerId }, + }); + await dnsStarted.promise; + + const updateResponse = await request("/sso/update-provider", { + method: "POST", + body: { + providerId, + samlConfig: { + audience: "updated-audience", + }, + }, + }); + expect(updateResponse.status).toBe(200); + + dnsCompletion.resolve([[provider.domainVerificationToken]]); + const verificationResponse = await verificationPromise; + expect(verificationResponse.status).toBe(204); + + const { provider: persistedProvider } = await getProvider(providerId); + expect(persistedProvider).toMatchObject({ + domain, + domainVerified: true, + samlConfig: { + audience: "updated-audience", + }, + }); + }); +}); diff --git a/packages/sso/src/domain-verification.test.ts b/packages/sso/src/domain-verification.test.ts index fbd80c62db..644f1db752 100644 --- a/packages/sso/src/domain-verification.test.ts +++ b/packages/sso/src/domain-verification.test.ts @@ -14,6 +14,14 @@ const dnsMock = vi.hoisted(() => { }; }); +function createDeferred() { + let resolve!: (value: T) => void; + const promise = new Promise((resolvePromise) => { + resolve = resolvePromise; + }); + return { promise, resolve }; +} + vi.mock("node:dns/promises", () => { return { ...dnsMock, @@ -559,6 +567,151 @@ describe("Domain verification", async () => { ); }); + /** + * @see https://github.com/better-auth/better-auth/security/advisories/GHSA-8c5h-wx78-2cfg + */ + it("rejects a stale proof after a domain update with the memory adapter", async () => { + const { auth, getAuthHeaders, registerSSOProvider } = createTestAuth(); + const headers = await getAuthHeaders(testUser); + const provider = await registerSSOProvider(headers); + + const dnsStarted = createDeferred(); + const dnsCompletion = createDeferred(); + dnsMock.resolveTxt.mockImplementation(async () => { + dnsStarted.resolve(); + return dnsCompletion.promise; + }); + + const verificationPromise = auth.api.verifyDomain({ + body: { + providerId: provider.providerId, + }, + headers, + asResponse: true, + }); + await dnsStarted.promise; + + const updateResponse = await auth.api.updateSSOProvider({ + body: { + providerId: provider.providerId, + domain: "changed.example", + }, + headers, + asResponse: true, + }); + expect(updateResponse.status).toBe(200); + + dnsCompletion.resolve([[provider.domainVerificationToken]]); + const verificationResponse = await verificationPromise; + expect(verificationResponse.status).toBe(409); + expect(await verificationResponse.json()).toEqual({ + code: "SSO_PROVIDER_CHANGED", + message: + "SSO provider changed while domain verification was in progress. Reload the provider and try again", + }); + + const persistedProvider = await auth.api.getSSOProvider({ + query: { providerId: provider.providerId }, + headers, + }); + expect(persistedProvider).toMatchObject({ + domain: "changed.example", + domainVerified: false, + }); + }); + + /** + * `domainVerified` only joins the schema once `domainVerification` is + * enabled, so providers registered before that have no stored value for the + * bit. Enabling the option later must not strand them. + * + * @see https://github.com/better-auth/better-auth/security/advisories/GHSA-8c5h-wx78-2cfg + */ + it("verifies a provider registered before domain verification was enabled", async () => { + const db = { + user: [] as Record[], + session: [] as Record[], + account: [] as Record[], + verification: [] as Record[], + ssoProvider: [] as Record[], + }; + const buildAuth = (domainVerificationEnabled: boolean) => + betterAuth({ + database: memoryAdapter(db), + baseURL: "http://localhost:3000", + emailAndPassword: { enabled: true }, + plugins: [ + sso( + domainVerificationEnabled + ? { domainVerification: { enabled: true } } + : {}, + ), + ], + }); + const signIn = async ( + auth: ReturnType, + options: { register?: boolean } = {}, + ) => { + const client = createAuthClient({ + baseURL: "http://localhost:3000", + plugins: [bearer()], + fetchOptions: { + customFetchImpl: async (url, init) => + auth.handler(new Request(url, init)), + }, + }); + if (options.register) { + await client.signUp.email(testUser); + } + const headers = new Headers(); + await client.signIn.email(testUser, { + throw: true, + onSuccess: setCookieToHeader(headers), + }); + return headers; + }; + + const legacyAuth = buildAuth(false); + await legacyAuth.api.registerSSOProvider({ + body: { + providerId: "pre-upgrade-provider", + issuer: "http://hello.com:8081", + domain: "http://hello.com:8081", + samlConfig: { + entryPoint: "http://idp.com:", + cert: "the-cert", + callbackUrl: "http://hello.com:8081/api/sso/saml2/callback", + spMetadata: {}, + }, + }, + headers: await signIn(legacyAuth, { register: true }), + }); + // Precondition: the column is absent, so the bit was never stored. + expect(db.ssoProvider[0]).not.toHaveProperty("domainVerified"); + + const auth = buildAuth(true); + const headers = await signIn(auth); + const { domainVerificationToken } = + await auth.api.requestDomainVerification({ + body: { providerId: "pre-upgrade-provider" }, + headers, + }); + dnsMock.resolveTxt.mockResolvedValue([[domainVerificationToken]]); + + const response = await auth.api.verifyDomain({ + body: { providerId: "pre-upgrade-provider" }, + headers, + asResponse: true, + }); + + expect(response.status).toBe(204); + const persistedProvider = await auth.api.getSSOProvider({ + query: { providerId: "pre-upgrade-provider" }, + headers, + }); + expect(persistedProvider).toMatchObject({ domainVerified: true }); + }); + it("should verify a provider domain ownership (custom token verification prefix)", async () => { const { auth, getAuthHeaders, registerSSOProvider } = createTestAuth({ domainVerification: { tokenPrefix: "auth-prefix" }, diff --git a/packages/sso/src/linking/org-assignment.security.test.ts b/packages/sso/src/linking/org-assignment.security.test.ts new file mode 100644 index 0000000000..ab2bbae466 --- /dev/null +++ b/packages/sso/src/linking/org-assignment.security.test.ts @@ -0,0 +1,286 @@ +import type { User } from "better-auth"; +import { organization } from "better-auth/plugins"; +import { getTestInstance } from "better-auth/test"; +import { SignJWT } from "jose"; +import { describe, expect, it, vi } from "vitest"; +import { sso } from ".."; + +const TEST_DOMAIN = "verified-corp.test"; +const TEST_EMAIL = `employee@${TEST_DOMAIN}`; + +function createInstance(domainVerification?: { enabled: true }) { + return getTestInstance({ + plugins: [ + sso(domainVerification ? { domainVerification } : {}), + organization(), + ], + }); +} + +type Instance = Awaited>; + +async function createGoogleIdToken(emailVerified: boolean, subject: string) { + return new SignJWT({ + aud: "test", + azp: "test", + email: TEST_EMAIL, + email_verified: emailVerified, + iss: "https://accounts.google.com", + name: "Verified Employee", + sub: subject, + }) + .setProtectedHeader({ alg: "HS256" }) + .setIssuedAt() + .setExpirationTime("1h") + .sign(new TextEncoder().encode("private-test-only")); +} + +function mockGoogleToken(idToken: string) { + const originalFetch = globalThis.fetch; + vi.spyOn(globalThis, "fetch").mockImplementation(async (input, init) => { + const url = + typeof input === "string" + ? input + : input instanceof URL + ? input.href + : input.url; + if (url === "https://oauth2.googleapis.com/token") { + return Response.json({ + access_token: "test-access-token", + expires_in: 3600, + id_token: idToken, + token_type: "Bearer", + }); + } + return originalFetch(input, init); + }); +} + +/** + * Creates an organization owned by the test user and an SSO provider that claims + * `TEST_DOMAIN` for it. + */ +async function registerOrganizationProvider( + instance: Instance, + options: { domainVerified: boolean }, +) { + const { headers } = await instance.signInWithTestUser(); + const targetOrganization = await instance.auth.api.createOrganization({ + body: { name: "Target Organization", slug: "target-organization" }, + headers, + }); + if (!targetOrganization) { + throw new Error("Target organization was not created"); + } + + await instance.auth.api.registerSSOProvider({ + body: { + domain: TEST_DOMAIN, + issuer: "https://idp.example.test", + organizationId: targetOrganization.id, + providerId: "target-provider", + samlConfig: { + audience: "target-provider", + callbackUrl: "http://localhost:3000/api/auth/sso/callback", + cert: "test-certificate", + entryPoint: "https://idp.example.test/sso", + idpMetadata: { entityID: "https://idp.example.test" }, + spMetadata: {}, + wantAssertionsSigned: true, + }, + }, + headers, + }); + + if (options.domainVerified) { + await instance.db.update({ + model: "ssoProvider", + where: [{ field: "providerId", value: "target-provider" }], + update: { domainVerified: true }, + }); + } + + return targetOrganization; +} + +/** Drives a full Google sign-in and returns the resulting user row. */ +async function signInWithGoogle(instance: Instance) { + const headers = new Headers(); + const signInResponse = await instance.client.signIn.social({ + callbackURL: "/signed-in", + fetchOptions: { onSuccess: instance.cookieSetter(headers) }, + provider: "google", + }); + const authorizationURL = signInResponse.data?.url; + if (!authorizationURL) { + throw new Error("Google authorization URL was not created"); + } + const state = new URL(authorizationURL).searchParams.get("state"); + if (!state) { + throw new Error("OAuth state was not created"); + } + + let callbackStatus: number | undefined; + await instance.client.$fetch("/callback/google", { + headers, + method: "GET", + onError(context) { + callbackStatus = context.response.status; + }, + query: { code: "test-authorization-code", state }, + }); + expect(callbackStatus).toBe(302); + + const user = await instance.db.findOne({ + model: "user", + where: [{ field: "email", value: TEST_EMAIL }], + }); + if (!user) { + throw new Error(`Google sign-in did not produce a user for ${TEST_EMAIL}`); + } + return user; +} + +/** Signs up a verified email-password user and links Google to it. */ +async function linkGoogleToExistingUser(instance: Instance) { + const sessionHeaders = new Headers(); + const signUpResponse = await instance.client.signUp.email( + { email: TEST_EMAIL, name: "Existing User", password: "test-password" }, + { onSuccess: instance.cookieSetter(sessionHeaders) }, + ); + const userId = signUpResponse.data?.user.id; + if (!userId) { + throw new Error("Existing user was not created"); + } + await instance.db.update({ + model: "user", + where: [{ field: "id", value: userId }], + update: { emailVerified: true }, + }); + + const linkResponse = await instance.client.linkSocial( + { callbackURL: "/settings", provider: "google" }, + { + headers: sessionHeaders, + onSuccess: instance.cookieSetter(sessionHeaders), + }, + ); + const authorizationURL = linkResponse.data?.url; + if (!authorizationURL) { + throw new Error("Google account-link URL was not created"); + } + const state = new URL(authorizationURL).searchParams.get("state"); + if (!state) { + throw new Error("OAuth state was not created"); + } + await instance.client.$fetch("/callback/google", { + headers: sessionHeaders, + method: "GET", + query: { code: "test-link-code", state }, + }); + + return { sessionHeaders, userId }; +} + +function findMembership( + instance: Instance, + organizationId: string, + userId: string, +) { + return instance.db.findOne({ + model: "member", + where: [ + { field: "organizationId", value: organizationId }, + { field: "userId", value: userId }, + ], + }); +} + +/** + * @see https://github.com/better-auth/better-auth/security/advisories/GHSA-phx7-w8x2-3xgf + */ +describe("SSO verified-domain organization assignment", () => { + it("does not auto-join a social-login user through an unverified SSO domain claim", async () => { + mockGoogleToken(await createGoogleIdToken(true, "disabled-verification")); + const instance = await createInstance(); + const target = await registerOrganizationProvider(instance, { + domainVerified: false, + }); + + const user = await signInWithGoogle(instance); + + await expect( + findMembership(instance, target.id, user.id), + ).resolves.toBeNull(); + }); + + it("auto-joins a verified social-login user through a verified SSO domain", async () => { + mockGoogleToken(await createGoogleIdToken(true, "verified-social")); + const instance = await createInstance({ enabled: true }); + const target = await registerOrganizationProvider(instance, { + domainVerified: true, + }); + + const user = await signInWithGoogle(instance); + + await expect( + findMembership(instance, target.id, user.id), + ).resolves.not.toBeNull(); + }); + + it("does not auto-join when the social provider does not verify the email", async () => { + mockGoogleToken(await createGoogleIdToken(false, "unverified-social")); + const instance = await createInstance({ enabled: true }); + const target = await registerOrganizationProvider(instance, { + domainVerified: true, + }); + + const user = await signInWithGoogle(instance); + + expect(user.emailVerified).toBe(false); + await expect( + findMembership(instance, target.id, user.id), + ).resolves.toBeNull(); + }); + + it("does not auto-join while a social account is linked to an existing user", async () => { + mockGoogleToken(await createGoogleIdToken(true, "linked-social")); + const instance = await createInstance({ enabled: true }); + const target = await registerOrganizationProvider(instance, { + domainVerified: true, + }); + + const { userId } = await linkGoogleToExistingUser(instance); + + await expect( + instance.db.findOne({ + model: "account", + where: [ + { field: "providerId", value: "google" }, + { field: "userId", value: userId }, + ], + }), + ).resolves.not.toBeNull(); + await expect( + findMembership(instance, target.id, userId), + ).resolves.toBeNull(); + }); + + it("auto-joins the linked user on their next social sign-in", async () => { + mockGoogleToken(await createGoogleIdToken(true, "linked-social")); + const instance = await createInstance({ enabled: true }); + const target = await registerOrganizationProvider(instance, { + domainVerified: true, + }); + const { sessionHeaders, userId } = await linkGoogleToExistingUser(instance); + await instance.client.signOut({ + fetchOptions: { headers: sessionHeaders }, + }); + + await signInWithGoogle(instance); + + await expect( + findMembership(instance, target.id, userId), + ).resolves.not.toBeNull(); + }); +}); diff --git a/packages/sso/src/linking/org-assignment.test.ts b/packages/sso/src/linking/org-assignment.test.ts index 9b5c090a18..82329d138b 100644 --- a/packages/sso/src/linking/org-assignment.test.ts +++ b/packages/sso/src/linking/org-assignment.test.ts @@ -2,107 +2,123 @@ import type { GenericEndpointContext, User } from "better-auth"; import { betterAuth } from "better-auth"; import { memoryAdapter } from "better-auth/adapters/memory"; import { organization } from "better-auth/plugins"; -import { describe, expect, it } from "vitest"; +import { describe, expect, it, vi } from "vitest"; import { sso } from ".."; -import { assignOrganizationByDomain } from "./org-assignment"; +import { + assignOrganizationByDomain, + assignOrganizationFromProvider, +} from "./org-assignment"; -describe("assignOrganizationByDomain", () => { - const createTestContext = () => { - const data = { - user: [] as User[], - session: [] as { id: string }[], - account: [] as { id: string }[], - ssoProvider: [] as { - id: string; - providerId: string; - issuer: string; - domain: string; - domainVerified: boolean; - organizationId: string | null; - userId: string; - }[], - member: [] as { - id: string; - organizationId: string; - userId: string; - role: string; - createdAt: Date; - }[], - organization: [] as { - id: string; - name: string; - slug: string; - createdAt: Date; - }[], - }; - - const memory = memoryAdapter(data); - - const auth = betterAuth({ - database: memory, - baseURL: "http://localhost:3000", - emailAndPassword: { - enabled: true, - }, - plugins: [ - sso({ - domainVerification: { - enabled: true, - }, - }), - organization(), - ], - }); - - const createContext = async () => { - const context = await auth.$context; - return { context } as unknown as Partial; - }; - - return { auth, data, createContext }; - }; - - const createUser = (overrides: Partial = {}): User => ({ - id: "user-1", - email: "alice@example.com", - name: "Alice", - emailVerified: true, - createdAt: new Date(), - updatedAt: new Date(), - ...overrides, - }); - - const createOrg = ( - overrides: Partial<{ id: string; name: string; slug: string }> = {}, - ) => ({ - id: "org-1", - name: "Test Org", - slug: "test-org", - createdAt: new Date(), - ...overrides, - }); - - const createProvider = ( - overrides: Partial<{ +const createTestContext = (domainVerificationEnabled = true) => { + const data = { + user: [] as User[], + session: [] as { id: string }[], + account: [] as { id: string }[], + ssoProvider: [] as { id: string; providerId: string; issuer: string; domain: string; - domainVerified: boolean; + domainVerified?: boolean; organizationId: string | null; userId: string; - }> = {}, - ) => ({ - id: "provider-1", - providerId: "test-provider", - issuer: "https://idp.example.com", - domain: "example.com", - domainVerified: false, - organizationId: "org-1" as string | null, - userId: "user-1", - ...overrides, + }[], + member: [] as { + id: string; + organizationId: string; + userId: string; + role: string; + createdAt: Date; + }[], + invitation: [] as { + id: string; + email: string; + organizationId: string; + inviterId: string; + role: string; + status: "pending" | "accepted" | "canceled" | "rejected"; + expiresAt: Date; + }[], + organization: [] as { + id: string; + name: string; + slug: string; + createdAt: Date; + }[], + }; + + const memory = memoryAdapter(data); + + const auth = betterAuth({ + database: memory, + baseURL: "http://localhost:3000", + emailAndPassword: { + enabled: true, + }, + plugins: [ + sso( + domainVerificationEnabled + ? { + domainVerification: { + enabled: true, + }, + } + : {}, + ), + organization(), + ], }); + const createContext = async () => { + const context = await auth.$context; + return { context } as unknown as Partial; + }; + + return { auth, data, createContext }; +}; + +const createUser = (overrides: Partial = {}): User => ({ + id: "user-1", + email: "alice@example.com", + name: "Alice", + emailVerified: true, + createdAt: new Date(), + updatedAt: new Date(), + ...overrides, +}); + +const createOrg = ( + overrides: Partial<{ id: string; name: string; slug: string }> = {}, +) => ({ + id: "org-1", + name: "Test Org", + slug: "test-org", + createdAt: new Date(), + ...overrides, +}); + +const createProvider = ( + overrides: Partial<{ + id: string; + providerId: string; + issuer: string; + domain: string; + domainVerified: boolean; + organizationId: string | null; + userId: string; + }> = {}, +) => ({ + id: "provider-1", + providerId: "test-provider", + issuer: "https://idp.example.com", + domain: "example.com", + domainVerified: false, + organizationId: "org-1" as string | null, + userId: "user-1", + ...overrides, +}); + +describe("assignOrganizationByDomain", () => { it("should NOT assign user to org when provider domain is unverified", async () => { const { data, createContext } = createTestContext(); @@ -274,8 +290,8 @@ describe("assignOrganizationByDomain", () => { expect(members).toHaveLength(0); }); - it("should assign user when verification is disabled (no domainVerified check)", async () => { - const { data, createContext } = createTestContext(); + it("should NOT assign user when domain verification is disabled", async () => { + const { data, createContext } = createTestContext(false); const org = createOrg(); data.organization.push(org); @@ -293,8 +309,7 @@ describe("assignOrganizationByDomain", () => { }); const members = data.member.filter((m) => m.userId === user.id); - expect(members).toHaveLength(1); - expect(members[0]?.organizationId).toBe(org.id); + expect(members).toHaveLength(0); }); it("should NOT assign user when already a member of the org", async () => { @@ -328,7 +343,12 @@ describe("assignOrganizationByDomain", () => { expect(members[0]?.role).toBe("admin"); }); - it("should only find verified provider when multiple providers claim same domain", async () => { + it.for([ + { insertionOrder: ["unverified", "verified"] }, + { insertionOrder: ["verified", "unverified"] }, + ])("should only find the verified provider when multiple providers claim the same domain ($insertionOrder)", async ({ + insertionOrder, + }) => { const { data, createContext } = createTestContext(); const legitOrg = createOrg({ @@ -343,23 +363,23 @@ describe("assignOrganizationByDomain", () => { }); data.organization.push(legitOrg, attackerOrg); - data.ssoProvider.push( - createProvider({ + const providers = { + unverified: createProvider({ id: "attacker-provider", providerId: "attacker-provider", issuer: "https://attacker.com", domainVerified: false, organizationId: attackerOrg.id, }), - ); - - data.ssoProvider.push( - createProvider({ + verified: createProvider({ id: "legit-provider", providerId: "legit-provider", domainVerified: true, organizationId: legitOrg.id, }), + }; + data.ssoProvider.push( + ...insertionOrder.map((key) => providers[key as keyof typeof providers]), ); const user = createUser(); @@ -375,4 +395,235 @@ describe("assignOrganizationByDomain", () => { expect(members).toHaveLength(1); expect(members[0]?.organizationId).toBe(legitOrg.id); }); + + it.for([ + { insertionOrder: ["first", "second"] }, + { insertionOrder: ["second", "first"] }, + ])("should NOT assign user when verified providers map one domain to different organizations ($insertionOrder)", async ({ + insertionOrder, + }) => { + const { data, createContext } = createTestContext(); + + const firstOrg = createOrg({ + id: "first-org", + name: "First Org", + slug: "first-org", + }); + const secondOrg = createOrg({ + id: "second-org", + name: "Second Org", + slug: "second-org", + }); + data.organization.push(firstOrg, secondOrg); + + const firstProvider = createProvider({ + id: "first-provider", + providerId: "first-provider", + domainVerified: true, + organizationId: firstOrg.id, + }); + const secondProvider = createProvider({ + id: "second-provider", + providerId: "second-provider", + domainVerified: true, + organizationId: secondOrg.id, + }); + const providers = { first: firstProvider, second: secondProvider }; + data.ssoProvider.push( + ...insertionOrder.map((key) => providers[key as keyof typeof providers]), + ); + + const user = createUser(); + data.user.push(user); + + const ctx = (await createContext()) as GenericEndpointContext; + await assignOrganizationByDomain(ctx, { + user, + domainVerification: { enabled: true }, + }); + + expect( + data.member.filter((member) => member.userId === user.id), + ).toHaveLength(0); + }); + + it("should assign once when verified providers map one domain to the same organization", async () => { + const { data, createContext } = createTestContext(); + const org = createOrg(); + data.organization.push(org); + data.ssoProvider.push( + createProvider({ + id: "second-provider", + providerId: "second-provider", + domainVerified: true, + organizationId: org.id, + }), + createProvider({ + id: "first-provider", + providerId: "first-provider", + domainVerified: true, + organizationId: org.id, + }), + ); + const user = createUser(); + data.user.push(user); + + const ctx = (await createContext()) as GenericEndpointContext; + await assignOrganizationByDomain(ctx, { + user, + domainVerification: { enabled: true }, + }); + + expect(data.member).toHaveLength(1); + expect(data.member[0]?.organizationId).toBe(org.id); + }); + + it("should require the canonical stored user email to be verified", async () => { + const { data, createContext } = createTestContext(); + + const org = createOrg(); + data.organization.push(org); + data.ssoProvider.push( + createProvider({ + domainVerified: true, + organizationId: org.id, + }), + ); + + const callbackUser = createUser({ emailVerified: true }); + data.user.push(createUser({ emailVerified: false })); + + const ctx = (await createContext()) as GenericEndpointContext; + await assignOrganizationByDomain(ctx, { + user: callbackUser, + domainVerification: { enabled: true }, + }); + + expect(data.member).toHaveLength(0); + }); + + it("should use a freshly verified canonical user instead of stale callback state", async () => { + const { data, createContext } = createTestContext(); + + const org = createOrg(); + data.organization.push(org); + data.ssoProvider.push( + createProvider({ + domainVerified: true, + organizationId: org.id, + }), + ); + + const callbackUser = createUser({ emailVerified: false }); + data.user.push(createUser({ emailVerified: true })); + + const ctx = (await createContext()) as GenericEndpointContext; + await assignOrganizationByDomain(ctx, { + user: callbackUser, + domainVerification: { enabled: true }, + }); + + const members = data.member.filter( + (member) => member.userId === callbackUser.id, + ); + expect(members).toHaveLength(1); + expect(members[0]?.organizationId).toBe(org.id); + }); + + it("should skip automatic membership while an invitation is pending", async () => { + const { data, createContext } = createTestContext(); + + const org = createOrg(); + const user = createUser(); + const invitation = { + id: "invitation-1", + email: user.email, + organizationId: org.id, + inviterId: "inviter-1", + role: "admin", + status: "pending" as const, + expiresAt: new Date(Date.now() + 60_000), + }; + data.organization.push(org); + data.user.push(user); + data.ssoProvider.push( + createProvider({ + domainVerified: true, + organizationId: org.id, + }), + ); + data.invitation.push(invitation); + + const ctx = (await createContext()) as GenericEndpointContext; + await assignOrganizationByDomain(ctx, { + user, + domainVerification: { enabled: true }, + }); + + expect(data.member).toHaveLength(0); + expect(data.invitation).toEqual([invitation]); + }); + + it("should surface adapter failures instead of treating them as ineligibility", async () => { + const { data, createContext } = createTestContext(); + const user = createUser(); + data.user.push(user); + const ctx = (await createContext()) as GenericEndpointContext; + vi.spyOn(ctx.context.adapter, "findMany").mockRejectedValueOnce( + new Error("provider lookup failed"), + ); + + await expect( + assignOrganizationByDomain(ctx, { + user, + domainVerification: { enabled: true }, + }), + ).rejects.toThrow("provider lookup failed"); + }); +}); + +describe("assignOrganizationFromProvider", () => { + it.for([ + "oidc", + "saml", + ] as const)("should preserve direct %s organization provisioning without domain verification", async (providerType) => { + const { data, createContext } = createTestContext(false); + + const user = createUser(); + const organizationRecord = createOrg({ + name: "Enterprise", + slug: "enterprise", + }); + const provider = { + ...createProvider({ + providerId: "enterprise-provider", + userId: "owner-1", + }), + organizationId: organizationRecord.id, + }; + data.user.push(user); + data.organization.push(organizationRecord); + data.ssoProvider.push(provider); + + const organizationId = organizationRecord.id; + const ctx = (await createContext()) as GenericEndpointContext; + await assignOrganizationFromProvider(ctx, { + user, + provider, + profile: { + providerType, + providerId: provider.providerId, + accountId: "enterprise-account", + email: user.email, + emailVerified: true, + }, + }); + + expect(data.member).toHaveLength(1); + expect(data.member[0]).toMatchObject({ + organizationId, + role: "member", + userId: user.id, + }); + }); }); diff --git a/packages/sso/src/linking/org-assignment.ts b/packages/sso/src/linking/org-assignment.ts index 72077ca2d4..7d411c43ab 100644 --- a/packages/sso/src/linking/org-assignment.ts +++ b/packages/sso/src/linking/org-assignment.ts @@ -1,6 +1,6 @@ import type { GenericEndpointContext, OAuth2Tokens, User } from "better-auth"; import type { SSOOptions, SSOProvider } from "../types"; -import { domainMatches } from "../utils"; +import { domainMatches, parseProviderDomains } from "../utils"; import type { NormalizedSSOProfile } from "./types"; export interface OrganizationProvisioningOptions { @@ -22,60 +22,6 @@ export interface AssignOrganizationFromProviderOptions { provisioningOptions?: OrganizationProvisioningOptions; } -/** - * Assigns a user to an organization based on the SSO provider's organizationId. - * Used in SSO flows (OIDC, SAML) where the provider is already linked to an org. - */ -export async function assignOrganizationFromProvider( - ctx: GenericEndpointContext, - options: AssignOrganizationFromProviderOptions, -): Promise { - const { user, profile, provider, token, provisioningOptions } = options; - - if (!provider.organizationId) { - return; - } - - if (provisioningOptions?.disabled) { - return; - } - - if (!ctx.context.hasPlugin("organization")) { - return; - } - - const isAlreadyMember = await ctx.context.adapter.findOne({ - model: "member", - where: [ - { field: "organizationId", value: provider.organizationId }, - { field: "userId", value: user.id }, - ], - }); - - if (isAlreadyMember) { - return; - } - - const role = provisioningOptions?.getRole - ? await provisioningOptions.getRole({ - user, - userInfo: profile.rawAttributes || {}, - token, - provider, - }) - : provisioningOptions?.defaultRole || "member"; - - await ctx.context.adapter.create({ - model: "member", - data: { - organizationId: provider.organizationId, - userId: user.id, - role, - createdAt: new Date(), - }, - }); -} - export interface AssignOrganizationByDomainOptions { user: User; provisioningOptions?: OrganizationProvisioningOptions; @@ -84,93 +30,209 @@ export interface AssignOrganizationByDomainOptions { }; } -/** - * Assigns a user to an organization based on their email domain. - * Looks up SSO providers that match the user's email domain and assigns - * the user to the associated organization. - * - * This enables domain-based org assignment for non-SSO sign-in methods - * (e.g., Google OAuth with @acme.com email gets added to Acme's org). - */ -export async function assignOrganizationByDomain( +type OrganizationAssignmentOptions = + | ({ + reason: "provider-bound"; + } & AssignOrganizationFromProviderOptions) + | ({ + reason: "verified-domain"; + } & AssignOrganizationByDomainOptions); + +type OrganizationAssignmentResult = + | "assigned" + | "already-member" + | "ambiguous-target" + | "ineligible-source" + | "invitation-pending" + | "no-target" + | "provisioning-disabled" + | "unverified-identity"; + +function getEmailDomain(email: string): string | null { + const normalizedEmail = email.trim().toLowerCase(); + const parts = normalizedEmail.split("@"); + if (parts.length !== 2 || !parts[0] || !parts[1]) { + return null; + } + const domain = parts[1]; + if (domain.includes("/") || domain.includes("\\") || domain.includes(":")) { + return null; + } + const normalizedDomains = parseProviderDomains(domain); + if (!normalizedDomains || normalizedDomains.length !== 1) { + return null; + } + return normalizedDomains[0] ?? null; +} + +async function findVerifiedDomainProviders( ctx: GenericEndpointContext, - options: AssignOrganizationByDomainOptions, -): Promise { - const { user, provisioningOptions, domainVerification } = options; - - if (provisioningOptions?.disabled) { - return; - } - - if (!ctx.context.hasPlugin("organization")) { - return; - } - - const domain = user.email.split("@")[1]; - if (!domain) { - return; - } - - // Support comma-separated domains for multi-domain SSO - // First try exact match (fast path) - const whereClause: { field: string; value: string | boolean }[] = [ - { field: "domain", value: domain }, - ]; - - if (domainVerification?.enabled) { - whereClause.push({ field: "domainVerified", value: true }); - } - - let ssoProvider = await ctx.context.adapter.findOne>({ + domain: string, +): Promise[]> { + const allVerifiedProviders = await ctx.context.adapter.findMany< + SSOProvider + >({ model: "ssoProvider", - where: whereClause, + where: [{ field: "domainVerified", value: true }], }); + return allVerifiedProviders.filter((provider) => + domainMatches(domain, provider.domain), + ); +} - // If not found, search all providers for comma-separated domain match - if (!ssoProvider) { - const allProviders = await ctx.context.adapter.findMany< - SSOProvider - >({ - model: "ssoProvider", - where: domainVerification?.enabled - ? [{ field: "domainVerified", value: true }] - : [], - }); - ssoProvider = - allProviders.find((p) => domainMatches(domain, p.domain)) ?? null; +async function assignOrganization( + ctx: GenericEndpointContext, + options: OrganizationAssignmentOptions, +): Promise { + if (options.provisioningOptions?.disabled) { + return "provisioning-disabled"; + } + if (!ctx.context.hasPlugin("organization")) { + return "ineligible-source"; } - if (!ssoProvider || !ssoProvider.organizationId) { - return; + let user: User; + let provider: SSOProvider; + let organizationId: string; + let userInfo: Record; + let token: OAuth2Tokens | undefined; + + if (options.reason === "provider-bound") { + if (!options.provider.organizationId) { + return "no-target"; + } + user = options.user; + provider = options.provider; + organizationId = options.provider.organizationId; + userInfo = options.profile.rawAttributes || {}; + token = options.token; + } else { + if (!options.domainVerification?.enabled) { + return "provisioning-disabled"; + } + const canonicalUser = await ctx.context.internalAdapter.findUserById( + options.user.id, + ); + if (!canonicalUser) { + ctx.context.logger.error( + "Unable to assign SSO organization membership because the canonical user was not found", + { userId: options.user.id }, + ); + return "ineligible-source"; + } + if (!canonicalUser.emailVerified) { + return "unverified-identity"; + } + const domain = getEmailDomain(canonicalUser.email); + if (!domain) { + return "unverified-identity"; + } + const matchingProviders = ( + await findVerifiedDomainProviders(ctx, domain) + ).filter( + ( + matchingProvider, + ): matchingProvider is SSOProvider & { + organizationId: string; + } => Boolean(matchingProvider.organizationId), + ); + const organizationIds = new Set( + matchingProviders.map( + (matchingProvider) => matchingProvider.organizationId, + ), + ); + if (organizationIds.size > 1) { + ctx.context.logger.warn( + "Skipped SSO organization provisioning because a verified domain maps to multiple organizations", + { domain, userId: canonicalUser.id }, + ); + return "ambiguous-target"; + } + if (matchingProviders.length === 0) { + return "no-target"; + } + matchingProviders.sort((left, right) => + left.providerId.localeCompare(right.providerId), + ); + const selectedProvider = matchingProviders[0]; + if (!selectedProvider) { + return "no-target"; + } + user = canonicalUser; + provider = selectedProvider; + organizationId = selectedProvider.organizationId; + userInfo = {}; + token = undefined; } const isAlreadyMember = await ctx.context.adapter.findOne({ model: "member", where: [ - { field: "organizationId", value: ssoProvider.organizationId }, + { field: "organizationId", value: organizationId }, { field: "userId", value: user.id }, ], }); - if (isAlreadyMember) { - return; + return "already-member"; } - const role = provisioningOptions?.getRole - ? await provisioningOptions.getRole({ - user, - userInfo: {}, - provider: ssoProvider, - }) - : provisioningOptions?.defaultRole || "member"; + const pendingInvitation = await ctx.context.adapter.findOne({ + model: "invitation", + where: [ + { field: "organizationId", value: organizationId }, + { field: "email", value: user.email.toLowerCase() }, + { field: "status", value: "pending" }, + ], + }); + if (pendingInvitation) { + return "invitation-pending"; + } + const role = options.provisioningOptions?.getRole + ? await options.provisioningOptions.getRole({ + user, + userInfo, + token, + provider, + }) + : options.provisioningOptions?.defaultRole || "member"; + + // FIXME(sso-membership-policy): route automatic SSO membership through the + // organization plugin's limits, hooks, additional fields, and atomic guard. await ctx.context.adapter.create({ model: "member", data: { - organizationId: ssoProvider.organizationId, + organizationId, userId: user.id, role, createdAt: new Date(), }, }); + return "assigned"; +} + +/** + * Assigns a user to the organization explicitly bound to an SSO provider. + */ +export async function assignOrganizationFromProvider( + ctx: GenericEndpointContext, + options: AssignOrganizationFromProviderOptions, +): Promise { + await assignOrganization(ctx, { + reason: "provider-bound", + ...options, + }); +} + +/** + * Assigns a verified user to the organization for their verified email domain. + */ +export async function assignOrganizationByDomain( + ctx: GenericEndpointContext, + options: AssignOrganizationByDomainOptions, +): Promise { + await assignOrganization(ctx, { + reason: "verified-domain", + ...options, + }); } diff --git a/packages/sso/src/routes/domain-verification.ts b/packages/sso/src/routes/domain-verification.ts index 1142bec49a..12ba2730aa 100644 --- a/packages/sso/src/routes/domain-verification.ts +++ b/packages/sso/src/routes/domain-verification.ts @@ -1,3 +1,4 @@ +import type { AuthContext } from "better-auth"; import { APIError, createAuthEndpoint, @@ -16,6 +17,37 @@ const domainVerificationBodySchema = z.object({ providerId: z.string(), }); +async function completeDomainVerification( + adapter: AuthContext["adapter"], + provider: { + id: string; + domain: string; + }, +) { + // Guarding on `id` and `domain` is what binds the proof to what DNS answered + // for. `domainVerified` stays out of the guard: it is nullable for providers + // that predate the option, and an equality guard would never match those. + const verifiedProvider = await adapter.incrementOne>({ + model: "ssoProvider", + where: [ + { field: "id", value: provider.id }, + { field: "domain", value: provider.domain }, + ], + increment: {}, + set: { + domainVerified: true, + }, + }); + + if (!verifiedProvider) { + throw new APIError("CONFLICT", { + code: "SSO_PROVIDER_CHANGED", + message: + "SSO provider changed while domain verification was in progress. Reload the provider and try again", + }); + } +} + export function getVerificationIdentifier( options: SSOOptions, providerId: string, @@ -109,7 +141,7 @@ export const verifyDomain = (options: SSOOptions) => { }, "409": { description: - "Domain has already been verified or no pending verification exists", + "Domain has already been verified, or the provider changed while verification was in progress", }, "502": { description: @@ -214,13 +246,7 @@ export const verifyDomain = (options: SSOOptions) => { // FIXME(next): this remains a provider-level proof bit. When the next // schema can change, store verification per normalized domain or force // previously verified multi-domain providers through re-verification. - await ctx.context.adapter.update>({ - model: "ssoProvider", - where: [{ field: "providerId", value: provider.providerId }], - update: { - domainVerified: true, - }, - }); + await completeDomainVerification(ctx.context.adapter, provider); ctx.setStatus(204); return; From be47e9418b4a25a4ecd51ba781d2296373b65a03 Mon Sep 17 00:00:00 2001 From: "better-release[bot]" <273320539+better-release[bot]@users.noreply.github.com> Date: Tue, 11 Aug 2026 13:56:17 -0400 Subject: [PATCH 6/6] chore: release v1.6.27 (#10686) --- .changeset/align-cli-upgrades.md | 5 ----- .changeset/calm-routes-align.md | 6 ------ .changeset/dedupe-session-suspense-retries.md | 5 ----- .changeset/guard-sso-domain-verification.md | 5 ----- .changeset/secure-sso-domain-provisioning.md | 5 ----- packages/api-key/CHANGELOG.md | 8 ++++++++ packages/api-key/package.json | 2 +- packages/better-auth/CHANGELOG.md | 17 +++++++++++++++++ packages/better-auth/package.json | 2 +- packages/cli/CHANGELOG.md | 11 +++++++++++ packages/cli/package.json | 2 +- packages/core/CHANGELOG.md | 6 ++++++ packages/core/package.json | 2 +- packages/drizzle-adapter/CHANGELOG.md | 7 +++++++ packages/drizzle-adapter/package.json | 2 +- packages/electron/CHANGELOG.md | 8 ++++++++ packages/electron/package.json | 2 +- packages/expo/CHANGELOG.md | 8 ++++++++ packages/expo/package.json | 2 +- packages/i18n/CHANGELOG.md | 8 ++++++++ packages/i18n/package.json | 2 +- packages/kysely-adapter/CHANGELOG.md | 7 +++++++ packages/kysely-adapter/package.json | 2 +- packages/memory-adapter/CHANGELOG.md | 7 +++++++ packages/memory-adapter/package.json | 2 +- packages/mongo-adapter/CHANGELOG.md | 7 +++++++ packages/mongo-adapter/package.json | 2 +- packages/oauth-provider/CHANGELOG.md | 8 ++++++++ packages/oauth-provider/package.json | 2 +- packages/passkey/CHANGELOG.md | 8 ++++++++ packages/passkey/package.json | 2 +- packages/prisma-adapter/CHANGELOG.md | 7 +++++++ packages/prisma-adapter/package.json | 2 +- packages/redis-storage/CHANGELOG.md | 7 +++++++ packages/redis-storage/package.json | 2 +- packages/scim/CHANGELOG.md | 8 ++++++++ packages/scim/package.json | 2 +- packages/sso/CHANGELOG.md | 12 ++++++++++++ packages/sso/package.json | 2 +- packages/stripe/CHANGELOG.md | 8 ++++++++ packages/stripe/package.json | 2 +- packages/telemetry/CHANGELOG.md | 7 +++++++ packages/telemetry/package.json | 2 +- packages/test-utils/CHANGELOG.md | 8 ++++++++ packages/test-utils/package.json | 2 +- 45 files changed, 187 insertions(+), 46 deletions(-) delete mode 100644 .changeset/align-cli-upgrades.md delete mode 100644 .changeset/calm-routes-align.md delete mode 100644 .changeset/dedupe-session-suspense-retries.md delete mode 100644 .changeset/guard-sso-domain-verification.md delete mode 100644 .changeset/secure-sso-domain-provisioning.md diff --git a/.changeset/align-cli-upgrades.md b/.changeset/align-cli-upgrades.md deleted file mode 100644 index 7a85fbaa00..0000000000 --- a/.changeset/align-cli-upgrades.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"auth": patch ---- - -The upgrade command now targets the running CLI version, including prereleases, instead of the npm `latest` tag. diff --git a/.changeset/calm-routes-align.md b/.changeset/calm-routes-align.md deleted file mode 100644 index 0211af20b1..0000000000 --- a/.changeset/calm-routes-align.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"@better-auth/core": patch -"better-auth": patch ---- - -Aligned endpoint and middleware context types with runtime route parameters, and preserved response headers when resolving sessions from endpoint contexts. diff --git a/.changeset/dedupe-session-suspense-retries.md b/.changeset/dedupe-session-suspense-retries.md deleted file mode 100644 index 78a4a01a8c..0000000000 --- a/.changeset/dedupe-session-suspense-retries.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"better-auth": patch ---- - -Deduplicate in-flight session requests when React retries a suspended component. diff --git a/.changeset/guard-sso-domain-verification.md b/.changeset/guard-sso-domain-verification.md deleted file mode 100644 index c9a52624c1..0000000000 --- a/.changeset/guard-sso-domain-verification.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@better-auth/sso": patch ---- - -Domain verification now applies only to the domains the provider held when the request started. If the provider changes while the DNS check is still running, the request returns `409` with the `SSO_PROVIDER_CHANGED` code instead of recording a result, so callers can reload the provider and retry. diff --git a/.changeset/secure-sso-domain-provisioning.md b/.changeset/secure-sso-domain-provisioning.md deleted file mode 100644 index 6119239506..0000000000 --- a/.changeset/secure-sso-domain-provisioning.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@better-auth/sso": patch ---- - -Automatic organization assignment from an email domain now requires a verified provider domain and a verified stored user email, so a social sign-in no longer joins an organization whose SSO provider only claims that domain. Explicit organization-bound OIDC and SAML provisioning is unchanged. diff --git a/packages/api-key/CHANGELOG.md b/packages/api-key/CHANGELOG.md index 7b483afffc..d025c60e01 100644 --- a/packages/api-key/CHANGELOG.md +++ b/packages/api-key/CHANGELOG.md @@ -1,5 +1,13 @@ # @better-auth/api-key +## 1.6.27 + +### Patch Changes + +- Updated dependencies [[`2ae491e`](https://github.com/better-auth/better-auth/commit/2ae491eac3ece50839a0eb2d4f868c4deedac67b), [`90b5093`](https://github.com/better-auth/better-auth/commit/90b509344794b8064700371cbc04b985d0519839)]: + - @better-auth/core@1.6.27 + - better-auth@1.6.27 + ## 1.6.26 ### Patch Changes diff --git a/packages/api-key/package.json b/packages/api-key/package.json index 8a176c23c8..c8d50af9af 100644 --- a/packages/api-key/package.json +++ b/packages/api-key/package.json @@ -1,6 +1,6 @@ { "name": "@better-auth/api-key", - "version": "1.6.26", + "version": "1.6.27", "description": "API Key plugin for Better Auth.", "type": "module", "license": "MIT", diff --git a/packages/better-auth/CHANGELOG.md b/packages/better-auth/CHANGELOG.md index 479c251646..191fad22a7 100644 --- a/packages/better-auth/CHANGELOG.md +++ b/packages/better-auth/CHANGELOG.md @@ -1,5 +1,22 @@ # better-auth +## 1.6.27 + +### Patch Changes + +- [#10657](https://github.com/better-auth/better-auth/pull/10657) [`2ae491e`](https://github.com/better-auth/better-auth/commit/2ae491eac3ece50839a0eb2d4f868c4deedac67b) Thanks [@bytaesu](https://github.com/bytaesu)! - Aligned endpoint and middleware context types with runtime route parameters, and preserved response headers when resolving sessions from endpoint contexts. + +- [#10676](https://github.com/better-auth/better-auth/pull/10676) [`90b5093`](https://github.com/better-auth/better-auth/commit/90b509344794b8064700371cbc04b985d0519839) Thanks [@bytaesu](https://github.com/bytaesu)! - Deduplicate in-flight session requests when React retries a suspended component. + +- Updated dependencies [[`2ae491e`](https://github.com/better-auth/better-auth/commit/2ae491eac3ece50839a0eb2d4f868c4deedac67b)]: + - @better-auth/core@1.6.27 + - @better-auth/drizzle-adapter@1.6.27 + - @better-auth/kysely-adapter@1.6.27 + - @better-auth/memory-adapter@1.6.27 + - @better-auth/mongo-adapter@1.6.27 + - @better-auth/prisma-adapter@1.6.27 + - @better-auth/telemetry@1.6.27 + ## 1.6.26 ### Patch Changes diff --git a/packages/better-auth/package.json b/packages/better-auth/package.json index aafc4a23a0..2225e113b5 100644 --- a/packages/better-auth/package.json +++ b/packages/better-auth/package.json @@ -1,6 +1,6 @@ { "name": "better-auth", - "version": "1.6.26", + "version": "1.6.27", "description": "The most comprehensive authentication framework for TypeScript.", "type": "module", "license": "MIT", diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index 67fe00bcd3..29a1f0c1d0 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,5 +1,16 @@ # auth +## 1.6.27 + +### Patch Changes + +- [#10743](https://github.com/better-auth/better-auth/pull/10743) [`b8077b7`](https://github.com/better-auth/better-auth/commit/b8077b74ef9a80a7757220b72834349bd8de05c0) Thanks [@bytaesu](https://github.com/bytaesu)! - The upgrade command now targets the running CLI version, including prereleases, instead of the npm `latest` tag. + +- Updated dependencies [[`2ae491e`](https://github.com/better-auth/better-auth/commit/2ae491eac3ece50839a0eb2d4f868c4deedac67b), [`90b5093`](https://github.com/better-auth/better-auth/commit/90b509344794b8064700371cbc04b985d0519839)]: + - @better-auth/core@1.6.27 + - better-auth@1.6.27 + - @better-auth/telemetry@1.6.27 + ## 1.6.26 ### Patch Changes diff --git a/packages/cli/package.json b/packages/cli/package.json index 5cecc883a6..adc5767d1e 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "auth", - "version": "1.6.26", + "version": "1.6.27", "description": "The CLI for Better Auth", "type": "module", "license": "MIT", diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index a19bc43fd0..51cb91d6a2 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -1,5 +1,11 @@ # @better-auth/core +## 1.6.27 + +### Patch Changes + +- [#10657](https://github.com/better-auth/better-auth/pull/10657) [`2ae491e`](https://github.com/better-auth/better-auth/commit/2ae491eac3ece50839a0eb2d4f868c4deedac67b) Thanks [@bytaesu](https://github.com/bytaesu)! - Aligned endpoint and middleware context types with runtime route parameters, and preserved response headers when resolving sessions from endpoint contexts. + ## 1.6.26 ### Patch Changes diff --git a/packages/core/package.json b/packages/core/package.json index 642bedf3ea..27bede373e 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@better-auth/core", - "version": "1.6.26", + "version": "1.6.27", "description": "The most comprehensive authentication framework for TypeScript.", "type": "module", "license": "MIT", diff --git a/packages/drizzle-adapter/CHANGELOG.md b/packages/drizzle-adapter/CHANGELOG.md index a0e627d0d4..bcc5c55327 100644 --- a/packages/drizzle-adapter/CHANGELOG.md +++ b/packages/drizzle-adapter/CHANGELOG.md @@ -1,5 +1,12 @@ # @better-auth/drizzle-adapter +## 1.6.27 + +### Patch Changes + +- Updated dependencies [[`2ae491e`](https://github.com/better-auth/better-auth/commit/2ae491eac3ece50839a0eb2d4f868c4deedac67b)]: + - @better-auth/core@1.6.27 + ## 1.6.26 ### Patch Changes diff --git a/packages/drizzle-adapter/package.json b/packages/drizzle-adapter/package.json index 4c9e9b37fc..ba678e78ff 100644 --- a/packages/drizzle-adapter/package.json +++ b/packages/drizzle-adapter/package.json @@ -1,6 +1,6 @@ { "name": "@better-auth/drizzle-adapter", - "version": "1.6.26", + "version": "1.6.27", "bugs": { "url": "https://github.com/better-auth/better-auth/issues" }, diff --git a/packages/electron/CHANGELOG.md b/packages/electron/CHANGELOG.md index 9f7e443934..a31c4723d2 100644 --- a/packages/electron/CHANGELOG.md +++ b/packages/electron/CHANGELOG.md @@ -1,5 +1,13 @@ # @better-auth/electron +## 1.6.27 + +### Patch Changes + +- Updated dependencies [[`2ae491e`](https://github.com/better-auth/better-auth/commit/2ae491eac3ece50839a0eb2d4f868c4deedac67b), [`90b5093`](https://github.com/better-auth/better-auth/commit/90b509344794b8064700371cbc04b985d0519839)]: + - @better-auth/core@1.6.27 + - better-auth@1.6.27 + ## 1.6.26 ### Patch Changes diff --git a/packages/electron/package.json b/packages/electron/package.json index ed29a9f586..c425d21f3f 100644 --- a/packages/electron/package.json +++ b/packages/electron/package.json @@ -1,6 +1,6 @@ { "name": "@better-auth/electron", - "version": "1.6.26", + "version": "1.6.27", "description": "Better Auth integration for Electron applications.", "type": "module", "license": "MIT", diff --git a/packages/expo/CHANGELOG.md b/packages/expo/CHANGELOG.md index 5cb26342ff..19d370b617 100644 --- a/packages/expo/CHANGELOG.md +++ b/packages/expo/CHANGELOG.md @@ -1,5 +1,13 @@ # @better-auth/expo +## 1.6.27 + +### Patch Changes + +- Updated dependencies [[`2ae491e`](https://github.com/better-auth/better-auth/commit/2ae491eac3ece50839a0eb2d4f868c4deedac67b), [`90b5093`](https://github.com/better-auth/better-auth/commit/90b509344794b8064700371cbc04b985d0519839)]: + - @better-auth/core@1.6.27 + - better-auth@1.6.27 + ## 1.6.26 ### Patch Changes diff --git a/packages/expo/package.json b/packages/expo/package.json index da397fd855..2a9e1f5981 100644 --- a/packages/expo/package.json +++ b/packages/expo/package.json @@ -1,6 +1,6 @@ { "name": "@better-auth/expo", - "version": "1.6.26", + "version": "1.6.27", "description": "Better Auth integration for Expo and React Native applications.", "type": "module", "license": "MIT", diff --git a/packages/i18n/CHANGELOG.md b/packages/i18n/CHANGELOG.md index 0b06954d49..342a2c356a 100644 --- a/packages/i18n/CHANGELOG.md +++ b/packages/i18n/CHANGELOG.md @@ -1,5 +1,13 @@ # @better-auth/i18n +## 1.6.27 + +### Patch Changes + +- Updated dependencies [[`2ae491e`](https://github.com/better-auth/better-auth/commit/2ae491eac3ece50839a0eb2d4f868c4deedac67b), [`90b5093`](https://github.com/better-auth/better-auth/commit/90b509344794b8064700371cbc04b985d0519839)]: + - @better-auth/core@1.6.27 + - better-auth@1.6.27 + ## 1.6.26 ### Patch Changes diff --git a/packages/i18n/package.json b/packages/i18n/package.json index dd969107f2..dd90d1fb02 100644 --- a/packages/i18n/package.json +++ b/packages/i18n/package.json @@ -1,6 +1,6 @@ { "name": "@better-auth/i18n", - "version": "1.6.26", + "version": "1.6.27", "description": "i18n plugin for Better Auth - translate error messages", "type": "module", "license": "MIT", diff --git a/packages/kysely-adapter/CHANGELOG.md b/packages/kysely-adapter/CHANGELOG.md index 64fb8933bb..337bade34e 100644 --- a/packages/kysely-adapter/CHANGELOG.md +++ b/packages/kysely-adapter/CHANGELOG.md @@ -1,5 +1,12 @@ # @better-auth/kysely-adapter +## 1.6.27 + +### Patch Changes + +- Updated dependencies [[`2ae491e`](https://github.com/better-auth/better-auth/commit/2ae491eac3ece50839a0eb2d4f868c4deedac67b)]: + - @better-auth/core@1.6.27 + ## 1.6.26 ### Patch Changes diff --git a/packages/kysely-adapter/package.json b/packages/kysely-adapter/package.json index e09c49dae5..6728207a20 100644 --- a/packages/kysely-adapter/package.json +++ b/packages/kysely-adapter/package.json @@ -1,6 +1,6 @@ { "name": "@better-auth/kysely-adapter", - "version": "1.6.26", + "version": "1.6.27", "description": "Kysely adapter for Better Auth", "type": "module", "license": "MIT", diff --git a/packages/memory-adapter/CHANGELOG.md b/packages/memory-adapter/CHANGELOG.md index c6454b5dd2..4627331ddd 100644 --- a/packages/memory-adapter/CHANGELOG.md +++ b/packages/memory-adapter/CHANGELOG.md @@ -1,5 +1,12 @@ # @better-auth/memory-adapter +## 1.6.27 + +### Patch Changes + +- Updated dependencies [[`2ae491e`](https://github.com/better-auth/better-auth/commit/2ae491eac3ece50839a0eb2d4f868c4deedac67b)]: + - @better-auth/core@1.6.27 + ## 1.6.26 ### Patch Changes diff --git a/packages/memory-adapter/package.json b/packages/memory-adapter/package.json index 721ec90e96..638c4cd459 100644 --- a/packages/memory-adapter/package.json +++ b/packages/memory-adapter/package.json @@ -1,6 +1,6 @@ { "name": "@better-auth/memory-adapter", - "version": "1.6.26", + "version": "1.6.27", "description": "Memory adapter for Better Auth", "type": "module", "license": "MIT", diff --git a/packages/mongo-adapter/CHANGELOG.md b/packages/mongo-adapter/CHANGELOG.md index 3dea804412..6977c80baf 100644 --- a/packages/mongo-adapter/CHANGELOG.md +++ b/packages/mongo-adapter/CHANGELOG.md @@ -1,5 +1,12 @@ # @better-auth/mongo-adapter +## 1.6.27 + +### Patch Changes + +- Updated dependencies [[`2ae491e`](https://github.com/better-auth/better-auth/commit/2ae491eac3ece50839a0eb2d4f868c4deedac67b)]: + - @better-auth/core@1.6.27 + ## 1.6.26 ### Patch Changes diff --git a/packages/mongo-adapter/package.json b/packages/mongo-adapter/package.json index 34659d3475..83f02a183d 100644 --- a/packages/mongo-adapter/package.json +++ b/packages/mongo-adapter/package.json @@ -1,6 +1,6 @@ { "name": "@better-auth/mongo-adapter", - "version": "1.6.26", + "version": "1.6.27", "description": "Mongo adapter for Better Auth", "type": "module", "license": "MIT", diff --git a/packages/oauth-provider/CHANGELOG.md b/packages/oauth-provider/CHANGELOG.md index 601fe7cfe9..49ea542ebe 100644 --- a/packages/oauth-provider/CHANGELOG.md +++ b/packages/oauth-provider/CHANGELOG.md @@ -1,5 +1,13 @@ # @better-auth/oauth-provider +## 1.6.27 + +### Patch Changes + +- Updated dependencies [[`2ae491e`](https://github.com/better-auth/better-auth/commit/2ae491eac3ece50839a0eb2d4f868c4deedac67b), [`90b5093`](https://github.com/better-auth/better-auth/commit/90b509344794b8064700371cbc04b985d0519839)]: + - @better-auth/core@1.6.27 + - better-auth@1.6.27 + ## 1.6.26 ### Patch Changes diff --git a/packages/oauth-provider/package.json b/packages/oauth-provider/package.json index f0b3099620..faddf9c4e3 100644 --- a/packages/oauth-provider/package.json +++ b/packages/oauth-provider/package.json @@ -1,6 +1,6 @@ { "name": "@better-auth/oauth-provider", - "version": "1.6.26", + "version": "1.6.27", "description": "An oauth provider plugin for Better Auth", "type": "module", "license": "MIT", diff --git a/packages/passkey/CHANGELOG.md b/packages/passkey/CHANGELOG.md index 75dc5fca54..93fbf4aa42 100644 --- a/packages/passkey/CHANGELOG.md +++ b/packages/passkey/CHANGELOG.md @@ -1,5 +1,13 @@ # @better-auth/passkey +## 1.6.27 + +### Patch Changes + +- Updated dependencies [[`2ae491e`](https://github.com/better-auth/better-auth/commit/2ae491eac3ece50839a0eb2d4f868c4deedac67b), [`90b5093`](https://github.com/better-auth/better-auth/commit/90b509344794b8064700371cbc04b985d0519839)]: + - @better-auth/core@1.6.27 + - better-auth@1.6.27 + ## 1.6.26 ### Patch Changes diff --git a/packages/passkey/package.json b/packages/passkey/package.json index f372e19a04..6668daaec3 100644 --- a/packages/passkey/package.json +++ b/packages/passkey/package.json @@ -1,6 +1,6 @@ { "name": "@better-auth/passkey", - "version": "1.6.26", + "version": "1.6.27", "description": "Passkey plugin for Better Auth", "type": "module", "license": "MIT", diff --git a/packages/prisma-adapter/CHANGELOG.md b/packages/prisma-adapter/CHANGELOG.md index 7336fa872f..0615390079 100644 --- a/packages/prisma-adapter/CHANGELOG.md +++ b/packages/prisma-adapter/CHANGELOG.md @@ -1,5 +1,12 @@ # @better-auth/prisma-adapter +## 1.6.27 + +### Patch Changes + +- Updated dependencies [[`2ae491e`](https://github.com/better-auth/better-auth/commit/2ae491eac3ece50839a0eb2d4f868c4deedac67b)]: + - @better-auth/core@1.6.27 + ## 1.6.26 ### Patch Changes diff --git a/packages/prisma-adapter/package.json b/packages/prisma-adapter/package.json index 76c5cc78f3..6cad1456c8 100644 --- a/packages/prisma-adapter/package.json +++ b/packages/prisma-adapter/package.json @@ -1,6 +1,6 @@ { "name": "@better-auth/prisma-adapter", - "version": "1.6.26", + "version": "1.6.27", "description": "Prisma adapter for Better Auth", "type": "module", "license": "MIT", diff --git a/packages/redis-storage/CHANGELOG.md b/packages/redis-storage/CHANGELOG.md index d298dc0cf0..93e4292a7a 100644 --- a/packages/redis-storage/CHANGELOG.md +++ b/packages/redis-storage/CHANGELOG.md @@ -1,5 +1,12 @@ # @better-auth/redis-storage +## 1.6.27 + +### Patch Changes + +- Updated dependencies [[`2ae491e`](https://github.com/better-auth/better-auth/commit/2ae491eac3ece50839a0eb2d4f868c4deedac67b)]: + - @better-auth/core@1.6.27 + ## 1.6.26 ### Patch Changes diff --git a/packages/redis-storage/package.json b/packages/redis-storage/package.json index 9b06aa3941..2a1289c878 100644 --- a/packages/redis-storage/package.json +++ b/packages/redis-storage/package.json @@ -1,6 +1,6 @@ { "name": "@better-auth/redis-storage", - "version": "1.6.26", + "version": "1.6.27", "description": "Redis storage for Better Auth secondary storage", "type": "module", "license": "MIT", diff --git a/packages/scim/CHANGELOG.md b/packages/scim/CHANGELOG.md index 5b1d7be9a1..495dbd5c45 100644 --- a/packages/scim/CHANGELOG.md +++ b/packages/scim/CHANGELOG.md @@ -1,5 +1,13 @@ # @better-auth/scim +## 1.6.27 + +### Patch Changes + +- Updated dependencies [[`2ae491e`](https://github.com/better-auth/better-auth/commit/2ae491eac3ece50839a0eb2d4f868c4deedac67b), [`90b5093`](https://github.com/better-auth/better-auth/commit/90b509344794b8064700371cbc04b985d0519839)]: + - @better-auth/core@1.6.27 + - better-auth@1.6.27 + ## 1.6.26 ### Patch Changes diff --git a/packages/scim/package.json b/packages/scim/package.json index cb3acbeb29..afc3473eb6 100644 --- a/packages/scim/package.json +++ b/packages/scim/package.json @@ -1,6 +1,6 @@ { "name": "@better-auth/scim", - "version": "1.6.26", + "version": "1.6.27", "description": "SCIM plugin for Better Auth", "type": "module", "license": "MIT", diff --git a/packages/sso/CHANGELOG.md b/packages/sso/CHANGELOG.md index bd5307f3b9..23755c7679 100644 --- a/packages/sso/CHANGELOG.md +++ b/packages/sso/CHANGELOG.md @@ -1,5 +1,17 @@ # @better-auth/sso +## 1.6.27 + +### Patch Changes + +- [`999acbd`](https://github.com/better-auth/better-auth/commit/999acbd41d4d6bad81b24ce9edef063632ad22bf) Thanks [@bytaesu](https://github.com/bytaesu)! - Domain verification now applies only to the domains the provider held when the request started. If the provider changes while the DNS check is still running, the request returns `409` with the `SSO_PROVIDER_CHANGED` code instead of recording a result, so callers can reload the provider and retry. + +- [`999acbd`](https://github.com/better-auth/better-auth/commit/999acbd41d4d6bad81b24ce9edef063632ad22bf) Thanks [@bytaesu](https://github.com/bytaesu)! - Automatic organization assignment from an email domain now requires a verified provider domain and a verified stored user email, so a social sign-in no longer joins an organization whose SSO provider only claims that domain. Explicit organization-bound OIDC and SAML provisioning is unchanged. + +- Updated dependencies [[`2ae491e`](https://github.com/better-auth/better-auth/commit/2ae491eac3ece50839a0eb2d4f868c4deedac67b), [`90b5093`](https://github.com/better-auth/better-auth/commit/90b509344794b8064700371cbc04b985d0519839)]: + - @better-auth/core@1.6.27 + - better-auth@1.6.27 + ## 1.6.26 ### Patch Changes diff --git a/packages/sso/package.json b/packages/sso/package.json index 895e419eb5..b9b923008d 100644 --- a/packages/sso/package.json +++ b/packages/sso/package.json @@ -1,6 +1,6 @@ { "name": "@better-auth/sso", - "version": "1.6.26", + "version": "1.6.27", "description": "SSO plugin for Better Auth", "type": "module", "license": "MIT", diff --git a/packages/stripe/CHANGELOG.md b/packages/stripe/CHANGELOG.md index 2e362836c6..02f54062b8 100644 --- a/packages/stripe/CHANGELOG.md +++ b/packages/stripe/CHANGELOG.md @@ -1,5 +1,13 @@ # @better-auth/stripe +## 1.6.27 + +### Patch Changes + +- Updated dependencies [[`2ae491e`](https://github.com/better-auth/better-auth/commit/2ae491eac3ece50839a0eb2d4f868c4deedac67b), [`90b5093`](https://github.com/better-auth/better-auth/commit/90b509344794b8064700371cbc04b985d0519839)]: + - @better-auth/core@1.6.27 + - better-auth@1.6.27 + ## 1.6.26 ### Patch Changes diff --git a/packages/stripe/package.json b/packages/stripe/package.json index 964dde05ed..62fa3d7bd5 100644 --- a/packages/stripe/package.json +++ b/packages/stripe/package.json @@ -1,6 +1,6 @@ { "name": "@better-auth/stripe", - "version": "1.6.26", + "version": "1.6.27", "description": "Stripe plugin for Better Auth", "type": "module", "license": "MIT", diff --git a/packages/telemetry/CHANGELOG.md b/packages/telemetry/CHANGELOG.md index c4c8e45a7e..776812fdcd 100644 --- a/packages/telemetry/CHANGELOG.md +++ b/packages/telemetry/CHANGELOG.md @@ -1,5 +1,12 @@ # @better-auth/telemetry +## 1.6.27 + +### Patch Changes + +- Updated dependencies [[`2ae491e`](https://github.com/better-auth/better-auth/commit/2ae491eac3ece50839a0eb2d4f868c4deedac67b)]: + - @better-auth/core@1.6.27 + ## 1.6.26 ### Patch Changes diff --git a/packages/telemetry/package.json b/packages/telemetry/package.json index 1cf505a987..d6d2d96fc6 100644 --- a/packages/telemetry/package.json +++ b/packages/telemetry/package.json @@ -1,6 +1,6 @@ { "name": "@better-auth/telemetry", - "version": "1.6.26", + "version": "1.6.27", "description": "Telemetry package for Better Auth", "type": "module", "license": "MIT", diff --git a/packages/test-utils/CHANGELOG.md b/packages/test-utils/CHANGELOG.md index b9cb659ca2..059c0b5808 100644 --- a/packages/test-utils/CHANGELOG.md +++ b/packages/test-utils/CHANGELOG.md @@ -1,5 +1,13 @@ # @better-auth/test-utils +## 1.6.27 + +### Patch Changes + +- Updated dependencies [[`2ae491e`](https://github.com/better-auth/better-auth/commit/2ae491eac3ece50839a0eb2d4f868c4deedac67b), [`90b5093`](https://github.com/better-auth/better-auth/commit/90b509344794b8064700371cbc04b985d0519839)]: + - @better-auth/core@1.6.27 + - better-auth@1.6.27 + ## 1.6.26 ### Patch Changes diff --git a/packages/test-utils/package.json b/packages/test-utils/package.json index 69fb3fd2b3..61ceb0bb2c 100644 --- a/packages/test-utils/package.json +++ b/packages/test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@better-auth/test-utils", - "version": "1.6.26", + "version": "1.6.27", "description": "Testing utilities for Better Auth adapter development", "type": "module", "license": "MIT",