diff --git a/.changeset/eleven-mice-jog.md b/.changeset/eleven-mice-jog.md new file mode 100644 index 00000000..5d4f30b6 --- /dev/null +++ b/.changeset/eleven-mice-jog.md @@ -0,0 +1,58 @@ +--- +'@opensaas/stack-auth': minor +--- + +Bump the `better-auth` dev dependency to `1.7.1` and move the peer range off the stale `^1.3.29` floor to `^1.4.0` (the release line where better-auth's `index: true` flags — the basis for #937's index emission — first shipped; below it the generated schema silently omitted indexes better-auth itself declares). + +**New required `account.issuer` column.** better-auth 1.7 adds a required `issuer` column to its `account` model. Because `deriveAuthLists` derives the Auth lists from better-auth's own `getAuthTables()` (#987/#997), this column now appears in the generated schema automatically — no code change was needed, only verification against real generated output. Existing projects upgrading to `better-auth@^1.7` will see a **new NOT NULL column** on their `account` table and need a backfill for existing rows. Following better-auth's own `createLocalAccountIssuer`/`createOAuthAccountIssuer` helpers (`@better-auth/core/db`): + +```sql +-- PostgreSQL / SQLite (|| is string concatenation on both) +UPDATE "Account" SET issuer = 'local:' || "providerId" WHERE issuer IS NULL AND "providerId" = 'credential'; +UPDATE "Account" SET issuer = 'local:oauth:' || "providerId" WHERE issuer IS NULL AND "providerId" != 'credential'; + +-- MySQL (|| is logical OR by default, NOT concatenation — use CONCAT instead) +UPDATE `Account` SET issuer = CONCAT('local:', providerId) WHERE issuer IS NULL AND providerId = 'credential'; +UPDATE `Account` SET issuer = CONCAT('local:oauth:', providerId) WHERE issuer IS NULL AND providerId != 'credential'; +``` + +`"Account"`/`` `Account` `` above is the stack's own greenfield default table name — substitute your project's actual (and, on Postgres, schema-qualified) table name if you renamed it via `authPlugin({ account: { tableName } })` or adopted an existing install with `adoptBetterAuthTables({ useBetterAuthTableNames: true })` (physical table `account`, commonly under a non-`public` schema). + +URL-encode `providerId` if it can contain characters outside `[A-Za-z0-9_-]`. If you configured a custom OIDC provider with its own issuer URL, use that provider's real issuer instead of the synthetic `local:oauth:` value. + +**The new `@@unique([issuer, accountId])` constraint is not yet emitted.** better-auth 1.7 also declares this composite unique index at the table level, but the stack only derives _field_-level `unique`/`index` flags today — table-level index derivation is #985, which hasn't landed. This is deliberately out of scope here (per #986's own triage note: build on #985 once it lands, don't duplicate it). When it does land, make sure your backfilled `issuer` values don't collide on `(issuer, accountId)` for any account, or the constraint will fail to apply. + +**Breaking (MCP plugin users only): `@better-auth/mcp` is now a separate package.** better-auth 1.7 split the `mcp` plugin out of `better-auth/plugins` into its own package, rebuilt on the OAuth Provider RFC 8707/9728 resource model. `@opensaas/stack-auth/plugins` now re-exports `mcp` from `@better-auth/mcp` (added as an optional peer — install it if you use MCP). The plugin also now **requires** a `resource` option: + +```typescript +import { mcp } from '@opensaas/stack-auth/plugins' +import { jwt } from 'better-auth/plugins' + +authPlugin({ + betterAuthPlugins: [ + // The OAuth Provider mcp() is built on issues JWT-based access tokens + // and requires better-auth's own jwt() plugin registered alongside it — + // omitting it throws `BetterAuthError: jwt_config` at init. + jwt(), + mcp({ + loginPage: '/sign-in', + // The page where a user approves/denies an MCP client's requested + // scopes — also required as of better-auth 1.7. + consentPage: '/consent', + // RFC 8707/9728 canonical resource identifier — required as of + // better-auth 1.7. Must match your `mcp.basePath`. HTTP is only + // accepted on loopback hosts. + resource: `${process.env.BETTER_AUTH_URL}/api/mcp`, + }), + ], +}) +``` + +better-auth 1.7's MCP plugin also declares a substantially different OAuth table set — the old `oauthApplication`/`oauthAccessToken`/`oauthConsent` three became seven tables (`oauthClient`/`oauthAccessToken`/`oauthConsent`/`oauthRefreshToken`/`oauthResource`/`oauthClientResource`/`oauthClientAssertion`). Since the derivation is schema-driven this needed no code changes, but if you have the MCP plugin enabled, running `pnpm generate` will produce a significantly different Prisma schema for these tables (new/renamed models, and `Session` gains reverse relations to the two token tables that now reference it). Review the diff and migrate your database accordingly. + +**Workspace-wide: pins `@better-auth/utils` to `0.5.0` via a root `pnpm.overrides`.** better-auth 1.7.1's own published packages disagree on this transitive dependency — `better-auth` pins it at exactly `0.4.2` while `better-call` (used by `@better-auth/core`, `@better-auth/oauth-provider`, and `@better-auth/mcp`) requires `^0.5.0` — so pnpm resolves two separate physical instances of `@better-auth/core` depending on which peer chain a given package sits in. That split is invisible at runtime but breaks TypeScript: `jwt()` (from `better-auth/plugins`) and `mcp()` (from `@better-auth/mcp`) end up typed against different `@better-auth/core` instances, so `betterAuthPlugins: [jwt(), mcp(...)]` fails to type-check with a `BetterAuthPlugin` structural-mismatch error even though both plugins are otherwise correctly configured. The override forces one instance workspace-wide. If you hit the same error in your own app, add the equivalent override to your own `package.json`. + +Also fixes two `deriveAuthLists` gaps surfaced by the MCP plugin's expanded schema: + +- the scalar-field builder now threads a static `defaultValue` through for `number`-typed fields (`integer()`/`bigInt()`), matching the existing `string`/`boolean` behavior (e.g. `oauthResource.policyVersion`, which defaults to `1`) +- a plugin table that declares only one of `createdAt`/`updatedAt` upstream (several of the new OAuth tables declare `createdAt` alone) is no longer silently dropped — it derives as an ordinary required column instead. Previously any model with an asymmetric timestamp pair had the field skipped entirely with no replacement, which crashed the first real write that supplied it (better-auth's own OAuth Provider does this at `betterAuth()` init time, seeding an `oauthResource` row) diff --git a/CLAUDE.md b/CLAUDE.md index 669b8b65..664d7c2e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -625,7 +625,7 @@ The stack provides Model Context Protocol server integration through `@opensaas/ 1. Enable MCP in config with `mcp: { enabled: true }` (the runtime reads `enabled`/`basePath`/`defaultTools`) 2. Core runtime derives CRUD tools for each list (query, create, update, delete) at request time -3. OAuth with AI assistants is wired through Better-auth's `mcp` plugin (`authPlugin({ betterAuthPlugins: [mcp({ loginPage: '/sign-in' })] })`) and the `createBetterAuthMcpAdapter` session provider +3. OAuth with AI assistants is wired through Better-auth's `mcp` plugin (`authPlugin({ betterAuthPlugins: [jwt(), mcp({ loginPage: '/sign-in', consentPage: '/consent', resource: '' })] })`, imported from `@opensaas/stack-auth/plugins` — the plugin itself comes from the optional `@better-auth/mcp` peer since better-auth 1.7 split it out of `better-auth/plugins`, and requires `jwt()` from `better-auth/plugins` registered alongside it) and the `createBetterAuthMcpAdapter` session provider 4. All tools respect existing access control rules 5. Custom tools can be added per-list via `mcp.customTools` (Zod or JSON Schema inputSchema); plugins can register global tools via `registerMcpTool` diff --git a/docs/content/how-to/mcp.md b/docs/content/how-to/mcp.md index 8a014f1f..095d2f00 100644 --- a/docs/content/how-to/mcp.md +++ b/docs/content/how-to/mcp.md @@ -39,13 +39,29 @@ In your `opensaas.config.ts`, configure the auth plugin with the MCP plugin: import { config, list } from '@opensaas/stack-core' import { authPlugin } from '@opensaas/stack-auth' import { mcp } from '@opensaas/stack-auth/plugins' +import { jwt } from 'better-auth/plugins' export default config({ plugins: [ authPlugin({ emailAndPassword: { enabled: true }, // Add MCP plugin to Better Auth - betterAuthPlugins: [mcp({ loginPage: '/sign-in' })], + betterAuthPlugins: [ + // better-auth 1.7's mcp() is built on the OAuth Provider, which + // issues JWT-based access tokens and requires better-auth's own + // jwt() plugin registered alongside it. + jwt(), + mcp({ + loginPage: '/sign-in', + // The page where a user approves/denies an MCP client's requested + // scopes — also required since better-auth 1.7's MCP plugin. + consentPage: '/consent', + // Canonical protected-resource identifier (RFC 8707/9728) — + // required since better-auth 1.7's MCP plugin, and must match + // `mcp.basePath` below. HTTP is only accepted on loopback hosts. + resource: `${process.env.BETTER_AUTH_URL || 'http://localhost:3000'}/api/mcp`, + }), + ], }), ], diff --git a/docs/content/reference/auth.md b/docs/content/reference/auth.md index 7dfdcd4d..03900c6a 100644 --- a/docs/content/reference/auth.md +++ b/docs/content/reference/auth.md @@ -286,10 +286,23 @@ Add Better Auth plugins for additional functionality: ```typescript import { authPlugin } from '@opensaas/stack-auth' import { mcp } from '@opensaas/stack-auth/plugins' +import { jwt } from 'better-auth/plugins' authPlugin({ betterAuthPlugins: [ - mcp({ loginPage: '/sign-in' }), + // better-auth 1.7's mcp() is built on the OAuth Provider, which issues + // JWT-based access tokens and requires better-auth's own jwt() plugin + // registered alongside it. + jwt(), + mcp({ + loginPage: '/sign-in', + // The page where a user approves/denies an MCP client's requested + // scopes — also required since better-auth 1.7's MCP plugin. + consentPage: '/consent', + // Canonical protected-resource identifier (RFC 8707/9728) — required + // since better-auth 1.7's MCP plugin. Must match `mcp.basePath` below. + resource: `${process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000'}/api/mcp`, + }), // Add other Better Auth plugins here ], }) @@ -669,12 +682,21 @@ To enable Model Context Protocol support with Better Auth authentication: ```typescript import { authPlugin } from '@opensaas/stack-auth' import { mcp } from '@opensaas/stack-auth/plugins' +import { jwt } from 'better-auth/plugins' export default config({ plugins: [ authPlugin({ emailAndPassword: { enabled: true }, - betterAuthPlugins: [mcp({ loginPage: '/sign-in' })], + betterAuthPlugins: [ + // better-auth 1.7's mcp() requires the jwt() plugin alongside it. + jwt(), + mcp({ + loginPage: '/sign-in', + consentPage: '/consent', + resource: `${process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000'}/api/mcp`, + }), + ], }), ], mcp: { diff --git a/examples/auth-demo/package.json b/examples/auth-demo/package.json index 95f12359..570f8eda 100644 --- a/examples/auth-demo/package.json +++ b/examples/auth-demo/package.json @@ -17,7 +17,7 @@ "@opensaas/stack-ui": "workspace:*", "@prisma/adapter-better-sqlite3": "^7.8.0", "@prisma/client": "^7.8.0", - "better-auth": "^1.6.25", + "better-auth": "^1.7.1", "next": "^16.2.10", "react": "^19.2.8", "react-dom": "^19.2.8" diff --git a/examples/auth-demo/prisma/schema.prisma b/examples/auth-demo/prisma/schema.prisma index 1cd982ad..e8274fc6 100644 --- a/examples/auth-demo/prisma/schema.prisma +++ b/examples/auth-demo/prisma/schema.prisma @@ -37,15 +37,15 @@ model User { } model Session { - id String @id @default(cuid()) - token String @unique - expiresAt DateTime? + id String @id @default(cuid()) + token String @unique + expiresAt DateTime ipAddress String? userAgent String? - userId String? @map("user") - user User? @relation(fields: [userId], references: [id]) - createdAt DateTime @default(now()) - updatedAt DateTime @default(now()) @updatedAt + userId String @map("userId") + user User @relation(onDelete: Cascade, fields: [userId], references: [id]) + createdAt DateTime @default(now()) + updatedAt DateTime @default(now()) @updatedAt @@index([userId]) } @@ -54,6 +54,7 @@ model Account { id String @id @default(cuid()) accountId String providerId String + issuer String accessToken String? refreshToken String? accessTokenExpiresAt DateTime? @@ -61,8 +62,8 @@ model Account { scope String? idToken String? password String? - userId String? @map("user") - user User? @relation(fields: [userId], references: [id]) + userId String @map("userId") + user User @relation(onDelete: Cascade, fields: [userId], references: [id]) createdAt DateTime @default(now()) updatedAt DateTime @default(now()) @updatedAt @@ -70,10 +71,12 @@ model Account { } model Verification { - id String @id @default(cuid()) + id String @id @default(cuid()) identifier String value String - expiresAt DateTime? - createdAt DateTime @default(now()) - updatedAt DateTime @default(now()) @updatedAt + expiresAt DateTime + createdAt DateTime @default(now()) + updatedAt DateTime @default(now()) @updatedAt + + @@index([identifier]) } diff --git a/examples/mcp-demo/opensaas.config.ts b/examples/mcp-demo/opensaas.config.ts index 9a76b070..2c7f5876 100644 --- a/examples/mcp-demo/opensaas.config.ts +++ b/examples/mcp-demo/opensaas.config.ts @@ -2,6 +2,7 @@ import { config, list } from '@opensaas/stack-core' import { text, relationship, select, timestamp } from '@opensaas/stack-core/fields' import { authPlugin } from '@opensaas/stack-auth' import { mcp } from '@opensaas/stack-auth/plugins' +import { jwt } from 'better-auth/plugins' import type { AccessControl } from '@opensaas/stack-core' import { z } from 'zod' import { PrismaBetterSqlite3 } from '@prisma/adapter-better-sqlite3' @@ -39,7 +40,21 @@ export default config({ plugins: [ authPlugin({ emailAndPassword: { enabled: true }, - betterAuthPlugins: [mcp({ loginPage: '/sign-in' })], + betterAuthPlugins: [ + // better-auth 1.7's mcp() is built on the OAuth Provider, which + // issues JWT-based access tokens and requires better-auth's own + // jwt() plugin registered alongside it. + jwt(), + mcp({ + loginPage: '/sign-in', + // Not implemented in this demo — a real project needs a page here + // for users to approve/deny an MCP client's requested scopes. + consentPage: '/consent', + // RFC 8707/9728 canonical resource identifier — must match the + // `mcp.basePath` below. HTTP is accepted only on loopback hosts. + resource: `${process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000'}/api/mcp`, + }), + ], extendUserList: { fields: { posts: relationship({ diff --git a/examples/mcp-demo/package.json b/examples/mcp-demo/package.json index aa3852b5..0a3acb70 100644 --- a/examples/mcp-demo/package.json +++ b/examples/mcp-demo/package.json @@ -17,6 +17,7 @@ "@opensaas/stack-ui": "workspace:*", "@prisma/adapter-better-sqlite3": "^7.8.0", "@prisma/client": "^7.8.0", + "better-auth": "^1.7.1", "next": "^16.2.10", "react": "^19.2.8", "react-dom": "^19.2.8", diff --git a/examples/starter-auth/package.json b/examples/starter-auth/package.json index 879467f0..58e5e4a6 100644 --- a/examples/starter-auth/package.json +++ b/examples/starter-auth/package.json @@ -20,7 +20,7 @@ "@prisma/adapter-better-sqlite3": "^7.8.0", "@prisma/client": "^7.8.0", "@tailwindcss/postcss": "^4.3.3", - "better-auth": "^1.6.25", + "better-auth": "^1.7.1", "better-sqlite3": "^12.6.2", "next": "^16.2.10", "postcss": "^8.5.26", diff --git a/examples/starter-auth/prisma/schema.prisma b/examples/starter-auth/prisma/schema.prisma index 7ccc72dc..9f8cb188 100644 --- a/examples/starter-auth/prisma/schema.prisma +++ b/examples/starter-auth/prisma/schema.prisma @@ -53,16 +53,19 @@ model Session { expiresAt DateTime ipAddress String? userAgent String? - userId String @map("user") + userId String @map("userId") user User @relation(onDelete: Cascade, fields: [userId], references: [id]) createdAt DateTime @default(now()) updatedAt DateTime @default(now()) @updatedAt + + @@index([userId]) } model Account { id String @id @default(cuid()) accountId String providerId String + issuer String accessToken String? refreshToken String? accessTokenExpiresAt DateTime? @@ -70,10 +73,12 @@ model Account { scope String? idToken String? password String? - userId String @map("user") + userId String @map("userId") user User @relation(onDelete: Cascade, fields: [userId], references: [id]) createdAt DateTime @default(now()) updatedAt DateTime @default(now()) @updatedAt + + @@index([userId]) } model Verification { @@ -83,4 +88,6 @@ model Verification { expiresAt DateTime createdAt DateTime @default(now()) updatedAt DateTime @default(now()) @updatedAt + + @@index([identifier]) } diff --git a/package.json b/package.json index adde3788..c8a8a348 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,11 @@ ], "author": "", "license": "MIT", + "pnpm": { + "overrides": { + "@better-auth/utils": "0.5.0" + } + }, "devDependencies": { "@changesets/changelog-github": "^0.7.0", "@changesets/cli": "^3.0.0", diff --git a/packages/auth/CLAUDE.md b/packages/auth/CLAUDE.md index dff3da7d..3f48c0d8 100644 --- a/packages/auth/CLAUDE.md +++ b/packages/auth/CLAUDE.md @@ -49,7 +49,16 @@ Pre-built forms (client components). Each takes **server action** props (not an ### Plugins (`src/plugins/index.ts`) -- Better-auth MCP plugin for OAuth authentication with AI assistants +- `mcp` - re-exported from the optional `@better-auth/mcp` peer (better-auth + 1.7 split it out of `better-auth/plugins`) for OAuth authentication with AI + assistants. Its `resource` option (the RFC 8707/9728 canonical + protected-resource URL) and `consentPage` (where a user approves/denies an + MCP client's requested scopes) are both required as of that split, and it + now needs better-auth's own `jwt()` plugin (from `better-auth/plugins`, + unaffected by the `@better-auth/mcp` split) registered alongside it — the + OAuth Provider `mcp()` is built on issues JWT-based access tokens and + throws `BetterAuthError: jwt_config` at init without it. See the + `betterAuthPlugins` example in the root `CLAUDE.md`'s MCP section. ## Architecture Patterns @@ -69,7 +78,11 @@ config({ The four Auth lists, the conditional `RateLimit` fifth, and every table a better-auth plugin declares in its own `schema` (e.g. the `mcp` plugin's -`oauthApplication`/`oauthAccessToken`/`oauthConsent`) are all **derived** +`oauthClient`/`oauthAccessToken`/`oauthConsent`/`oauthRefreshToken`/ +`oauthResource`/`oauthClientResource`/`oauthClientAssertion` — better-auth +1.7 split the plugin into `@better-auth/mcp` and rebuilt it on the OAuth +Provider RFC 8707/9728 resource model, a wider table set than the pre-1.7 +`oauthApplication`/`oauthAccessToken`/`oauthConsent` three) are all **derived** from better-auth's own resolved table definitions, not hand-transcribed. The pure derivation lives in `src/config/derive-auth-lists.ts` (`deriveAuthLists`), which `getAuthLists` and the plugin's `init` consume. It calls `getAuthTables` @@ -108,13 +121,13 @@ lists cannot silently drift from what better-auth itself declares (issue child model's own key, with a documented override map in `derive-auth-lists.ts` for a collision or bad pluralization - a **plugin table**'s list key is PascalCased from better-auth's resolved - `modelName` (`oauthApplication` → `OauthApplication`), with `db.map` set + `modelName` (`oauthClient` → `OauthClient`), with `db.map` set back to the original whenever the case changed; its scalar/FK fields go through the exact same derivation as a base model's, including the reverse relation onto whichever list it references (base or another plugin table). A reference whose target field isn't the target's `id` (better-auth's own oidc-provider schema does this — `oauthAccessToken.clientId` references - `oauthApplication.clientId`, not its `id`) stays a plain scalar column, + `oauthClient.clientId`, not its `id`) stays a plain scalar column, since `relationship()` can only express an `id`-based FK. Plugin tables ship closed like the base models, with no `access` passthrough at all — see ADR-0034 and "Access control on Auth lists" below diff --git a/packages/auth/package.json b/packages/auth/package.json index 950009a3..e71f1865 100644 --- a/packages/auth/package.json +++ b/packages/auth/package.json @@ -57,12 +57,19 @@ "url": "https://github.com/OpenSaasAU/stack/issues" }, "peerDependencies": { + "@better-auth/mcp": "^1.7.0", "@opensaas/stack-core": "^0", - "better-auth": "^1.3.29", + "better-auth": "^1.4.0", "next": "^15.0.0 || ^16.0.0", "react": "^18.0.0 || ^19.0.0" }, + "peerDependenciesMeta": { + "@better-auth/mcp": { + "optional": true + } + }, "devDependencies": { + "@better-auth/mcp": "^1.7.1", "@opensaas/stack-cli": "workspace:*", "@opensaas/stack-core": "workspace:*", "@types/node": "^26.1.1", @@ -70,7 +77,7 @@ "@typescript/native": "npm:typescript@^7.0.2", "@vitest/coverage-v8": "^4.1.10", "@vitest/ui": "^4.1.10", - "better-auth": "^1.6.25", + "better-auth": "^1.7.1", "next": "^16.2.10", "react": "^19.2.8", "typescript": "npm:@typescript/typescript6@^6.0.2", diff --git a/packages/auth/src/config/derive-auth-lists.ts b/packages/auth/src/config/derive-auth-lists.ts index 98677f7d..2d0b145d 100644 --- a/packages/auth/src/config/derive-auth-lists.ts +++ b/packages/auth/src/config/derive-auth-lists.ts @@ -79,6 +79,11 @@ const FIELD_ORDER: Partial> = { account: [ 'accountId', 'providerId', + // `issuer` (better-auth 1.7+, issue #986) groups with accountId/providerId + // as the account's identity fields — together the table-level + // `@@unique([issuer, accountId])` better-auth declares (not yet emitted; + // blocked on #985's table-level `db.indexes` derivation). + 'issuer', 'user', 'accessToken', 'refreshToken', @@ -95,6 +100,24 @@ const FIELD_ORDER: Partial> = { /** Carried via list-level `db.timestamps` (see `listDb`) rather than as ordinary derived fields. */ const TIMESTAMP_FIELDS = new Set(['createdAt', 'updatedAt']) +/** + * Whether a model declares BOTH `createdAt` and `updatedAt` upstream — the + * only shape `db.timestamps: true` can express, since it always emits both + * columns together (`resolveListTimestamps` in the Prisma generator). The + * four base models always satisfy this. A better-auth plugin table is not + * guaranteed to: the MCP plugin's OAuth tables (better-auth 1.7, issue #992) + * include several with only `createdAt` (e.g. `oauthAccessToken`, + * `oauthRefreshToken`, `oauthClientResource`) — those fall through to the + * general field-derivation loop below instead, so `createdAt` alone is still + * derived as an ordinary scalar column rather than silently dropped (which + * previously crashed a real write the moment better-auth's own adapter tried + * to set it — see `oauthResource`'s seeded row in the MCP OAuth cascade e2e + * test). + */ +function hasSymmetricTimestamps(upstreamFields: Record): boolean { + return 'createdAt' in upstreamFields && 'updatedAt' in upstreamFields +} + /** * The reverse relation field name a foreign key implies on its target model * (e.g. `User.sessions`) has no source in better-auth's metadata at all — @@ -210,18 +233,23 @@ function buildScalarField(fieldKey: string, upstream: DBFieldAttribute): FieldCo } case 'date': return timestamp({ ...(isIndexed ? { isIndexed } : {}), db }) - case 'number': + case 'number': { + const staticDefault = + typeof upstream.defaultValue === 'function' ? undefined : upstream.defaultValue return upstream.bigint ? bigInt({ ...(isRequired ? { validation: { isRequired: true as const } } : {}), ...(isIndexed ? { isIndexed } : {}), + ...(staticDefault !== undefined ? { defaultValue: staticDefault as number } : {}), db, }) : integer({ ...(isRequired ? { validation: { isRequired: true as const } } : {}), ...(isIndexed ? { isIndexed } : {}), + ...(staticDefault !== undefined ? { defaultValue: staticDefault as number } : {}), db, }) + } default: // better-auth's `DBFieldType` also allows `json`, `string[]`/`number[]`, // and an enum array — none of which any built-in plugin (MCP, admin, @@ -428,8 +456,16 @@ export function deriveAuthLists( for (const modelKey of Object.keys(tables)) { const upstreamFields = tables[modelKey].fields + // Base models always carry `db.timestamps: true` below regardless of + // this check; a plugin table only skips createdAt/updatedAt here when it + // declares both (so db.timestamps: true, set further down, can stand in + // for them) — otherwise they fall through and derive as ordinary scalar + // columns instead of being silently dropped. + const skipTimestampFields = + (BASE_MODEL_KEYS as readonly string[]).includes(modelKey) || + hasSymmetricTimestamps(upstreamFields) for (const [fieldKey, upstream] of Object.entries(upstreamFields)) { - if (TIMESTAMP_FIELDS.has(fieldKey)) continue + if (TIMESTAMP_FIELDS.has(fieldKey) && skipTimestampFields) continue if (upstream.references) { const targetModelKey = upstream.references.model @@ -557,9 +593,18 @@ export function deriveAuthLists( if (!listKey) continue // unreachable: buildModelRegistry registers every table key const physicalName = tables[modelKey].modelName + const timestamps = hasSymmetricTimestamps(tables[modelKey].fields) + const mapNeeded = listKey !== physicalName lists[listKey] = list({ fields: assembleFields(modelKey), - ...(listKey !== physicalName ? { db: { map: physicalName } } : {}), + ...(timestamps || mapNeeded + ? { + db: { + ...(timestamps ? { timestamps: true as const } : {}), + ...(mapNeeded ? { map: physicalName } : {}), + }, + } + : {}), }) } diff --git a/packages/auth/src/config/types.ts b/packages/auth/src/config/types.ts index 3bc601a8..a0c86b43 100644 --- a/packages/auth/src/config/types.ts +++ b/packages/auth/src/config/types.ts @@ -367,10 +367,12 @@ export type AuthConfig = { * * @example * ```typescript - * import { mcp } from 'better-auth/plugins' + * import { mcp } from '@opensaas/stack-auth/plugins' + * import { jwt } from 'better-auth/plugins' * * betterAuthPlugins: [ - * mcp({ loginPage: '/sign-in' }) + * jwt(), // required alongside mcp() since better-auth 1.7 + * mcp({ loginPage: '/sign-in', consentPage: '/consent', resource: 'https://your-app.com/api/mcp' }) * ] * ``` */ diff --git a/packages/auth/src/plugins/index.ts b/packages/auth/src/plugins/index.ts index 51cae2bd..e701ea06 100644 --- a/packages/auth/src/plugins/index.ts +++ b/packages/auth/src/plugins/index.ts @@ -1 +1,6 @@ -export { mcp } from 'better-auth/plugins' +/** + * better-auth 1.7 split the MCP plugin out of `better-auth/plugins` into its + * own `@better-auth/mcp` package (an optional peer of this package) — see the + * 1.7.1 upgrade note in the auth package's CHANGELOG. + */ +export { mcp } from '@better-auth/mcp' diff --git a/packages/auth/tests/auth-lists-drift.test.ts b/packages/auth/tests/auth-lists-drift.test.ts index ab0911c6..15eda2f9 100644 --- a/packages/auth/tests/auth-lists-drift.test.ts +++ b/packages/auth/tests/auth-lists-drift.test.ts @@ -1,6 +1,6 @@ import { describe, it, expect } from 'vitest' import { getAuthTables } from 'better-auth/db' -import { mcp } from 'better-auth/plugins' +import { mcp } from '@better-auth/mcp' import type { BetterAuthOptions } from 'better-auth' import type { DBFieldAttribute } from 'better-auth/db' import type { FieldConfig } from '@opensaas/stack-core' @@ -38,11 +38,13 @@ type Divergence = { const FK_BRIDGE: Record> = { session: { userId: 'user' }, account: { userId: 'user' }, - // The MCP plugin's OAuth tables (issue #992) — `clientId` is deliberately - // NOT bridged here: it references oauthApplication.clientId, not its `id`, - // so it stays a plain scalar column and is compared via `compareScalarField`. - oauthApplication: { userId: 'user' }, - oauthAccessToken: { userId: 'user' }, + // The MCP plugin's OAuth tables (issue #992) — `clientId`/`resourceId` are + // deliberately NOT bridged here: they reference oauthClient.clientId / + // oauthResource.identifier, not an `id`, so they stay plain scalar columns + // and are compared via `compareScalarField`. + oauthClient: { userId: 'user' }, + oauthRefreshToken: { userId: 'user', sessionId: 'session' }, + oauthAccessToken: { userId: 'user', sessionId: 'session', refreshId: 'refresh' }, oauthConsent: { userId: 'user' }, } @@ -157,10 +159,17 @@ function compareScalarField( }) } - // Only a static, non-function default is comparable — better-auth's - // function-valued defaults (e.g. `createdAt: () => new Date()`) are applied - // at write time, not modeled as a derived field's own `defaultValue`. - if (typeof upstream.defaultValue !== 'function' && upstream.defaultValue !== undefined) { + // Only a static, non-function default on a modeled type is comparable — + // better-auth's function-valued defaults (e.g. `createdAt: () => new + // Date()`) are applied at write time, not modeled as a derived field's own + // `defaultValue`; a type this derivation doesn't model at all (`json`, + // `string[]`, ...) falls back to `text()` (see `expectedType` above), which + // can't faithfully carry that type's own default either. + if ( + expectedType !== undefined && + typeof upstream.defaultValue !== 'function' && + upstream.defaultValue !== undefined + ) { if (derived.defaultValue !== upstream.defaultValue) { divergences.push({ model, @@ -431,12 +440,20 @@ describe('derived better-auth plugin tables match better-auth’s own table defi verification: { modelName: 'Verification', fields: {} }, } - const plugin = mcp({ loginPage: '/sign-in' }) + const plugin = mcp({ + loginPage: '/sign-in', + consentPage: '/consent', + resource: 'https://example.com/mcp', + }) const upstreamTables = getAuthTables({ plugins: [plugin] }) const { keys, lists } = deriveAuthLists(defaultModels, {}, {}, [plugin]) const pluginModelToListKey: Record = { - oauthApplication: 'OauthApplication', + oauthClient: 'OauthClient', + oauthClientAssertion: 'OauthClientAssertion', + oauthClientResource: 'OauthClientResource', oauthAccessToken: 'OauthAccessToken', + oauthRefreshToken: 'OauthRefreshToken', + oauthResource: 'OauthResource', oauthConsent: 'OauthConsent', } const listKeys = { ...keys, ...pluginModelToListKey } diff --git a/packages/auth/tests/generated-fk-shape.test.ts b/packages/auth/tests/generated-fk-shape.test.ts index b023826d..5b184ac5 100644 --- a/packages/auth/tests/generated-fk-shape.test.ts +++ b/packages/auth/tests/generated-fk-shape.test.ts @@ -3,7 +3,7 @@ import { config, list } from '@opensaas/stack-core' import { text, relationship } from '@opensaas/stack-core/fields' import type { OpenSaasConfig } from '@opensaas/stack-core' import type { Plugin } from '@opensaas/stack-core/extend' -import { mcp } from 'better-auth/plugins' +import { mcp } from '@better-auth/mcp' import { generatePrismaSchema } from '@opensaas/stack-cli/generator/prisma' import { authPlugin } from '../src/config/plugin.js' import { adoptBetterAuthTables } from '../src/config/adopt-better-auth-tables.js' @@ -97,6 +97,33 @@ describe('generated auth schema — Session/Account/Verification mirror better-a }) }) +describe('generated auth schema — account.issuer (better-auth 1.7, issue #986)', () => { + it('emits a required, non-nullable issuer column on Account, positioned after providerId', async () => { + const schema = await generateSchema({ + db: { provider: 'sqlite' }, + plugins: [authPlugin({ emailAndPassword: { enabled: true } })], + lists: {}, + }) + + const block = modelBlock(schema, 'Account') + expect(block).toMatch(/issuer\s+String\s/) + expect(block).not.toMatch(/issuer\s+String\?/) + + // FIELD_ORDER groups issuer with accountId/providerId — the fields that + // together form better-auth's table-level @@unique([issuer, accountId]), + // which this derivation does not yet emit (blocked on #985's table-level + // db.indexes derivation). + const providerIdLine = block.indexOf('providerId') + const issuerLine = block.indexOf('issuer') + expect(providerIdLine).toBeGreaterThan(-1) + expect(issuerLine).toBeGreaterThan(providerIdLine) + + // Not yet emitted — see the migration note in the auth package's + // CHANGELOG and issue #985. + expect(block).not.toContain('@@unique([issuer, accountId])') + }) +}) + describe('generated auth schema — adopted index names match better-auth exactly (issue #937)', () => { it('derives session_userId_idx / account_userId_idx / verification_identifier_idx under adoptBetterAuthTables()', async () => { const schema = await generateSchema({ @@ -231,13 +258,19 @@ describe('generated MCP plugin OAuth schema gets real foreign keys and cascades plugins: [ authPlugin({ emailAndPassword: { enabled: true }, - betterAuthPlugins: [mcp({ loginPage: '/sign-in' })], + betterAuthPlugins: [ + mcp({ + loginPage: '/sign-in', + consentPage: '/consent', + resource: 'https://example.com/mcp', + }), + ], }), ], lists: {}, }) - for (const model of ['OauthApplication', 'OauthAccessToken', 'OauthConsent']) { + for (const model of ['OauthClient', 'OauthAccessToken', 'OauthConsent']) { const block = modelBlock(schema, model) expect(block).toContain('@relation(onDelete: Cascade, fields: [userId], references: [id])') expect(block).toContain('@map("userId")') @@ -251,24 +284,36 @@ describe('generated MCP plugin OAuth schema gets real foreign keys and cascades plugins: [ authPlugin({ emailAndPassword: { enabled: true }, - betterAuthPlugins: [mcp({ loginPage: '/sign-in' })], + betterAuthPlugins: [ + mcp({ + loginPage: '/sign-in', + consentPage: '/consent', + resource: 'https://example.com/mcp', + }), + ], }), ], lists: {}, }) - expect(modelBlock(schema, 'OauthApplication')).toContain('@@map("oauthApplication")') + expect(modelBlock(schema, 'OauthClient')).toContain('@@map("oauthClient")') expect(modelBlock(schema, 'OauthAccessToken')).toContain('@@map("oauthAccessToken")') expect(modelBlock(schema, 'OauthConsent')).toContain('@@map("oauthConsent")') }) - it('leaves the non-PK clientId reference (oauthAccessToken/oauthConsent -> oauthApplication.clientId) as an indexed plain column, not a relation', async () => { + it('leaves the non-PK clientId reference (oauthAccessToken/oauthConsent -> oauthClient.clientId) as an indexed plain column, not a relation', async () => { const schema = await generateSchema({ db: { provider: 'sqlite' }, plugins: [ authPlugin({ emailAndPassword: { enabled: true }, - betterAuthPlugins: [mcp({ loginPage: '/sign-in' })], + betterAuthPlugins: [ + mcp({ + loginPage: '/sign-in', + consentPage: '/consent', + resource: 'https://example.com/mcp', + }), + ], }), ], lists: {}, @@ -277,18 +322,24 @@ describe('generated MCP plugin OAuth schema gets real foreign keys and cascades for (const model of ['OauthAccessToken', 'OauthConsent']) { const block = modelBlock(schema, model) expect(block).toMatch(/clientId\s+String\s/) - expect(block).not.toContain('clientId OauthApplication') + expect(block).not.toContain('clientId OauthClient') expect(block).toContain('@@index([clientId])') } }) - it('adds a reverse collection on User for every OAuth table, leaving Session/Account/Verification unchanged', async () => { + it('adds a reverse collection on User for every OAuth table referencing it, and on Session for the two token tables that also reference it, leaving Account/Verification unchanged', async () => { const withMcp = await generateSchema({ db: { provider: 'sqlite' }, plugins: [ authPlugin({ emailAndPassword: { enabled: true }, - betterAuthPlugins: [mcp({ loginPage: '/sign-in' })], + betterAuthPlugins: [ + mcp({ + loginPage: '/sign-in', + consentPage: '/consent', + resource: 'https://example.com/mcp', + }), + ], }), ], lists: {}, @@ -300,33 +351,48 @@ describe('generated MCP plugin OAuth schema gets real foreign keys and cascades }) const user = modelBlock(withMcp, 'User') - expect(user).toContain('oauthApplications') + expect(user).toContain('oauthClients') expect(user).toContain('oauthAccessTokens') + expect(user).toContain('oauthRefreshTokens') expect(user).toContain('oauthConsents') - for (const model of ['Session', 'Account', 'Verification']) { + // oauthAccessToken.sessionId / oauthRefreshToken.sessionId both reference + // session.id (unlike better-auth's pre-1.7 MCP schema, which had no + // session-scoped OAuth tables at all) — Session gains reverse collections + // for both, Account/Verification stay untouched by the MCP plugin. + const session = modelBlock(withMcp, 'Session') + expect(session).toContain('oauthAccessTokens') + expect(session).toContain('oauthRefreshTokens') + + for (const model of ['Account', 'Verification']) { expect(modelBlock(withMcp, model)).toBe(modelBlock(withoutMcp, model)) } }) - it('generates a required, non-nullable userId FK on OauthConsent (required upstream), and an optional one on OauthApplication (optional upstream)', async () => { + it('generates a required, non-nullable userId FK on OauthRefreshToken (required upstream), and an optional one on OauthClient (optional upstream)', async () => { const schema = await generateSchema({ db: { provider: 'sqlite' }, plugins: [ authPlugin({ emailAndPassword: { enabled: true }, - betterAuthPlugins: [mcp({ loginPage: '/sign-in' })], + betterAuthPlugins: [ + mcp({ + loginPage: '/sign-in', + consentPage: '/consent', + resource: 'https://example.com/mcp', + }), + ], }), ], lists: {}, }) - const consent = modelBlock(schema, 'OauthConsent') - expect(consent).toMatch(/userId\s+String\s/) - expect(consent).not.toMatch(/userId\s+String\?/) + const refreshToken = modelBlock(schema, 'OauthRefreshToken') + expect(refreshToken).toMatch(/userId\s+String\s/) + expect(refreshToken).not.toMatch(/userId\s+String\?/) - const application = modelBlock(schema, 'OauthApplication') - expect(application).toMatch(/userId\s+String\?/) + const client = modelBlock(schema, 'OauthClient') + expect(client).toMatch(/userId\s+String\?/) }) }) diff --git a/packages/auth/tests/mcp-oauth-cascade-e2e.test.ts b/packages/auth/tests/mcp-oauth-cascade-e2e.test.ts index 81c60e71..b6603679 100644 --- a/packages/auth/tests/mcp-oauth-cascade-e2e.test.ts +++ b/packages/auth/tests/mcp-oauth-cascade-e2e.test.ts @@ -53,17 +53,34 @@ function configSource(): string { return `import { config } from '@opensaas/stack-core' import { authPlugin } from '@opensaas/stack-auth' import { mcp } from '@opensaas/stack-auth/plugins' +import { jwt } from 'better-auth/plugins' import { PrismaBetterSqlite3 } from '@prisma/adapter-better-sqlite3' export default config({ plugins: [ authPlugin({ emailAndPassword: { enabled: true }, - betterAuthPlugins: [mcp({ loginPage: '/sign-in' })], + // better-auth 1.7's mcp() is built on the OAuth Provider, which issues + // JWT-based access tokens and requires better-auth's own jwt() plugin + // to be registered alongside it (throws BetterAuthError: jwt_config + // otherwise) — see @better-auth/mcp's own usage example. + betterAuthPlugins: [ + jwt(), + mcp({ loginPage: '/sign-in', consentPage: '/consent', resource: 'http://localhost:3000/api/mcp' }), + ], // Not modelled by AuthConfig — passed through verbatim so /delete-user // deletes immediately instead of requiring an email-verification // round trip (see packages/auth/CLAUDE.md, "betterAuthOptions"). - betterAuthOptions: { user: { deleteUser: { enabled: true } } }, + // baseURL is explicit rather than relying on the BETTER_AUTH_URL env + // var: better-auth 1.7's OAuth Provider needs a resolvable URL to build + // its own endpoint/issuer URLs during init, and this test constructs + // \`auth\` in-process via createAuth() (not through the CLI subprocess + // that does see BETTER_AUTH_URL), so the auto-detected origin would be + // undefined here. + betterAuthOptions: { + baseURL: 'http://localhost:3000', + user: { deleteUser: { enabled: true } }, + }, }), ], db: { @@ -142,7 +159,7 @@ async function cleanupProject(dir: string): Promise { describe.skipIf(!prerequisitesPresent)( 'MCP plugin OAuth tables cascade on user deletion — live end-to-end (issue #992)', () => { - it('deleting a user via better-auth’s own /delete-user removes their OAuth applications, access tokens and consents; another user’s rows survive', async () => { + it('deleting a user via better-auth’s own /delete-user removes their OAuth clients, access tokens and consents; another user’s rows survive', async () => { const dir = await setupProject() try { const { auth, context } = await createAuthInstanceForProject(dir) @@ -170,37 +187,44 @@ describe.skipIf(!prerequisitesPresent)( // OAuth rows are created through the raw Prisma client — these lists // ship closed (ADR-0013), and this is the same path better-auth's own - // OAuth flows write through in production. + // OAuth flows write through in production. better-auth 1.7's OAuth + // Provider (issue #986) splits what the pre-1.7 MCP plugin modelled + // as a single "application" with embedded access/refresh tokens into + // oauthClient (the registered client) plus a standalone + // oauthAccessToken (no more combined access+refresh token row). for (const [suffix, userId] of [ ['deleted', deletedUserId], ['survivor', survivorUserId], ]) { - await prisma.oauthApplication.create({ + await prisma.oauthClient.create({ data: { name: `App ${suffix}`, clientId: `client-${suffix}`, - redirectUrls: 'http://localhost/callback', - type: 'web', + redirectUris: 'http://localhost/callback', userId, }, }) await prisma.oauthAccessToken.create({ data: { - accessToken: `access-${suffix}`, - refreshToken: `refresh-${suffix}`, - accessTokenExpiresAt: new Date(Date.now() + 3_600_000), - refreshTokenExpiresAt: new Date(Date.now() + 7_200_000), + token: `access-${suffix}`, clientId: `client-${suffix}`, scopes: 'openid', + expiresAt: new Date(Date.now() + 3_600_000), + // oauthAccessToken declares createdAt but not updatedAt + // upstream, so it derives as an ordinary required column with + // no DB-level default (see hasSymmetricTimestamps in + // derive-auth-lists.ts) — better-auth's own adapter always + // supplies it explicitly, so this raw-Prisma seed must too. + createdAt: new Date(), userId, }, }) await prisma.oauthConsent.create({ - data: { clientId: `client-${suffix}`, scopes: 'openid', consentGiven: true, userId }, + data: { clientId: `client-${suffix}`, scopes: 'openid', userId }, }) } - expect(await prisma.oauthApplication.count()).toBe(2) + expect(await prisma.oauthClient.count()).toBe(2) expect(await prisma.oauthAccessToken.count()).toBe(2) expect(await prisma.oauthConsent.count()).toBe(2) @@ -214,9 +238,7 @@ describe.skipIf(!prerequisitesPresent)( // No orphans: every row belonging to the deleted user is gone via the // database cascade, not just the user row itself. - expect( - await prisma.oauthApplication.findFirst({ where: { userId: deletedUserId } }), - ).toBeNull() + expect(await prisma.oauthClient.findFirst({ where: { userId: deletedUserId } })).toBeNull() expect( await prisma.oauthAccessToken.findFirst({ where: { userId: deletedUserId } }), ).toBeNull() @@ -225,7 +247,7 @@ describe.skipIf(!prerequisitesPresent)( // The other user's rows are untouched — the cascade is scoped to the // deleted user's own foreign key, not a wholesale table wipe. expect(await prisma.user.findUnique({ where: { id: survivorUserId } })).not.toBeNull() - expect(await prisma.oauthApplication.count()).toBe(1) + expect(await prisma.oauthClient.count()).toBe(1) expect(await prisma.oauthAccessToken.count()).toBe(1) expect(await prisma.oauthConsent.count()).toBe(1) diff --git a/packages/auth/tests/plugin-table-derivation.test.ts b/packages/auth/tests/plugin-table-derivation.test.ts index 3630e634..6807d845 100644 --- a/packages/auth/tests/plugin-table-derivation.test.ts +++ b/packages/auth/tests/plugin-table-derivation.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect, vi } from 'vitest' -import { mcp } from 'better-auth/plugins' +import { mcp } from '@better-auth/mcp' import { deriveAuthLists } from '../src/config/derive-auth-lists.js' import type { NormalizedAuthModels } from '../src/config/types.js' @@ -22,36 +22,46 @@ const defaultModels: NormalizedAuthModels = { describe('deriveAuthLists — better-auth plugin tables (issue #992)', () => { it('derives every MCP OAuth table under a PascalCase key, mapped back to its camelCase physical table', () => { - const { lists } = deriveAuthLists(defaultModels, {}, {}, [mcp({ loginPage: '/sign-in' })]) + const { lists } = deriveAuthLists(defaultModels, {}, {}, [ + mcp({ loginPage: '/sign-in', consentPage: '/consent', resource: 'https://example.com/mcp' }), + ]) expect(Object.keys(lists).sort()).toEqual([ 'Account', 'OauthAccessToken', - 'OauthApplication', + 'OauthClient', + 'OauthClientAssertion', + 'OauthClientResource', 'OauthConsent', + 'OauthRefreshToken', + 'OauthResource', 'Session', 'User', 'Verification', ]) - expect(lists.OauthApplication.db?.map).toBe('oauthApplication') + expect(lists.OauthClient.db?.map).toBe('oauthClient') expect(lists.OauthAccessToken.db?.map).toBe('oauthAccessToken') expect(lists.OauthConsent.db?.map).toBe('oauthConsent') }) it('generates a real relationship (not a bare column) for a PK-targeting reference (userId -> user.id)', () => { - const { lists } = deriveAuthLists(defaultModels, {}, {}, [mcp({ loginPage: '/sign-in' })]) - const userField = lists.OauthApplication.fields.user + const { lists } = deriveAuthLists(defaultModels, {}, {}, [ + mcp({ loginPage: '/sign-in', consentPage: '/consent', resource: 'https://example.com/mcp' }), + ]) + const userField = lists.OauthClient.fields.user expect(userField.type).toBe('relationship') - expect(userField.ref).toBe('User.oauthApplications') + expect(userField.ref).toBe('User.oauthClients') expect(userField.db?.foreignKey).toEqual({ map: 'userId' }) - // oauthApplication.userId is optional upstream (required: false). + // oauthClient.userId is optional upstream (required: false). expect(userField.db?.isNullable).toBe(true) }) it('carries the referential action from `onDelete: cascade` through to the generated relation', () => { - const { lists } = deriveAuthLists(defaultModels, {}, {}, [mcp({ loginPage: '/sign-in' })]) - const userField = lists.OauthApplication.fields.user + const { lists } = deriveAuthLists(defaultModels, {}, {}, [ + mcp({ loginPage: '/sign-in', consentPage: '/consent', resource: 'https://example.com/mcp' }), + ]) + const userField = lists.OauthClient.fields.user const extend = userField.db?.extendPrismaSchema const relationLine = extend ? extend({ relationLine: '@relation(fields: [x], references: [id])' }).relationLine @@ -85,8 +95,10 @@ describe('deriveAuthLists — better-auth plugin tables (issue #992)', () => { expect(relationLine).toContain('onDelete: Restrict') }) - it('leaves a non-PK-targeting reference as a plain scalar column (clientId -> oauthApplication.clientId)', () => { - const { lists } = deriveAuthLists(defaultModels, {}, {}, [mcp({ loginPage: '/sign-in' })]) + it('leaves a non-PK-targeting reference as a plain scalar column (clientId -> oauthClient.clientId)', () => { + const { lists } = deriveAuthLists(defaultModels, {}, {}, [ + mcp({ loginPage: '/sign-in', consentPage: '/consent', resource: 'https://example.com/mcp' }), + ]) const clientId = lists.OauthAccessToken.fields.clientId expect(clientId.type).toBe('text') @@ -97,11 +109,13 @@ describe('deriveAuthLists — better-auth plugin tables (issue #992)', () => { }) it('adds a reverse relation on the target base list for every plugin-table reference to it, with no name collisions', () => { - const { lists } = deriveAuthLists(defaultModels, {}, {}, [mcp({ loginPage: '/sign-in' })]) + const { lists } = deriveAuthLists(defaultModels, {}, {}, [ + mcp({ loginPage: '/sign-in', consentPage: '/consent', resource: 'https://example.com/mcp' }), + ]) - expect(lists.User.fields.oauthApplications).toMatchObject({ + expect(lists.User.fields.oauthClients).toMatchObject({ type: 'relationship', - ref: 'OauthApplication.user', + ref: 'OauthClient.user', many: true, }) expect(lists.User.fields.oauthAccessTokens).toMatchObject({ @@ -136,9 +150,11 @@ describe('deriveAuthLists — better-auth plugin tables (issue #992)', () => { }) it('ships plugin-table lists closed — no access control', () => { - const { lists } = deriveAuthLists(defaultModels, {}, {}, [mcp({ loginPage: '/sign-in' })]) + const { lists } = deriveAuthLists(defaultModels, {}, {}, [ + mcp({ loginPage: '/sign-in', consentPage: '/consent', resource: 'https://example.com/mcp' }), + ]) - expect(lists.OauthApplication.access).toBeUndefined() + expect(lists.OauthClient.access).toBeUndefined() expect(lists.OauthAccessToken.access).toBeUndefined() expect(lists.OauthConsent.access).toBeUndefined() }) @@ -244,9 +260,11 @@ describe('deriveAuthLists — better-auth plugin tables (issue #992)', () => { account: { modelName: 'AuthAccount', fields: {} }, verification: { modelName: 'AuthVerification', fields: {} }, } - const { lists } = deriveAuthLists(remappedModels, {}, {}, [mcp({ loginPage: '/sign-in' })]) + const { lists } = deriveAuthLists(remappedModels, {}, {}, [ + mcp({ loginPage: '/sign-in', consentPage: '/consent', resource: 'https://example.com/mcp' }), + ]) - expect(lists.OauthApplication.fields.user.ref).toBe('AuthUser.oauthApplications') - expect(lists.AuthUser.fields.oauthApplications).toBeDefined() + expect(lists.OauthClient.fields.user.ref).toBe('AuthUser.oauthClients') + expect(lists.AuthUser.fields.oauthClients).toBeDefined() }) }) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3fb675a2..1b078589 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4,6 +4,9 @@ settings: autoInstallPeers: true excludeLinksFromLockfile: false +overrides: + '@better-auth/utils': 0.5.0 + importers: .: @@ -151,8 +154,8 @@ importers: specifier: ^7.8.0 version: 7.8.0(@typescript/typescript6@6.0.2)(prisma@7.9.0(@types/react@19.2.14)(@typescript/typescript6@6.0.2)(better-sqlite3@12.6.2)(magicast@0.5.2)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)) better-auth: - specifier: ^1.6.25 - version: 1.6.25(@opentelemetry/api@1.9.1)(@prisma/client@7.8.0(@typescript/typescript6@6.0.2)(prisma@7.9.0(@types/react@19.2.14)(@typescript/typescript6@6.0.2)(better-sqlite3@12.6.2)(magicast@0.5.2)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)))(better-sqlite3@12.6.2)(mongodb@7.1.0)(mysql2@3.15.3)(next@16.2.10(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.61.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(pg@8.22.0)(prisma@7.9.0(@types/react@19.2.14)(@typescript/typescript6@6.0.2)(better-sqlite3@12.6.2)(magicast@0.5.2)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(vitest@4.1.10) + specifier: ^1.7.1 + version: 1.7.1(@opentelemetry/api@1.9.1)(@prisma/client@7.8.0(@typescript/typescript6@6.0.2)(prisma@7.9.0(@types/react@19.2.14)(@typescript/typescript6@6.0.2)(better-sqlite3@12.6.2)(magicast@0.5.2)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)))(better-sqlite3@12.6.2)(mongodb@7.1.0)(mysql2@3.15.3)(next@16.2.10(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.61.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(pg@8.22.0)(prisma@7.9.0(@types/react@19.2.14)(@typescript/typescript6@6.0.2)(better-sqlite3@12.6.2)(magicast@0.5.2)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(vitest@4.1.10) next: specifier: ^16.2.10 version: 16.2.10(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.61.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) @@ -477,6 +480,9 @@ importers: '@prisma/client': specifier: ^7.8.0 version: 7.8.0(@typescript/typescript6@6.0.2)(prisma@7.9.0(@types/react@19.2.14)(@typescript/typescript6@6.0.2)(better-sqlite3@12.6.2)(magicast@0.5.2)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)) + better-auth: + specifier: ^1.7.1 + version: 1.7.1(@opentelemetry/api@1.9.1)(@prisma/client@7.8.0(@typescript/typescript6@6.0.2)(prisma@7.9.0(@types/react@19.2.14)(@typescript/typescript6@6.0.2)(better-sqlite3@12.6.2)(magicast@0.5.2)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)))(better-sqlite3@12.6.2)(mongodb@7.1.0)(mysql2@3.15.3)(next@16.2.10(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.61.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(pg@8.22.0)(prisma@7.9.0(@types/react@19.2.14)(@typescript/typescript6@6.0.2)(better-sqlite3@12.6.2)(magicast@0.5.2)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(vitest@4.1.10) next: specifier: ^16.2.10 version: 16.2.10(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.61.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) @@ -754,8 +760,8 @@ importers: specifier: ^4.3.3 version: 4.3.3 better-auth: - specifier: ^1.6.25 - version: 1.6.25(@opentelemetry/api@1.9.1)(@prisma/client@7.8.0(@typescript/typescript6@6.0.2)(prisma@7.9.0(@types/react@19.2.14)(@typescript/typescript6@6.0.2)(better-sqlite3@12.6.2)(magicast@0.5.2)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)))(better-sqlite3@12.6.2)(mongodb@7.1.0)(mysql2@3.15.3)(next@16.2.10(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.61.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(pg@8.22.0)(prisma@7.9.0(@types/react@19.2.14)(@typescript/typescript6@6.0.2)(better-sqlite3@12.6.2)(magicast@0.5.2)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(vitest@4.1.10) + specifier: ^1.7.1 + version: 1.7.1(@opentelemetry/api@1.9.1)(@prisma/client@7.8.0(@typescript/typescript6@6.0.2)(prisma@7.9.0(@types/react@19.2.14)(@typescript/typescript6@6.0.2)(better-sqlite3@12.6.2)(magicast@0.5.2)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)))(better-sqlite3@12.6.2)(mongodb@7.1.0)(mysql2@3.15.3)(next@16.2.10(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.61.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(pg@8.22.0)(prisma@7.9.0(@types/react@19.2.14)(@typescript/typescript6@6.0.2)(better-sqlite3@12.6.2)(magicast@0.5.2)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(vitest@4.1.10) better-sqlite3: specifier: ^12.6.2 version: 12.6.2 @@ -863,6 +869,9 @@ importers: packages/auth: devDependencies: + '@better-auth/mcp': + specifier: ^1.7.1 + version: 1.7.1(@better-auth/core@1.7.1(@better-auth/utils@0.5.0)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.4.0(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2))(@better-auth/utils@0.5.0)(@better-fetch/fetch@1.3.1)(better-auth@1.7.1(@opentelemetry/api@1.9.1)(@prisma/client@7.8.0(@typescript/typescript6@6.0.2)(prisma@7.9.0(@types/react@19.2.14)(@typescript/typescript6@6.0.2)(better-sqlite3@12.6.2)(magicast@0.5.2)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)))(better-sqlite3@12.6.2)(mongodb@7.1.0)(mysql2@3.15.3)(next@16.2.10(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.61.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(pg@8.22.0)(prisma@7.9.0(@types/react@19.2.14)(@typescript/typescript6@6.0.2)(better-sqlite3@12.6.2)(magicast@0.5.2)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(vitest@4.1.10))(better-call@1.4.0(zod@4.4.3)) '@opensaas/stack-cli': specifier: workspace:* version: link:../cli @@ -885,8 +894,8 @@ importers: specifier: ^4.1.10 version: 4.1.10(vitest@4.1.10) better-auth: - specifier: ^1.6.25 - version: 1.6.25(@opentelemetry/api@1.9.1)(@prisma/client@7.8.0(@typescript/typescript6@6.0.2)(prisma@7.9.0(@types/react@19.2.14)(@typescript/typescript6@6.0.2)(better-sqlite3@12.6.2)(magicast@0.5.2)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)))(better-sqlite3@12.6.2)(mongodb@7.1.0)(mysql2@3.15.3)(next@16.2.10(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.61.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(pg@8.22.0)(prisma@7.9.0(@types/react@19.2.14)(@typescript/typescript6@6.0.2)(better-sqlite3@12.6.2)(magicast@0.5.2)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(vitest@4.1.10) + specifier: ^1.7.1 + version: 1.7.1(@opentelemetry/api@1.9.1)(@prisma/client@7.8.0(@typescript/typescript6@6.0.2)(prisma@7.9.0(@types/react@19.2.14)(@typescript/typescript6@6.0.2)(better-sqlite3@12.6.2)(magicast@0.5.2)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)))(better-sqlite3@12.6.2)(mongodb@7.1.0)(mysql2@3.15.3)(next@16.2.10(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.61.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(pg@8.22.0)(prisma@7.9.0(@types/react@19.2.14)(@typescript/typescript6@6.0.2)(better-sqlite3@12.6.2)(magicast@0.5.2)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(vitest@4.1.10) next: specifier: ^16.2.10 version: 16.2.10(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.61.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) @@ -1599,14 +1608,14 @@ packages: resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} engines: {node: '>=18'} - '@better-auth/core@1.6.25': - resolution: {integrity: sha512-lMTlhtwyK4NpY9kPF+2rQCRKYpg136d3gM2xl8esxT1PjJx5Nh5YwZvxcYCIjDuO759sx6TCloJTuwcZGG6ZBw==} + '@better-auth/core@1.7.1': + resolution: {integrity: sha512-eZ9lqcnVLMZ3QtUByRo4VZqkB1ESyRddd9NfWjBdDPgh+jcwLScoIUAqhtHLR8zaSUJZah8OLGlkzObyPdUH7A==} peerDependencies: - '@better-auth/utils': 0.4.2 + '@better-auth/utils': 0.5.0 '@better-fetch/fetch': 1.3.1 '@cloudflare/workers-types': '>=4' '@opentelemetry/api': ^1.9.0 - better-call: 1.3.7 + better-call: 1.4.0 jose: ^6.1.0 kysely: ^0.28.5 || ^0.29.0 nanostores: ^1.0.1 @@ -1616,47 +1625,63 @@ packages: '@opentelemetry/api': optional: true - '@better-auth/drizzle-adapter@1.6.25': - resolution: {integrity: sha512-ru/DeKjFPQUVeKkxF/ScazmPqIY7lwfkAV5Yt4j24wmn1Y8vFwoiPRnHgXUeZqBs10+nubaRwEqLF39CP6EhRw==} + '@better-auth/drizzle-adapter@1.7.1': + resolution: {integrity: sha512-qlqNyg5V9bXHSP68/vtlsiZayhR4hgvEGiS/E3SIj8bCpWWFGmyQkxJbQCqpBmC7vT30wE/kNtJMHIgnV3rkiw==} peerDependencies: - '@better-auth/core': ^1.6.25 - '@better-auth/utils': 0.4.2 - drizzle-orm: ^0.45.2 + '@better-auth/core': ^1.7.1 + '@better-auth/utils': 0.5.0 + drizzle-orm: ^0.45.2 || >=1.0.0-rc.1 <2.0.0 peerDependenciesMeta: drizzle-orm: optional: true - '@better-auth/kysely-adapter@1.6.25': - resolution: {integrity: sha512-zxiePhtN1YClS1irKYPVwWfN6kYp+QoYlz1hdQUOj8hXyo2aE/ny4RNAb6v332b0+U6Vu88EhYITRPdmvCo6uA==} + '@better-auth/kysely-adapter@1.7.1': + resolution: {integrity: sha512-yWCpE1cZpMUj37nD6JFDK+GDR8zS37L5WI73il3qbU9TXtWsxUQKc/5c3IHsHizWQsmcQI8uv2pAFKxsRDa+AQ==} peerDependencies: - '@better-auth/core': ^1.6.25 - '@better-auth/utils': 0.4.2 + '@better-auth/core': ^1.7.1 + '@better-auth/utils': 0.5.0 kysely: ^0.28.17 || ^0.29.0 peerDependenciesMeta: kysely: optional: true - '@better-auth/memory-adapter@1.6.25': - resolution: {integrity: sha512-GhEzTumc8yfTz+OZ6pMg06BA49xob49x1bX+1mEl/FStDJoSF+6mTfI5M2ytFxaiN89336/aUjkW8u+qRyLexw==} + '@better-auth/mcp@1.7.1': + resolution: {integrity: sha512-uBDWaaDPYQ2Q4QaesQVhZkaTSXrnFvW6Za0mr8lYTL2TvnKkzEWT0decyQGONwhswLLAlMGLKG4M2JeIATgGGA==} + peerDependencies: + '@better-auth/core': ^1.7.1 + better-auth: ^1.7.1 + better-call: 1.4.0 + + '@better-auth/memory-adapter@1.7.1': + resolution: {integrity: sha512-6NX1yv88DeqdoG7owYFqKwlrDGaIPhsC52JUGrUgeGVKyOq8a/6hHlHsqG1C2FwT23SHQiKVYCEAG9N6aH5OvQ==} peerDependencies: - '@better-auth/core': ^1.6.25 - '@better-auth/utils': 0.4.2 + '@better-auth/core': ^1.7.1 + '@better-auth/utils': 0.5.0 - '@better-auth/mongo-adapter@1.6.25': - resolution: {integrity: sha512-ZtMmjcOdXR2Ziqx5y8ptTOaNpe0snNfALbBUPXJsgeyeRkDJDYzyLZ8MpuvNBTNllNeIFDbiXWAK5k+pEBZrUQ==} + '@better-auth/mongo-adapter@1.7.1': + resolution: {integrity: sha512-9ILTcNqhG37QK//qR4UhYLyKzNqq6w6zVTf5KX6xkiTjNcV7Oh1yS31lkIJEVTqRNcy9AoV6FZMW6Bbsm8IMDA==} peerDependencies: - '@better-auth/core': ^1.6.25 - '@better-auth/utils': 0.4.2 + '@better-auth/core': ^1.7.1 + '@better-auth/utils': 0.5.0 mongodb: ^6.0.0 || ^7.0.0 peerDependenciesMeta: mongodb: optional: true - '@better-auth/prisma-adapter@1.6.25': - resolution: {integrity: sha512-ym7B6Iqcry+/4aQnYpFwqP/GBIiXvjrm/5B6+0qmx8mkTY/apHFTpHuGzUYYNf4vPTtzF3eYY2+s2GOsomKaRg==} + '@better-auth/oauth-provider@1.7.1': + resolution: {integrity: sha512-VWIw7ti6rodlbbdSbn0mts/TZcBWUj6YaoIpREmv70eoGmWTa6MPWEbGuUdADQe3Vy4YqysIbmQA6qgRqfLTaw==} peerDependencies: - '@better-auth/core': ^1.6.25 - '@better-auth/utils': 0.4.2 + '@better-auth/core': ^1.7.1 + '@better-auth/utils': 0.5.0 + '@better-fetch/fetch': 1.3.1 + better-auth: ^1.7.1 + better-call: 1.4.0 + + '@better-auth/prisma-adapter@1.7.1': + resolution: {integrity: sha512-ZiUcafQ85InAofcUjyGgCPjKLfQjXr9SvDmMjuFUW8oEbreA6C6GaFAEA77VuV2doZUQlzvQQ4gCoTmS28W92A==} + peerDependencies: + '@better-auth/core': ^1.7.1 + '@better-auth/utils': 0.5.0 '@prisma/client': ^5.0.0 || ^6.0.0 || ^7.0.0 prisma: ^5.0.0 || ^6.0.0 || ^7.0.0 peerDependenciesMeta: @@ -1665,15 +1690,15 @@ packages: prisma: optional: true - '@better-auth/telemetry@1.6.25': - resolution: {integrity: sha512-2ZfC9lp7tU6Jw/q2Lz/bKfQqGMdMwc/IQDTYdBhvtGi24qInYVnhp2ZCW57hHM9j+fq1ULOtxgg6M3T1LEaihw==} + '@better-auth/telemetry@1.7.1': + resolution: {integrity: sha512-kLKjMfFlTbyt49DGeI9okHAsn0MtBZcMoQYKaEdgR0H3BHzqqyzePcQz/hxAmRgjB4p/6inise3zJwhX0sgXrQ==} peerDependencies: - '@better-auth/core': ^1.6.25 - '@better-auth/utils': 0.4.2 + '@better-auth/core': ^1.7.1 + '@better-auth/utils': 0.5.0 '@better-fetch/fetch': 1.3.1 - '@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==} @@ -4647,8 +4672,8 @@ packages: resolution: {integrity: sha512-GlF5wPWnSa/X5LKM1o0wz0suXIINz1iHRLvTS+sLyi7XPbe5ycmYI3DlZqVGZZtDgl4DmasFg7gOB3JYbphV5g==} hasBin: true - better-auth@1.6.25: - resolution: {integrity: sha512-fvoq+oCO+FF5fpP3XfU7znRyGFpHB77UG2EyxsKNy+Cak7Q5pELu+auvvDveQbWQxcoKugZ7jYQQPFQLpUTGOw==} + better-auth@1.7.1: + resolution: {integrity: sha512-g8WlTQijxXWJjPVZfFu1+EJg9cwwHrKDmIkcYMzx8CzYA+tDxl6NI7qQbKkbgw5UtHILsT5VH+RMzFzwnVJqAg==} peerDependencies: '@lynx-js/react': '*' '@prisma/client': ^5.0.0 || ^6.0.0 || ^7.0.0 @@ -4656,8 +4681,8 @@ packages: '@tanstack/react-start': ^1.0.0 '@tanstack/solid-start': ^1.0.0 better-sqlite3: ^12.0.0 - drizzle-kit: '>=0.31.4' - drizzle-orm: ^0.45.2 + drizzle-kit: '>=0.31.4 || >=1.0.0-beta.1' + drizzle-orm: ^0.45.2 || >=1.0.0-rc.1 <2.0.0 mongodb: ^6.0.0 || ^7.0.0 mysql2: ^3.0.0 next: ^14.0.0 || ^15.0.0 || ^16.0.0 @@ -4709,8 +4734,8 @@ packages: vue: optional: true - 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: @@ -6928,8 +6953,8 @@ packages: rope-sequence@1.3.4: resolution: {integrity: sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==} - rou3@0.7.12: - resolution: {integrity: sha512-iFE4hLDuloSWcD7mjdCDhx2bKcIsYbtOTpfH5MHHLSKMOUyjqQXTeZVa289uuwEGEKFoE/BAPbhaU4B774nceg==} + rou3@0.9.2: + resolution: {integrity: sha512-3SOzvaAg8rkHrXtRjpCvCvbyO5to9oOO27Z/XqHEYXfMRVSw/qMIVdmaOk9W2lcRLtR6dlqTjo9hDeJk70QBYQ==} router@2.2.0: resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} @@ -8160,13 +8185,13 @@ snapshots: '@bcoe/v8-coverage@1.0.2': {} - '@better-auth/core@1.6.25(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2)': + '@better-auth/core@1.7.1(@better-auth/utils@0.5.0)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.4.0(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2)': dependencies: - '@better-auth/utils': 0.4.2 + '@better-auth/utils': 0.5.0 '@better-fetch/fetch': 1.3.1 '@opentelemetry/semantic-conventions': 1.43.0 '@standard-schema/spec': 1.1.0 - better-call: 1.3.7(zod@4.4.3) + better-call: 1.4.0(zod@4.4.3) jose: 6.2.8 kysely: 0.29.4 nanostores: 1.4.2 @@ -8174,45 +8199,66 @@ snapshots: optionalDependencies: '@opentelemetry/api': 1.9.1 - '@better-auth/drizzle-adapter@1.6.25(@better-auth/core@1.6.25(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2))(@better-auth/utils@0.4.2)': + '@better-auth/drizzle-adapter@1.7.1(@better-auth/core@1.7.1(@better-auth/utils@0.5.0)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.4.0(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2))(@better-auth/utils@0.5.0)': dependencies: - '@better-auth/core': 1.6.25(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2) - '@better-auth/utils': 0.4.2 + '@better-auth/core': 1.7.1(@better-auth/utils@0.5.0)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.4.0(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2) + '@better-auth/utils': 0.5.0 - '@better-auth/kysely-adapter@1.6.25(@better-auth/core@1.6.25(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2))(@better-auth/utils@0.4.2)(kysely@0.29.4)': + '@better-auth/kysely-adapter@1.7.1(@better-auth/core@1.7.1(@better-auth/utils@0.5.0)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.4.0(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2))(@better-auth/utils@0.5.0)(kysely@0.29.4)': dependencies: - '@better-auth/core': 1.6.25(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2) - '@better-auth/utils': 0.4.2 + '@better-auth/core': 1.7.1(@better-auth/utils@0.5.0)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.4.0(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2) + '@better-auth/utils': 0.5.0 optionalDependencies: kysely: 0.29.4 - '@better-auth/memory-adapter@1.6.25(@better-auth/core@1.6.25(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2))(@better-auth/utils@0.4.2)': + '@better-auth/mcp@1.7.1(@better-auth/core@1.7.1(@better-auth/utils@0.5.0)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.4.0(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2))(@better-auth/utils@0.5.0)(@better-fetch/fetch@1.3.1)(better-auth@1.7.1(@opentelemetry/api@1.9.1)(@prisma/client@7.8.0(@typescript/typescript6@6.0.2)(prisma@7.9.0(@types/react@19.2.14)(@typescript/typescript6@6.0.2)(better-sqlite3@12.6.2)(magicast@0.5.2)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)))(better-sqlite3@12.6.2)(mongodb@7.1.0)(mysql2@3.15.3)(next@16.2.10(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.61.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(pg@8.22.0)(prisma@7.9.0(@types/react@19.2.14)(@typescript/typescript6@6.0.2)(better-sqlite3@12.6.2)(magicast@0.5.2)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(vitest@4.1.10))(better-call@1.4.0(zod@4.4.3))': + dependencies: + '@better-auth/core': 1.7.1(@better-auth/utils@0.5.0)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.4.0(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2) + '@better-auth/oauth-provider': 1.7.1(@better-auth/core@1.7.1(@better-auth/utils@0.5.0)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.4.0(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2))(@better-auth/utils@0.5.0)(@better-fetch/fetch@1.3.1)(better-auth@1.7.1(@opentelemetry/api@1.9.1)(@prisma/client@7.8.0(@typescript/typescript6@6.0.2)(prisma@7.9.0(@types/react@19.2.14)(@typescript/typescript6@6.0.2)(better-sqlite3@12.6.2)(magicast@0.5.2)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)))(better-sqlite3@12.6.2)(mongodb@7.1.0)(mysql2@3.15.3)(next@16.2.10(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.61.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(pg@8.22.0)(prisma@7.9.0(@types/react@19.2.14)(@typescript/typescript6@6.0.2)(better-sqlite3@12.6.2)(magicast@0.5.2)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(vitest@4.1.10))(better-call@1.4.0(zod@4.4.3)) + better-auth: 1.7.1(@opentelemetry/api@1.9.1)(@prisma/client@7.8.0(@typescript/typescript6@6.0.2)(prisma@7.9.0(@types/react@19.2.14)(@typescript/typescript6@6.0.2)(better-sqlite3@12.6.2)(magicast@0.5.2)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)))(better-sqlite3@12.6.2)(mongodb@7.1.0)(mysql2@3.15.3)(next@16.2.10(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.61.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(pg@8.22.0)(prisma@7.9.0(@types/react@19.2.14)(@typescript/typescript6@6.0.2)(better-sqlite3@12.6.2)(magicast@0.5.2)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(vitest@4.1.10) + better-call: 1.4.0(zod@4.4.3) + jose: 6.2.8 + transitivePeerDependencies: + - '@better-auth/utils' + - '@better-fetch/fetch' + + '@better-auth/memory-adapter@1.7.1(@better-auth/core@1.7.1(@better-auth/utils@0.5.0)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.4.0(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2))(@better-auth/utils@0.5.0)': dependencies: - '@better-auth/core': 1.6.25(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2) - '@better-auth/utils': 0.4.2 + '@better-auth/core': 1.7.1(@better-auth/utils@0.5.0)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.4.0(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2) + '@better-auth/utils': 0.5.0 - '@better-auth/mongo-adapter@1.6.25(@better-auth/core@1.6.25(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2))(@better-auth/utils@0.4.2)(mongodb@7.1.0)': + '@better-auth/mongo-adapter@1.7.1(@better-auth/core@1.7.1(@better-auth/utils@0.5.0)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.4.0(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2))(@better-auth/utils@0.5.0)(mongodb@7.1.0)': dependencies: - '@better-auth/core': 1.6.25(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2) - '@better-auth/utils': 0.4.2 + '@better-auth/core': 1.7.1(@better-auth/utils@0.5.0)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.4.0(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2) + '@better-auth/utils': 0.5.0 optionalDependencies: mongodb: 7.1.0 - '@better-auth/prisma-adapter@1.6.25(@better-auth/core@1.6.25(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2))(@better-auth/utils@0.4.2)(@prisma/client@7.8.0(@typescript/typescript6@6.0.2)(prisma@7.9.0(@types/react@19.2.14)(@typescript/typescript6@6.0.2)(better-sqlite3@12.6.2)(magicast@0.5.2)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)))(prisma@7.9.0(@types/react@19.2.14)(@typescript/typescript6@6.0.2)(better-sqlite3@12.6.2)(magicast@0.5.2)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))': + '@better-auth/oauth-provider@1.7.1(@better-auth/core@1.7.1(@better-auth/utils@0.5.0)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.4.0(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2))(@better-auth/utils@0.5.0)(@better-fetch/fetch@1.3.1)(better-auth@1.7.1(@opentelemetry/api@1.9.1)(@prisma/client@7.8.0(@typescript/typescript6@6.0.2)(prisma@7.9.0(@types/react@19.2.14)(@typescript/typescript6@6.0.2)(better-sqlite3@12.6.2)(magicast@0.5.2)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)))(better-sqlite3@12.6.2)(mongodb@7.1.0)(mysql2@3.15.3)(next@16.2.10(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.61.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(pg@8.22.0)(prisma@7.9.0(@types/react@19.2.14)(@typescript/typescript6@6.0.2)(better-sqlite3@12.6.2)(magicast@0.5.2)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(vitest@4.1.10))(better-call@1.4.0(zod@4.4.3))': + dependencies: + '@better-auth/core': 1.7.1(@better-auth/utils@0.5.0)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.4.0(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2) + '@better-auth/utils': 0.5.0 + '@better-fetch/fetch': 1.3.1 + better-auth: 1.7.1(@opentelemetry/api@1.9.1)(@prisma/client@7.8.0(@typescript/typescript6@6.0.2)(prisma@7.9.0(@types/react@19.2.14)(@typescript/typescript6@6.0.2)(better-sqlite3@12.6.2)(magicast@0.5.2)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)))(better-sqlite3@12.6.2)(mongodb@7.1.0)(mysql2@3.15.3)(next@16.2.10(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.61.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(pg@8.22.0)(prisma@7.9.0(@types/react@19.2.14)(@typescript/typescript6@6.0.2)(better-sqlite3@12.6.2)(magicast@0.5.2)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(vitest@4.1.10) + better-call: 1.4.0(zod@4.4.3) + jose: 6.2.8 + zod: 4.4.3 + + '@better-auth/prisma-adapter@1.7.1(@better-auth/core@1.7.1(@better-auth/utils@0.5.0)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.4.0(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2))(@better-auth/utils@0.5.0)(@prisma/client@7.8.0(@typescript/typescript6@6.0.2)(prisma@7.9.0(@types/react@19.2.14)(@typescript/typescript6@6.0.2)(better-sqlite3@12.6.2)(magicast@0.5.2)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)))(prisma@7.9.0(@types/react@19.2.14)(@typescript/typescript6@6.0.2)(better-sqlite3@12.6.2)(magicast@0.5.2)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))': dependencies: - '@better-auth/core': 1.6.25(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2) - '@better-auth/utils': 0.4.2 + '@better-auth/core': 1.7.1(@better-auth/utils@0.5.0)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.4.0(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2) + '@better-auth/utils': 0.5.0 optionalDependencies: '@prisma/client': 7.8.0(@typescript/typescript6@6.0.2)(prisma@7.9.0(@types/react@19.2.14)(@typescript/typescript6@6.0.2)(better-sqlite3@12.6.2)(magicast@0.5.2)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)) prisma: 7.9.0(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(@typescript/typescript6@6.0.2)(better-sqlite3@12.6.2)(magicast@0.5.2)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@better-auth/telemetry@1.6.25(@better-auth/core@1.6.25(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2))(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)': + '@better-auth/telemetry@1.7.1(@better-auth/core@1.7.1(@better-auth/utils@0.5.0)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.4.0(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2))(@better-auth/utils@0.5.0)(@better-fetch/fetch@1.3.1)': dependencies: - '@better-auth/core': 1.6.25(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2) - '@better-auth/utils': 0.4.2 + '@better-auth/core': 1.7.1(@better-auth/utils@0.5.0)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.4.0(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2) + '@better-auth/utils': 0.5.0 '@better-fetch/fetch': 1.3.1 - '@better-auth/utils@0.4.2': + '@better-auth/utils@0.5.0': dependencies: '@noble/hashes': 2.2.0 @@ -10914,20 +10960,20 @@ snapshots: bcryptjs@3.0.3: {} - better-auth@1.6.25(@opentelemetry/api@1.9.1)(@prisma/client@7.8.0(@typescript/typescript6@6.0.2)(prisma@7.9.0(@types/react@19.2.14)(@typescript/typescript6@6.0.2)(better-sqlite3@12.6.2)(magicast@0.5.2)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)))(better-sqlite3@12.6.2)(mongodb@7.1.0)(mysql2@3.15.3)(next@16.2.10(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.61.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(pg@8.22.0)(prisma@7.9.0(@types/react@19.2.14)(@typescript/typescript6@6.0.2)(better-sqlite3@12.6.2)(magicast@0.5.2)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(vitest@4.1.10): + better-auth@1.7.1(@opentelemetry/api@1.9.1)(@prisma/client@7.8.0(@typescript/typescript6@6.0.2)(prisma@7.9.0(@types/react@19.2.14)(@typescript/typescript6@6.0.2)(better-sqlite3@12.6.2)(magicast@0.5.2)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)))(better-sqlite3@12.6.2)(mongodb@7.1.0)(mysql2@3.15.3)(next@16.2.10(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.61.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(pg@8.22.0)(prisma@7.9.0(@types/react@19.2.14)(@typescript/typescript6@6.0.2)(better-sqlite3@12.6.2)(magicast@0.5.2)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(vitest@4.1.10): dependencies: - '@better-auth/core': 1.6.25(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2) - '@better-auth/drizzle-adapter': 1.6.25(@better-auth/core@1.6.25(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2))(@better-auth/utils@0.4.2) - '@better-auth/kysely-adapter': 1.6.25(@better-auth/core@1.6.25(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2))(@better-auth/utils@0.4.2)(kysely@0.29.4) - '@better-auth/memory-adapter': 1.6.25(@better-auth/core@1.6.25(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2))(@better-auth/utils@0.4.2) - '@better-auth/mongo-adapter': 1.6.25(@better-auth/core@1.6.25(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2))(@better-auth/utils@0.4.2)(mongodb@7.1.0) - '@better-auth/prisma-adapter': 1.6.25(@better-auth/core@1.6.25(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2))(@better-auth/utils@0.4.2)(@prisma/client@7.8.0(@typescript/typescript6@6.0.2)(prisma@7.9.0(@types/react@19.2.14)(@typescript/typescript6@6.0.2)(better-sqlite3@12.6.2)(magicast@0.5.2)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)))(prisma@7.9.0(@types/react@19.2.14)(@typescript/typescript6@6.0.2)(better-sqlite3@12.6.2)(magicast@0.5.2)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)) - '@better-auth/telemetry': 1.6.25(@better-auth/core@1.6.25(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2))(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1) - '@better-auth/utils': 0.4.2 + '@better-auth/core': 1.7.1(@better-auth/utils@0.5.0)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.4.0(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2) + '@better-auth/drizzle-adapter': 1.7.1(@better-auth/core@1.7.1(@better-auth/utils@0.5.0)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.4.0(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2))(@better-auth/utils@0.5.0) + '@better-auth/kysely-adapter': 1.7.1(@better-auth/core@1.7.1(@better-auth/utils@0.5.0)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.4.0(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2))(@better-auth/utils@0.5.0)(kysely@0.29.4) + '@better-auth/memory-adapter': 1.7.1(@better-auth/core@1.7.1(@better-auth/utils@0.5.0)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.4.0(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2))(@better-auth/utils@0.5.0) + '@better-auth/mongo-adapter': 1.7.1(@better-auth/core@1.7.1(@better-auth/utils@0.5.0)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.4.0(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2))(@better-auth/utils@0.5.0)(mongodb@7.1.0) + '@better-auth/prisma-adapter': 1.7.1(@better-auth/core@1.7.1(@better-auth/utils@0.5.0)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.4.0(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2))(@better-auth/utils@0.5.0)(@prisma/client@7.8.0(@typescript/typescript6@6.0.2)(prisma@7.9.0(@types/react@19.2.14)(@typescript/typescript6@6.0.2)(better-sqlite3@12.6.2)(magicast@0.5.2)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)))(prisma@7.9.0(@types/react@19.2.14)(@typescript/typescript6@6.0.2)(better-sqlite3@12.6.2)(magicast@0.5.2)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)) + '@better-auth/telemetry': 1.7.1(@better-auth/core@1.7.1(@better-auth/utils@0.5.0)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.4.0(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2))(@better-auth/utils@0.5.0)(@better-fetch/fetch@1.3.1) + '@better-auth/utils': 0.5.0 '@better-fetch/fetch': 1.3.1 '@noble/ciphers': 2.2.0 '@noble/hashes': 2.2.0 - better-call: 1.3.7(zod@4.4.3) + better-call: 1.4.0(zod@4.4.3) defu: 6.1.7 jose: 6.2.8 kysely: 0.29.4 @@ -10948,11 +10994,11 @@ snapshots: - '@cloudflare/workers-types' - '@opentelemetry/api' - better-call@1.3.7(zod@4.4.3): + better-call@1.4.0(zod@4.4.3): dependencies: - '@better-auth/utils': 0.4.2 + '@better-auth/utils': 0.5.0 '@better-fetch/fetch': 1.3.1 - rou3: 0.7.12 + rou3: 0.9.2 set-cookie-parser: 3.1.2 optionalDependencies: zod: 4.4.3 @@ -11608,8 +11654,8 @@ snapshots: '@next/eslint-plugin-next': 16.2.10 eslint: 9.39.5(jiti@2.7.0) eslint-import-resolver-node: 0.3.10 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.5(jiti@2.7.0)) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.63.0(@typescript/typescript6@6.0.2)(eslint@9.39.5(jiti@2.7.0)))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.5(jiti@2.7.0)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.63.0(@typescript/typescript6@6.0.2)(eslint@9.39.5(jiti@2.7.0)))(eslint@9.39.5(jiti@2.7.0)))(eslint@9.39.5(jiti@2.7.0)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.63.0(@typescript/typescript6@6.0.2)(eslint@9.39.5(jiti@2.7.0)))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.63.0(@typescript/typescript6@6.0.2)(eslint@9.39.5(jiti@2.7.0)))(eslint@9.39.5(jiti@2.7.0)))(eslint@9.39.5(jiti@2.7.0)))(eslint@9.39.5(jiti@2.7.0)) eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.5(jiti@2.7.0)) eslint-plugin-react: 7.37.5(eslint@9.39.5(jiti@2.7.0)) eslint-plugin-react-hooks: 7.1.1(eslint@9.39.5(jiti@2.7.0)) @@ -11635,7 +11681,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.5(jiti@2.7.0)): + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.63.0(@typescript/typescript6@6.0.2)(eslint@9.39.5(jiti@2.7.0)))(eslint@9.39.5(jiti@2.7.0)))(eslint@9.39.5(jiti@2.7.0)): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.4.3 @@ -11646,22 +11692,22 @@ snapshots: tinyglobby: 0.2.17 unrs-resolver: 1.12.2 optionalDependencies: - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.63.0(@typescript/typescript6@6.0.2)(eslint@9.39.5(jiti@2.7.0)))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.5(jiti@2.7.0)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.63.0(@typescript/typescript6@6.0.2)(eslint@9.39.5(jiti@2.7.0)))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.63.0(@typescript/typescript6@6.0.2)(eslint@9.39.5(jiti@2.7.0)))(eslint@9.39.5(jiti@2.7.0)))(eslint@9.39.5(jiti@2.7.0)))(eslint@9.39.5(jiti@2.7.0)) transitivePeerDependencies: - supports-color - eslint-module-utils@2.14.0(@typescript-eslint/parser@8.63.0(@typescript/typescript6@6.0.2)(eslint@9.39.5(jiti@2.7.0)))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.5(jiti@2.7.0)): + eslint-module-utils@2.14.0(@typescript-eslint/parser@8.63.0(@typescript/typescript6@6.0.2)(eslint@9.39.5(jiti@2.7.0)))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.63.0(@typescript/typescript6@6.0.2)(eslint@9.39.5(jiti@2.7.0)))(eslint@9.39.5(jiti@2.7.0)))(eslint@9.39.5(jiti@2.7.0)))(eslint@9.39.5(jiti@2.7.0)): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 8.63.0(@typescript/typescript6@6.0.2)(eslint@9.39.5(jiti@2.7.0)) eslint: 9.39.5(jiti@2.7.0) eslint-import-resolver-node: 0.3.10 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.5(jiti@2.7.0)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.63.0(@typescript/typescript6@6.0.2)(eslint@9.39.5(jiti@2.7.0)))(eslint@9.39.5(jiti@2.7.0)))(eslint@9.39.5(jiti@2.7.0)) transitivePeerDependencies: - supports-color - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.63.0(@typescript/typescript6@6.0.2)(eslint@9.39.5(jiti@2.7.0)))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.5(jiti@2.7.0)): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.63.0(@typescript/typescript6@6.0.2)(eslint@9.39.5(jiti@2.7.0)))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.63.0(@typescript/typescript6@6.0.2)(eslint@9.39.5(jiti@2.7.0)))(eslint@9.39.5(jiti@2.7.0)))(eslint@9.39.5(jiti@2.7.0)))(eslint@9.39.5(jiti@2.7.0)): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -11672,7 +11718,7 @@ snapshots: doctrine: 2.1.0 eslint: 9.39.5(jiti@2.7.0) eslint-import-resolver-node: 0.3.10 - eslint-module-utils: 2.14.0(@typescript-eslint/parser@8.63.0(@typescript/typescript6@6.0.2)(eslint@9.39.5(jiti@2.7.0)))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.5(jiti@2.7.0)) + eslint-module-utils: 2.14.0(@typescript-eslint/parser@8.63.0(@typescript/typescript6@6.0.2)(eslint@9.39.5(jiti@2.7.0)))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.63.0(@typescript/typescript6@6.0.2)(eslint@9.39.5(jiti@2.7.0)))(eslint@9.39.5(jiti@2.7.0)))(eslint@9.39.5(jiti@2.7.0)))(eslint@9.39.5(jiti@2.7.0)) hasown: 2.0.4 is-core-module: 2.16.2 is-glob: 4.0.3 @@ -13424,7 +13470,7 @@ snapshots: rope-sequence@1.3.4: {} - rou3@0.7.12: {} + rou3@0.9.2: {} router@2.2.0: dependencies: