From 9284108dc4496e5d4e7bbb0560066c48a15a2f6a Mon Sep 17 00:00:00 2001 From: Stephen Shkeda Date: Thu, 20 Aug 2026 22:40:48 -0400 Subject: [PATCH 01/29] Migrate Convex adapter to Better Auth 1.7 Add the Better Auth 1.7 adapter operations and compound uniqueness support, provide the staged account issuer and OAuth provider migration path, and update examples, generated bindings, tests, and documentation. --- docs/content/docs/features/local-install.mdx | 1 + docs/content/docs/framework-guides/expo.mdx | 2 +- docs/content/docs/framework-guides/next.mdx | 2 +- docs/content/docs/framework-guides/react.mdx | 2 +- .../docs/framework-guides/sveltekit.mdx | 2 +- .../docs/framework-guides/tanstack-start.mdx | 2 +- docs/content/docs/migrations/meta.json | 2 +- .../docs/migrations/migrate-to-0-13.mdx | 427 + docs/content/docs/supported-plugins.mdx | 7 + .../app/(auth)/settings/EnableTwoFactor.tsx | 2 +- examples/next/app/(unauth)/sign-in/SignIn.tsx | 4 +- .../convex/betterAuth/_generated/component.ts | 329 + examples/next/convex/betterAuth/adapter.ts | 1 + .../next/convex/betterAuth/generatedSchema.ts | 17 +- examples/next/lib/auth-client.tsx | 2 - examples/next/package-lock.json | 50 +- examples/react/package-lock.json | 50 +- .../convex/betterAuth/_generated/component.ts | 1991 +- .../tanstack/convex/betterAuth/adapter.ts | 1 + examples/tanstack/convex/betterAuth/schema.ts | 47 +- examples/tanstack/package-lock.json | 50 +- .../src/components/EnableTwoFactor.tsx | 2 +- package-lock.json | 305 +- package.json | 10 +- src/auth-options.ts | 5 - src/client/adapter-utils.ts | 92 +- src/client/adapter.ts | 78 +- src/client/create-api.ts | 124 +- src/client/create-client.ts | 1 + src/client/create-schema.test.ts | 26 + src/client/create-schema.ts | 39 +- src/client/triggers.test.ts | 39 +- src/component/_generated/api.ts | 2 + src/component/_generated/component.ts | 16553 +++++++++++----- src/component/adapter.ts | 1 + src/component/migrations.test.ts | 516 + src/component/migrations.ts | 349 + src/component/schema.ts | 31 +- .../testProfiles/adapterAdditionalFields.ts | 1 + .../testProfiles/adapterOrganizationJoins.ts | 1 + .../testProfiles/adapterPluginTable.ts | 1 + .../testProfiles/adapterRenameField.ts | 1 + .../testProfiles/adapterRenameUserCustom.ts | 1 + .../testProfiles/adapterRenameUserTable.ts | 1 + .../schema.profile-plugin-table.ts | 3 + src/plugins/convex/index.test.ts | 44 + src/plugins/convex/index.ts | 68 +- src/plugins/cross-domain/index.test.ts | 10 +- src/plugins/cross-domain/index.ts | 2 - src/react/index.tsx | 72 +- src/react/index.types.test.ts | 9 + src/test/adapter-factory/convex-custom.ts | 86 +- 52 files changed, 14974 insertions(+), 6490 deletions(-) create mode 100644 docs/content/docs/migrations/migrate-to-0-13.mdx create mode 100644 src/client/create-schema.test.ts create mode 100644 src/component/migrations.test.ts create mode 100644 src/component/migrations.ts create mode 100644 src/react/index.types.test.ts diff --git a/docs/content/docs/features/local-install.mdx b/docs/content/docs/features/local-install.mdx index db3ecc5f..081e1ae8 100644 --- a/docs/content/docs/features/local-install.mdx +++ b/docs/content/docs/features/local-install.mdx @@ -118,6 +118,7 @@ installed component. findOne, findMany, updateOne, + incrementOne, updateMany, deleteOne, deleteMany, diff --git a/docs/content/docs/framework-guides/expo.mdx b/docs/content/docs/framework-guides/expo.mdx index cd712d69..41c692fb 100644 --- a/docs/content/docs/framework-guides/expo.mdx +++ b/docs/content/docs/framework-guides/expo.mdx @@ -17,7 +17,7 @@ description: Install and configure Convex + Better Auth for Expo. ```npm npm install convex@latest @convex-dev/better-auth - npm install better-auth@~1.6.15 @better-auth/expo@~1.6.15 + npm install better-auth@~1.7.1 @better-auth/expo@~1.7.1 ``` diff --git a/docs/content/docs/framework-guides/next.mdx b/docs/content/docs/framework-guides/next.mdx index b2b2b626..92e071bf 100644 --- a/docs/content/docs/framework-guides/next.mdx +++ b/docs/content/docs/framework-guides/next.mdx @@ -22,7 +22,7 @@ description: Install and configure Convex + Better Auth for Next.js. ```npm npm install convex@latest @convex-dev/better-auth - npm install better-auth@~1.6.15 + npm install better-auth@~1.7.1 ``` diff --git a/docs/content/docs/framework-guides/react.mdx b/docs/content/docs/framework-guides/react.mdx index 80528313..6bde2c0e 100644 --- a/docs/content/docs/framework-guides/react.mdx +++ b/docs/content/docs/framework-guides/react.mdx @@ -22,7 +22,7 @@ description: Install and configure Convex + Better Auth for React. ```npm npm install convex@latest @convex-dev/better-auth - npm install better-auth@~1.6.15 + npm install better-auth@~1.7.1 ``` diff --git a/docs/content/docs/framework-guides/sveltekit.mdx b/docs/content/docs/framework-guides/sveltekit.mdx index 2702fec0..ab684188 100644 --- a/docs/content/docs/framework-guides/sveltekit.mdx +++ b/docs/content/docs/framework-guides/sveltekit.mdx @@ -99,7 +99,7 @@ npm install convex @mmailaender/convex-svelte ```npm npm install @convex-dev/better-auth @mmailaender/convex-better-auth-svelte - npm install better-auth@~1.6.15 + npm install better-auth@~1.7.1 ``` diff --git a/docs/content/docs/framework-guides/tanstack-start.mdx b/docs/content/docs/framework-guides/tanstack-start.mdx index 3d9196ba..f34e93fd 100644 --- a/docs/content/docs/framework-guides/tanstack-start.mdx +++ b/docs/content/docs/framework-guides/tanstack-start.mdx @@ -23,7 +23,7 @@ description: Install and configure Convex + Better Auth for TanStack Start. ```npm npm install convex@latest @convex-dev/better-auth - npm install better-auth@~1.6.15 + npm install better-auth@~1.7.1 npm install @types/node --save-dev ``` diff --git a/docs/content/docs/migrations/meta.json b/docs/content/docs/migrations/meta.json index c5bc92ec..0da75a15 100644 --- a/docs/content/docs/migrations/meta.json +++ b/docs/content/docs/migrations/meta.json @@ -1,5 +1,5 @@ { "title": "Migrations", - "pages": ["migrate-to-0-12", "migrate-to-0-11", "migrate-to-0-10", "migrate-to-0-9", "migrate-to-0-8"], + "pages": ["migrate-to-0-13", "migrate-to-0-12", "migrate-to-0-11", "migrate-to-0-10", "migrate-to-0-9", "migrate-to-0-8"], "defaultOpen": true } diff --git a/docs/content/docs/migrations/migrate-to-0-13.mdx b/docs/content/docs/migrations/migrate-to-0-13.mdx new file mode 100644 index 00000000..1ecef37f --- /dev/null +++ b/docs/content/docs/migrations/migrate-to-0-13.mdx @@ -0,0 +1,427 @@ +--- +title: Migrate to 0.13 +description: Migrate to @convex-dev/better-auth@0.13 +--- + +Version 0.13 supports Better Auth 1.7. Better Auth 1.7 adds an issuer-scoped +account identity, requires atomic adapter operations, moves the OIDC provider to +its own package, and changes the Generic OAuth client API. The bundled component +does not include the schema-changing replacement provider. If your application +acts as an OAuth or OIDC provider, switch to [Local Install](/local-install). + +Review Better Auth's +[complete 1.7 upgrade guide](https://www.better-auth.com/docs/guides/1-7-upgrade-guide) +alongside this component-specific migration. + + + Existing applications must backfill account issuers. Use a maintenance window, + stop authentication writes, and back up your Better Auth data before + upgrading. + + +## Before upgrading + +Inventory every distinct `providerId` in the `account` table and match it to the +`accountIssuer` Better Auth 1.7 will use for that exact provider configuration. +Do not infer an issuer from an email address, profile field, or unverified +request value. + +| Account type | Issuer | +| ------------------------------------- | ------------------------------------------ | +| Email and password | `local:credential` | +| SIWE | `local:siwe` | +| OAuth provider with a verified issuer | The exact issuer published by the provider | +| OAuth provider without an issuer | `local:oauth:` | + +The component migration handles `credential` and `siwe` automatically. Pass an +explicit mapping for every other legacy provider. For an OAuth provider without +an `accountIssuer`, use Better Auth's helper rather than constructing the value +by hand: + +```ts +import { createOAuthAccountIssuer } from "better-auth/db"; + +createOAuthAccountIssuer("github"); // local:oauth:github +createOAuthAccountIssuer("team/github"); // local:oauth:team%2Fgithub +``` + +Providers with an explicit or discovered `accountIssuer` must use that exact +verified value. For example, Better Auth's built-in Google provider uses +`https://accounts.google.com`, while its built-in GitHub provider uses the +synthetic `local:oauth:github` value. Existing rows already written by Better +Auth 1.7 retain their stored issuer and do not need a mapping on a rerun. + + + Better Auth 1.7 changes Microsoft identities from the app-specific `sub` claim + to the stable directory `oid` claim. Before running this component migration, + update `accountId` for `microsoft` and `microsoft-entra-id` rows using + verified ID tokens or a trusted Microsoft Entra export. The old account row + cannot safely supply this mapping by itself. See Better Auth's upgrade guide + for the full provider-specific procedure. + + +Also audit custom OAuth providers whose immutable identifier is not their +default `sub` or `id`; configure Better Auth's `accountSubject` and migrate the +stored `accountId` from trusted provider data before the backfill. + +### If your app is an OAuth or OIDC provider + +Better Auth replaced `oidcProvider` with `@better-auth/oauth-provider` and +changed its storage model. The replacement is not included in the bundled +component. Complete the account backfill on the staged bundled component first, +then: + +1. Export every legacy `oauthApplication` client registration. The migration + query described below redacts client secrets; confidential clients receive + new secrets when re-registered. +2. Clear the legacy `oauthAccessToken`, `oauthConsent`, and `oauthApplication` + tables while the bundled migration functions are still available. +3. Convert the existing `betterAuth` component mount to Local Install and deploy + the generated Better Auth 1.7 schema with `@better-auth/oauth-provider`. +4. Re-register the saved clients in the Local Install `oauthClient` table. Map + `redirectUrls` to `redirectUris`, convert the legacy `metadata` string to + JSON, and review grant types and the token endpoint authentication method. + +Legacy access and refresh tokens cannot be reused with the new provider. + +## Upgrade and backfill accounts + +
+ +
+ +### Start the maintenance window + +Back up the Better Auth component data, disable the Better Auth HTTP routes, and +pause every background job or admin function that can create or update an +`account`. Leave writes stopped through the validation and backfill steps. + +If a 1.7 sign-in reaches an un-migrated account, the new `(issuer, accountId)` +lookup cannot find it and may create a duplicate account. A read-only UI is not +enough if server jobs can still write authentication data. + +
+ +
+ +### Install dependencies + +```npm +npm install @convex-dev/better-auth@^0.13.0 +npm install better-auth@~1.7.1 +``` + +
+ +
+ +### Deploy the staged component schema + +Run your normal Convex development or deploy command. The bundled component +temporarily accepts accounts without `issuer` so the backfill can run, while +Better Auth 1.7 supplies `issuer` for every new account. It retains the legacy +OAuth application, token, and consent tables so identity-provider deployments +can export and clear their old component data before moving to Local Install. It +does not add the replacement provider's schema to the bundled component. + +Do not restore authentication writes after this deploy. + +
+ +
+ +### Add validation and migration wrappers + +Create temporary internal functions in your application. Replace the example +mapping with the issuers verified from your Better Auth provider configuration. + +```ts title="convex/migrations.ts" +import { createOAuthAccountIssuer } from "better-auth/db"; +import { internalMutation, internalQuery } from "./_generated/server"; +import { components } from "./_generated/api"; +import { v } from "convex/values"; + +const providerIssuers = { + github: createOAuthAccountIssuer("github"), + google: "https://accounts.google.com", + // "my-oidc-provider": "https://issuer.example.com", +}; + +export const validateAccountIssuerBackfill = internalQuery({ + args: { cursor: v.union(v.string(), v.null()) }, + handler: async (ctx, { cursor }) => { + return await ctx.runQuery( + components.betterAuth.migrations.validateAccountIssuerBackfill, + { + providerIssuers, + paginationOpts: { cursor, numItems: 100 }, + } + ); + }, +}); + +export const backfillAccountIssuers = internalMutation({ + args: { cursor: v.union(v.string(), v.null()) }, + handler: async (ctx, { cursor }) => { + return await ctx.runMutation( + components.betterAuth.migrations.backfillAccountIssuers, + { + providerIssuers, + paginationOpts: { cursor, numItems: 100 }, + } + ); + }, +}); +``` + +The migration normalizes credential accounts so `accountId` is the linked user +ID, as required by Better Auth 1.7. Both functions calculate the target identity +of legacy rows outside the current page, so a collision split across pages is +detected before either row is changed. + +
+ +
+ +### Validate every page without writing + +Start the validation query with a null cursor: + +```bash +npx convex run migrations:validateAccountIssuerBackfill '{"cursor":null}' +``` + +If the result has `isDone: false`, run it again with the returned +`continueCursor`. Repeat until `isDone` is true. This pass must finish before +the first backfill mutation. The query stops at the first invalid account in a +page; fix that record or mapping and rerun the same page until validation can +advance. Resolve missing mappings, issuer mismatches, and collisions from +trusted provider data; never merge users based only on matching email. + +Treat every cursor as opaque and only advance with `continueCursor` while +`isDone` is false. The cursor returned with `isDone: true` is terminal and +replaying it is a no-op. Start again with a null cursor when intentionally +beginning a new validation or migration pass. + +Run the same process with `--prod` for production. + +
+ +
+ +### Backfill every page + +After validation finishes, start the mutation with a new null cursor: + +```bash +npx convex run migrations:backfillAccountIssuers '{"cursor":null}' +``` + +Repeat with each returned `continueCursor` until `isDone` is true. Do not reuse +the validation cursor. The mutation repeats the collision checks and is safe to +rerun for accounts already migrated with the same identity. Add `--prod` to each +command when running against production. + +
+ +
+ +### Verify before resuming writes + +Confirm that a second validation pass reports `pending: 0` on every page. +Applications acting as an OAuth or OIDC provider must now complete the provider +conversion below while remaining in maintenance mode. Other applications can +temporarily enable the HTTP routes for a controlled smoke test. Verify +email-and-password sign-in, returning OAuth or SSO sign-in, explicit account +linking and unlinking, provider profile retrieval, and token refresh for every +configured provider. Only then restore normal public traffic and background +writes. + +
+ +
+ +## Update Better Auth configuration + +### Account selectors + +Account-management APIs now select the local Better Auth account row, not the +provider-side subject. Read `id` from `listAccounts` and pass that value as +`accountId` to `unlinkAccount`. For `getAccessToken`, `refreshToken`, and +`accountInfo`, pass exactly one of `{ accountId: account.id }` or +`{ useAccountCookie: true }`. Remove `providerId` from these selectors; the +provider-side identity remains available as `account.accountId`. + +### Generic OAuth + +Generic OAuth now uses the standard social client API: + +```ts +await authClient.signIn.social({ provider: "my-provider" }); +``` + +Remove `genericOAuthClient()` from the client plugins. Update provider callback +URLs from `/api/auth/oauth2/callback/:id` to `/api/auth/callback/:id`. + +### Database joins + +If you enabled `experimental.joins`, move the option to +`advanced.database.joins`. The Convex adapter still disables native joins and +uses its existing relation queries. + +### OAuth or OIDC provider + +This section applies only if the application previously used the bundled +`oidcProvider` plugin to act as an identity provider. Complete it after the +account-issuer backfill, while the staged bundled component is still mounted and +authentication writes remain stopped. + +Add temporary internal wrappers for the staged component's legacy OAuth +functions: + +```ts title="convex/migrations.ts" +export const listLegacyOAuthApplications = internalQuery({ + args: { cursor: v.union(v.string(), v.null()) }, + handler: async (ctx, { cursor }) => { + return await ctx.runQuery( + components.betterAuth.migrations.listLegacyOAuthApplications, + { paginationOpts: { cursor, numItems: 100 } } + ); + }, +}); + +export const clearLegacyOAuthProviderRecords = internalMutation({ + args: { + table: v.union( + v.literal("oauthApplication"), + v.literal("oauthAccessToken"), + v.literal("oauthConsent") + ), + cursor: v.union(v.string(), v.null()), + }, + handler: async (ctx, { table, cursor }) => { + return await ctx.runMutation( + components.betterAuth.migrations.clearLegacyOAuthProviderRecords, + { table, paginationOpts: { cursor, numItems: 100 } } + ); + }, +}); +``` + +Run `listLegacyOAuthApplications` through every cursor page and save the +redacted registration metadata somewhere access-controlled. The result says +whether each client had a secret but never returns `clientSecret` itself. Other +fields, especially custom `metadata`, may still contain sensitive application +data and must be reviewed before reuse. Existing confidential clients receive a +new secret when re-registered. + +```bash +npx convex run migrations:listLegacyOAuthApplications '{"cursor":null}' +``` + +Repeat with each returned `continueCursor` until `isDone` is true. Add `--prod` +to each command when running against production. Follow the same terminal cursor +rule as the account migration above. + +After verifying the exported client metadata, run +`clearLegacyOAuthProviderRecords` through every page for `oauthAccessToken`, +`oauthConsent`, and finally `oauthApplication`. This intentionally invalidates +all legacy provider tokens and consents and removes the old client secrets. Run +it before any 1.7 provider tokens are issued. + +```bash +npx convex run migrations:clearLegacyOAuthProviderRecords \ + '{"table":"oauthAccessToken","cursor":null}' +npx convex run migrations:clearLegacyOAuthProviderRecords \ + '{"table":"oauthConsent","cursor":null}' +npx convex run migrations:clearLegacyOAuthProviderRecords \ + '{"table":"oauthApplication","cursor":null}' +``` + +For each table, repeat the command with its returned `continueCursor` until +`isDone` is true. Add `--prod` to each production command. Do not switch to +Local Install until all three tables report `isDone: true`; the bundled +migration functions are not available after the switch. A cursor returned with +`isDone: true` is terminal; start from null to intentionally run a new pass. + +#### Convert the staged component to Local Install + +1. Follow the [Local Install guide](/local-install) to create the local + `convex/betterAuth` component. Keep the component mounted as `betterAuth` in + `app.use(betterAuth)` so the generated `components.betterAuth` path does not + change. Export every adapter function shown there, including `incrementOne`. +2. Add the replacement package and review Better Auth's full OAuth provider + migration requirements: + +```npm +npm install @better-auth/oauth-provider@~1.7.1 +``` + +```ts +import { oauthProvider } from "@better-auth/oauth-provider"; +import { jwt } from "better-auth/plugins/jwt"; + +betterAuth({ + plugins: [ + jwt(), + oauthProvider({ + loginPage: "/sign-in", + consentPage: "/consent", + }), + ], +}); +``` + +3. Generate the Local Install schema only after the static Better Auth options + include `oauthProvider()`. The account backfill is complete and all three + legacy provider tables are empty, so this converting deployment can use the + final generated Better Auth 1.7 schema rather than the temporary merged + schema described below for existing Local Install deployments. +4. Deploy the local component while writes and public HTTP routes remain + stopped. Before registering clients, confirm that existing users, accounts, + and sessions are still present under the unchanged `components.betterAuth` + mount. If they are not, roll back the component registration and restore the + backup; do not resume traffic. +5. Re-register the exported clients using the new provider, map `redirectUrls` + to `redirectUris`, convert the legacy `metadata` string to JSON, review grant + types and the token endpoint authentication method, and distribute new client + secrets through the normal secret channel. Existing provider tokens must be + issued again. +6. Run the controlled smoke tests described above, including authorization, + consent, token issuance, refresh, user info, and logout, before restoring + normal traffic and background writes. + +## Local Install additional steps + +This section applies to applications that already used Local Install before the +upgrade. They control their own schema and cannot call the bundled component +migration functions above. Generate the 1.7 schema to a side file instead of +overwriting the deployed schema: + +```npm +cd convex/betterAuth +npx auth generate --output generatedSchema.next.ts +``` + +Before deploying, copy the new definitions into your active schema with these +temporary changes: + +1. Make `account.issuer` optional, but add the `accountId_issuer` compound + index. +2. If you used `oidcProvider`, retain `oauthApplication` and merge the legacy + and new `oauthAccessToken` and `oauthConsent` fields as optional unions. +3. Add the new OAuth tables without removing legacy records. + +Implement the same two-pass process against your local `account` table: a +read-only paginated validation that calculates every target identity and checks +legacy candidates outside the page, followed by the paginated write migration. +Do not deploy the required `issuer` validator from `generatedSchema.next.ts` +until validation reports no pending rows and the backfill is complete. + +If the local deployment used `oidcProvider`, also implement local equivalents of +the export and clear functions above. Redact `clientSecret` from the export, +save the client metadata, and clear `oauthAccessToken`, `oauthConsent`, and +`oauthApplication` before replacing the temporary table definitions with the +final generated definitions. Run Convex code generation and deploy, then +re-register clients and complete the provider smoke tests before resuming +authentication writes. diff --git a/docs/content/docs/supported-plugins.mdx b/docs/content/docs/supported-plugins.mdx index f3df30ef..c166b9e6 100644 --- a/docs/content/docs/supported-plugins.mdx +++ b/docs/content/docs/supported-plugins.mdx @@ -22,6 +22,13 @@ Install](/local-install). - [Two Factor](https://www.better-auth.com/docs/plugins/2fa) - [Username](https://www.better-auth.com/docs/plugins/username) +### Local Install plugins + +Schema-changing plugins that are not bundled with the component require +[Local Install](/local-install). This includes the +[`@better-auth/oauth-provider`](https://www.better-auth.com/docs/plugins/oauth-provider) +package used when your application acts as an OAuth or OIDC provider. + ### Incompatible plugins Official Better Auth plugins that are incompatible with Convex + Better Auth diff --git a/examples/next/app/(auth)/settings/EnableTwoFactor.tsx b/examples/next/app/(auth)/settings/EnableTwoFactor.tsx index e33859de..f7311e8a 100644 --- a/examples/next/app/(auth)/settings/EnableTwoFactor.tsx +++ b/examples/next/app/(auth)/settings/EnableTwoFactor.tsx @@ -53,7 +53,7 @@ export default function EnableTwoFactor() { const { data } = await authClient.twoFactor.enable({ password, }); - if (data?.totpURI) { + if (data?.method === "totp") { setTotpUri(data.totpURI); if (data.backupCodes) { setBackupCodes(data.backupCodes); diff --git a/examples/next/app/(unauth)/sign-in/SignIn.tsx b/examples/next/app/(unauth)/sign-in/SignIn.tsx index 9d5d4a8d..96e6c9bc 100644 --- a/examples/next/app/(unauth)/sign-in/SignIn.tsx +++ b/examples/next/app/(unauth)/sign-in/SignIn.tsx @@ -152,9 +152,9 @@ export default function SignIn() { }; const handleSlackSignIn = async () => { - await authClient.signIn.oauth2( + await authClient.signIn.social( { - providerId: "slack", + provider: "slack", }, { onRequest: () => { diff --git a/examples/next/convex/betterAuth/_generated/component.ts b/examples/next/convex/betterAuth/_generated/component.ts index 97ab5f56..e7ed48e3 100644 --- a/examples/next/convex/betterAuth/_generated/component.ts +++ b/examples/next/convex/betterAuth/_generated/component.ts @@ -65,6 +65,7 @@ export type ComponentApi = accountId: string; createdAt: number; idToken?: null | string; + issuer: string; password?: null | string; providerId: string; refreshToken?: null | string; @@ -88,6 +89,8 @@ export type ComponentApi = | { data: { backupCodes: string; + failedVerificationCount?: null | number; + lockedUntil?: null | number; secret: string; userId: string; verified?: null | boolean; @@ -96,7 +99,9 @@ export type ComponentApi = } | { data: { + alg?: null | string; createdAt: number; + crv?: null | string; expiresAt?: null | number; privateKey: string; publicKey: string; @@ -194,6 +199,7 @@ export type ComponentApi = where?: Array<{ connector?: "AND" | "OR"; field: + | "issuer" | "accountId" | "providerId" | "userId" @@ -271,6 +277,8 @@ export type ComponentApi = | "backupCodes" | "userId" | "verified" + | "failedVerificationCount" + | "lockedUntil" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -303,6 +311,8 @@ export type ComponentApi = | "privateKey" | "createdAt" | "expiresAt" + | "alg" + | "crv" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -424,6 +434,7 @@ export type ComponentApi = where?: Array<{ connector?: "AND" | "OR"; field: + | "issuer" | "accountId" | "providerId" | "userId" @@ -501,6 +512,8 @@ export type ComponentApi = | "backupCodes" | "userId" | "verified" + | "failedVerificationCount" + | "lockedUntil" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -533,6 +546,8 @@ export type ComponentApi = | "privateKey" | "createdAt" | "expiresAt" + | "alg" + | "crv" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -654,6 +669,300 @@ export type ComponentApi = any, Name >; + incrementOne: FunctionReference< + "mutation", + "internal", + { + input: + | { + increment: Record; + model: "user"; + set?: { + createdAt?: number; + displayUsername?: null | string; + email?: string; + emailVerified?: boolean; + foo?: null | string; + image?: null | string; + isAnonymous?: null | boolean; + name?: string; + twoFactorEnabled?: null | boolean; + updatedAt?: number; + userId?: null | string; + username?: null | string; + }; + where: Array<{ + connector?: "AND" | "OR"; + field: + | "name" + | "email" + | "emailVerified" + | "image" + | "createdAt" + | "updatedAt" + | "isAnonymous" + | "username" + | "displayUsername" + | "twoFactorEnabled" + | "userId" + | "foo" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + increment: Record; + model: "session"; + set?: { + createdAt?: number; + expiresAt?: number; + ipAddress?: null | string; + token?: string; + updatedAt?: number; + userAgent?: null | string; + userId?: string; + }; + where: Array<{ + connector?: "AND" | "OR"; + field: + | "expiresAt" + | "token" + | "createdAt" + | "updatedAt" + | "ipAddress" + | "userAgent" + | "userId" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + increment: Record; + model: "account"; + set?: { + accessToken?: null | string; + accessTokenExpiresAt?: null | number; + accountId?: string; + createdAt?: number; + idToken?: null | string; + issuer?: string; + password?: null | string; + providerId?: string; + refreshToken?: null | string; + refreshTokenExpiresAt?: null | number; + scope?: null | string; + updatedAt?: number; + userId?: string; + }; + where: Array<{ + connector?: "AND" | "OR"; + field: + | "issuer" + | "accountId" + | "providerId" + | "userId" + | "accessToken" + | "refreshToken" + | "idToken" + | "accessTokenExpiresAt" + | "refreshTokenExpiresAt" + | "scope" + | "password" + | "createdAt" + | "updatedAt" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + increment: Record; + model: "verification"; + set?: { + createdAt?: number; + expiresAt?: number; + identifier?: string; + updatedAt?: number; + value?: string; + }; + where: Array<{ + connector?: "AND" | "OR"; + field: + | "identifier" + | "value" + | "expiresAt" + | "createdAt" + | "updatedAt" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + increment: Record; + model: "twoFactor"; + set?: { + backupCodes?: string; + failedVerificationCount?: null | number; + lockedUntil?: null | number; + secret?: string; + userId?: string; + verified?: null | boolean; + }; + where: Array<{ + connector?: "AND" | "OR"; + field: + | "secret" + | "backupCodes" + | "userId" + | "verified" + | "failedVerificationCount" + | "lockedUntil" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + increment: Record; + model: "jwks"; + set?: { + alg?: null | string; + createdAt?: number; + crv?: null | string; + expiresAt?: null | number; + privateKey?: string; + publicKey?: string; + }; + where: Array<{ + connector?: "AND" | "OR"; + field: + | "publicKey" + | "privateKey" + | "createdAt" + | "expiresAt" + | "alg" + | "crv" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + }; + onUpdateHandle?: string; + }, + any, + Name + >; updateMany: FunctionReference< "mutation", "internal", @@ -765,6 +1074,7 @@ export type ComponentApi = accountId?: string; createdAt?: number; idToken?: null | string; + issuer?: string; password?: null | string; providerId?: string; refreshToken?: null | string; @@ -776,6 +1086,7 @@ export type ComponentApi = where?: Array<{ connector?: "AND" | "OR"; field: + | "issuer" | "accountId" | "providerId" | "userId" @@ -855,6 +1166,8 @@ export type ComponentApi = model: "twoFactor"; update: { backupCodes?: string; + failedVerificationCount?: null | number; + lockedUntil?: null | number; secret?: string; userId?: string; verified?: null | boolean; @@ -866,6 +1179,8 @@ export type ComponentApi = | "backupCodes" | "userId" | "verified" + | "failedVerificationCount" + | "lockedUntil" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -892,7 +1207,9 @@ export type ComponentApi = | { model: "jwks"; update: { + alg?: null | string; createdAt?: number; + crv?: null | string; expiresAt?: null | number; privateKey?: string; publicKey?: string; @@ -904,6 +1221,8 @@ export type ComponentApi = | "privateKey" | "createdAt" | "expiresAt" + | "alg" + | "crv" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -1051,6 +1370,7 @@ export type ComponentApi = accountId?: string; createdAt?: number; idToken?: null | string; + issuer?: string; password?: null | string; providerId?: string; refreshToken?: null | string; @@ -1062,6 +1382,7 @@ export type ComponentApi = where?: Array<{ connector?: "AND" | "OR"; field: + | "issuer" | "accountId" | "providerId" | "userId" @@ -1141,6 +1462,8 @@ export type ComponentApi = model: "twoFactor"; update: { backupCodes?: string; + failedVerificationCount?: null | number; + lockedUntil?: null | number; secret?: string; userId?: string; verified?: null | boolean; @@ -1152,6 +1475,8 @@ export type ComponentApi = | "backupCodes" | "userId" | "verified" + | "failedVerificationCount" + | "lockedUntil" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -1178,7 +1503,9 @@ export type ComponentApi = | { model: "jwks"; update: { + alg?: null | string; createdAt?: number; + crv?: null | string; expiresAt?: null | number; privateKey?: string; publicKey?: string; @@ -1190,6 +1517,8 @@ export type ComponentApi = | "privateKey" | "createdAt" | "expiresAt" + | "alg" + | "crv" | "_id"; mode?: "sensitive" | "insensitive"; operator?: diff --git a/examples/next/convex/betterAuth/adapter.ts b/examples/next/convex/betterAuth/adapter.ts index 58547fd9..69c8c7c1 100644 --- a/examples/next/convex/betterAuth/adapter.ts +++ b/examples/next/convex/betterAuth/adapter.ts @@ -7,6 +7,7 @@ export const { findOne, findMany, updateOne, + incrementOne, updateMany, deleteOne, deleteMany, diff --git a/examples/next/convex/betterAuth/generatedSchema.ts b/examples/next/convex/betterAuth/generatedSchema.ts index c352749c..5c1024ac 100644 --- a/examples/next/convex/betterAuth/generatedSchema.ts +++ b/examples/next/convex/betterAuth/generatedSchema.ts @@ -28,7 +28,7 @@ export const tables = { userId: v.optional(v.union(v.null(), v.string())), foo: v.optional(v.union(v.null(), v.string())), }) - .index("email_name", ["email","name"]) + .index("email_name", ["email", "name"]) .index("name", ["name"]) .index("userId", ["userId"]) .index("username", ["username"]), @@ -42,10 +42,11 @@ export const tables = { userId: v.string(), }) .index("expiresAt", ["expiresAt"]) - .index("expiresAt_userId", ["expiresAt","userId"]) + .index("expiresAt_userId", ["expiresAt", "userId"]) .index("token", ["token"]) .index("userId", ["userId"]), account: defineTable({ + issuer: v.string(), accountId: v.string(), providerId: v.string(), userId: v.string(), @@ -59,9 +60,10 @@ export const tables = { createdAt: v.number(), updatedAt: v.number(), }) + .index("accountId_issuer", ["accountId", "issuer"]) .index("accountId", ["accountId"]) - .index("accountId_providerId", ["accountId","providerId"]) - .index("providerId_userId", ["providerId","userId"]) + .index("accountId_providerId", ["accountId", "providerId"]) + .index("providerId_userId", ["providerId", "userId"]) .index("userId", ["userId"]), verification: defineTable({ identifier: v.string(), @@ -77,13 +79,16 @@ export const tables = { backupCodes: v.string(), userId: v.string(), verified: v.optional(v.union(v.null(), v.boolean())), - }) - .index("userId", ["userId"]), + failedVerificationCount: v.optional(v.union(v.null(), v.number())), + lockedUntil: v.optional(v.union(v.null(), v.number())), + }).index("userId", ["userId"]), jwks: defineTable({ publicKey: v.string(), privateKey: v.string(), createdAt: v.number(), expiresAt: v.optional(v.union(v.null(), v.number())), + alg: v.optional(v.union(v.null(), v.string())), + crv: v.optional(v.union(v.null(), v.string())), }), }; diff --git a/examples/next/lib/auth-client.tsx b/examples/next/lib/auth-client.tsx index 6f5ca766..00128b37 100644 --- a/examples/next/lib/auth-client.tsx +++ b/examples/next/lib/auth-client.tsx @@ -4,7 +4,6 @@ import { twoFactorClient, magicLinkClient, emailOTPClient, - genericOAuthClient, anonymousClient, inferAdditionalFields, } from "better-auth/client/plugins"; @@ -24,7 +23,6 @@ export const authClient = createAuthClient({ magicLinkClient(), emailOTPClient(), twoFactorClient(), - genericOAuthClient(), convexClient(), ], }); diff --git a/examples/next/package-lock.json b/examples/next/package-lock.json index 531829d1..d87138e0 100644 --- a/examples/next/package-lock.json +++ b/examples/next/package-lock.json @@ -49,7 +49,7 @@ }, "../..": { "name": "@convex-dev/better-auth", - "version": "0.12.3", + "version": "0.12.5", "license": "Apache-2.0", "dependencies": { "@better-fetch/fetch": "^1.1.18", @@ -62,8 +62,8 @@ "zod": "^4.0.0" }, "devDependencies": { - "@better-auth/core": "1.6.15", - "@better-auth/test-utils": "1.6.15", + "@better-auth/core": "1.7.1", + "@better-auth/test-utils": "1.7.1", "@convex-dev/eslint-plugin": "2.0.0", "@edge-runtime/vm": "5.0.0", "@eslint/eslintrc": "3.3.5", @@ -74,7 +74,7 @@ "@types/react": "19.2.14", "@types/react-dom": "19.2.3", "@types/semver": "7.7.1", - "better-auth": "1.6.15", + "better-auth": "1.7.1", "chokidar-cli": "3.0.0", "concurrently": "10.0.3", "convex": "1.41.0", @@ -92,34 +92,34 @@ "react-dom": "19.2.6", "typescript": "5.9.3", "typescript-eslint": "8.59.2", - "vitest": "4.1.5" + "vitest": "4.1.10" }, "peerDependencies": { - "better-auth": ">=1.6.11 <1.7.0", + "better-auth": ">=1.7.1 <1.8.0", "convex": "^1.25.0", "react": "^18.3.1 || ^19.0.0" } }, "../../node_modules/better-auth": { - "version": "1.6.15", - "license": "MIT", - "dependencies": { - "@better-auth/core": "1.6.15", - "@better-auth/drizzle-adapter": "1.6.15", - "@better-auth/kysely-adapter": "1.6.15", - "@better-auth/memory-adapter": "1.6.15", - "@better-auth/mongo-adapter": "1.6.15", - "@better-auth/prisma-adapter": "1.6.15", - "@better-auth/telemetry": "1.6.15", - "@better-auth/utils": "0.4.1", - "@better-fetch/fetch": "1.1.21", - "@noble/ciphers": "^2.1.1", - "@noble/hashes": "^2.0.1", - "better-call": "1.3.5", + "version": "1.7.1", + "license": "MIT", + "dependencies": { + "@better-auth/core": "1.7.1", + "@better-auth/drizzle-adapter": "1.7.1", + "@better-auth/kysely-adapter": "1.7.1", + "@better-auth/memory-adapter": "1.7.1", + "@better-auth/mongo-adapter": "1.7.1", + "@better-auth/prisma-adapter": "1.7.1", + "@better-auth/telemetry": "1.7.1", + "@better-auth/utils": "0.4.2", + "@better-fetch/fetch": "1.3.1", + "@noble/ciphers": "^2.2.0", + "@noble/hashes": "^2.2.0", + "better-call": "1.4.0", "defu": "^6.1.4", - "jose": "^6.1.3", + "jose": "^6.2.3", "kysely": "^0.28.17 || ^0.29.0", - "nanostores": "^1.1.1", + "nanostores": "^1.3.0", "zod": "^4.3.6" }, "peerDependencies": { @@ -129,8 +129,8 @@ "@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", diff --git a/examples/react/package-lock.json b/examples/react/package-lock.json index 5851edc8..c4ae80ab 100644 --- a/examples/react/package-lock.json +++ b/examples/react/package-lock.json @@ -48,7 +48,7 @@ }, "../..": { "name": "@convex-dev/better-auth", - "version": "0.12.3", + "version": "0.12.5", "license": "Apache-2.0", "dependencies": { "@better-fetch/fetch": "^1.1.18", @@ -61,8 +61,8 @@ "zod": "^4.0.0" }, "devDependencies": { - "@better-auth/core": "1.6.15", - "@better-auth/test-utils": "1.6.15", + "@better-auth/core": "1.7.1", + "@better-auth/test-utils": "1.7.1", "@convex-dev/eslint-plugin": "2.0.0", "@edge-runtime/vm": "5.0.0", "@eslint/eslintrc": "3.3.5", @@ -73,7 +73,7 @@ "@types/react": "19.2.14", "@types/react-dom": "19.2.3", "@types/semver": "7.7.1", - "better-auth": "1.6.15", + "better-auth": "1.7.1", "chokidar-cli": "3.0.0", "concurrently": "10.0.3", "convex": "1.41.0", @@ -91,34 +91,34 @@ "react-dom": "19.2.6", "typescript": "5.9.3", "typescript-eslint": "8.59.2", - "vitest": "4.1.5" + "vitest": "4.1.10" }, "peerDependencies": { - "better-auth": ">=1.6.11 <1.7.0", + "better-auth": ">=1.7.1 <1.8.0", "convex": "^1.25.0", "react": "^18.3.1 || ^19.0.0" } }, "../../node_modules/better-auth": { - "version": "1.6.15", - "license": "MIT", - "dependencies": { - "@better-auth/core": "1.6.15", - "@better-auth/drizzle-adapter": "1.6.15", - "@better-auth/kysely-adapter": "1.6.15", - "@better-auth/memory-adapter": "1.6.15", - "@better-auth/mongo-adapter": "1.6.15", - "@better-auth/prisma-adapter": "1.6.15", - "@better-auth/telemetry": "1.6.15", - "@better-auth/utils": "0.4.1", - "@better-fetch/fetch": "1.1.21", - "@noble/ciphers": "^2.1.1", - "@noble/hashes": "^2.0.1", - "better-call": "1.3.5", + "version": "1.7.1", + "license": "MIT", + "dependencies": { + "@better-auth/core": "1.7.1", + "@better-auth/drizzle-adapter": "1.7.1", + "@better-auth/kysely-adapter": "1.7.1", + "@better-auth/memory-adapter": "1.7.1", + "@better-auth/mongo-adapter": "1.7.1", + "@better-auth/prisma-adapter": "1.7.1", + "@better-auth/telemetry": "1.7.1", + "@better-auth/utils": "0.4.2", + "@better-fetch/fetch": "1.3.1", + "@noble/ciphers": "^2.2.0", + "@noble/hashes": "^2.2.0", + "better-call": "1.4.0", "defu": "^6.1.4", - "jose": "^6.1.3", + "jose": "^6.2.3", "kysely": "^0.28.17 || ^0.29.0", - "nanostores": "^1.1.1", + "nanostores": "^1.3.0", "zod": "^4.3.6" }, "peerDependencies": { @@ -128,8 +128,8 @@ "@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", diff --git a/examples/tanstack/convex/betterAuth/_generated/component.ts b/examples/tanstack/convex/betterAuth/_generated/component.ts index 14a14be6..835c4644 100644 --- a/examples/tanstack/convex/betterAuth/_generated/component.ts +++ b/examples/tanstack/convex/betterAuth/_generated/component.ts @@ -8,7 +8,7 @@ * @module */ -import type { FunctionReference } from "convex/server"; +import type { FunctionReference } from 'convex/server' /** * A utility for referencing a Convex component's exposed API. @@ -25,1184 +25,1509 @@ export type ComponentApi = { adapter: { create: FunctionReference< - "mutation", - "internal", + 'mutation', + 'internal', { input: | { data: { - createdAt: number; - email: string; - emailVerified: boolean; - foo?: null | string; - image?: null | string; - isAnonymous?: null | boolean; - name: string; - twoFactorEnabled?: null | boolean; - updatedAt: number; - userId?: null | string; - }; - model: "user"; + createdAt: number + email: string + emailVerified: boolean + foo?: null | string + image?: null | string + isAnonymous?: null | boolean + name: string + twoFactorEnabled?: null | boolean + updatedAt: number + userId?: null | string + } + model: 'user' } | { data: { - createdAt: number; - expiresAt: number; - ipAddress?: null | string; - token: string; - updatedAt: number; - userAgent?: null | string; - userId: string; - }; - model: "session"; + createdAt: number + expiresAt: number + ipAddress?: null | string + token: string + updatedAt: number + userAgent?: null | string + userId: string + } + model: 'session' } | { data: { - accessToken?: null | string; - accessTokenExpiresAt?: null | number; - accountId: string; - createdAt: number; - idToken?: null | string; - password?: null | string; - providerId: string; - refreshToken?: null | string; - refreshTokenExpiresAt?: null | number; - scope?: null | string; - updatedAt: number; - userId: string; - }; - model: "account"; + accessToken?: null | string + accessTokenExpiresAt?: null | number + accountId: string + createdAt: number + idToken?: null | string + issuer: string + password?: null | string + providerId: string + refreshToken?: null | string + refreshTokenExpiresAt?: null | number + scope?: null | string + updatedAt: number + userId: string + } + model: 'account' } | { data: { - createdAt: number; - expiresAt: number; - identifier: string; - updatedAt: number; - value: string; - }; - model: "verification"; + createdAt: number + expiresAt: number + identifier: string + updatedAt: number + value: string + } + model: 'verification' } | { data: { - backupCodes: string; - secret: string; - userId: string; - verified?: null | boolean; - }; - model: "twoFactor"; + backupCodes: string + failedVerificationCount?: null | number + lockedUntil?: null | number + secret: string + userId: string + verified?: null | boolean + } + model: 'twoFactor' } | { data: { - createdAt: number; - expiresAt?: null | number; - privateKey: string; - publicKey: string; - }; - model: "jwks"; - }; - onCreateHandle?: string; - select?: Array; + alg?: null | string + createdAt: number + crv?: null | string + expiresAt?: null | number + privateKey: string + publicKey: string + } + model: 'jwks' + } + onCreateHandle?: string + select?: Array }, any, Name - >; + > deleteMany: FunctionReference< - "mutation", - "internal", + 'mutation', + 'internal', { input: | { - model: "user"; + model: 'user' where?: Array<{ - connector?: "AND" | "OR"; + connector?: 'AND' | 'OR' field: - | "name" - | "email" - | "emailVerified" - | "image" - | "createdAt" - | "updatedAt" - | "twoFactorEnabled" - | "isAnonymous" - | "userId" - | "foo" - | "_id"; - mode?: "sensitive" | "insensitive"; + | 'name' + | 'email' + | 'emailVerified' + | 'image' + | 'createdAt' + | 'updatedAt' + | 'twoFactorEnabled' + | 'isAnonymous' + | 'userId' + | 'foo' + | '_id' + mode?: 'sensitive' | 'insensitive' operator?: - | "lt" - | "lte" - | "gt" - | "gte" - | "eq" - | "in" - | "not_in" - | "ne" - | "contains" - | "starts_with" - | "ends_with"; + | 'lt' + | 'lte' + | 'gt' + | 'gte' + | 'eq' + | 'in' + | 'not_in' + | 'ne' + | 'contains' + | 'starts_with' + | 'ends_with' value: | string | number | boolean | Array | Array - | null; - }>; + | null + }> } | { - model: "session"; + model: 'session' where?: Array<{ - connector?: "AND" | "OR"; + connector?: 'AND' | 'OR' field: - | "expiresAt" - | "token" - | "createdAt" - | "updatedAt" - | "ipAddress" - | "userAgent" - | "userId" - | "_id"; - mode?: "sensitive" | "insensitive"; + | 'expiresAt' + | 'token' + | 'createdAt' + | 'updatedAt' + | 'ipAddress' + | 'userAgent' + | 'userId' + | '_id' + mode?: 'sensitive' | 'insensitive' operator?: - | "lt" - | "lte" - | "gt" - | "gte" - | "eq" - | "in" - | "not_in" - | "ne" - | "contains" - | "starts_with" - | "ends_with"; + | 'lt' + | 'lte' + | 'gt' + | 'gte' + | 'eq' + | 'in' + | 'not_in' + | 'ne' + | 'contains' + | 'starts_with' + | 'ends_with' value: | string | number | boolean | Array | Array - | null; - }>; + | null + }> } | { - model: "account"; + model: 'account' where?: Array<{ - connector?: "AND" | "OR"; + connector?: 'AND' | 'OR' field: - | "accountId" - | "providerId" - | "userId" - | "accessToken" - | "refreshToken" - | "idToken" - | "accessTokenExpiresAt" - | "refreshTokenExpiresAt" - | "scope" - | "password" - | "createdAt" - | "updatedAt" - | "_id"; - mode?: "sensitive" | "insensitive"; + | 'issuer' + | 'accountId' + | 'providerId' + | 'userId' + | 'accessToken' + | 'refreshToken' + | 'idToken' + | 'accessTokenExpiresAt' + | 'refreshTokenExpiresAt' + | 'scope' + | 'password' + | 'createdAt' + | 'updatedAt' + | '_id' + mode?: 'sensitive' | 'insensitive' operator?: - | "lt" - | "lte" - | "gt" - | "gte" - | "eq" - | "in" - | "not_in" - | "ne" - | "contains" - | "starts_with" - | "ends_with"; + | 'lt' + | 'lte' + | 'gt' + | 'gte' + | 'eq' + | 'in' + | 'not_in' + | 'ne' + | 'contains' + | 'starts_with' + | 'ends_with' value: | string | number | boolean | Array | Array - | null; - }>; + | null + }> } | { - model: "verification"; + model: 'verification' where?: Array<{ - connector?: "AND" | "OR"; + connector?: 'AND' | 'OR' field: - | "identifier" - | "value" - | "expiresAt" - | "createdAt" - | "updatedAt" - | "_id"; - mode?: "sensitive" | "insensitive"; + | 'identifier' + | 'value' + | 'expiresAt' + | 'createdAt' + | 'updatedAt' + | '_id' + mode?: 'sensitive' | 'insensitive' operator?: - | "lt" - | "lte" - | "gt" - | "gte" - | "eq" - | "in" - | "not_in" - | "ne" - | "contains" - | "starts_with" - | "ends_with"; + | 'lt' + | 'lte' + | 'gt' + | 'gte' + | 'eq' + | 'in' + | 'not_in' + | 'ne' + | 'contains' + | 'starts_with' + | 'ends_with' value: | string | number | boolean | Array | Array - | null; - }>; + | null + }> } | { - model: "twoFactor"; + model: 'twoFactor' where?: Array<{ - connector?: "AND" | "OR"; + connector?: 'AND' | 'OR' field: - | "secret" - | "backupCodes" - | "userId" - | "verified" - | "_id"; - mode?: "sensitive" | "insensitive"; + | 'secret' + | 'backupCodes' + | 'userId' + | 'verified' + | 'failedVerificationCount' + | 'lockedUntil' + | '_id' + mode?: 'sensitive' | 'insensitive' operator?: - | "lt" - | "lte" - | "gt" - | "gte" - | "eq" - | "in" - | "not_in" - | "ne" - | "contains" - | "starts_with" - | "ends_with"; + | 'lt' + | 'lte' + | 'gt' + | 'gte' + | 'eq' + | 'in' + | 'not_in' + | 'ne' + | 'contains' + | 'starts_with' + | 'ends_with' value: | string | number | boolean | Array | Array - | null; - }>; + | null + }> } | { - model: "jwks"; + model: 'jwks' where?: Array<{ - connector?: "AND" | "OR"; + connector?: 'AND' | 'OR' field: - | "publicKey" - | "privateKey" - | "createdAt" - | "expiresAt" - | "_id"; - mode?: "sensitive" | "insensitive"; + | 'publicKey' + | 'privateKey' + | 'createdAt' + | 'expiresAt' + | 'alg' + | 'crv' + | '_id' + mode?: 'sensitive' | 'insensitive' operator?: - | "lt" - | "lte" - | "gt" - | "gte" - | "eq" - | "in" - | "not_in" - | "ne" - | "contains" - | "starts_with" - | "ends_with"; + | 'lt' + | 'lte' + | 'gt' + | 'gte' + | 'eq' + | 'in' + | 'not_in' + | 'ne' + | 'contains' + | 'starts_with' + | 'ends_with' value: | string | number | boolean | Array | Array - | null; - }>; - }; - onDeleteHandle?: string; + | null + }> + } + onDeleteHandle?: string paginationOpts: { - cursor: string | null; - endCursor?: string | null; - id?: number; - maximumBytesRead?: number; - maximumRowsRead?: number; - numItems: number; - }; + cursor: string | null + endCursor?: string | null + id?: number + maximumBytesRead?: number + maximumRowsRead?: number + numItems: number + } }, any, Name - >; + > deleteOne: FunctionReference< - "mutation", - "internal", + 'mutation', + 'internal', { input: | { - model: "user"; + model: 'user' where?: Array<{ - connector?: "AND" | "OR"; + connector?: 'AND' | 'OR' field: - | "name" - | "email" - | "emailVerified" - | "image" - | "createdAt" - | "updatedAt" - | "twoFactorEnabled" - | "isAnonymous" - | "userId" - | "foo" - | "_id"; - mode?: "sensitive" | "insensitive"; + | 'name' + | 'email' + | 'emailVerified' + | 'image' + | 'createdAt' + | 'updatedAt' + | 'twoFactorEnabled' + | 'isAnonymous' + | 'userId' + | 'foo' + | '_id' + mode?: 'sensitive' | 'insensitive' operator?: - | "lt" - | "lte" - | "gt" - | "gte" - | "eq" - | "in" - | "not_in" - | "ne" - | "contains" - | "starts_with" - | "ends_with"; + | 'lt' + | 'lte' + | 'gt' + | 'gte' + | 'eq' + | 'in' + | 'not_in' + | 'ne' + | 'contains' + | 'starts_with' + | 'ends_with' value: | string | number | boolean | Array | Array - | null; - }>; + | null + }> } | { - model: "session"; + model: 'session' where?: Array<{ - connector?: "AND" | "OR"; + connector?: 'AND' | 'OR' field: - | "expiresAt" - | "token" - | "createdAt" - | "updatedAt" - | "ipAddress" - | "userAgent" - | "userId" - | "_id"; - mode?: "sensitive" | "insensitive"; + | 'expiresAt' + | 'token' + | 'createdAt' + | 'updatedAt' + | 'ipAddress' + | 'userAgent' + | 'userId' + | '_id' + mode?: 'sensitive' | 'insensitive' operator?: - | "lt" - | "lte" - | "gt" - | "gte" - | "eq" - | "in" - | "not_in" - | "ne" - | "contains" - | "starts_with" - | "ends_with"; + | 'lt' + | 'lte' + | 'gt' + | 'gte' + | 'eq' + | 'in' + | 'not_in' + | 'ne' + | 'contains' + | 'starts_with' + | 'ends_with' value: | string | number | boolean | Array | Array - | null; - }>; + | null + }> } | { - model: "account"; + model: 'account' where?: Array<{ - connector?: "AND" | "OR"; + connector?: 'AND' | 'OR' field: - | "accountId" - | "providerId" - | "userId" - | "accessToken" - | "refreshToken" - | "idToken" - | "accessTokenExpiresAt" - | "refreshTokenExpiresAt" - | "scope" - | "password" - | "createdAt" - | "updatedAt" - | "_id"; - mode?: "sensitive" | "insensitive"; + | 'issuer' + | 'accountId' + | 'providerId' + | 'userId' + | 'accessToken' + | 'refreshToken' + | 'idToken' + | 'accessTokenExpiresAt' + | 'refreshTokenExpiresAt' + | 'scope' + | 'password' + | 'createdAt' + | 'updatedAt' + | '_id' + mode?: 'sensitive' | 'insensitive' operator?: - | "lt" - | "lte" - | "gt" - | "gte" - | "eq" - | "in" - | "not_in" - | "ne" - | "contains" - | "starts_with" - | "ends_with"; + | 'lt' + | 'lte' + | 'gt' + | 'gte' + | 'eq' + | 'in' + | 'not_in' + | 'ne' + | 'contains' + | 'starts_with' + | 'ends_with' value: | string | number | boolean | Array | Array - | null; - }>; + | null + }> } | { - model: "verification"; + model: 'verification' where?: Array<{ - connector?: "AND" | "OR"; + connector?: 'AND' | 'OR' field: - | "identifier" - | "value" - | "expiresAt" - | "createdAt" - | "updatedAt" - | "_id"; - mode?: "sensitive" | "insensitive"; + | 'identifier' + | 'value' + | 'expiresAt' + | 'createdAt' + | 'updatedAt' + | '_id' + mode?: 'sensitive' | 'insensitive' operator?: - | "lt" - | "lte" - | "gt" - | "gte" - | "eq" - | "in" - | "not_in" - | "ne" - | "contains" - | "starts_with" - | "ends_with"; + | 'lt' + | 'lte' + | 'gt' + | 'gte' + | 'eq' + | 'in' + | 'not_in' + | 'ne' + | 'contains' + | 'starts_with' + | 'ends_with' value: | string | number | boolean | Array | Array - | null; - }>; + | null + }> } | { - model: "twoFactor"; + model: 'twoFactor' where?: Array<{ - connector?: "AND" | "OR"; + connector?: 'AND' | 'OR' field: - | "secret" - | "backupCodes" - | "userId" - | "verified" - | "_id"; - mode?: "sensitive" | "insensitive"; + | 'secret' + | 'backupCodes' + | 'userId' + | 'verified' + | 'failedVerificationCount' + | 'lockedUntil' + | '_id' + mode?: 'sensitive' | 'insensitive' operator?: - | "lt" - | "lte" - | "gt" - | "gte" - | "eq" - | "in" - | "not_in" - | "ne" - | "contains" - | "starts_with" - | "ends_with"; + | 'lt' + | 'lte' + | 'gt' + | 'gte' + | 'eq' + | 'in' + | 'not_in' + | 'ne' + | 'contains' + | 'starts_with' + | 'ends_with' value: | string | number | boolean | Array | Array - | null; - }>; + | null + }> } | { - model: "jwks"; + model: 'jwks' where?: Array<{ - connector?: "AND" | "OR"; + connector?: 'AND' | 'OR' field: - | "publicKey" - | "privateKey" - | "createdAt" - | "expiresAt" - | "_id"; - mode?: "sensitive" | "insensitive"; + | 'publicKey' + | 'privateKey' + | 'createdAt' + | 'expiresAt' + | 'alg' + | 'crv' + | '_id' + mode?: 'sensitive' | 'insensitive' operator?: - | "lt" - | "lte" - | "gt" - | "gte" - | "eq" - | "in" - | "not_in" - | "ne" - | "contains" - | "starts_with" - | "ends_with"; + | 'lt' + | 'lte' + | 'gt' + | 'gte' + | 'eq' + | 'in' + | 'not_in' + | 'ne' + | 'contains' + | 'starts_with' + | 'ends_with' value: | string | number | boolean | Array | Array - | null; - }>; - }; - onDeleteHandle?: string; + | null + }> + } + onDeleteHandle?: string }, any, Name - >; + > findMany: FunctionReference< - "query", - "internal", + 'query', + 'internal', { - join?: any; - limit?: number; + join?: any + limit?: number model: - | "user" - | "session" - | "account" - | "verification" - | "twoFactor" - | "jwks"; - offset?: number; + | 'user' + | 'session' + | 'account' + | 'verification' + | 'twoFactor' + | 'jwks' + offset?: number paginationOpts: { - cursor: string | null; - endCursor?: string | null; - id?: number; - maximumBytesRead?: number; - maximumRowsRead?: number; - numItems: number; - }; - select?: Array; - sortBy?: { direction: "asc" | "desc"; field: string }; + cursor: string | null + endCursor?: string | null + id?: number + maximumBytesRead?: number + maximumRowsRead?: number + numItems: number + } + select?: Array + sortBy?: { direction: 'asc' | 'desc'; field: string } where?: Array<{ - connector?: "AND" | "OR"; - field: string; - mode?: "sensitive" | "insensitive"; + connector?: 'AND' | 'OR' + field: string + mode?: 'sensitive' | 'insensitive' operator?: - | "lt" - | "lte" - | "gt" - | "gte" - | "eq" - | "in" - | "not_in" - | "ne" - | "contains" - | "starts_with" - | "ends_with"; + | 'lt' + | 'lte' + | 'gt' + | 'gte' + | 'eq' + | 'in' + | 'not_in' + | 'ne' + | 'contains' + | 'starts_with' + | 'ends_with' value: | string | number | boolean | Array | Array - | null; - }>; + | null + }> }, any, Name - >; + > findOne: FunctionReference< - "query", - "internal", + 'query', + 'internal', { - join?: any; + join?: any model: - | "user" - | "session" - | "account" - | "verification" - | "twoFactor" - | "jwks"; - select?: Array; + | 'user' + | 'session' + | 'account' + | 'verification' + | 'twoFactor' + | 'jwks' + select?: Array where?: Array<{ - connector?: "AND" | "OR"; - field: string; - mode?: "sensitive" | "insensitive"; + connector?: 'AND' | 'OR' + field: string + mode?: 'sensitive' | 'insensitive' operator?: - | "lt" - | "lte" - | "gt" - | "gte" - | "eq" - | "in" - | "not_in" - | "ne" - | "contains" - | "starts_with" - | "ends_with"; + | 'lt' + | 'lte' + | 'gt' + | 'gte' + | 'eq' + | 'in' + | 'not_in' + | 'ne' + | 'contains' + | 'starts_with' + | 'ends_with' value: | string | number | boolean | Array | Array - | null; - }>; + | null + }> + }, + any, + Name + > + incrementOne: FunctionReference< + 'mutation', + 'internal', + { + input: + | { + increment: Record + model: 'user' + set?: { + createdAt?: number + email?: string + emailVerified?: boolean + foo?: null | string + image?: null | string + isAnonymous?: null | boolean + name?: string + twoFactorEnabled?: null | boolean + updatedAt?: number + userId?: null | string + } + where: Array<{ + connector?: 'AND' | 'OR' + field: + | 'name' + | 'email' + | 'emailVerified' + | 'image' + | 'createdAt' + | 'updatedAt' + | 'twoFactorEnabled' + | 'isAnonymous' + | 'userId' + | 'foo' + | '_id' + mode?: 'sensitive' | 'insensitive' + operator?: + | 'lt' + | 'lte' + | 'gt' + | 'gte' + | 'eq' + | 'in' + | 'not_in' + | 'ne' + | 'contains' + | 'starts_with' + | 'ends_with' + value: + | string + | number + | boolean + | Array + | Array + | null + }> + } + | { + increment: Record + model: 'session' + set?: { + createdAt?: number + expiresAt?: number + ipAddress?: null | string + token?: string + updatedAt?: number + userAgent?: null | string + userId?: string + } + where: Array<{ + connector?: 'AND' | 'OR' + field: + | 'expiresAt' + | 'token' + | 'createdAt' + | 'updatedAt' + | 'ipAddress' + | 'userAgent' + | 'userId' + | '_id' + mode?: 'sensitive' | 'insensitive' + operator?: + | 'lt' + | 'lte' + | 'gt' + | 'gte' + | 'eq' + | 'in' + | 'not_in' + | 'ne' + | 'contains' + | 'starts_with' + | 'ends_with' + value: + | string + | number + | boolean + | Array + | Array + | null + }> + } + | { + increment: Record + model: 'account' + set?: { + accessToken?: null | string + accessTokenExpiresAt?: null | number + accountId?: string + createdAt?: number + idToken?: null | string + issuer?: string + password?: null | string + providerId?: string + refreshToken?: null | string + refreshTokenExpiresAt?: null | number + scope?: null | string + updatedAt?: number + userId?: string + } + where: Array<{ + connector?: 'AND' | 'OR' + field: + | 'issuer' + | 'accountId' + | 'providerId' + | 'userId' + | 'accessToken' + | 'refreshToken' + | 'idToken' + | 'accessTokenExpiresAt' + | 'refreshTokenExpiresAt' + | 'scope' + | 'password' + | 'createdAt' + | 'updatedAt' + | '_id' + mode?: 'sensitive' | 'insensitive' + operator?: + | 'lt' + | 'lte' + | 'gt' + | 'gte' + | 'eq' + | 'in' + | 'not_in' + | 'ne' + | 'contains' + | 'starts_with' + | 'ends_with' + value: + | string + | number + | boolean + | Array + | Array + | null + }> + } + | { + increment: Record + model: 'verification' + set?: { + createdAt?: number + expiresAt?: number + identifier?: string + updatedAt?: number + value?: string + } + where: Array<{ + connector?: 'AND' | 'OR' + field: + | 'identifier' + | 'value' + | 'expiresAt' + | 'createdAt' + | 'updatedAt' + | '_id' + mode?: 'sensitive' | 'insensitive' + operator?: + | 'lt' + | 'lte' + | 'gt' + | 'gte' + | 'eq' + | 'in' + | 'not_in' + | 'ne' + | 'contains' + | 'starts_with' + | 'ends_with' + value: + | string + | number + | boolean + | Array + | Array + | null + }> + } + | { + increment: Record + model: 'twoFactor' + set?: { + backupCodes?: string + failedVerificationCount?: null | number + lockedUntil?: null | number + secret?: string + userId?: string + verified?: null | boolean + } + where: Array<{ + connector?: 'AND' | 'OR' + field: + | 'secret' + | 'backupCodes' + | 'userId' + | 'verified' + | 'failedVerificationCount' + | 'lockedUntil' + | '_id' + mode?: 'sensitive' | 'insensitive' + operator?: + | 'lt' + | 'lte' + | 'gt' + | 'gte' + | 'eq' + | 'in' + | 'not_in' + | 'ne' + | 'contains' + | 'starts_with' + | 'ends_with' + value: + | string + | number + | boolean + | Array + | Array + | null + }> + } + | { + increment: Record + model: 'jwks' + set?: { + alg?: null | string + createdAt?: number + crv?: null | string + expiresAt?: null | number + privateKey?: string + publicKey?: string + } + where: Array<{ + connector?: 'AND' | 'OR' + field: + | 'publicKey' + | 'privateKey' + | 'createdAt' + | 'expiresAt' + | 'alg' + | 'crv' + | '_id' + mode?: 'sensitive' | 'insensitive' + operator?: + | 'lt' + | 'lte' + | 'gt' + | 'gte' + | 'eq' + | 'in' + | 'not_in' + | 'ne' + | 'contains' + | 'starts_with' + | 'ends_with' + value: + | string + | number + | boolean + | Array + | Array + | null + }> + } + onUpdateHandle?: string }, any, Name - >; + > updateMany: FunctionReference< - "mutation", - "internal", + 'mutation', + 'internal', { input: | { - model: "user"; + model: 'user' update: { - createdAt?: number; - email?: string; - emailVerified?: boolean; - foo?: null | string; - image?: null | string; - isAnonymous?: null | boolean; - name?: string; - twoFactorEnabled?: null | boolean; - updatedAt?: number; - userId?: null | string; - }; + createdAt?: number + email?: string + emailVerified?: boolean + foo?: null | string + image?: null | string + isAnonymous?: null | boolean + name?: string + twoFactorEnabled?: null | boolean + updatedAt?: number + userId?: null | string + } where?: Array<{ - connector?: "AND" | "OR"; + connector?: 'AND' | 'OR' field: - | "name" - | "email" - | "emailVerified" - | "image" - | "createdAt" - | "updatedAt" - | "twoFactorEnabled" - | "isAnonymous" - | "userId" - | "foo" - | "_id"; - mode?: "sensitive" | "insensitive"; + | 'name' + | 'email' + | 'emailVerified' + | 'image' + | 'createdAt' + | 'updatedAt' + | 'twoFactorEnabled' + | 'isAnonymous' + | 'userId' + | 'foo' + | '_id' + mode?: 'sensitive' | 'insensitive' operator?: - | "lt" - | "lte" - | "gt" - | "gte" - | "eq" - | "in" - | "not_in" - | "ne" - | "contains" - | "starts_with" - | "ends_with"; + | 'lt' + | 'lte' + | 'gt' + | 'gte' + | 'eq' + | 'in' + | 'not_in' + | 'ne' + | 'contains' + | 'starts_with' + | 'ends_with' value: | string | number | boolean | Array | Array - | null; - }>; + | null + }> } | { - model: "session"; + model: 'session' update: { - createdAt?: number; - expiresAt?: number; - ipAddress?: null | string; - token?: string; - updatedAt?: number; - userAgent?: null | string; - userId?: string; - }; + createdAt?: number + expiresAt?: number + ipAddress?: null | string + token?: string + updatedAt?: number + userAgent?: null | string + userId?: string + } where?: Array<{ - connector?: "AND" | "OR"; + connector?: 'AND' | 'OR' field: - | "expiresAt" - | "token" - | "createdAt" - | "updatedAt" - | "ipAddress" - | "userAgent" - | "userId" - | "_id"; - mode?: "sensitive" | "insensitive"; + | 'expiresAt' + | 'token' + | 'createdAt' + | 'updatedAt' + | 'ipAddress' + | 'userAgent' + | 'userId' + | '_id' + mode?: 'sensitive' | 'insensitive' operator?: - | "lt" - | "lte" - | "gt" - | "gte" - | "eq" - | "in" - | "not_in" - | "ne" - | "contains" - | "starts_with" - | "ends_with"; + | 'lt' + | 'lte' + | 'gt' + | 'gte' + | 'eq' + | 'in' + | 'not_in' + | 'ne' + | 'contains' + | 'starts_with' + | 'ends_with' value: | string | number | boolean | Array | Array - | null; - }>; + | null + }> } | { - model: "account"; + model: 'account' update: { - accessToken?: null | string; - accessTokenExpiresAt?: null | number; - accountId?: string; - createdAt?: number; - idToken?: null | string; - password?: null | string; - providerId?: string; - refreshToken?: null | string; - refreshTokenExpiresAt?: null | number; - scope?: null | string; - updatedAt?: number; - userId?: string; - }; + accessToken?: null | string + accessTokenExpiresAt?: null | number + accountId?: string + createdAt?: number + idToken?: null | string + issuer?: string + password?: null | string + providerId?: string + refreshToken?: null | string + refreshTokenExpiresAt?: null | number + scope?: null | string + updatedAt?: number + userId?: string + } where?: Array<{ - connector?: "AND" | "OR"; + connector?: 'AND' | 'OR' field: - | "accountId" - | "providerId" - | "userId" - | "accessToken" - | "refreshToken" - | "idToken" - | "accessTokenExpiresAt" - | "refreshTokenExpiresAt" - | "scope" - | "password" - | "createdAt" - | "updatedAt" - | "_id"; - mode?: "sensitive" | "insensitive"; + | 'issuer' + | 'accountId' + | 'providerId' + | 'userId' + | 'accessToken' + | 'refreshToken' + | 'idToken' + | 'accessTokenExpiresAt' + | 'refreshTokenExpiresAt' + | 'scope' + | 'password' + | 'createdAt' + | 'updatedAt' + | '_id' + mode?: 'sensitive' | 'insensitive' operator?: - | "lt" - | "lte" - | "gt" - | "gte" - | "eq" - | "in" - | "not_in" - | "ne" - | "contains" - | "starts_with" - | "ends_with"; + | 'lt' + | 'lte' + | 'gt' + | 'gte' + | 'eq' + | 'in' + | 'not_in' + | 'ne' + | 'contains' + | 'starts_with' + | 'ends_with' value: | string | number | boolean | Array | Array - | null; - }>; + | null + }> } | { - model: "verification"; + model: 'verification' update: { - createdAt?: number; - expiresAt?: number; - identifier?: string; - updatedAt?: number; - value?: string; - }; + createdAt?: number + expiresAt?: number + identifier?: string + updatedAt?: number + value?: string + } where?: Array<{ - connector?: "AND" | "OR"; + connector?: 'AND' | 'OR' field: - | "identifier" - | "value" - | "expiresAt" - | "createdAt" - | "updatedAt" - | "_id"; - mode?: "sensitive" | "insensitive"; + | 'identifier' + | 'value' + | 'expiresAt' + | 'createdAt' + | 'updatedAt' + | '_id' + mode?: 'sensitive' | 'insensitive' operator?: - | "lt" - | "lte" - | "gt" - | "gte" - | "eq" - | "in" - | "not_in" - | "ne" - | "contains" - | "starts_with" - | "ends_with"; + | 'lt' + | 'lte' + | 'gt' + | 'gte' + | 'eq' + | 'in' + | 'not_in' + | 'ne' + | 'contains' + | 'starts_with' + | 'ends_with' value: | string | number | boolean | Array | Array - | null; - }>; + | null + }> } | { - model: "twoFactor"; + model: 'twoFactor' update: { - backupCodes?: string; - secret?: string; - userId?: string; - verified?: null | boolean; - }; + backupCodes?: string + failedVerificationCount?: null | number + lockedUntil?: null | number + secret?: string + userId?: string + verified?: null | boolean + } where?: Array<{ - connector?: "AND" | "OR"; + connector?: 'AND' | 'OR' field: - | "secret" - | "backupCodes" - | "userId" - | "verified" - | "_id"; - mode?: "sensitive" | "insensitive"; + | 'secret' + | 'backupCodes' + | 'userId' + | 'verified' + | 'failedVerificationCount' + | 'lockedUntil' + | '_id' + mode?: 'sensitive' | 'insensitive' operator?: - | "lt" - | "lte" - | "gt" - | "gte" - | "eq" - | "in" - | "not_in" - | "ne" - | "contains" - | "starts_with" - | "ends_with"; + | 'lt' + | 'lte' + | 'gt' + | 'gte' + | 'eq' + | 'in' + | 'not_in' + | 'ne' + | 'contains' + | 'starts_with' + | 'ends_with' value: | string | number | boolean | Array | Array - | null; - }>; + | null + }> } | { - model: "jwks"; + model: 'jwks' update: { - createdAt?: number; - expiresAt?: null | number; - privateKey?: string; - publicKey?: string; - }; + alg?: null | string + createdAt?: number + crv?: null | string + expiresAt?: null | number + privateKey?: string + publicKey?: string + } where?: Array<{ - connector?: "AND" | "OR"; + connector?: 'AND' | 'OR' field: - | "publicKey" - | "privateKey" - | "createdAt" - | "expiresAt" - | "_id"; - mode?: "sensitive" | "insensitive"; + | 'publicKey' + | 'privateKey' + | 'createdAt' + | 'expiresAt' + | 'alg' + | 'crv' + | '_id' + mode?: 'sensitive' | 'insensitive' operator?: - | "lt" - | "lte" - | "gt" - | "gte" - | "eq" - | "in" - | "not_in" - | "ne" - | "contains" - | "starts_with" - | "ends_with"; + | 'lt' + | 'lte' + | 'gt' + | 'gte' + | 'eq' + | 'in' + | 'not_in' + | 'ne' + | 'contains' + | 'starts_with' + | 'ends_with' value: | string | number | boolean | Array | Array - | null; - }>; - }; - onUpdateHandle?: string; + | null + }> + } + onUpdateHandle?: string paginationOpts: { - cursor: string | null; - endCursor?: string | null; - id?: number; - maximumBytesRead?: number; - maximumRowsRead?: number; - numItems: number; - }; + cursor: string | null + endCursor?: string | null + id?: number + maximumBytesRead?: number + maximumRowsRead?: number + numItems: number + } }, any, Name - >; + > updateOne: FunctionReference< - "mutation", - "internal", + 'mutation', + 'internal', { input: | { - model: "user"; + model: 'user' update: { - createdAt?: number; - email?: string; - emailVerified?: boolean; - foo?: null | string; - image?: null | string; - isAnonymous?: null | boolean; - name?: string; - twoFactorEnabled?: null | boolean; - updatedAt?: number; - userId?: null | string; - }; + createdAt?: number + email?: string + emailVerified?: boolean + foo?: null | string + image?: null | string + isAnonymous?: null | boolean + name?: string + twoFactorEnabled?: null | boolean + updatedAt?: number + userId?: null | string + } where?: Array<{ - connector?: "AND" | "OR"; + connector?: 'AND' | 'OR' field: - | "name" - | "email" - | "emailVerified" - | "image" - | "createdAt" - | "updatedAt" - | "twoFactorEnabled" - | "isAnonymous" - | "userId" - | "foo" - | "_id"; - mode?: "sensitive" | "insensitive"; + | 'name' + | 'email' + | 'emailVerified' + | 'image' + | 'createdAt' + | 'updatedAt' + | 'twoFactorEnabled' + | 'isAnonymous' + | 'userId' + | 'foo' + | '_id' + mode?: 'sensitive' | 'insensitive' operator?: - | "lt" - | "lte" - | "gt" - | "gte" - | "eq" - | "in" - | "not_in" - | "ne" - | "contains" - | "starts_with" - | "ends_with"; + | 'lt' + | 'lte' + | 'gt' + | 'gte' + | 'eq' + | 'in' + | 'not_in' + | 'ne' + | 'contains' + | 'starts_with' + | 'ends_with' value: | string | number | boolean | Array | Array - | null; - }>; + | null + }> } | { - model: "session"; + model: 'session' update: { - createdAt?: number; - expiresAt?: number; - ipAddress?: null | string; - token?: string; - updatedAt?: number; - userAgent?: null | string; - userId?: string; - }; + createdAt?: number + expiresAt?: number + ipAddress?: null | string + token?: string + updatedAt?: number + userAgent?: null | string + userId?: string + } where?: Array<{ - connector?: "AND" | "OR"; + connector?: 'AND' | 'OR' field: - | "expiresAt" - | "token" - | "createdAt" - | "updatedAt" - | "ipAddress" - | "userAgent" - | "userId" - | "_id"; - mode?: "sensitive" | "insensitive"; + | 'expiresAt' + | 'token' + | 'createdAt' + | 'updatedAt' + | 'ipAddress' + | 'userAgent' + | 'userId' + | '_id' + mode?: 'sensitive' | 'insensitive' operator?: - | "lt" - | "lte" - | "gt" - | "gte" - | "eq" - | "in" - | "not_in" - | "ne" - | "contains" - | "starts_with" - | "ends_with"; + | 'lt' + | 'lte' + | 'gt' + | 'gte' + | 'eq' + | 'in' + | 'not_in' + | 'ne' + | 'contains' + | 'starts_with' + | 'ends_with' value: | string | number | boolean | Array | Array - | null; - }>; + | null + }> } | { - model: "account"; + model: 'account' update: { - accessToken?: null | string; - accessTokenExpiresAt?: null | number; - accountId?: string; - createdAt?: number; - idToken?: null | string; - password?: null | string; - providerId?: string; - refreshToken?: null | string; - refreshTokenExpiresAt?: null | number; - scope?: null | string; - updatedAt?: number; - userId?: string; - }; + accessToken?: null | string + accessTokenExpiresAt?: null | number + accountId?: string + createdAt?: number + idToken?: null | string + issuer?: string + password?: null | string + providerId?: string + refreshToken?: null | string + refreshTokenExpiresAt?: null | number + scope?: null | string + updatedAt?: number + userId?: string + } where?: Array<{ - connector?: "AND" | "OR"; + connector?: 'AND' | 'OR' field: - | "accountId" - | "providerId" - | "userId" - | "accessToken" - | "refreshToken" - | "idToken" - | "accessTokenExpiresAt" - | "refreshTokenExpiresAt" - | "scope" - | "password" - | "createdAt" - | "updatedAt" - | "_id"; - mode?: "sensitive" | "insensitive"; + | 'issuer' + | 'accountId' + | 'providerId' + | 'userId' + | 'accessToken' + | 'refreshToken' + | 'idToken' + | 'accessTokenExpiresAt' + | 'refreshTokenExpiresAt' + | 'scope' + | 'password' + | 'createdAt' + | 'updatedAt' + | '_id' + mode?: 'sensitive' | 'insensitive' operator?: - | "lt" - | "lte" - | "gt" - | "gte" - | "eq" - | "in" - | "not_in" - | "ne" - | "contains" - | "starts_with" - | "ends_with"; + | 'lt' + | 'lte' + | 'gt' + | 'gte' + | 'eq' + | 'in' + | 'not_in' + | 'ne' + | 'contains' + | 'starts_with' + | 'ends_with' value: | string | number | boolean | Array | Array - | null; - }>; + | null + }> } | { - model: "verification"; + model: 'verification' update: { - createdAt?: number; - expiresAt?: number; - identifier?: string; - updatedAt?: number; - value?: string; - }; + createdAt?: number + expiresAt?: number + identifier?: string + updatedAt?: number + value?: string + } where?: Array<{ - connector?: "AND" | "OR"; + connector?: 'AND' | 'OR' field: - | "identifier" - | "value" - | "expiresAt" - | "createdAt" - | "updatedAt" - | "_id"; - mode?: "sensitive" | "insensitive"; + | 'identifier' + | 'value' + | 'expiresAt' + | 'createdAt' + | 'updatedAt' + | '_id' + mode?: 'sensitive' | 'insensitive' operator?: - | "lt" - | "lte" - | "gt" - | "gte" - | "eq" - | "in" - | "not_in" - | "ne" - | "contains" - | "starts_with" - | "ends_with"; + | 'lt' + | 'lte' + | 'gt' + | 'gte' + | 'eq' + | 'in' + | 'not_in' + | 'ne' + | 'contains' + | 'starts_with' + | 'ends_with' value: | string | number | boolean | Array | Array - | null; - }>; + | null + }> } | { - model: "twoFactor"; + model: 'twoFactor' update: { - backupCodes?: string; - secret?: string; - userId?: string; - verified?: null | boolean; - }; + backupCodes?: string + failedVerificationCount?: null | number + lockedUntil?: null | number + secret?: string + userId?: string + verified?: null | boolean + } where?: Array<{ - connector?: "AND" | "OR"; + connector?: 'AND' | 'OR' field: - | "secret" - | "backupCodes" - | "userId" - | "verified" - | "_id"; - mode?: "sensitive" | "insensitive"; + | 'secret' + | 'backupCodes' + | 'userId' + | 'verified' + | 'failedVerificationCount' + | 'lockedUntil' + | '_id' + mode?: 'sensitive' | 'insensitive' operator?: - | "lt" - | "lte" - | "gt" - | "gte" - | "eq" - | "in" - | "not_in" - | "ne" - | "contains" - | "starts_with" - | "ends_with"; + | 'lt' + | 'lte' + | 'gt' + | 'gte' + | 'eq' + | 'in' + | 'not_in' + | 'ne' + | 'contains' + | 'starts_with' + | 'ends_with' value: | string | number | boolean | Array | Array - | null; - }>; + | null + }> } | { - model: "jwks"; + model: 'jwks' update: { - createdAt?: number; - expiresAt?: null | number; - privateKey?: string; - publicKey?: string; - }; + alg?: null | string + createdAt?: number + crv?: null | string + expiresAt?: null | number + privateKey?: string + publicKey?: string + } where?: Array<{ - connector?: "AND" | "OR"; + connector?: 'AND' | 'OR' field: - | "publicKey" - | "privateKey" - | "createdAt" - | "expiresAt" - | "_id"; - mode?: "sensitive" | "insensitive"; + | 'publicKey' + | 'privateKey' + | 'createdAt' + | 'expiresAt' + | 'alg' + | 'crv' + | '_id' + mode?: 'sensitive' | 'insensitive' operator?: - | "lt" - | "lte" - | "gt" - | "gte" - | "eq" - | "in" - | "not_in" - | "ne" - | "contains" - | "starts_with" - | "ends_with"; + | 'lt' + | 'lte' + | 'gt' + | 'gte' + | 'eq' + | 'in' + | 'not_in' + | 'ne' + | 'contains' + | 'starts_with' + | 'ends_with' value: | string | number | boolean | Array | Array - | null; - }>; - }; - onUpdateHandle?: string; + | null + }> + } + onUpdateHandle?: string }, any, Name - >; - }; - }; + > + } + } diff --git a/examples/tanstack/convex/betterAuth/adapter.ts b/examples/tanstack/convex/betterAuth/adapter.ts index e8f90a76..301e9e9e 100644 --- a/examples/tanstack/convex/betterAuth/adapter.ts +++ b/examples/tanstack/convex/betterAuth/adapter.ts @@ -7,6 +7,7 @@ export const { findOne, findMany, updateOne, + incrementOne, updateMany, deleteOne, deleteMany, diff --git a/examples/tanstack/convex/betterAuth/schema.ts b/examples/tanstack/convex/betterAuth/schema.ts index 2fade4aa..3f246602 100644 --- a/examples/tanstack/convex/betterAuth/schema.ts +++ b/examples/tanstack/convex/betterAuth/schema.ts @@ -3,15 +3,15 @@ * To regenerate the schema, from your project root: * * cd convex/betterAuth - * npx auth generate + * npx auth generate --output schema.ts * * To customize the schema, generate to an alternate file and import * the table definitions to your schema file. See * https://labs.convex.dev/better-auth/features/local-install#adding-custom-indexes. */ -import { defineSchema, defineTable } from "convex/server"; -import { v } from "convex/values"; +import { defineSchema, defineTable } from 'convex/server' +import { v } from 'convex/values' export const tables = { user: defineTable({ @@ -26,9 +26,9 @@ export const tables = { userId: v.optional(v.union(v.null(), v.string())), foo: v.optional(v.union(v.null(), v.string())), }) - .index("email_name", ["email","name"]) - .index("name", ["name"]) - .index("userId", ["userId"]), + .index('email_name', ['email', 'name']) + .index('name', ['name']) + .index('userId', ['userId']), session: defineTable({ expiresAt: v.number(), token: v.string(), @@ -38,11 +38,12 @@ export const tables = { userAgent: v.optional(v.union(v.null(), v.string())), userId: v.string(), }) - .index("expiresAt", ["expiresAt"]) - .index("expiresAt_userId", ["expiresAt","userId"]) - .index("token", ["token"]) - .index("userId", ["userId"]), + .index('expiresAt', ['expiresAt']) + .index('expiresAt_userId', ['expiresAt', 'userId']) + .index('token', ['token']) + .index('userId', ['userId']), account: defineTable({ + issuer: v.string(), accountId: v.string(), providerId: v.string(), userId: v.string(), @@ -56,10 +57,11 @@ export const tables = { createdAt: v.number(), updatedAt: v.number(), }) - .index("accountId", ["accountId"]) - .index("accountId_providerId", ["accountId","providerId"]) - .index("providerId_userId", ["providerId","userId"]) - .index("userId", ["userId"]), + .index('accountId_issuer', ['accountId', 'issuer']) + .index('accountId', ['accountId']) + .index('accountId_providerId', ['accountId', 'providerId']) + .index('providerId_userId', ['providerId', 'userId']) + .index('userId', ['userId']), verification: defineTable({ identifier: v.string(), value: v.string(), @@ -67,23 +69,26 @@ export const tables = { createdAt: v.number(), updatedAt: v.number(), }) - .index("expiresAt", ["expiresAt"]) - .index("identifier", ["identifier"]), + .index('expiresAt', ['expiresAt']) + .index('identifier', ['identifier']), twoFactor: defineTable({ secret: v.string(), backupCodes: v.string(), userId: v.string(), verified: v.optional(v.union(v.null(), v.boolean())), - }) - .index("userId", ["userId"]), + failedVerificationCount: v.optional(v.union(v.null(), v.number())), + lockedUntil: v.optional(v.union(v.null(), v.number())), + }).index('userId', ['userId']), jwks: defineTable({ publicKey: v.string(), privateKey: v.string(), createdAt: v.number(), expiresAt: v.optional(v.union(v.null(), v.number())), + alg: v.optional(v.union(v.null(), v.string())), + crv: v.optional(v.union(v.null(), v.string())), }), -}; +} -const schema = defineSchema(tables); +const schema = defineSchema(tables) -export default schema; +export default schema diff --git a/examples/tanstack/package-lock.json b/examples/tanstack/package-lock.json index 4da291a1..b0098662 100644 --- a/examples/tanstack/package-lock.json +++ b/examples/tanstack/package-lock.json @@ -57,7 +57,7 @@ }, "../..": { "name": "@convex-dev/better-auth", - "version": "0.12.3", + "version": "0.12.5", "license": "Apache-2.0", "dependencies": { "@better-fetch/fetch": "^1.1.18", @@ -70,8 +70,8 @@ "zod": "^4.0.0" }, "devDependencies": { - "@better-auth/core": "1.6.15", - "@better-auth/test-utils": "1.6.15", + "@better-auth/core": "1.7.1", + "@better-auth/test-utils": "1.7.1", "@convex-dev/eslint-plugin": "2.0.0", "@edge-runtime/vm": "5.0.0", "@eslint/eslintrc": "3.3.5", @@ -82,7 +82,7 @@ "@types/react": "19.2.14", "@types/react-dom": "19.2.3", "@types/semver": "7.7.1", - "better-auth": "1.6.15", + "better-auth": "1.7.1", "chokidar-cli": "3.0.0", "concurrently": "10.0.3", "convex": "1.41.0", @@ -100,34 +100,34 @@ "react-dom": "19.2.6", "typescript": "5.9.3", "typescript-eslint": "8.59.2", - "vitest": "4.1.5" + "vitest": "4.1.10" }, "peerDependencies": { - "better-auth": ">=1.6.11 <1.7.0", + "better-auth": ">=1.7.1 <1.8.0", "convex": "^1.25.0", "react": "^18.3.1 || ^19.0.0" } }, "../../node_modules/better-auth": { - "version": "1.6.15", - "license": "MIT", - "dependencies": { - "@better-auth/core": "1.6.15", - "@better-auth/drizzle-adapter": "1.6.15", - "@better-auth/kysely-adapter": "1.6.15", - "@better-auth/memory-adapter": "1.6.15", - "@better-auth/mongo-adapter": "1.6.15", - "@better-auth/prisma-adapter": "1.6.15", - "@better-auth/telemetry": "1.6.15", - "@better-auth/utils": "0.4.1", - "@better-fetch/fetch": "1.1.21", - "@noble/ciphers": "^2.1.1", - "@noble/hashes": "^2.0.1", - "better-call": "1.3.5", + "version": "1.7.1", + "license": "MIT", + "dependencies": { + "@better-auth/core": "1.7.1", + "@better-auth/drizzle-adapter": "1.7.1", + "@better-auth/kysely-adapter": "1.7.1", + "@better-auth/memory-adapter": "1.7.1", + "@better-auth/mongo-adapter": "1.7.1", + "@better-auth/prisma-adapter": "1.7.1", + "@better-auth/telemetry": "1.7.1", + "@better-auth/utils": "0.4.2", + "@better-fetch/fetch": "1.3.1", + "@noble/ciphers": "^2.2.0", + "@noble/hashes": "^2.2.0", + "better-call": "1.4.0", "defu": "^6.1.4", - "jose": "^6.1.3", + "jose": "^6.2.3", "kysely": "^0.28.17 || ^0.29.0", - "nanostores": "^1.1.1", + "nanostores": "^1.3.0", "zod": "^4.3.6" }, "peerDependencies": { @@ -137,8 +137,8 @@ "@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", diff --git a/examples/tanstack/src/components/EnableTwoFactor.tsx b/examples/tanstack/src/components/EnableTwoFactor.tsx index 5f837a5d..22a62cf0 100644 --- a/examples/tanstack/src/components/EnableTwoFactor.tsx +++ b/examples/tanstack/src/components/EnableTwoFactor.tsx @@ -46,7 +46,7 @@ export default function EnableTwoFactor({ onBack }: { onBack: () => void }) { const { data } = await authClient.twoFactor.enable({ password, }) - if (data?.totpURI) { + if (data?.method === 'totp') { setTotpUri(data.totpURI) if (data.backupCodes) { setBackupCodes(data.backupCodes) diff --git a/package-lock.json b/package-lock.json index e4c9df9a..34bbc015 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,8 +19,8 @@ "zod": "^4.0.0" }, "devDependencies": { - "@better-auth/core": "1.6.15", - "@better-auth/test-utils": "1.6.15", + "@better-auth/core": "1.7.1", + "@better-auth/test-utils": "1.7.1", "@convex-dev/eslint-plugin": "2.0.0", "@edge-runtime/vm": "5.0.0", "@eslint/eslintrc": "3.3.5", @@ -31,7 +31,7 @@ "@types/react": "19.2.14", "@types/react-dom": "19.2.3", "@types/semver": "7.7.1", - "better-auth": "1.6.15", + "better-auth": "1.7.1", "chokidar-cli": "3.0.0", "concurrently": "10.0.3", "convex": "1.41.0", @@ -49,10 +49,10 @@ "react-dom": "19.2.6", "typescript": "5.9.3", "typescript-eslint": "8.59.2", - "vitest": "4.1.5" + "vitest": "4.1.10" }, "peerDependencies": { - "better-auth": ">=1.6.11 <1.7.0", + "better-auth": ">=1.7.1 <1.8.0", "convex": "^1.25.0", "react": "^18.3.1 || ^19.0.0" } @@ -360,22 +360,22 @@ } }, "node_modules/@better-auth/core": { - "version": "1.6.15", - "resolved": "https://registry.npmjs.org/@better-auth/core/-/core-1.6.15.tgz", - "integrity": "sha512-gPgeEn+2t2cohSKj1CJFCJLo4jNqvtvhKP/y6Uwuurg4ReEf2Y9PUCYffvbp7DDc+GevWTuD0Cv9x0+e83+HpA==", + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@better-auth/core/-/core-1.7.1.tgz", + "integrity": "sha512-eZ9lqcnVLMZ3QtUByRo4VZqkB1ESyRddd9NfWjBdDPgh+jcwLScoIUAqhtHLR8zaSUJZah8OLGlkzObyPdUH7A==", "dev": true, "license": "MIT", "dependencies": { - "@opentelemetry/semantic-conventions": "^1.39.0", + "@opentelemetry/semantic-conventions": "^1.41.1", "@standard-schema/spec": "^1.1.0", "zod": "^4.3.6" }, "peerDependencies": { - "@better-auth/utils": "0.4.1", - "@better-fetch/fetch": "1.1.21", + "@better-auth/utils": "0.4.2", + "@better-fetch/fetch": "1.3.1", "@cloudflare/workers-types": ">=4", "@opentelemetry/api": "^1.9.0", - "better-call": "1.3.5", + "better-call": "1.4.0", "jose": "^6.1.0", "kysely": "^0.28.5 || ^0.29.0", "nanostores": "^1.0.1" @@ -390,15 +390,15 @@ } }, "node_modules/@better-auth/drizzle-adapter": { - "version": "1.6.15", - "resolved": "https://registry.npmjs.org/@better-auth/drizzle-adapter/-/drizzle-adapter-1.6.15.tgz", - "integrity": "sha512-+ho2RozN6cZmCN+6XkZd/ec5iolVlefgInQ7znJ18qRPbgllHxdyu9tGrgKZyEsXSCyqAa6wi5Yb4hd3WZmTNQ==", + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@better-auth/drizzle-adapter/-/drizzle-adapter-1.7.1.tgz", + "integrity": "sha512-qlqNyg5V9bXHSP68/vtlsiZayhR4hgvEGiS/E3SIj8bCpWWFGmyQkxJbQCqpBmC7vT30wE/kNtJMHIgnV3rkiw==", "dev": true, "license": "MIT", "peerDependencies": { - "@better-auth/core": "^1.6.15", - "@better-auth/utils": "0.4.1", - "drizzle-orm": "^0.45.2" + "@better-auth/core": "^1.7.1", + "@better-auth/utils": "0.4.2", + "drizzle-orm": "^0.45.2 || >=1.0.0-rc.1 <2.0.0" }, "peerDependenciesMeta": { "drizzle-orm": { @@ -407,14 +407,14 @@ } }, "node_modules/@better-auth/kysely-adapter": { - "version": "1.6.15", - "resolved": "https://registry.npmjs.org/@better-auth/kysely-adapter/-/kysely-adapter-1.6.15.tgz", - "integrity": "sha512-E29Sugm+DWRK4oQNBPrjPA4kBYiKy2bBtX1arIlkuyAB9A1BEb4Xn+8t7wlNIF5eFcxpkFZ3F80ceSpWDSYU0Q==", + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@better-auth/kysely-adapter/-/kysely-adapter-1.7.1.tgz", + "integrity": "sha512-yWCpE1cZpMUj37nD6JFDK+GDR8zS37L5WI73il3qbU9TXtWsxUQKc/5c3IHsHizWQsmcQI8uv2pAFKxsRDa+AQ==", "dev": true, "license": "MIT", "peerDependencies": { - "@better-auth/core": "^1.6.15", - "@better-auth/utils": "0.4.1", + "@better-auth/core": "^1.7.1", + "@better-auth/utils": "0.4.2", "kysely": "^0.28.17 || ^0.29.0" }, "peerDependenciesMeta": { @@ -424,25 +424,25 @@ } }, "node_modules/@better-auth/memory-adapter": { - "version": "1.6.15", - "resolved": "https://registry.npmjs.org/@better-auth/memory-adapter/-/memory-adapter-1.6.15.tgz", - "integrity": "sha512-BRE5Ft0Tn5thOPjCmyXHge7kOI/paLybaKDU+Hzg24DZMU6JvWooYWzvf52r1viDbxq5tz0G97iyaH4bouDyug==", + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@better-auth/memory-adapter/-/memory-adapter-1.7.1.tgz", + "integrity": "sha512-6NX1yv88DeqdoG7owYFqKwlrDGaIPhsC52JUGrUgeGVKyOq8a/6hHlHsqG1C2FwT23SHQiKVYCEAG9N6aH5OvQ==", "dev": true, "license": "MIT", "peerDependencies": { - "@better-auth/core": "^1.6.15", - "@better-auth/utils": "0.4.1" + "@better-auth/core": "^1.7.1", + "@better-auth/utils": "0.4.2" } }, "node_modules/@better-auth/mongo-adapter": { - "version": "1.6.15", - "resolved": "https://registry.npmjs.org/@better-auth/mongo-adapter/-/mongo-adapter-1.6.15.tgz", - "integrity": "sha512-r0X9AtFhwDeOU1KMP9kZ5NeV0O3TSFzn8+uidaH+aicy2e1dNUsd9nPYrozEloSxdA2ZOnv5gD4jzH7HPeKkUQ==", + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@better-auth/mongo-adapter/-/mongo-adapter-1.7.1.tgz", + "integrity": "sha512-9ILTcNqhG37QK//qR4UhYLyKzNqq6w6zVTf5KX6xkiTjNcV7Oh1yS31lkIJEVTqRNcy9AoV6FZMW6Bbsm8IMDA==", "dev": true, "license": "MIT", "peerDependencies": { - "@better-auth/core": "^1.6.15", - "@better-auth/utils": "0.4.1", + "@better-auth/core": "^1.7.1", + "@better-auth/utils": "0.4.2", "mongodb": "^6.0.0 || ^7.0.0" }, "peerDependenciesMeta": { @@ -452,14 +452,14 @@ } }, "node_modules/@better-auth/prisma-adapter": { - "version": "1.6.15", - "resolved": "https://registry.npmjs.org/@better-auth/prisma-adapter/-/prisma-adapter-1.6.15.tgz", - "integrity": "sha512-pUOlIUnpMu2l8C2ytgu46+OoOr3LZ+aLpJMFRGIPdnWl1BF2co2YaoZPT3+ZBu1Lzm0FrBN1ZRCAzZbkFoeIAQ==", + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@better-auth/prisma-adapter/-/prisma-adapter-1.7.1.tgz", + "integrity": "sha512-ZiUcafQ85InAofcUjyGgCPjKLfQjXr9SvDmMjuFUW8oEbreA6C6GaFAEA77VuV2doZUQlzvQQ4gCoTmS28W92A==", "dev": true, "license": "MIT", "peerDependencies": { - "@better-auth/core": "^1.6.15", - "@better-auth/utils": "0.4.1", + "@better-auth/core": "^1.7.1", + "@better-auth/utils": "0.4.2", "@prisma/client": "^5.0.0 || ^6.0.0 || ^7.0.0", "prisma": "^5.0.0 || ^6.0.0 || ^7.0.0" }, @@ -473,33 +473,33 @@ } }, "node_modules/@better-auth/telemetry": { - "version": "1.6.15", - "resolved": "https://registry.npmjs.org/@better-auth/telemetry/-/telemetry-1.6.15.tgz", - "integrity": "sha512-eoFlUPVrVXLML9saHD11OSKKSRCAYETgRgBW4vQF3Y6pCgmThKD9oehYk3kkb/FsRgfqmsocmvAFqsiyjPIygg==", + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@better-auth/telemetry/-/telemetry-1.7.1.tgz", + "integrity": "sha512-kLKjMfFlTbyt49DGeI9okHAsn0MtBZcMoQYKaEdgR0H3BHzqqyzePcQz/hxAmRgjB4p/6inise3zJwhX0sgXrQ==", "dev": true, "license": "MIT", "peerDependencies": { - "@better-auth/core": "^1.6.15", - "@better-auth/utils": "0.4.1", - "@better-fetch/fetch": "1.1.21" + "@better-auth/core": "^1.7.1", + "@better-auth/utils": "0.4.2", + "@better-fetch/fetch": "1.3.1" } }, "node_modules/@better-auth/test-utils": { - "version": "1.6.15", - "resolved": "https://registry.npmjs.org/@better-auth/test-utils/-/test-utils-1.6.15.tgz", - "integrity": "sha512-q5rvEsAhBHAXniwMlZpE1GiRSBPu/3rZM4iA5yOJ5uDw7fZjvWuufsJUc8raQuripXquLQZBYlGtVInHBadMyw==", + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@better-auth/test-utils/-/test-utils-1.7.1.tgz", + "integrity": "sha512-Whb2s5EX9xwninIW70p+YVmcgzWUoYktKFJm0eNWXw/Noqv9+Op27ZUuKk8hMmCImnm6YuToSzkz3OBAP5OYPg==", "dev": true, "license": "MIT", "peerDependencies": { - "@better-auth/core": "^1.6.15", - "better-auth": "^1.6.15", - "vitest": "^4.1.5" + "@better-auth/core": "^1.7.1", + "better-auth": "^1.7.1", + "vitest": "^4.1.10" } }, "node_modules/@better-auth/utils": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@better-auth/utils/-/utils-0.4.1.tgz", - "integrity": "sha512-SZBPRPF3z0nBvE5ygOkxae35wnnXPRShmqFo78S+qslLeFoPu/pMgnXAuNKFMMybac3tiLaVg1e3MQW5MC+1iA==", + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@better-auth/utils/-/utils-0.4.2.tgz", + "integrity": "sha512-AUxrvu+HaaODsUyzDxFgwd/8RZ1yZaYo42LXKSrU2oGgR38pS1ij8nqQKNgtTWoYGpNevNXtCfgTy6loHveW9A==", "dev": true, "license": "MIT", "dependencies": { @@ -507,9 +507,10 @@ } }, "node_modules/@better-fetch/fetch": { - "version": "1.1.21", - "resolved": "https://registry.npmjs.org/@better-fetch/fetch/-/fetch-1.1.21.tgz", - "integrity": "sha512-/ImESw0sskqlVR94jB+5+Pxjf+xBwDZF/N5+y2/q4EqD7IARUTSpPfIo8uf39SYpCxyOCtbyYpUrZ3F/k0zT4A==" + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@better-fetch/fetch/-/fetch-1.3.1.tgz", + "integrity": "sha512-ABkD1WhyfPZprKRQI3bhATjeiFuNWC9PXhfGWqL+sg/gKrM977oFrYkdb4msM3hgUGonr7KlOsOFT5TU2rht9g==", + "license": "MIT" }, "node_modules/@convex-dev/eslint-plugin": { "version": "2.0.0", @@ -2117,9 +2118,9 @@ } }, "node_modules/@noble/hashes": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.0.1.tgz", - "integrity": "sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.3.0.tgz", + "integrity": "sha512-oN+QwyX7VSHotibwubG3kpzbwKrfnyR6OOO+3Nk/53ADL7FmgHHz4TgrbaYKvvOw09u6QTx0oiH1cNCIOuN0CQ==", "dev": true, "license": "MIT", "engines": { @@ -2182,9 +2183,9 @@ } }, "node_modules/@opentelemetry/semantic-conventions": { - "version": "1.40.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.40.0.tgz", - "integrity": "sha512-cifvXDhcqMwwTlTK04GBNeIe7yyo28Mfby85QXFe1Yk8nmi36Ab/5UQwptOx84SsoGNRg+EVSjwzfSZMy6pmlw==", + "version": "1.43.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.43.0.tgz", + "integrity": "sha512-eSYWTm620tTk45EKSedaUL8MFYI8hW164hIXsgIHyxu3VobUB3fFCu5t0hQby6OoWRPsG1KkKUG2M5UadiLiVg==", "dev": true, "license": "Apache-2.0", "engines": { @@ -3377,16 +3378,16 @@ } }, "node_modules/@vitest/expect": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.5.tgz", - "integrity": "sha512-PWBaRY5JoKuRnHlUHfpV/KohFylaDZTupcXN1H9vYryNLOnitSw60Mw9IAE2r67NbwwzBw/Cc/8q9BK3kIX8Kw==", + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.10.tgz", + "integrity": "sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==", "dev": true, "license": "MIT", "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", - "@vitest/spy": "4.1.5", - "@vitest/utils": "4.1.5", + "@vitest/spy": "4.1.10", + "@vitest/utils": "4.1.10", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" }, @@ -3395,13 +3396,13 @@ } }, "node_modules/@vitest/mocker": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.5.tgz", - "integrity": "sha512-/x2EmFC4mT4NNzqvC3fmesuV97w5FC903KPmey4gsnJiMQ3Be1IlDKVaDaG8iqaLFHqJ2FVEkxZk5VmeLjIItw==", + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.10.tgz", + "integrity": "sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "4.1.5", + "@vitest/spy": "4.1.10", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, @@ -3422,9 +3423,9 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.5.tgz", - "integrity": "sha512-7I3q6l5qr03dVfMX2wCo9FxwSJbPdwKjy2uu/YPpU3wfHvIL4QHwVRp57OfGrDFeUJ8/8QdfBKIV12FTtLn00g==", + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.10.tgz", + "integrity": "sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==", "dev": true, "license": "MIT", "dependencies": { @@ -3435,13 +3436,13 @@ } }, "node_modules/@vitest/runner": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.5.tgz", - "integrity": "sha512-2D+o7Pr82IEO46YPpoA/YU0neeyr6FTerQb5Ro7BUnBuv6NQtT/kmVnczngiMEBhzgqz2UZYl5gArejsyERDSQ==", + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.10.tgz", + "integrity": "sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "4.1.5", + "@vitest/utils": "4.1.10", "pathe": "^2.0.3" }, "funding": { @@ -3449,14 +3450,14 @@ } }, "node_modules/@vitest/snapshot": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.5.tgz", - "integrity": "sha512-zypXEt4KH/XgKGPUz4eC2AvErYx0My5hfL8oDb1HzGFpEk1P62bxSohdyOmvz+d9UJwanI68MKwr2EquOaOgMQ==", + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.10.tgz", + "integrity": "sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.5", - "@vitest/utils": "4.1.5", + "@vitest/pretty-format": "4.1.10", + "@vitest/utils": "4.1.10", "magic-string": "^0.30.21", "pathe": "^2.0.3" }, @@ -3465,9 +3466,9 @@ } }, "node_modules/@vitest/spy": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.5.tgz", - "integrity": "sha512-2lNOsh6+R2Idnf1TCZqSwYlKN2E/iDlD8sgU59kYVl+OMDmvldO1VDk39smRfpUNwYpNRVn3w4YfuC7KfbBnkQ==", + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.10.tgz", + "integrity": "sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==", "dev": true, "license": "MIT", "funding": { @@ -3475,13 +3476,13 @@ } }, "node_modules/@vitest/utils": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.5.tgz", - "integrity": "sha512-76wdkrmfXfqGjueGgnb45ITPyUi1ycZ4IHgC2bhPDUfWHklY/q3MdLOAB+TF1e6xfl8NxNY0ZYaPCFNWSsw3Ug==", + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.10.tgz", + "integrity": "sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.5", + "@vitest/pretty-format": "4.1.10", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" }, @@ -3829,28 +3830,28 @@ } }, "node_modules/better-auth": { - "version": "1.6.15", - "resolved": "https://registry.npmjs.org/better-auth/-/better-auth-1.6.15.tgz", - "integrity": "sha512-0nuQuEru3ZrLF+9xFUuN3llAmR+6gHLtLunoXaZxB9lXGjSmfBcc6SZUgYq4DfzugPnLvdnzYazsyprZFSFC4Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@better-auth/core": "1.6.15", - "@better-auth/drizzle-adapter": "1.6.15", - "@better-auth/kysely-adapter": "1.6.15", - "@better-auth/memory-adapter": "1.6.15", - "@better-auth/mongo-adapter": "1.6.15", - "@better-auth/prisma-adapter": "1.6.15", - "@better-auth/telemetry": "1.6.15", - "@better-auth/utils": "0.4.1", - "@better-fetch/fetch": "1.1.21", - "@noble/ciphers": "^2.1.1", - "@noble/hashes": "^2.0.1", - "better-call": "1.3.5", + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/better-auth/-/better-auth-1.7.1.tgz", + "integrity": "sha512-g8WlTQijxXWJjPVZfFu1+EJg9cwwHrKDmIkcYMzx8CzYA+tDxl6NI7qQbKkbgw5UtHILsT5VH+RMzFzwnVJqAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@better-auth/core": "1.7.1", + "@better-auth/drizzle-adapter": "1.7.1", + "@better-auth/kysely-adapter": "1.7.1", + "@better-auth/memory-adapter": "1.7.1", + "@better-auth/mongo-adapter": "1.7.1", + "@better-auth/prisma-adapter": "1.7.1", + "@better-auth/telemetry": "1.7.1", + "@better-auth/utils": "0.4.2", + "@better-fetch/fetch": "1.3.1", + "@noble/ciphers": "^2.2.0", + "@noble/hashes": "^2.2.0", + "better-call": "1.4.0", "defu": "^6.1.4", - "jose": "^6.1.3", + "jose": "^6.2.3", "kysely": "^0.28.17 || ^0.29.0", - "nanostores": "^1.1.1", + "nanostores": "^1.3.0", "zod": "^4.3.6" }, "peerDependencies": { @@ -3860,8 +3861,8 @@ "@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", @@ -3935,16 +3936,16 @@ } }, "node_modules/better-call": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/better-call/-/better-call-1.3.5.tgz", - "integrity": "sha512-kOFJkBP7utAQLEYrobZm3vkTH8mXq5GNgvjc5/XEST1ilVHaxXUXfeDeFlqoETMtyqS4+3/h4ONX2i++ebZrvA==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/better-call/-/better-call-1.4.0.tgz", + "integrity": "sha512-bBKOT4vv1kZLDgxVePdilk/Jwkn+dtRRsmi3DzHcDP+WnswyVl6dR59l2HEeP/0cB+bDoopASAesWDPIdd/zZA==", "dev": true, "license": "MIT", "dependencies": { - "@better-auth/utils": "^0.4.0", - "@better-fetch/fetch": "^1.1.21", - "rou3": "^0.7.12", - "set-cookie-parser": "^3.0.1" + "@better-auth/utils": "^0.5.0", + "@better-fetch/fetch": "^1.3.1", + "rou3": "^0.9.1", + "set-cookie-parser": "^3.1.2" }, "peerDependencies": { "zod": "^4.0.0" @@ -3955,6 +3956,16 @@ } } }, + "node_modules/better-call/node_modules/@better-auth/utils": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@better-auth/utils/-/utils-0.5.0.tgz", + "integrity": "sha512-BL8W4EfIZFwlu0r54m3v1ztjDhu6dDe/amLTm0xybmbZaNgYUqhD3SjpAsnq0q8YD6/ki4iwIgxJNLP/N3TxiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@noble/hashes": "^2.0.1" + } + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -7094,9 +7105,9 @@ } }, "node_modules/nanostores": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/nanostores/-/nanostores-1.2.0.tgz", - "integrity": "sha512-F0wCzbsH80G7XXo0Jd9/AVQC7ouWY6idUCTnMwW5t/Rv9W8qmO6endavDwg7TNp5GbugwSukFMVZqzPSrSMndg==", + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/nanostores/-/nanostores-1.5.2.tgz", + "integrity": "sha512-B0UbxzK1s0CN8Xht6r+7iT5+xV8PTaRERR1nATeplRv1Rw5YLWfVAid0hkqY3EceqpG4RjTk8GAwIxQY39Rnwg==", "dev": true, "funding": [ { @@ -7859,9 +7870,9 @@ "license": "MIT" }, "node_modules/rou3": { - "version": "0.7.12", - "resolved": "https://registry.npmjs.org/rou3/-/rou3-0.7.12.tgz", - "integrity": "sha512-iFE4hLDuloSWcD7mjdCDhx2bKcIsYbtOTpfH5MHHLSKMOUyjqQXTeZVa289uuwEGEKFoE/BAPbhaU4B774nceg==", + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/rou3/-/rou3-0.9.2.tgz", + "integrity": "sha512-3SOzvaAg8rkHrXtRjpCvCvbyO5to9oOO27Z/XqHEYXfMRVSw/qMIVdmaOk9W2lcRLtR6dlqTjo9hDeJk70QBYQ==", "dev": true, "license": "MIT" }, @@ -7987,9 +7998,9 @@ "license": "ISC" }, "node_modules/set-cookie-parser": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-3.1.0.tgz", - "integrity": "sha512-kjnC1DXBHcxaOaOXBHBeRtltsDG2nUiUni+jP92M9gYdW12rsmx92UsfpH7o5tDRs7I1ZZPSQJQGv3UaRfCiuw==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-3.1.2.tgz", + "integrity": "sha512-5/r/lTwbJ3zQ+qwdUFZYeRNqda7P5HD8zQKqlSjdGt1/S0cjLAphHusj4Y58ahDtWn/g32xrIS58/ikOvwl0Lw==", "dev": true, "license": "MIT" }, @@ -8514,9 +8525,9 @@ } }, "node_modules/tinyrainbow": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", - "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.1.tgz", + "integrity": "sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw==", "dev": true, "license": "MIT", "engines": { @@ -8973,19 +8984,19 @@ } }, "node_modules/vitest": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.5.tgz", - "integrity": "sha512-9Xx1v3/ih3m9hN+SbfkUyy0JAs72ap3r7joc87XL6jwF0jGg6mFBvQ1SrwaX+h8BlkX6Hz9shdd1uo6AF+ZGpg==", + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.10.tgz", + "integrity": "sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/expect": "4.1.5", - "@vitest/mocker": "4.1.5", - "@vitest/pretty-format": "4.1.5", - "@vitest/runner": "4.1.5", - "@vitest/snapshot": "4.1.5", - "@vitest/spy": "4.1.5", - "@vitest/utils": "4.1.5", + "@vitest/expect": "4.1.10", + "@vitest/mocker": "4.1.10", + "@vitest/pretty-format": "4.1.10", + "@vitest/runner": "4.1.10", + "@vitest/snapshot": "4.1.10", + "@vitest/spy": "4.1.10", + "@vitest/utils": "4.1.10", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", @@ -9013,12 +9024,12 @@ "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", - "@vitest/browser-playwright": "4.1.5", - "@vitest/browser-preview": "4.1.5", - "@vitest/browser-webdriverio": "4.1.5", - "@vitest/coverage-istanbul": "4.1.5", - "@vitest/coverage-v8": "4.1.5", - "@vitest/ui": "4.1.5", + "@vitest/browser-playwright": "4.1.10", + "@vitest/browser-preview": "4.1.10", + "@vitest/browser-webdriverio": "4.1.10", + "@vitest/coverage-istanbul": "4.1.10", + "@vitest/coverage-v8": "4.1.10", + "@vitest/ui": "4.1.10", "happy-dom": "*", "jsdom": "*", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" diff --git a/package.json b/package.json index cdbd412d..2b1d3fe3 100644 --- a/package.json +++ b/package.json @@ -101,13 +101,13 @@ } }, "peerDependencies": { - "better-auth": ">=1.6.11 <1.7.0", + "better-auth": ">=1.7.1 <1.8.0", "convex": "^1.25.0", "react": "^18.3.1 || ^19.0.0" }, "devDependencies": { - "@better-auth/core": "1.6.15", - "@better-auth/test-utils": "1.6.15", + "@better-auth/core": "1.7.1", + "@better-auth/test-utils": "1.7.1", "@convex-dev/eslint-plugin": "2.0.0", "@edge-runtime/vm": "5.0.0", "@eslint/eslintrc": "3.3.5", @@ -118,7 +118,7 @@ "@types/react": "19.2.14", "@types/react-dom": "19.2.3", "@types/semver": "7.7.1", - "better-auth": "1.6.15", + "better-auth": "1.7.1", "chokidar-cli": "3.0.0", "concurrently": "10.0.3", "convex": "1.41.0", @@ -136,7 +136,7 @@ "react-dom": "19.2.6", "typescript": "5.9.3", "typescript-eslint": "8.59.2", - "vitest": "4.1.5" + "vitest": "4.1.10" }, "types": "./dist/client/index.d.ts", "module": "./dist/client/index.js", diff --git a/src/auth-options.ts b/src/auth-options.ts index 6281482f..eda273f4 100644 --- a/src/auth-options.ts +++ b/src/auth-options.ts @@ -5,7 +5,6 @@ import { emailOTP } from "better-auth/plugins/email-otp"; import { genericOAuth } from "better-auth/plugins/generic-oauth"; import { jwt } from "better-auth/plugins/jwt"; import { magicLink } from "better-auth/plugins/magic-link"; -import { oidcProvider } from "better-auth/plugins/oidc-provider"; import { oneTimeToken } from "better-auth/plugins/one-time-token"; import { phoneNumber } from "better-auth/plugins/phone-number"; import { twoFactor } from "better-auth/plugins/two-factor"; @@ -35,10 +34,6 @@ export const options = { }, ], }), - oidcProvider({ - loginPage: "/login", - __skipDeprecationWarning: true, - }), bearer(), oneTimeToken(), jwt(), diff --git a/src/client/adapter-utils.ts b/src/client/adapter-utils.ts index 7e04ffdf..1e2cec8f 100644 --- a/src/client/adapter-utils.ts +++ b/src/client/adapter-utils.ts @@ -58,33 +58,44 @@ export const adapterArgsValidator = v.object({ offset: v.optional(v.number()), }); -const isUniqueField = ( +const getUniqueConstraints = ( betterAuthSchema: BetterAuthDBSchema, - model: string, - field: string + model: string ) => { - const fields = Object.values(betterAuthSchema).find( + const table = Object.values(betterAuthSchema).find( (value) => value.modelName === model - )?.fields; - if (!fields) { - return false; + ); + if (!table) { + return []; } - return Object.entries(fields) + const singleFieldConstraints = Object.entries(table.fields) .filter(([, value]) => value.unique) - .map(([key]) => key) - .includes(field); + .map(([key, value]) => [value.fieldName ?? key]); + const tableConstraints = + table.indexes + ?.filter((index) => index.unique) + .map((index) => + index.fields.map((field) => table.fields[field]?.fieldName ?? field) + ) ?? []; + return [...singleFieldConstraints, ...tableConstraints]; }; +const isUniqueField = ( + betterAuthSchema: BetterAuthDBSchema, + model: string, + field: string +) => + getUniqueConstraints(betterAuthSchema, model).some( + (constraint) => constraint.length === 1 && constraint[0] === field + ); export const hasUniqueFields = ( betterAuthSchema: BetterAuthDBSchema, model: string, input: Record ) => { - for (const field of Object.keys(input)) { - if (isUniqueField(betterAuthSchema, model, field)) { - return true; - } - } - return false; + const inputFields = new Set(Object.keys(input)); + return getUniqueConstraints(betterAuthSchema, model).some((constraint) => + constraint.every((field) => inputFields.has(field)) + ); }; const findIndex = ( @@ -246,31 +257,44 @@ export const checkUniqueFields = async < input: Record, doc?: Record ) => { - if (!hasUniqueFields(betterAuthSchema, table, input)) { - return; - } - for (const field of Object.keys(input)) { - if (!isUniqueField(betterAuthSchema, table, field)) { + const mergedDoc = { ...doc, ...input }; + for (const constraint of getUniqueConstraints(betterAuthSchema, table)) { + if ( + !constraint.some((field) => field in input) || + !constraint.every((field) => mergedDoc[field] !== undefined) + ) { continue; } - const { index } = - findIndex(schema, { - model: table, - where: [ - { field, operator: "eq", value: input[field as keyof typeof input] }, - ], - }) || {}; + const { index } = findIndex(schema, { + model: table, + where: constraint.map((field) => ({ + field, + operator: "eq" as const, + value: mergedDoc[field], + })), + }) || { index: undefined }; if (!index) { - throw new Error(`No index found for ${table}${field}`); + throw new Error( + `No index found for unique constraint ${table}.${constraint.join("+")}` + ); } - const existingDoc = await ctx.db + const existingDocs = await ctx.db .query(table as any) .withIndex(index.indexDescriptor, (q) => - q.eq(field, input[field as keyof typeof input]) + index.fields + .slice(0, constraint.length) + .reduce( + (query: any, field: string) => query.eq(field, mergedDoc[field]), + q + ) ) - .unique(); - if (existingDoc && existingDoc._id !== doc?._id) { - throw new Error(`${table} ${field} already exists`); + .take(2); + if (existingDocs.some((existingDoc) => existingDoc._id !== doc?._id)) { + const constraintName = + constraint.length === 1 + ? constraint[0] + : `unique constraint ${constraint.join("+")}`; + throw new Error(`${table} ${constraintName} already exists`); } } }; diff --git a/src/client/adapter.ts b/src/client/adapter.ts index c40b60ac..43944443 100644 --- a/src/client/adapter.ts +++ b/src/client/adapter.ts @@ -3,6 +3,7 @@ import type { DBAdapterDebugLogOption } from "better-auth/adapters"; import { createFunctionHandle } from "convex/server"; import type { FunctionHandle, + FunctionReference, GenericActionCtx, GenericDataModel, PaginationOptions, @@ -17,10 +18,9 @@ import { asyncMap } from "convex-helpers"; import { prop, sortBy } from "remeda"; import { isRunMutationCtx } from "../utils/index.js"; import type { Doc, TableNames } from "../component/_generated/dataModel.js"; -import type { ComponentApi } from "../component/_generated/component.js"; import type { AuthFunctions, GenericCtx, Triggers } from "./index.js"; -let didWarnExperimentalJoinsUnsupported = false; +let didWarnJoinsUnsupported = false; const handlePagination = async ( next: ({ @@ -184,7 +184,16 @@ export const convexAdapter = < >( ctx: Ctx, api: { - adapter: ComponentApi["adapter"]; + adapter: { + create: FunctionReference<"mutation", "internal">; + findOne: FunctionReference<"query", "internal">; + findMany: FunctionReference<"query", "internal">; + updateOne: FunctionReference<"mutation", "internal">; + incrementOne: FunctionReference<"mutation", "internal">; + updateMany: FunctionReference<"mutation", "internal">; + deleteOne: FunctionReference<"mutation", "internal">; + deleteMany: FunctionReference<"mutation", "internal">; + }; }, config: { debugLogs?: DBAdapterDebugLogOption; @@ -228,16 +237,19 @@ export const convexAdapter = < adapter: ({ options }) => { // Disable telemetry in all cases because it requires Node options.telemetry = { enabled: false }; - if (options.experimental?.joins) { - options.experimental = { - ...options.experimental, - joins: false, + if (options.advanced?.database?.joins) { + options.advanced = { + ...options.advanced, + database: { + ...options.advanced.database, + joins: false, + }, }; - if (!didWarnExperimentalJoinsUnsupported) { - didWarnExperimentalJoinsUnsupported = true; + if (!didWarnJoinsUnsupported) { + didWarnJoinsUnsupported = true; // eslint-disable-next-line no-console console.warn( - "[convex-better-auth] Better Auth experimental.joins is not supported by the Convex adapter yet. Forcing experimental.joins = false." + "[convex-better-auth] Better Auth advanced.database.joins is not supported by the Convex adapter yet. Forcing advanced.database.joins = false." ); } } @@ -410,6 +422,52 @@ export const convexAdapter = < onUpdateHandle: onUpdateHandle, }); }, + consumeOne: async (data): Promise => { + if (!("runMutation" in ctx)) { + throw new Error("ctx is not a mutation ctx"); + } + if (!data.where.length) { + return null; + } + const onDeleteHandle = + config.authFunctions?.onDelete && + config.triggers?.[data.model]?.onDelete + ? ((await createFunctionHandle( + config.authFunctions.onDelete + )) as FunctionHandle<"mutation">) + : undefined; + return ctx.runMutation(api.adapter.deleteOne, { + input: { + model: data.model as TableNames, + where: parseWhere(data.where), + }, + onDeleteHandle, + }); + }, + incrementOne: async (data): Promise => { + if (!("runMutation" in ctx)) { + throw new Error("ctx is not a mutation ctx"); + } + if (!data.where.length) { + return null; + } + const onUpdateHandle = + config.authFunctions?.onUpdate && + config.triggers?.[data.model]?.onUpdate + ? ((await createFunctionHandle( + config.authFunctions.onUpdate + )) as FunctionHandle<"mutation">) + : undefined; + return ctx.runMutation(api.adapter.incrementOne, { + input: { + model: data.model as TableNames, + where: parseWhere(data.where), + increment: data.increment, + set: data.set as any, + }, + onUpdateHandle, + }); + }, delete: async (data) => { if (!("runMutation" in ctx)) { throw new Error("ctx is not a mutation ctx"); diff --git a/src/client/create-api.ts b/src/client/create-api.ts index 97679db6..0cec263c 100644 --- a/src/client/create-api.ts +++ b/src/client/create-api.ts @@ -3,7 +3,12 @@ import { paginationOptsValidator, queryGeneric, } from "convex/server"; -import type { FunctionHandle, SchemaDefinition } from "convex/server"; +import type { + FunctionHandle, + GenericDataModel, + GenericMutationCtx, + SchemaDefinition, +} from "convex/server"; import { v } from "convex/values"; import type { GenericId } from "convex/values"; import { asyncMap } from "convex-helpers"; @@ -63,6 +68,42 @@ export const createApi = >( createAuthOptions: (ctx: any) => BetterAuthOptions ) => { const betterAuthSchema = getAuthTables(createAuthOptions({} as any)); + const applySingleUpdate = async >( + ctx: GenericMutationCtx, + model: TableNames, + doc: Doc, + update: Record, + onUpdateHandle?: string + ): Promise => { + await checkUniqueFields(ctx, schema, betterAuthSchema, model, update, doc); + await ctx.db.patch(model, doc._id as GenericId, update as any); + const updatedDoc = await ctx.db.get( + model, + doc._id as GenericId + ); + if (!updatedDoc) { + throw new Error(`Failed to update ${model}`); + } + if (!onUpdateHandle) { + return updatedDoc as Doc; + } + await ctx.runMutation(onUpdateHandle as FunctionHandle<"mutation">, { + model, + newDoc: updatedDoc, + oldDoc: doc, + }); + const triggeredDoc = await ctx.db.get( + model, + doc._id as GenericId + ); + if (!triggeredDoc) { + throw new Error( + `Failed to update ${model} (deleted by onUpdate trigger?)` + ); + } + return triggeredDoc as Doc; + }; + return { create: mutationGeneric({ args: { @@ -168,49 +209,64 @@ export const createApi = >( handler: async (ctx, args) => { const doc = await listOne(ctx, schema, betterAuthSchema, args.input); if (!doc) { - throw new Error(`Failed to update ${args.input.model}`); + return null; } - await checkUniqueFields( + return await applySingleUpdate( ctx, - schema, - betterAuthSchema, args.input.model, + doc, args.input.update, - doc + args.onUpdateHandle ); - await ctx.db.patch( - args.input.model, - doc._id as GenericId, - args.input.update as any - ); - const updatedDoc = await ctx.db.get( - args.input.model, - doc._id as GenericId - ); - if (!updatedDoc) { - throw new Error(`Failed to update ${args.input.model}`); - } - if (args.onUpdateHandle) { - await ctx.runMutation( - args.onUpdateHandle as FunctionHandle<"mutation">, - { - model: args.input.model, - newDoc: updatedDoc, - oldDoc: doc, + }, + }), + incrementOne: mutationGeneric({ + args: { + input: v.union( + ...Object.entries(schema.tables).map( + ([name, table]: [string, Schema["tables"][string]]) => { + const tableName = name as TableNames; + return v.object({ + model: v.literal(tableName), + where: v.array(whereValidator(schema, tableName)), + increment: v.record(v.string(), v.number()), + set: v.optional(v.object(partial(table.validator.fields))), + }); } - ); - const innerUpdatedDoc = await ctx.db.get( - args.input.model, - doc._id as GenericId - ); - if (!innerUpdatedDoc) { + ) + ), + onUpdateHandle: v.optional(v.string()), + }, + handler: async (ctx, args) => { + if (!args.input.where.length) { + return null; + } + const doc = await listOne(ctx, schema, betterAuthSchema, args.input); + if (!doc) { + return null; + } + + const update: Record = { ...args.input.set }; + for (const [field, delta] of Object.entries(args.input.increment)) { + const current = doc[field]; + if ( + current !== undefined && + current !== null && + typeof current !== "number" + ) { throw new Error( - `Failed to update ${args.input.model} (deleted by onUpdate trigger?)` + `Cannot increment non-numeric field ${args.input.model}.${field}` ); } - return innerUpdatedDoc; + update[field] = (current ?? 0) + delta; } - return updatedDoc; + return await applySingleUpdate( + ctx, + args.input.model, + doc, + update, + args.onUpdateHandle + ); }, }), updateMany: mutationGeneric({ diff --git a/src/client/create-client.ts b/src/client/create-client.ts index 9b4c3f77..61c68212 100644 --- a/src/client/create-client.ts +++ b/src/client/create-client.ts @@ -65,6 +65,7 @@ type SlimComponentApi = { findOne: FunctionReference<"query", "internal">; findMany: FunctionReference<"query", "internal">; updateOne: FunctionReference<"mutation", "internal">; + incrementOne: FunctionReference<"mutation", "internal">; updateMany: FunctionReference<"mutation", "internal">; deleteOne: FunctionReference<"mutation", "internal">; deleteMany: FunctionReference<"mutation", "internal">; diff --git a/src/client/create-schema.test.ts b/src/client/create-schema.test.ts new file mode 100644 index 00000000..18956bb0 --- /dev/null +++ b/src/client/create-schema.test.ts @@ -0,0 +1,26 @@ +import { describe, expect, it } from "vitest"; +import type { BetterAuthDBSchema } from "better-auth/db"; +import { createSchema } from "./create-schema.js"; + +describe("createSchema", () => { + it("generates table-level compound indexes without mutating their order", async () => { + const indexFields = ["issuer", "accountId"] as [string, ...string[]]; + const tables = { + account: { + modelName: "account", + fields: { + issuer: { type: "string", required: true }, + accountId: { type: "string", required: true }, + }, + indexes: [{ fields: indexFields, unique: true }], + }, + } satisfies BetterAuthDBSchema; + + const { code } = await createSchema({ tables }); + + expect(code).toContain( + '.index("accountId_issuer", ["accountId","issuer"])' + ); + expect(indexFields).toEqual(["issuer", "accountId"]); + }); +}); diff --git a/src/client/create-schema.ts b/src/client/create-schema.ts index 464274c0..0c465c74 100644 --- a/src/client/create-schema.ts +++ b/src/client/create-schema.ts @@ -53,10 +53,14 @@ const specialFields = (tables: BetterAuthDBSchema) => const mergedIndexFields = (tables: BetterAuthDBSchema) => Object.fromEntries( Object.entries(tables).map(([key, table]) => { - const manualIndexes = + const schemaIndexes = + table.indexes?.map((index) => + index.fields.map((field) => table.fields[field]?.fieldName ?? field) + ) ?? []; + const manualIndexes: string[][] = indexFields[key as keyof typeof indexFields]?.map((index) => { return typeof index === "string" - ? (table.fields[index]?.fieldName ?? index) + ? [table.fields[index]?.fieldName ?? index] : index.map((i) => table.fields[i]?.fieldName ?? i); }) || []; const specialFieldIndexes = Object.keys( @@ -64,11 +68,27 @@ const mergedIndexFields = (tables: BetterAuthDBSchema) => {} ).filter( (index) => - !manualIndexes.some((m) => - Array.isArray(m) ? m[0] === index : m === index - ) + !manualIndexes.some((manualIndex) => manualIndex[0] === index) ); - return [key, manualIndexes.concat(specialFieldIndexes)]; + const indexes = schemaIndexes.concat( + manualIndexes, + specialFieldIndexes.map((field) => [field]) + ); + const deduplicated = indexes.filter((index, indexPosition) => { + const fields = [...index].sort(); + return ( + indexes.findIndex((candidate) => { + const candidateFields = [...candidate].sort(); + return ( + candidateFields.length === fields.length && + candidateFields.every( + (field, fieldPosition) => field === fields[fieldPosition] + ) + ); + }) === indexPosition + ); + }); + return [key, deduplicated]; }) ); @@ -110,7 +130,10 @@ export const createSchema = async ({ projectRoot !== null ? path.relative(projectRoot, cwd).split(path.sep).join("/") : null; - const generateCommand = `npx auth generate${file ? ` --output ${file}` : ""}`; + const outputPath = file ? path.relative(cwd, file) : file; + const generateCommand = `npx auth generate${ + outputPath ? ` --output ${outputPath}` : "" + }`; const cdTarget = resolveCdTarget(relativeDir, path.basename(cwd)); const commandBlock = cdTarget ? ` * cd ${cdTarget}\n * ${generateCommand}` @@ -167,7 +190,7 @@ export const tables = { mergedIndexFields(tables)[ tableKey as keyof typeof mergedIndexFields ]?.map((index) => { - const indexArray = Array.isArray(index) ? index.sort() : [index]; + const indexArray = Array.isArray(index) ? [...index].sort() : [index]; const indexName = indexArray.join("_"); return `.index("${indexName}", ${JSON.stringify(indexArray)})`; }) || []; diff --git a/src/client/triggers.test.ts b/src/client/triggers.test.ts index 73ab6e68..8ed3ca11 100644 --- a/src/client/triggers.test.ts +++ b/src/client/triggers.test.ts @@ -53,6 +53,43 @@ describe("trigger result propagation", () => { onUpdateHandle: handle, }); }); - expect(result.userAgent).toBe("trigger-ran-on-update"); + expect(result).toMatchObject({ userAgent: "trigger-ran-on-update" }); + }); + + it("api.adapter.incrementOne returns the doc reflecting onUpdateHandle writes", async () => { + const t = convexTest(schema, import.meta.glob("../component/**/*.*s")); + const result = await t.run(async (ctx) => { + await ctx.runMutation(api.adapter.create, { + input: { + model: "session", + data: { + ...baseSessionData(), + expiresAt: 100, + token: "increment-token-1", + }, + }, + }); + const handle = await createFunctionHandle( + internal.testTriggerHandlers.sessionOnUpdateUpdater + ); + return await ctx.runMutation(api.adapter.incrementOne, { + input: { + model: "session", + increment: { expiresAt: 1 }, + where: [ + { + field: "token", + operator: "eq", + value: "increment-token-1", + }, + ], + }, + onUpdateHandle: handle, + }); + }); + expect(result).toMatchObject({ + expiresAt: 101, + userAgent: "trigger-ran-on-update", + }); }); }); diff --git a/src/component/_generated/api.ts b/src/component/_generated/api.ts index 72723865..4fd79042 100644 --- a/src/component/_generated/api.ts +++ b/src/component/_generated/api.ts @@ -9,6 +9,7 @@ */ import type * as adapter from "../adapter.js"; +import type * as migrations from "../migrations.js"; import type * as testProfiles_adapterAdditionalFields from "../testProfiles/adapterAdditionalFields.js"; import type * as testProfiles_adapterOrganizationJoins from "../testProfiles/adapterOrganizationJoins.js"; import type * as testProfiles_adapterPluginTable from "../testProfiles/adapterPluginTable.js"; @@ -26,6 +27,7 @@ import { anyApi, componentsGeneric } from "convex/server"; const fullApi: ApiFromModules<{ adapter: typeof adapter; + migrations: typeof migrations; "testProfiles/adapterAdditionalFields": typeof testProfiles_adapterAdditionalFields; "testProfiles/adapterOrganizationJoins": typeof testProfiles_adapterOrganizationJoins; "testProfiles/adapterPluginTable": typeof testProfiles_adapterPluginTable; diff --git a/src/component/_generated/component.ts b/src/component/_generated/component.ts index b7e27676..9a550292 100644 --- a/src/component/_generated/component.ts +++ b/src/component/_generated/component.ts @@ -66,6 +66,7 @@ export type ComponentApi = accountId: string; createdAt: number; idToken?: null | string; + issuer?: null | string; password?: null | string; providerId: string; refreshToken?: null | string; @@ -89,6 +90,8 @@ export type ComponentApi = | { data: { backupCodes: string; + failedVerificationCount?: null | number; + lockedUntil?: null | number; secret: string; userId: string; verified?: null | boolean; @@ -138,7 +141,9 @@ export type ComponentApi = } | { data: { + alg?: null | string; createdAt: number; + crv?: null | string; expiresAt?: null | number; privateKey: string; publicKey: string; @@ -241,6 +246,7 @@ export type ComponentApi = where?: Array<{ connector?: "AND" | "OR"; field: + | "issuer" | "accountId" | "providerId" | "userId" @@ -318,6 +324,8 @@ export type ComponentApi = | "backupCodes" | "userId" | "verified" + | "failedVerificationCount" + | "lockedUntil" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -460,6 +468,8 @@ export type ComponentApi = | "privateKey" | "createdAt" | "expiresAt" + | "alg" + | "crv" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -609,6 +619,7 @@ export type ComponentApi = where?: Array<{ connector?: "AND" | "OR"; field: + | "issuer" | "accountId" | "providerId" | "userId" @@ -686,6 +697,8 @@ export type ComponentApi = | "backupCodes" | "userId" | "verified" + | "failedVerificationCount" + | "lockedUntil" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -828,6 +841,8 @@ export type ComponentApi = | "privateKey" | "createdAt" | "expiresAt" + | "alg" + | "crv" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -984,14 +999,15 @@ export type ComponentApi = any, Name >; - updateMany: FunctionReference< + incrementOne: FunctionReference< "mutation", "internal", { input: | { + increment: Record; model: "user"; - update: { + set?: { createdAt?: number; displayUsername?: null | string; email?: string; @@ -1006,7 +1022,7 @@ export type ComponentApi = userId?: null | string; username?: null | string; }; - where?: Array<{ + where: Array<{ connector?: "AND" | "OR"; field: | "name" @@ -1046,8 +1062,9 @@ export type ComponentApi = }>; } | { + increment: Record; model: "session"; - update: { + set?: { createdAt?: number; expiresAt?: number; ipAddress?: null | string; @@ -1056,7 +1073,7 @@ export type ComponentApi = userAgent?: null | string; userId?: string; }; - where?: Array<{ + where: Array<{ connector?: "AND" | "OR"; field: | "expiresAt" @@ -1090,13 +1107,15 @@ export type ComponentApi = }>; } | { + increment: Record; model: "account"; - update: { + set?: { accessToken?: null | string; accessTokenExpiresAt?: null | number; accountId?: string; createdAt?: number; idToken?: null | string; + issuer?: null | string; password?: null | string; providerId?: string; refreshToken?: null | string; @@ -1105,9 +1124,10 @@ export type ComponentApi = updatedAt?: number; userId?: string; }; - where?: Array<{ + where: Array<{ connector?: "AND" | "OR"; field: + | "issuer" | "accountId" | "providerId" | "userId" @@ -1144,15 +1164,16 @@ export type ComponentApi = }>; } | { + increment: Record; model: "verification"; - update: { + set?: { createdAt?: number; expiresAt?: number; identifier?: string; updatedAt?: number; value?: string; }; - where?: Array<{ + where: Array<{ connector?: "AND" | "OR"; field: | "identifier" @@ -1184,20 +1205,25 @@ export type ComponentApi = }>; } | { + increment: Record; model: "twoFactor"; - update: { + set?: { backupCodes?: string; + failedVerificationCount?: null | number; + lockedUntil?: null | number; secret?: string; userId?: string; verified?: null | boolean; }; - where?: Array<{ + where: Array<{ connector?: "AND" | "OR"; field: | "secret" | "backupCodes" | "userId" | "verified" + | "failedVerificationCount" + | "lockedUntil" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -1222,8 +1248,9 @@ export type ComponentApi = }>; } | { + increment: Record; model: "oauthApplication"; - update: { + set?: { clientId?: null | string; clientSecret?: null | string; createdAt?: null | number; @@ -1236,7 +1263,7 @@ export type ComponentApi = updatedAt?: null | number; userId?: null | string; }; - where?: Array<{ + where: Array<{ connector?: "AND" | "OR"; field: | "name" @@ -1274,8 +1301,9 @@ export type ComponentApi = }>; } | { + increment: Record; model: "oauthAccessToken"; - update: { + set?: { accessToken?: null | string; accessTokenExpiresAt?: null | number; clientId?: null | string; @@ -1286,7 +1314,7 @@ export type ComponentApi = updatedAt?: null | number; userId?: null | string; }; - where?: Array<{ + where: Array<{ connector?: "AND" | "OR"; field: | "accessToken" @@ -1322,8 +1350,9 @@ export type ComponentApi = }>; } | { + increment: Record; model: "oauthConsent"; - update: { + set?: { clientId?: null | string; consentGiven?: null | boolean; createdAt?: null | number; @@ -1331,7 +1360,7 @@ export type ComponentApi = updatedAt?: null | number; userId?: null | string; }; - where?: Array<{ + where: Array<{ connector?: "AND" | "OR"; field: | "clientId" @@ -1364,20 +1393,25 @@ export type ComponentApi = }>; } | { + increment: Record; model: "jwks"; - update: { + set?: { + alg?: null | string; createdAt?: number; + crv?: null | string; expiresAt?: null | number; privateKey?: string; publicKey?: string; }; - where?: Array<{ + where: Array<{ connector?: "AND" | "OR"; field: | "publicKey" | "privateKey" | "createdAt" | "expiresAt" + | "alg" + | "crv" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -1402,9 +1436,10 @@ export type ComponentApi = }>; } | { + increment: Record; model: "rateLimit"; - update: { count?: number; key?: string; lastRequest?: number }; - where?: Array<{ + set?: { count?: number; key?: string; lastRequest?: number }; + where: Array<{ connector?: "AND" | "OR"; field: "key" | "count" | "lastRequest" | "_id"; mode?: "sensitive" | "insensitive"; @@ -1430,19 +1465,11 @@ export type ComponentApi = }>; }; onUpdateHandle?: string; - paginationOpts: { - cursor: string | null; - endCursor?: string | null; - id?: number; - maximumBytesRead?: number; - maximumRowsRead?: number; - numItems: number; - }; }, any, Name >; - updateOne: FunctionReference< + updateMany: FunctionReference< "mutation", "internal", { @@ -1555,6 +1582,7 @@ export type ComponentApi = accountId?: string; createdAt?: number; idToken?: null | string; + issuer?: null | string; password?: null | string; providerId?: string; refreshToken?: null | string; @@ -1566,6 +1594,7 @@ export type ComponentApi = where?: Array<{ connector?: "AND" | "OR"; field: + | "issuer" | "accountId" | "providerId" | "userId" @@ -1645,6 +1674,8 @@ export type ComponentApi = model: "twoFactor"; update: { backupCodes?: string; + failedVerificationCount?: null | number; + lockedUntil?: null | number; secret?: string; userId?: string; verified?: null | boolean; @@ -1656,6 +1687,8 @@ export type ComponentApi = | "backupCodes" | "userId" | "verified" + | "failedVerificationCount" + | "lockedUntil" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -1824,7 +1857,9 @@ export type ComponentApi = | { model: "jwks"; update: { + alg?: null | string; createdAt?: number; + crv?: null | string; expiresAt?: null | number; privateKey?: string; publicKey?: string; @@ -1836,6 +1871,8 @@ export type ComponentApi = | "privateKey" | "createdAt" | "expiresAt" + | "alg" + | "crv" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -1888,212 +1925,4391 @@ export type ComponentApi = }>; }; onUpdateHandle?: string; + paginationOpts: { + cursor: string | null; + endCursor?: string | null; + id?: number; + maximumBytesRead?: number; + maximumRowsRead?: number; + numItems: number; + }; }, any, Name >; - }; - testProfiles: { - adapterAdditionalFields: { - create: FunctionReference< - "mutation", - "internal", - { - input: - | { - data: { - cbDefaultValueField?: null | string; - createdAt: number; - customField?: null | string; - dateField?: null | number; - displayUsername?: null | string; - email: string; - emailVerified: boolean; - image?: null | string; - isAnonymous?: null | boolean; - name: string; - numericField?: null | number; - phoneNumber?: null | string; - phoneNumberVerified?: null | boolean; - testField?: null | string; - twoFactorEnabled?: null | boolean; - updatedAt: number; - userId?: null | string; - username?: null | string; - }; - model: "user"; - } - | { - data: { - createdAt: number; - expiresAt: number; - ipAddress?: null | string; - token: string; - updatedAt: number; - userAgent?: null | string; - userId: string; - }; - model: "session"; - } - | { - data: { - accessToken?: null | string; - accessTokenExpiresAt?: null | number; - accountId: string; - createdAt: number; - idToken?: null | string; - password?: null | string; - providerId: string; - refreshToken?: null | string; - refreshTokenExpiresAt?: null | number; - scope?: null | string; - updatedAt: number; - userId: string; - }; - model: "account"; - } - | { - data: { - createdAt: number; - expiresAt: number; - identifier: string; - updatedAt: number; - value: string; - }; - model: "verification"; - } - | { - data: { - backupCodes: string; - secret: string; - userId: string; - verified?: null | boolean; - }; - model: "twoFactor"; - } - | { - data: { - clientId?: null | string; - clientSecret?: null | string; - createdAt?: null | number; - disabled?: null | boolean; - icon?: null | string; - metadata?: null | string; - name?: null | string; - redirectUrls?: null | string; - type?: null | string; - updatedAt?: null | number; - userId?: null | string; - }; - model: "oauthApplication"; - } - | { - data: { - accessToken?: null | string; - accessTokenExpiresAt?: null | number; - clientId?: null | string; - createdAt?: null | number; - refreshToken?: null | string; - refreshTokenExpiresAt?: null | number; - scopes?: null | string; - updatedAt?: null | number; - userId?: null | string; - }; - model: "oauthAccessToken"; - } - | { - data: { - clientId?: null | string; - consentGiven?: null | boolean; - createdAt?: null | number; - scopes?: null | string; - updatedAt?: null | number; - userId?: null | string; - }; - model: "oauthConsent"; - } - | { - data: { - createdAt: number; - expiresAt?: null | number; - privateKey: string; - publicKey: string; - }; - model: "jwks"; - } - | { - data: { count: number; key: string; lastRequest: number }; - model: "rateLimit"; + updateOne: FunctionReference< + "mutation", + "internal", + { + input: + | { + model: "user"; + update: { + createdAt?: number; + displayUsername?: null | string; + email?: string; + emailVerified?: boolean; + image?: null | string; + isAnonymous?: null | boolean; + name?: string; + phoneNumber?: null | string; + phoneNumberVerified?: null | boolean; + twoFactorEnabled?: null | boolean; + updatedAt?: number; + userId?: null | string; + username?: null | string; }; - onCreateHandle?: string; - select?: Array; - }, - any, - Name - >; - deleteMany: FunctionReference< - "mutation", - "internal", - { - input: - | { - model: "user"; - where?: Array<{ - connector?: "AND" | "OR"; - field: - | "name" - | "email" - | "emailVerified" - | "image" - | "createdAt" - | "updatedAt" - | "twoFactorEnabled" - | "isAnonymous" - | "username" - | "displayUsername" - | "phoneNumber" - | "phoneNumberVerified" - | "userId" - | "customField" - | "numericField" - | "testField" - | "cbDefaultValueField" - | "dateField" - | "_id"; - mode?: "sensitive" | "insensitive"; - operator?: - | "lt" - | "lte" - | "gt" - | "gte" - | "eq" - | "in" - | "not_in" - | "ne" - | "contains" - | "starts_with" - | "ends_with"; - value: - | string - | number - | boolean - | Array - | Array - | null; - }>; - } - | { - model: "session"; - where?: Array<{ - connector?: "AND" | "OR"; - field: - | "expiresAt" - | "token" + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "name" + | "email" + | "emailVerified" + | "image" + | "createdAt" + | "updatedAt" + | "twoFactorEnabled" + | "isAnonymous" + | "username" + | "displayUsername" + | "phoneNumber" + | "phoneNumberVerified" + | "userId" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "session"; + update: { + createdAt?: number; + expiresAt?: number; + ipAddress?: null | string; + token?: string; + updatedAt?: number; + userAgent?: null | string; + userId?: string; + }; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "expiresAt" + | "token" + | "createdAt" + | "updatedAt" + | "ipAddress" + | "userAgent" + | "userId" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "account"; + update: { + accessToken?: null | string; + accessTokenExpiresAt?: null | number; + accountId?: string; + createdAt?: number; + idToken?: null | string; + issuer?: null | string; + password?: null | string; + providerId?: string; + refreshToken?: null | string; + refreshTokenExpiresAt?: null | number; + scope?: null | string; + updatedAt?: number; + userId?: string; + }; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "issuer" + | "accountId" + | "providerId" + | "userId" + | "accessToken" + | "refreshToken" + | "idToken" + | "accessTokenExpiresAt" + | "refreshTokenExpiresAt" + | "scope" + | "password" + | "createdAt" + | "updatedAt" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "verification"; + update: { + createdAt?: number; + expiresAt?: number; + identifier?: string; + updatedAt?: number; + value?: string; + }; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "identifier" + | "value" + | "expiresAt" + | "createdAt" + | "updatedAt" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "twoFactor"; + update: { + backupCodes?: string; + failedVerificationCount?: null | number; + lockedUntil?: null | number; + secret?: string; + userId?: string; + verified?: null | boolean; + }; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "secret" + | "backupCodes" + | "userId" + | "verified" + | "failedVerificationCount" + | "lockedUntil" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "oauthApplication"; + update: { + clientId?: null | string; + clientSecret?: null | string; + createdAt?: null | number; + disabled?: null | boolean; + icon?: null | string; + metadata?: null | string; + name?: null | string; + redirectUrls?: null | string; + type?: null | string; + updatedAt?: null | number; + userId?: null | string; + }; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "name" + | "icon" + | "metadata" + | "clientId" + | "clientSecret" + | "redirectUrls" + | "type" + | "disabled" + | "userId" + | "createdAt" + | "updatedAt" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "oauthAccessToken"; + update: { + accessToken?: null | string; + accessTokenExpiresAt?: null | number; + clientId?: null | string; + createdAt?: null | number; + refreshToken?: null | string; + refreshTokenExpiresAt?: null | number; + scopes?: null | string; + updatedAt?: null | number; + userId?: null | string; + }; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "accessToken" + | "refreshToken" + | "accessTokenExpiresAt" + | "refreshTokenExpiresAt" + | "clientId" + | "userId" + | "scopes" + | "createdAt" + | "updatedAt" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "oauthConsent"; + update: { + clientId?: null | string; + consentGiven?: null | boolean; + createdAt?: null | number; + scopes?: null | string; + updatedAt?: null | number; + userId?: null | string; + }; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "clientId" + | "userId" + | "scopes" + | "createdAt" + | "updatedAt" + | "consentGiven" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "jwks"; + update: { + alg?: null | string; + createdAt?: number; + crv?: null | string; + expiresAt?: null | number; + privateKey?: string; + publicKey?: string; + }; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "publicKey" + | "privateKey" + | "createdAt" + | "expiresAt" + | "alg" + | "crv" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "rateLimit"; + update: { count?: number; key?: string; lastRequest?: number }; + where?: Array<{ + connector?: "AND" | "OR"; + field: "key" | "count" | "lastRequest" | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + }; + onUpdateHandle?: string; + }, + any, + Name + >; + }; + migrations: { + backfillAccountIssuers: FunctionReference< + "mutation", + "internal", + { + paginationOpts: { + cursor: string | null; + endCursor?: string | null; + id?: number; + maximumBytesRead?: number; + maximumRowsRead?: number; + numItems: number; + }; + providerIssuers: Record; + }, + { + alreadyMigrated: number; + continueCursor: string; + isDone: boolean; + migrated: number; + }, + Name + >; + clearLegacyOAuthProviderRecords: FunctionReference< + "mutation", + "internal", + { + paginationOpts: { + cursor: string | null; + endCursor?: string | null; + id?: number; + maximumBytesRead?: number; + maximumRowsRead?: number; + numItems: number; + }; + table: "oauthApplication" | "oauthAccessToken" | "oauthConsent"; + }, + { continueCursor: string; deleted: number; isDone: boolean }, + Name + >; + listLegacyOAuthApplications: FunctionReference< + "query", + "internal", + { + paginationOpts: { + cursor: string | null; + endCursor?: string | null; + id?: number; + maximumBytesRead?: number; + maximumRowsRead?: number; + numItems: number; + }; + }, + { + continueCursor: string; + isDone: boolean; + page: Array<{ + _creationTime: number; + _id: string; + clientId?: null | string; + createdAt?: null | number; + disabled?: null | boolean; + hadClientSecret: boolean; + icon?: null | string; + metadata?: null | string; + name?: null | string; + redirectUrls?: null | string; + type?: null | string; + updatedAt?: null | number; + userId?: null | string; + }>; + pageStatus?: "SplitRecommended" | "SplitRequired" | null; + splitCursor?: string | null; + }, + Name + >; + validateAccountIssuerBackfill: FunctionReference< + "query", + "internal", + { + paginationOpts: { + cursor: string | null; + endCursor?: string | null; + id?: number; + maximumBytesRead?: number; + maximumRowsRead?: number; + numItems: number; + }; + providerIssuers: Record; + }, + { + alreadyMigrated: number; + continueCursor: string; + isDone: boolean; + pending: number; + }, + Name + >; + }; + testProfiles: { + adapterAdditionalFields: { + create: FunctionReference< + "mutation", + "internal", + { + input: + | { + data: { + cbDefaultValueField?: null | string; + createdAt: number; + customField?: null | string; + dateField?: null | number; + displayUsername?: null | string; + email: string; + emailVerified: boolean; + image?: null | string; + isAnonymous?: null | boolean; + name: string; + numericField?: null | number; + phoneNumber?: null | string; + phoneNumberVerified?: null | boolean; + testField?: null | string; + twoFactorEnabled?: null | boolean; + updatedAt: number; + userId?: null | string; + username?: null | string; + }; + model: "user"; + } + | { + data: { + createdAt: number; + expiresAt: number; + ipAddress?: null | string; + token: string; + updatedAt: number; + userAgent?: null | string; + userId: string; + }; + model: "session"; + } + | { + data: { + accessToken?: null | string; + accessTokenExpiresAt?: null | number; + accountId: string; + createdAt: number; + idToken?: null | string; + issuer?: null | string; + password?: null | string; + providerId: string; + refreshToken?: null | string; + refreshTokenExpiresAt?: null | number; + scope?: null | string; + updatedAt: number; + userId: string; + }; + model: "account"; + } + | { + data: { + createdAt: number; + expiresAt: number; + identifier: string; + updatedAt: number; + value: string; + }; + model: "verification"; + } + | { + data: { + backupCodes: string; + failedVerificationCount?: null | number; + lockedUntil?: null | number; + secret: string; + userId: string; + verified?: null | boolean; + }; + model: "twoFactor"; + } + | { + data: { + clientId?: null | string; + clientSecret?: null | string; + createdAt?: null | number; + disabled?: null | boolean; + icon?: null | string; + metadata?: null | string; + name?: null | string; + redirectUrls?: null | string; + type?: null | string; + updatedAt?: null | number; + userId?: null | string; + }; + model: "oauthApplication"; + } + | { + data: { + accessToken?: null | string; + accessTokenExpiresAt?: null | number; + clientId?: null | string; + createdAt?: null | number; + refreshToken?: null | string; + refreshTokenExpiresAt?: null | number; + scopes?: null | string; + updatedAt?: null | number; + userId?: null | string; + }; + model: "oauthAccessToken"; + } + | { + data: { + clientId?: null | string; + consentGiven?: null | boolean; + createdAt?: null | number; + scopes?: null | string; + updatedAt?: null | number; + userId?: null | string; + }; + model: "oauthConsent"; + } + | { + data: { + alg?: null | string; + createdAt: number; + crv?: null | string; + expiresAt?: null | number; + privateKey: string; + publicKey: string; + }; + model: "jwks"; + } + | { + data: { count: number; key: string; lastRequest: number }; + model: "rateLimit"; + }; + onCreateHandle?: string; + select?: Array; + }, + any, + Name + >; + deleteMany: FunctionReference< + "mutation", + "internal", + { + input: + | { + model: "user"; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "name" + | "email" + | "emailVerified" + | "image" + | "createdAt" + | "updatedAt" + | "twoFactorEnabled" + | "isAnonymous" + | "username" + | "displayUsername" + | "phoneNumber" + | "phoneNumberVerified" + | "userId" + | "customField" + | "numericField" + | "testField" + | "cbDefaultValueField" + | "dateField" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "session"; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "expiresAt" + | "token" + | "createdAt" + | "updatedAt" + | "ipAddress" + | "userAgent" + | "userId" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "account"; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "issuer" + | "accountId" + | "providerId" + | "userId" + | "accessToken" + | "refreshToken" + | "idToken" + | "accessTokenExpiresAt" + | "refreshTokenExpiresAt" + | "scope" + | "password" + | "createdAt" + | "updatedAt" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "verification"; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "identifier" + | "value" + | "expiresAt" + | "createdAt" + | "updatedAt" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "twoFactor"; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "secret" + | "backupCodes" + | "userId" + | "verified" + | "failedVerificationCount" + | "lockedUntil" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "oauthApplication"; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "name" + | "icon" + | "metadata" + | "clientId" + | "clientSecret" + | "redirectUrls" + | "type" + | "disabled" + | "userId" + | "createdAt" + | "updatedAt" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "oauthAccessToken"; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "accessToken" + | "refreshToken" + | "accessTokenExpiresAt" + | "refreshTokenExpiresAt" + | "clientId" + | "userId" + | "scopes" + | "createdAt" + | "updatedAt" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "oauthConsent"; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "clientId" + | "userId" + | "scopes" + | "createdAt" + | "updatedAt" + | "consentGiven" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "jwks"; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "publicKey" + | "privateKey" + | "createdAt" + | "expiresAt" + | "alg" + | "crv" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "rateLimit"; + where?: Array<{ + connector?: "AND" | "OR"; + field: "key" | "count" | "lastRequest" | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + }; + onDeleteHandle?: string; + paginationOpts: { + cursor: string | null; + endCursor?: string | null; + id?: number; + maximumBytesRead?: number; + maximumRowsRead?: number; + numItems: number; + }; + }, + any, + Name + >; + deleteOne: FunctionReference< + "mutation", + "internal", + { + input: + | { + model: "user"; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "name" + | "email" + | "emailVerified" + | "image" + | "createdAt" + | "updatedAt" + | "twoFactorEnabled" + | "isAnonymous" + | "username" + | "displayUsername" + | "phoneNumber" + | "phoneNumberVerified" + | "userId" + | "customField" + | "numericField" + | "testField" + | "cbDefaultValueField" + | "dateField" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "session"; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "expiresAt" + | "token" + | "createdAt" + | "updatedAt" + | "ipAddress" + | "userAgent" + | "userId" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "account"; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "issuer" + | "accountId" + | "providerId" + | "userId" + | "accessToken" + | "refreshToken" + | "idToken" + | "accessTokenExpiresAt" + | "refreshTokenExpiresAt" + | "scope" + | "password" + | "createdAt" + | "updatedAt" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "verification"; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "identifier" + | "value" + | "expiresAt" + | "createdAt" + | "updatedAt" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "twoFactor"; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "secret" + | "backupCodes" + | "userId" + | "verified" + | "failedVerificationCount" + | "lockedUntil" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "oauthApplication"; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "name" + | "icon" + | "metadata" + | "clientId" + | "clientSecret" + | "redirectUrls" + | "type" + | "disabled" + | "userId" + | "createdAt" + | "updatedAt" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "oauthAccessToken"; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "accessToken" + | "refreshToken" + | "accessTokenExpiresAt" + | "refreshTokenExpiresAt" + | "clientId" + | "userId" + | "scopes" + | "createdAt" + | "updatedAt" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "oauthConsent"; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "clientId" + | "userId" + | "scopes" + | "createdAt" + | "updatedAt" + | "consentGiven" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "jwks"; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "publicKey" + | "privateKey" + | "createdAt" + | "expiresAt" + | "alg" + | "crv" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "rateLimit"; + where?: Array<{ + connector?: "AND" | "OR"; + field: "key" | "count" | "lastRequest" | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + }; + onDeleteHandle?: string; + }, + any, + Name + >; + findMany: FunctionReference< + "query", + "internal", + { + join?: any; + limit?: number; + model: + | "user" + | "session" + | "account" + | "verification" + | "twoFactor" + | "oauthApplication" + | "oauthAccessToken" + | "oauthConsent" + | "jwks" + | "rateLimit"; + offset?: number; + paginationOpts: { + cursor: string | null; + endCursor?: string | null; + id?: number; + maximumBytesRead?: number; + maximumRowsRead?: number; + numItems: number; + }; + select?: Array; + sortBy?: { direction: "asc" | "desc"; field: string }; + where?: Array<{ + connector?: "AND" | "OR"; + field: string; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + }, + any, + Name + >; + findOne: FunctionReference< + "query", + "internal", + { + join?: any; + model: + | "user" + | "session" + | "account" + | "verification" + | "twoFactor" + | "oauthApplication" + | "oauthAccessToken" + | "oauthConsent" + | "jwks" + | "rateLimit"; + select?: Array; + where?: Array<{ + connector?: "AND" | "OR"; + field: string; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + }, + any, + Name + >; + incrementOne: FunctionReference< + "mutation", + "internal", + { + input: + | { + increment: Record; + model: "user"; + set?: { + cbDefaultValueField?: null | string; + createdAt?: number; + customField?: null | string; + dateField?: null | number; + displayUsername?: null | string; + email?: string; + emailVerified?: boolean; + image?: null | string; + isAnonymous?: null | boolean; + name?: string; + numericField?: null | number; + phoneNumber?: null | string; + phoneNumberVerified?: null | boolean; + testField?: null | string; + twoFactorEnabled?: null | boolean; + updatedAt?: number; + userId?: null | string; + username?: null | string; + }; + where: Array<{ + connector?: "AND" | "OR"; + field: + | "name" + | "email" + | "emailVerified" + | "image" + | "createdAt" + | "updatedAt" + | "twoFactorEnabled" + | "isAnonymous" + | "username" + | "displayUsername" + | "phoneNumber" + | "phoneNumberVerified" + | "userId" + | "customField" + | "numericField" + | "testField" + | "cbDefaultValueField" + | "dateField" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + increment: Record; + model: "session"; + set?: { + createdAt?: number; + expiresAt?: number; + ipAddress?: null | string; + token?: string; + updatedAt?: number; + userAgent?: null | string; + userId?: string; + }; + where: Array<{ + connector?: "AND" | "OR"; + field: + | "expiresAt" + | "token" + | "createdAt" + | "updatedAt" + | "ipAddress" + | "userAgent" + | "userId" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + increment: Record; + model: "account"; + set?: { + accessToken?: null | string; + accessTokenExpiresAt?: null | number; + accountId?: string; + createdAt?: number; + idToken?: null | string; + issuer?: null | string; + password?: null | string; + providerId?: string; + refreshToken?: null | string; + refreshTokenExpiresAt?: null | number; + scope?: null | string; + updatedAt?: number; + userId?: string; + }; + where: Array<{ + connector?: "AND" | "OR"; + field: + | "issuer" + | "accountId" + | "providerId" + | "userId" + | "accessToken" + | "refreshToken" + | "idToken" + | "accessTokenExpiresAt" + | "refreshTokenExpiresAt" + | "scope" + | "password" + | "createdAt" + | "updatedAt" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + increment: Record; + model: "verification"; + set?: { + createdAt?: number; + expiresAt?: number; + identifier?: string; + updatedAt?: number; + value?: string; + }; + where: Array<{ + connector?: "AND" | "OR"; + field: + | "identifier" + | "value" + | "expiresAt" + | "createdAt" + | "updatedAt" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + increment: Record; + model: "twoFactor"; + set?: { + backupCodes?: string; + failedVerificationCount?: null | number; + lockedUntil?: null | number; + secret?: string; + userId?: string; + verified?: null | boolean; + }; + where: Array<{ + connector?: "AND" | "OR"; + field: + | "secret" + | "backupCodes" + | "userId" + | "verified" + | "failedVerificationCount" + | "lockedUntil" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + increment: Record; + model: "oauthApplication"; + set?: { + clientId?: null | string; + clientSecret?: null | string; + createdAt?: null | number; + disabled?: null | boolean; + icon?: null | string; + metadata?: null | string; + name?: null | string; + redirectUrls?: null | string; + type?: null | string; + updatedAt?: null | number; + userId?: null | string; + }; + where: Array<{ + connector?: "AND" | "OR"; + field: + | "name" + | "icon" + | "metadata" + | "clientId" + | "clientSecret" + | "redirectUrls" + | "type" + | "disabled" + | "userId" + | "createdAt" + | "updatedAt" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + increment: Record; + model: "oauthAccessToken"; + set?: { + accessToken?: null | string; + accessTokenExpiresAt?: null | number; + clientId?: null | string; + createdAt?: null | number; + refreshToken?: null | string; + refreshTokenExpiresAt?: null | number; + scopes?: null | string; + updatedAt?: null | number; + userId?: null | string; + }; + where: Array<{ + connector?: "AND" | "OR"; + field: + | "accessToken" + | "refreshToken" + | "accessTokenExpiresAt" + | "refreshTokenExpiresAt" + | "clientId" + | "userId" + | "scopes" + | "createdAt" + | "updatedAt" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + increment: Record; + model: "oauthConsent"; + set?: { + clientId?: null | string; + consentGiven?: null | boolean; + createdAt?: null | number; + scopes?: null | string; + updatedAt?: null | number; + userId?: null | string; + }; + where: Array<{ + connector?: "AND" | "OR"; + field: + | "clientId" + | "userId" + | "scopes" + | "createdAt" + | "updatedAt" + | "consentGiven" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + increment: Record; + model: "jwks"; + set?: { + alg?: null | string; + createdAt?: number; + crv?: null | string; + expiresAt?: null | number; + privateKey?: string; + publicKey?: string; + }; + where: Array<{ + connector?: "AND" | "OR"; + field: + | "publicKey" + | "privateKey" + | "createdAt" + | "expiresAt" + | "alg" + | "crv" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + increment: Record; + model: "rateLimit"; + set?: { count?: number; key?: string; lastRequest?: number }; + where: Array<{ + connector?: "AND" | "OR"; + field: "key" | "count" | "lastRequest" | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + }; + onUpdateHandle?: string; + }, + any, + Name + >; + updateMany: FunctionReference< + "mutation", + "internal", + { + input: + | { + model: "user"; + update: { + cbDefaultValueField?: null | string; + createdAt?: number; + customField?: null | string; + dateField?: null | number; + displayUsername?: null | string; + email?: string; + emailVerified?: boolean; + image?: null | string; + isAnonymous?: null | boolean; + name?: string; + numericField?: null | number; + phoneNumber?: null | string; + phoneNumberVerified?: null | boolean; + testField?: null | string; + twoFactorEnabled?: null | boolean; + updatedAt?: number; + userId?: null | string; + username?: null | string; + }; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "name" + | "email" + | "emailVerified" + | "image" + | "createdAt" + | "updatedAt" + | "twoFactorEnabled" + | "isAnonymous" + | "username" + | "displayUsername" + | "phoneNumber" + | "phoneNumberVerified" + | "userId" + | "customField" + | "numericField" + | "testField" + | "cbDefaultValueField" + | "dateField" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "session"; + update: { + createdAt?: number; + expiresAt?: number; + ipAddress?: null | string; + token?: string; + updatedAt?: number; + userAgent?: null | string; + userId?: string; + }; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "expiresAt" + | "token" + | "createdAt" + | "updatedAt" + | "ipAddress" + | "userAgent" + | "userId" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "account"; + update: { + accessToken?: null | string; + accessTokenExpiresAt?: null | number; + accountId?: string; + createdAt?: number; + idToken?: null | string; + issuer?: null | string; + password?: null | string; + providerId?: string; + refreshToken?: null | string; + refreshTokenExpiresAt?: null | number; + scope?: null | string; + updatedAt?: number; + userId?: string; + }; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "issuer" + | "accountId" + | "providerId" + | "userId" + | "accessToken" + | "refreshToken" + | "idToken" + | "accessTokenExpiresAt" + | "refreshTokenExpiresAt" + | "scope" + | "password" + | "createdAt" + | "updatedAt" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "verification"; + update: { + createdAt?: number; + expiresAt?: number; + identifier?: string; + updatedAt?: number; + value?: string; + }; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "identifier" + | "value" + | "expiresAt" + | "createdAt" + | "updatedAt" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "twoFactor"; + update: { + backupCodes?: string; + failedVerificationCount?: null | number; + lockedUntil?: null | number; + secret?: string; + userId?: string; + verified?: null | boolean; + }; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "secret" + | "backupCodes" + | "userId" + | "verified" + | "failedVerificationCount" + | "lockedUntil" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "oauthApplication"; + update: { + clientId?: null | string; + clientSecret?: null | string; + createdAt?: null | number; + disabled?: null | boolean; + icon?: null | string; + metadata?: null | string; + name?: null | string; + redirectUrls?: null | string; + type?: null | string; + updatedAt?: null | number; + userId?: null | string; + }; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "name" + | "icon" + | "metadata" + | "clientId" + | "clientSecret" + | "redirectUrls" + | "type" + | "disabled" + | "userId" + | "createdAt" + | "updatedAt" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "oauthAccessToken"; + update: { + accessToken?: null | string; + accessTokenExpiresAt?: null | number; + clientId?: null | string; + createdAt?: null | number; + refreshToken?: null | string; + refreshTokenExpiresAt?: null | number; + scopes?: null | string; + updatedAt?: null | number; + userId?: null | string; + }; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "accessToken" + | "refreshToken" + | "accessTokenExpiresAt" + | "refreshTokenExpiresAt" + | "clientId" + | "userId" + | "scopes" + | "createdAt" + | "updatedAt" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "oauthConsent"; + update: { + clientId?: null | string; + consentGiven?: null | boolean; + createdAt?: null | number; + scopes?: null | string; + updatedAt?: null | number; + userId?: null | string; + }; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "clientId" + | "userId" + | "scopes" + | "createdAt" + | "updatedAt" + | "consentGiven" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "jwks"; + update: { + alg?: null | string; + createdAt?: number; + crv?: null | string; + expiresAt?: null | number; + privateKey?: string; + publicKey?: string; + }; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "publicKey" + | "privateKey" + | "createdAt" + | "expiresAt" + | "alg" + | "crv" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "rateLimit"; + update: { + count?: number; + key?: string; + lastRequest?: number; + }; + where?: Array<{ + connector?: "AND" | "OR"; + field: "key" | "count" | "lastRequest" | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + }; + onUpdateHandle?: string; + paginationOpts: { + cursor: string | null; + endCursor?: string | null; + id?: number; + maximumBytesRead?: number; + maximumRowsRead?: number; + numItems: number; + }; + }, + any, + Name + >; + updateOne: FunctionReference< + "mutation", + "internal", + { + input: + | { + model: "user"; + update: { + cbDefaultValueField?: null | string; + createdAt?: number; + customField?: null | string; + dateField?: null | number; + displayUsername?: null | string; + email?: string; + emailVerified?: boolean; + image?: null | string; + isAnonymous?: null | boolean; + name?: string; + numericField?: null | number; + phoneNumber?: null | string; + phoneNumberVerified?: null | boolean; + testField?: null | string; + twoFactorEnabled?: null | boolean; + updatedAt?: number; + userId?: null | string; + username?: null | string; + }; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "name" + | "email" + | "emailVerified" + | "image" + | "createdAt" + | "updatedAt" + | "twoFactorEnabled" + | "isAnonymous" + | "username" + | "displayUsername" + | "phoneNumber" + | "phoneNumberVerified" + | "userId" + | "customField" + | "numericField" + | "testField" + | "cbDefaultValueField" + | "dateField" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "session"; + update: { + createdAt?: number; + expiresAt?: number; + ipAddress?: null | string; + token?: string; + updatedAt?: number; + userAgent?: null | string; + userId?: string; + }; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "expiresAt" + | "token" + | "createdAt" + | "updatedAt" + | "ipAddress" + | "userAgent" + | "userId" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "account"; + update: { + accessToken?: null | string; + accessTokenExpiresAt?: null | number; + accountId?: string; + createdAt?: number; + idToken?: null | string; + issuer?: null | string; + password?: null | string; + providerId?: string; + refreshToken?: null | string; + refreshTokenExpiresAt?: null | number; + scope?: null | string; + updatedAt?: number; + userId?: string; + }; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "issuer" + | "accountId" + | "providerId" + | "userId" + | "accessToken" + | "refreshToken" + | "idToken" + | "accessTokenExpiresAt" + | "refreshTokenExpiresAt" + | "scope" + | "password" + | "createdAt" + | "updatedAt" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "verification"; + update: { + createdAt?: number; + expiresAt?: number; + identifier?: string; + updatedAt?: number; + value?: string; + }; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "identifier" + | "value" + | "expiresAt" + | "createdAt" + | "updatedAt" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "twoFactor"; + update: { + backupCodes?: string; + failedVerificationCount?: null | number; + lockedUntil?: null | number; + secret?: string; + userId?: string; + verified?: null | boolean; + }; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "secret" + | "backupCodes" + | "userId" + | "verified" + | "failedVerificationCount" + | "lockedUntil" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "oauthApplication"; + update: { + clientId?: null | string; + clientSecret?: null | string; + createdAt?: null | number; + disabled?: null | boolean; + icon?: null | string; + metadata?: null | string; + name?: null | string; + redirectUrls?: null | string; + type?: null | string; + updatedAt?: null | number; + userId?: null | string; + }; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "name" + | "icon" + | "metadata" + | "clientId" + | "clientSecret" + | "redirectUrls" + | "type" + | "disabled" + | "userId" + | "createdAt" + | "updatedAt" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "oauthAccessToken"; + update: { + accessToken?: null | string; + accessTokenExpiresAt?: null | number; + clientId?: null | string; + createdAt?: null | number; + refreshToken?: null | string; + refreshTokenExpiresAt?: null | number; + scopes?: null | string; + updatedAt?: null | number; + userId?: null | string; + }; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "accessToken" + | "refreshToken" + | "accessTokenExpiresAt" + | "refreshTokenExpiresAt" + | "clientId" + | "userId" + | "scopes" + | "createdAt" + | "updatedAt" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "oauthConsent"; + update: { + clientId?: null | string; + consentGiven?: null | boolean; + createdAt?: null | number; + scopes?: null | string; + updatedAt?: null | number; + userId?: null | string; + }; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "clientId" + | "userId" + | "scopes" + | "createdAt" + | "updatedAt" + | "consentGiven" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "jwks"; + update: { + alg?: null | string; + createdAt?: number; + crv?: null | string; + expiresAt?: null | number; + privateKey?: string; + publicKey?: string; + }; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "publicKey" + | "privateKey" + | "createdAt" + | "expiresAt" + | "alg" + | "crv" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "rateLimit"; + update: { + count?: number; + key?: string; + lastRequest?: number; + }; + where?: Array<{ + connector?: "AND" | "OR"; + field: "key" | "count" | "lastRequest" | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + }; + onUpdateHandle?: string; + }, + any, + Name + >; + }; + adapterOrganizationJoins: { + create: FunctionReference< + "mutation", + "internal", + { + input: + | { + data: { + cbDefaultValueField?: null | string; + createdAt: number; + customField?: null | string; + dateField?: null | number; + displayUsername?: null | string; + email?: null | string; + emailVerified: boolean; + email_address?: null | string; + image?: null | string; + isAnonymous?: null | boolean; + name: string; + numericField?: null | number; + phoneNumber?: null | string; + phoneNumberVerified?: null | boolean; + testField?: null | string; + twoFactorEnabled?: null | boolean; + updatedAt: number; + userId?: null | string; + username?: null | string; + }; + model: "user"; + } + | { + data: { + createdAt: number; + expiresAt: number; + ipAddress?: null | string; + token: string; + updatedAt: number; + userAgent?: null | string; + userId: string; + }; + model: "session"; + } + | { + data: { + accessToken?: null | string; + accessTokenExpiresAt?: null | number; + accountId: string; + createdAt: number; + idToken?: null | string; + issuer?: null | string; + password?: null | string; + providerId: string; + refreshToken?: null | string; + refreshTokenExpiresAt?: null | number; + scope?: null | string; + updatedAt: number; + userId: string; + }; + model: "account"; + } + | { + data: { + createdAt: number; + expiresAt: number; + identifier: string; + updatedAt: number; + value: string; + }; + model: "verification"; + } + | { + data: { + backupCodes: string; + failedVerificationCount?: null | number; + lockedUntil?: null | number; + secret: string; + userId: string; + verified?: null | boolean; + }; + model: "twoFactor"; + } + | { + data: { + clientId?: null | string; + clientSecret?: null | string; + createdAt?: null | number; + disabled?: null | boolean; + icon?: null | string; + metadata?: null | string; + name?: null | string; + redirectUrls?: null | string; + type?: null | string; + updatedAt?: null | number; + userId?: null | string; + }; + model: "oauthApplication"; + } + | { + data: { + accessToken?: null | string; + accessTokenExpiresAt?: null | number; + clientId?: null | string; + createdAt?: null | number; + refreshToken?: null | string; + refreshTokenExpiresAt?: null | number; + scopes?: null | string; + updatedAt?: null | number; + userId?: null | string; + }; + model: "oauthAccessToken"; + } + | { + data: { + clientId?: null | string; + consentGiven?: null | boolean; + createdAt?: null | number; + scopes?: null | string; + updatedAt?: null | number; + userId?: null | string; + }; + model: "oauthConsent"; + } + | { + data: { + alg?: null | string; + createdAt: number; + crv?: null | string; + expiresAt?: null | number; + privateKey: string; + publicKey: string; + }; + model: "jwks"; + } + | { + data: { count: number; key: string; lastRequest: number }; + model: "rateLimit"; + } + | { + data: { + cbDefaultValueField?: null | string; + createdAt: number; + customField?: null | string; + dateField?: null | number; + displayUsername?: null | string; + email?: null | string; + emailVerified: boolean; + email_address?: null | string; + image?: null | string; + isAnonymous?: null | boolean; + name: string; + numericField?: null | number; + phoneNumber?: null | string; + phoneNumberVerified?: null | boolean; + testField?: null | string; + twoFactorEnabled?: null | boolean; + updatedAt: number; + userId?: null | string; + username?: null | string; + }; + model: "user_custom"; + } + | { + data: { + cbDefaultValueField?: null | string; + createdAt: number; + customField?: null | string; + dateField?: null | number; + displayUsername?: null | string; + email?: null | string; + emailVerified: boolean; + email_address?: null | string; + image?: null | string; + isAnonymous?: null | boolean; + name: string; + numericField?: null | number; + phoneNumber?: null | string; + phoneNumberVerified?: null | boolean; + testField?: null | string; + twoFactorEnabled?: null | boolean; + updatedAt: number; + userId?: null | string; + username?: null | string; + }; + model: "user_table"; + } + | { data: { oneToOne: string }; model: "oneToOneTable" } + | { + data: { + oneToOne?: null | string; + one_to_one?: null | string; + }; + model: "one_to_one_table"; + } + | { + data: { + cbDefaultValueField?: null | string; + json?: any; + nullableReference?: null | string; + numberArray?: null | Array; + stringArray?: null | Array; + testField?: null | string; + }; + model: "testModel"; + } + | { + data: { + createdAt: number; + logo?: null | string; + metadata?: null | string; + name: string; + slug: string; + updatedAt?: null | number; + }; + model: "organization"; + } + | { + data: { + createdAt: number; + organizationId: string; + role: string; + updatedAt?: null | number; + userId: string; + }; + model: "member"; + } + | { + data: { + createdAt: number; + memberCount: number; + name: string; + organizationId: string; + updatedAt?: null | number; + }; + model: "team"; + } + | { + data: { + createdAt?: null | number; + membershipKey?: null | string; + teamId: string; + userId: string; + }; + model: "teamMember"; + } + | { + data: { + createdAt?: null | number; + email?: null | string; + expiresAt?: null | number; + inviterId?: null | string; + organizationId?: null | string; + role?: null | string; + status?: null | string; + teamId?: null | string; + updatedAt?: null | number; + }; + model: "invitation"; + }; + onCreateHandle?: string; + select?: Array; + }, + any, + Name + >; + deleteMany: FunctionReference< + "mutation", + "internal", + { + input: + | { + model: "user"; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "name" + | "email" + | "email_address" + | "emailVerified" + | "image" + | "createdAt" + | "updatedAt" + | "twoFactorEnabled" + | "isAnonymous" + | "username" + | "displayUsername" + | "phoneNumber" + | "phoneNumberVerified" + | "userId" + | "testField" + | "cbDefaultValueField" + | "customField" + | "numericField" + | "dateField" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "session"; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "expiresAt" + | "token" + | "createdAt" + | "updatedAt" + | "ipAddress" + | "userAgent" + | "userId" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "account"; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "issuer" + | "accountId" + | "providerId" + | "userId" + | "accessToken" + | "refreshToken" + | "idToken" + | "accessTokenExpiresAt" + | "refreshTokenExpiresAt" + | "scope" + | "password" + | "createdAt" + | "updatedAt" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "verification"; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "identifier" + | "value" + | "expiresAt" + | "createdAt" + | "updatedAt" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "twoFactor"; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "secret" + | "backupCodes" + | "userId" + | "verified" + | "failedVerificationCount" + | "lockedUntil" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "oauthApplication"; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "name" + | "icon" + | "metadata" + | "clientId" + | "clientSecret" + | "redirectUrls" + | "type" + | "disabled" + | "userId" + | "createdAt" + | "updatedAt" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "oauthAccessToken"; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "accessToken" + | "refreshToken" + | "accessTokenExpiresAt" + | "refreshTokenExpiresAt" + | "clientId" + | "userId" + | "scopes" + | "createdAt" + | "updatedAt" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "oauthConsent"; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "clientId" + | "userId" + | "scopes" + | "createdAt" + | "updatedAt" + | "consentGiven" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "jwks"; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "publicKey" + | "privateKey" + | "createdAt" + | "expiresAt" + | "alg" + | "crv" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "rateLimit"; + where?: Array<{ + connector?: "AND" | "OR"; + field: "key" | "count" | "lastRequest" | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "user_custom"; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "name" + | "email" + | "email_address" + | "emailVerified" + | "image" + | "createdAt" + | "updatedAt" + | "twoFactorEnabled" + | "isAnonymous" + | "username" + | "displayUsername" + | "phoneNumber" + | "phoneNumberVerified" + | "userId" + | "testField" + | "cbDefaultValueField" + | "customField" + | "numericField" + | "dateField" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "user_table"; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "name" + | "email" + | "email_address" + | "emailVerified" + | "image" + | "createdAt" + | "updatedAt" + | "twoFactorEnabled" + | "isAnonymous" + | "username" + | "displayUsername" + | "phoneNumber" + | "phoneNumberVerified" + | "userId" + | "testField" + | "cbDefaultValueField" + | "customField" + | "numericField" + | "dateField" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "oneToOneTable"; + where?: Array<{ + connector?: "AND" | "OR"; + field: "oneToOne" | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "one_to_one_table"; + where?: Array<{ + connector?: "AND" | "OR"; + field: "oneToOne" | "one_to_one" | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "testModel"; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "nullableReference" + | "testField" + | "cbDefaultValueField" + | "stringArray" + | "numberArray" + | "json" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "organization"; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "name" + | "slug" + | "logo" + | "metadata" + | "createdAt" + | "updatedAt" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "member"; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "organizationId" + | "userId" + | "role" + | "createdAt" + | "updatedAt" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "team"; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "name" + | "memberCount" + | "organizationId" + | "createdAt" + | "updatedAt" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "teamMember"; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "teamId" + | "userId" + | "membershipKey" + | "createdAt" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "invitation"; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "email" + | "role" + | "status" + | "organizationId" + | "teamId" + | "inviterId" + | "expiresAt" + | "createdAt" + | "updatedAt" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + }; + onDeleteHandle?: string; + paginationOpts: { + cursor: string | null; + endCursor?: string | null; + id?: number; + maximumBytesRead?: number; + maximumRowsRead?: number; + numItems: number; + }; + }, + any, + Name + >; + deleteOne: FunctionReference< + "mutation", + "internal", + { + input: + | { + model: "user"; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "name" + | "email" + | "email_address" + | "emailVerified" + | "image" + | "createdAt" + | "updatedAt" + | "twoFactorEnabled" + | "isAnonymous" + | "username" + | "displayUsername" + | "phoneNumber" + | "phoneNumberVerified" + | "userId" + | "testField" + | "cbDefaultValueField" + | "customField" + | "numericField" + | "dateField" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "session"; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "expiresAt" + | "token" + | "createdAt" + | "updatedAt" + | "ipAddress" + | "userAgent" + | "userId" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "account"; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "issuer" + | "accountId" + | "providerId" + | "userId" + | "accessToken" + | "refreshToken" + | "idToken" + | "accessTokenExpiresAt" + | "refreshTokenExpiresAt" + | "scope" + | "password" + | "createdAt" + | "updatedAt" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "verification"; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "identifier" + | "value" + | "expiresAt" | "createdAt" | "updatedAt" - | "ipAddress" - | "userAgent" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "twoFactor"; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "secret" + | "backupCodes" + | "userId" + | "verified" + | "failedVerificationCount" + | "lockedUntil" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "oauthApplication"; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "name" + | "icon" + | "metadata" + | "clientId" + | "clientSecret" + | "redirectUrls" + | "type" + | "disabled" + | "userId" + | "createdAt" + | "updatedAt" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "oauthAccessToken"; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "accessToken" + | "refreshToken" + | "accessTokenExpiresAt" + | "refreshTokenExpiresAt" + | "clientId" + | "userId" + | "scopes" + | "createdAt" + | "updatedAt" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "oauthConsent"; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "clientId" + | "userId" + | "scopes" + | "createdAt" + | "updatedAt" + | "consentGiven" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "jwks"; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "publicKey" + | "privateKey" + | "createdAt" + | "expiresAt" + | "alg" + | "crv" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "rateLimit"; + where?: Array<{ + connector?: "AND" | "OR"; + field: "key" | "count" | "lastRequest" | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "user_custom"; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "name" + | "email" + | "email_address" + | "emailVerified" + | "image" + | "createdAt" + | "updatedAt" + | "twoFactorEnabled" + | "isAnonymous" + | "username" + | "displayUsername" + | "phoneNumber" + | "phoneNumberVerified" | "userId" + | "testField" + | "cbDefaultValueField" + | "customField" + | "numericField" + | "dateField" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -2118,22 +6334,29 @@ export type ComponentApi = }>; } | { - model: "account"; + model: "user_table"; where?: Array<{ connector?: "AND" | "OR"; field: - | "accountId" - | "providerId" - | "userId" - | "accessToken" - | "refreshToken" - | "idToken" - | "accessTokenExpiresAt" - | "refreshTokenExpiresAt" - | "scope" - | "password" + | "name" + | "email" + | "email_address" + | "emailVerified" + | "image" | "createdAt" | "updatedAt" + | "twoFactorEnabled" + | "isAnonymous" + | "username" + | "displayUsername" + | "phoneNumber" + | "phoneNumberVerified" + | "userId" + | "testField" + | "cbDefaultValueField" + | "customField" + | "numericField" + | "dateField" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -2158,16 +6381,10 @@ export type ComponentApi = }>; } | { - model: "verification"; + model: "oneToOneTable"; where?: Array<{ connector?: "AND" | "OR"; - field: - | "identifier" - | "value" - | "expiresAt" - | "createdAt" - | "updatedAt" - | "_id"; + field: "oneToOne" | "_id"; mode?: "sensitive" | "insensitive"; operator?: | "lt" @@ -2191,14 +6408,43 @@ export type ComponentApi = }>; } | { - model: "twoFactor"; + model: "one_to_one_table"; + where?: Array<{ + connector?: "AND" | "OR"; + field: "oneToOne" | "one_to_one" | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "testModel"; where?: Array<{ connector?: "AND" | "OR"; field: - | "secret" - | "backupCodes" - | "userId" - | "verified" + | "nullableReference" + | "testField" + | "cbDefaultValueField" + | "stringArray" + | "numberArray" + | "json" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -2223,19 +6469,14 @@ export type ComponentApi = }>; } | { - model: "oauthApplication"; + model: "organization"; where?: Array<{ connector?: "AND" | "OR"; field: | "name" - | "icon" + | "slug" + | "logo" | "metadata" - | "clientId" - | "clientSecret" - | "redirectUrls" - | "type" - | "disabled" - | "userId" | "createdAt" | "updatedAt" | "_id"; @@ -2262,17 +6503,13 @@ export type ComponentApi = }>; } | { - model: "oauthAccessToken"; + model: "member"; where?: Array<{ connector?: "AND" | "OR"; field: - | "accessToken" - | "refreshToken" - | "accessTokenExpiresAt" - | "refreshTokenExpiresAt" - | "clientId" + | "organizationId" | "userId" - | "scopes" + | "role" | "createdAt" | "updatedAt" | "_id"; @@ -2299,16 +6536,15 @@ export type ComponentApi = }>; } | { - model: "oauthConsent"; + model: "team"; where?: Array<{ connector?: "AND" | "OR"; field: - | "clientId" - | "userId" - | "scopes" + | "name" + | "memberCount" + | "organizationId" | "createdAt" | "updatedAt" - | "consentGiven" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -2333,14 +6569,14 @@ export type ComponentApi = }>; } | { - model: "jwks"; + model: "teamMember"; where?: Array<{ connector?: "AND" | "OR"; field: - | "publicKey" - | "privateKey" + | "teamId" + | "userId" + | "membershipKey" | "createdAt" - | "expiresAt" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -2365,10 +6601,20 @@ export type ComponentApi = }>; } | { - model: "rateLimit"; + model: "invitation"; where?: Array<{ connector?: "AND" | "OR"; - field: "key" | "count" | "lastRequest" | "_id"; + field: + | "email" + | "role" + | "status" + | "organizationId" + | "teamId" + | "inviterId" + | "expiresAt" + | "createdAt" + | "updatedAt" + | "_id"; mode?: "sensitive" | "insensitive"; operator?: | "lt" @@ -2392,6 +6638,38 @@ export type ComponentApi = }>; }; onDeleteHandle?: string; + }, + any, + Name + >; + findMany: FunctionReference< + "query", + "internal", + { + join?: any; + limit?: number; + model: + | "user" + | "session" + | "account" + | "verification" + | "twoFactor" + | "oauthApplication" + | "oauthAccessToken" + | "oauthConsent" + | "jwks" + | "rateLimit" + | "user_custom" + | "user_table" + | "oneToOneTable" + | "one_to_one_table" + | "testModel" + | "organization" + | "member" + | "team" + | "teamMember" + | "invitation"; + offset?: number; paginationOpts: { cursor: string | null; endCursor?: string | null; @@ -2400,22 +6678,126 @@ export type ComponentApi = maximumRowsRead?: number; numItems: number; }; + select?: Array; + sortBy?: { direction: "asc" | "desc"; field: string }; + where?: Array<{ + connector?: "AND" | "OR"; + field: string; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; }, any, Name >; - deleteOne: FunctionReference< + findOne: FunctionReference< + "query", + "internal", + { + join?: any; + model: + | "user" + | "session" + | "account" + | "verification" + | "twoFactor" + | "oauthApplication" + | "oauthAccessToken" + | "oauthConsent" + | "jwks" + | "rateLimit" + | "user_custom" + | "user_table" + | "oneToOneTable" + | "one_to_one_table" + | "testModel" + | "organization" + | "member" + | "team" + | "teamMember" + | "invitation"; + select?: Array; + where?: Array<{ + connector?: "AND" | "OR"; + field: string; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + }, + any, + Name + >; + incrementOne: FunctionReference< "mutation", "internal", { input: | { + increment: Record; model: "user"; - where?: Array<{ + set?: { + cbDefaultValueField?: null | string; + createdAt?: number; + customField?: null | string; + dateField?: null | number; + displayUsername?: null | string; + email?: null | string; + emailVerified?: boolean; + email_address?: null | string; + image?: null | string; + isAnonymous?: null | boolean; + name?: string; + numericField?: null | number; + phoneNumber?: null | string; + phoneNumberVerified?: null | boolean; + testField?: null | string; + twoFactorEnabled?: null | boolean; + updatedAt?: number; + userId?: null | string; + username?: null | string; + }; + where: Array<{ connector?: "AND" | "OR"; field: | "name" | "email" + | "email_address" | "emailVerified" | "image" | "createdAt" @@ -2427,10 +6809,10 @@ export type ComponentApi = | "phoneNumber" | "phoneNumberVerified" | "userId" - | "customField" - | "numericField" | "testField" | "cbDefaultValueField" + | "customField" + | "numericField" | "dateField" | "_id"; mode?: "sensitive" | "insensitive"; @@ -2456,8 +6838,18 @@ export type ComponentApi = }>; } | { + increment: Record; model: "session"; - where?: Array<{ + set?: { + createdAt?: number; + expiresAt?: number; + ipAddress?: null | string; + token?: string; + updatedAt?: number; + userAgent?: null | string; + userId?: string; + }; + where: Array<{ connector?: "AND" | "OR"; field: | "expiresAt" @@ -2491,10 +6883,27 @@ export type ComponentApi = }>; } | { + increment: Record; model: "account"; - where?: Array<{ + set?: { + accessToken?: null | string; + accessTokenExpiresAt?: null | number; + accountId?: string; + createdAt?: number; + idToken?: null | string; + issuer?: null | string; + password?: null | string; + providerId?: string; + refreshToken?: null | string; + refreshTokenExpiresAt?: null | number; + scope?: null | string; + updatedAt?: number; + userId?: string; + }; + where: Array<{ connector?: "AND" | "OR"; field: + | "issuer" | "accountId" | "providerId" | "userId" @@ -2531,8 +6940,16 @@ export type ComponentApi = }>; } | { + increment: Record; model: "verification"; - where?: Array<{ + set?: { + createdAt?: number; + expiresAt?: number; + identifier?: string; + updatedAt?: number; + value?: string; + }; + where: Array<{ connector?: "AND" | "OR"; field: | "identifier" @@ -2564,14 +6981,25 @@ export type ComponentApi = }>; } | { + increment: Record; model: "twoFactor"; - where?: Array<{ + set?: { + backupCodes?: string; + failedVerificationCount?: null | number; + lockedUntil?: null | number; + secret?: string; + userId?: string; + verified?: null | boolean; + }; + where: Array<{ connector?: "AND" | "OR"; field: | "secret" | "backupCodes" | "userId" | "verified" + | "failedVerificationCount" + | "lockedUntil" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -2596,8 +7024,22 @@ export type ComponentApi = }>; } | { + increment: Record; model: "oauthApplication"; - where?: Array<{ + set?: { + clientId?: null | string; + clientSecret?: null | string; + createdAt?: null | number; + disabled?: null | boolean; + icon?: null | string; + metadata?: null | string; + name?: null | string; + redirectUrls?: null | string; + type?: null | string; + updatedAt?: null | number; + userId?: null | string; + }; + where: Array<{ connector?: "AND" | "OR"; field: | "name" @@ -2635,8 +7077,20 @@ export type ComponentApi = }>; } | { + increment: Record; model: "oauthAccessToken"; - where?: Array<{ + set?: { + accessToken?: null | string; + accessTokenExpiresAt?: null | number; + clientId?: null | string; + createdAt?: null | number; + refreshToken?: null | string; + refreshTokenExpiresAt?: null | number; + scopes?: null | string; + updatedAt?: null | number; + userId?: null | string; + }; + where: Array<{ connector?: "AND" | "OR"; field: | "accessToken" @@ -2672,8 +7126,17 @@ export type ComponentApi = }>; } | { + increment: Record; model: "oauthConsent"; - where?: Array<{ + set?: { + clientId?: null | string; + consentGiven?: null | boolean; + createdAt?: null | number; + scopes?: null | string; + updatedAt?: null | number; + userId?: null | string; + }; + where: Array<{ connector?: "AND" | "OR"; field: | "clientId" @@ -2703,188 +7166,92 @@ export type ComponentApi = | Array | Array | null; - }>; - } - | { - model: "jwks"; - where?: Array<{ - connector?: "AND" | "OR"; - field: - | "publicKey" - | "privateKey" - | "createdAt" - | "expiresAt" - | "_id"; - mode?: "sensitive" | "insensitive"; - operator?: - | "lt" - | "lte" - | "gt" - | "gte" - | "eq" - | "in" - | "not_in" - | "ne" - | "contains" - | "starts_with" - | "ends_with"; - value: - | string - | number - | boolean - | Array - | Array - | null; - }>; - } - | { - model: "rateLimit"; - where?: Array<{ - connector?: "AND" | "OR"; - field: "key" | "count" | "lastRequest" | "_id"; - mode?: "sensitive" | "insensitive"; - operator?: - | "lt" - | "lte" - | "gt" - | "gte" - | "eq" - | "in" - | "not_in" - | "ne" - | "contains" - | "starts_with" - | "ends_with"; - value: - | string - | number - | boolean - | Array - | Array - | null; - }>; - }; - onDeleteHandle?: string; - }, - any, - Name - >; - findMany: FunctionReference< - "query", - "internal", - { - join?: any; - limit?: number; - model: - | "user" - | "session" - | "account" - | "verification" - | "twoFactor" - | "oauthApplication" - | "oauthAccessToken" - | "oauthConsent" - | "jwks" - | "rateLimit"; - offset?: number; - paginationOpts: { - cursor: string | null; - endCursor?: string | null; - id?: number; - maximumBytesRead?: number; - maximumRowsRead?: number; - numItems: number; - }; - select?: Array; - sortBy?: { direction: "asc" | "desc"; field: string }; - where?: Array<{ - connector?: "AND" | "OR"; - field: string; - mode?: "sensitive" | "insensitive"; - operator?: - | "lt" - | "lte" - | "gt" - | "gte" - | "eq" - | "in" - | "not_in" - | "ne" - | "contains" - | "starts_with" - | "ends_with"; - value: - | string - | number - | boolean - | Array - | Array - | null; - }>; - }, - any, - Name - >; - findOne: FunctionReference< - "query", - "internal", - { - join?: any; - model: - | "user" - | "session" - | "account" - | "verification" - | "twoFactor" - | "oauthApplication" - | "oauthAccessToken" - | "oauthConsent" - | "jwks" - | "rateLimit"; - select?: Array; - where?: Array<{ - connector?: "AND" | "OR"; - field: string; - mode?: "sensitive" | "insensitive"; - operator?: - | "lt" - | "lte" - | "gt" - | "gte" - | "eq" - | "in" - | "not_in" - | "ne" - | "contains" - | "starts_with" - | "ends_with"; - value: - | string - | number - | boolean - | Array - | Array - | null; - }>; - }, - any, - Name - >; - updateMany: FunctionReference< - "mutation", - "internal", - { - input: + }>; + } | { - model: "user"; - update: { + increment: Record; + model: "jwks"; + set?: { + alg?: null | string; + createdAt?: number; + crv?: null | string; + expiresAt?: null | number; + privateKey?: string; + publicKey?: string; + }; + where: Array<{ + connector?: "AND" | "OR"; + field: + | "publicKey" + | "privateKey" + | "createdAt" + | "expiresAt" + | "alg" + | "crv" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + increment: Record; + model: "rateLimit"; + set?: { count?: number; key?: string; lastRequest?: number }; + where: Array<{ + connector?: "AND" | "OR"; + field: "key" | "count" | "lastRequest" | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + increment: Record; + model: "user_custom"; + set?: { cbDefaultValueField?: null | string; createdAt?: number; customField?: null | string; dateField?: null | number; displayUsername?: null | string; - email?: string; + email?: null | string; emailVerified?: boolean; + email_address?: null | string; image?: null | string; isAnonymous?: null | boolean; name?: string; @@ -2897,11 +7264,12 @@ export type ComponentApi = userId?: null | string; username?: null | string; }; - where?: Array<{ + where: Array<{ connector?: "AND" | "OR"; field: | "name" | "email" + | "email_address" | "emailVerified" | "image" | "createdAt" @@ -2913,10 +7281,10 @@ export type ComponentApi = | "phoneNumber" | "phoneNumberVerified" | "userId" - | "customField" - | "numericField" | "testField" | "cbDefaultValueField" + | "customField" + | "numericField" | "dateField" | "_id"; mode?: "sensitive" | "insensitive"; @@ -2942,26 +7310,51 @@ export type ComponentApi = }>; } | { - model: "session"; - update: { + increment: Record; + model: "user_table"; + set?: { + cbDefaultValueField?: null | string; createdAt?: number; - expiresAt?: number; - ipAddress?: null | string; - token?: string; + customField?: null | string; + dateField?: null | number; + displayUsername?: null | string; + email?: null | string; + emailVerified?: boolean; + email_address?: null | string; + image?: null | string; + isAnonymous?: null | boolean; + name?: string; + numericField?: null | number; + phoneNumber?: null | string; + phoneNumberVerified?: null | boolean; + testField?: null | string; + twoFactorEnabled?: null | boolean; updatedAt?: number; - userAgent?: null | string; - userId?: string; + userId?: null | string; + username?: null | string; }; - where?: Array<{ + where: Array<{ connector?: "AND" | "OR"; field: - | "expiresAt" - | "token" + | "name" + | "email" + | "email_address" + | "emailVerified" + | "image" | "createdAt" | "updatedAt" - | "ipAddress" - | "userAgent" + | "twoFactorEnabled" + | "isAnonymous" + | "username" + | "displayUsername" + | "phoneNumber" + | "phoneNumberVerified" | "userId" + | "testField" + | "cbDefaultValueField" + | "customField" + | "numericField" + | "dateField" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -2986,37 +7379,12 @@ export type ComponentApi = }>; } | { - model: "account"; - update: { - accessToken?: null | string; - accessTokenExpiresAt?: null | number; - accountId?: string; - createdAt?: number; - idToken?: null | string; - password?: null | string; - providerId?: string; - refreshToken?: null | string; - refreshTokenExpiresAt?: null | number; - scope?: null | string; - updatedAt?: number; - userId?: string; - }; - where?: Array<{ + increment: Record; + model: "oneToOneTable"; + set?: { oneToOne?: string }; + where: Array<{ connector?: "AND" | "OR"; - field: - | "accountId" - | "providerId" - | "userId" - | "accessToken" - | "refreshToken" - | "idToken" - | "accessTokenExpiresAt" - | "refreshTokenExpiresAt" - | "scope" - | "password" - | "createdAt" - | "updatedAt" - | "_id"; + field: "oneToOne" | "_id"; mode?: "sensitive" | "insensitive"; operator?: | "lt" @@ -3040,23 +7408,15 @@ export type ComponentApi = }>; } | { - model: "verification"; - update: { - createdAt?: number; - expiresAt?: number; - identifier?: string; - updatedAt?: number; - value?: string; + increment: Record; + model: "one_to_one_table"; + set?: { + oneToOne?: null | string; + one_to_one?: null | string; }; - where?: Array<{ + where: Array<{ connector?: "AND" | "OR"; - field: - | "identifier" - | "value" - | "expiresAt" - | "createdAt" - | "updatedAt" - | "_id"; + field: "oneToOne" | "one_to_one" | "_id"; mode?: "sensitive" | "insensitive"; operator?: | "lt" @@ -3080,20 +7440,25 @@ export type ComponentApi = }>; } | { - model: "twoFactor"; - update: { - backupCodes?: string; - secret?: string; - userId?: string; - verified?: null | boolean; + increment: Record; + model: "testModel"; + set?: { + cbDefaultValueField?: null | string; + json?: any; + nullableReference?: null | string; + numberArray?: null | Array; + stringArray?: null | Array; + testField?: null | string; }; - where?: Array<{ + where: Array<{ connector?: "AND" | "OR"; field: - | "secret" - | "backupCodes" - | "userId" - | "verified" + | "nullableReference" + | "testField" + | "cbDefaultValueField" + | "stringArray" + | "numberArray" + | "json" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -3118,32 +7483,23 @@ export type ComponentApi = }>; } | { - model: "oauthApplication"; - update: { - clientId?: null | string; - clientSecret?: null | string; - createdAt?: null | number; - disabled?: null | boolean; - icon?: null | string; + increment: Record; + model: "organization"; + set?: { + createdAt?: number; + logo?: null | string; metadata?: null | string; - name?: null | string; - redirectUrls?: null | string; - type?: null | string; + name?: string; + slug?: string; updatedAt?: null | number; - userId?: null | string; }; - where?: Array<{ + where: Array<{ connector?: "AND" | "OR"; field: | "name" - | "icon" + | "slug" + | "logo" | "metadata" - | "clientId" - | "clientSecret" - | "redirectUrls" - | "type" - | "disabled" - | "userId" | "createdAt" | "updatedAt" | "_id"; @@ -3170,28 +7526,21 @@ export type ComponentApi = }>; } | { - model: "oauthAccessToken"; - update: { - accessToken?: null | string; - accessTokenExpiresAt?: null | number; - clientId?: null | string; - createdAt?: null | number; - refreshToken?: null | string; - refreshTokenExpiresAt?: null | number; - scopes?: null | string; + increment: Record; + model: "member"; + set?: { + createdAt?: number; + organizationId?: string; + role?: string; updatedAt?: null | number; - userId?: null | string; + userId?: string; }; - where?: Array<{ + where: Array<{ connector?: "AND" | "OR"; field: - | "accessToken" - | "refreshToken" - | "accessTokenExpiresAt" - | "refreshTokenExpiresAt" - | "clientId" + | "organizationId" | "userId" - | "scopes" + | "role" | "createdAt" | "updatedAt" | "_id"; @@ -3215,27 +7564,26 @@ export type ComponentApi = | Array | Array | null; - }>; - } - | { - model: "oauthConsent"; - update: { - clientId?: null | string; - consentGiven?: null | boolean; - createdAt?: null | number; - scopes?: null | string; + }>; + } + | { + increment: Record; + model: "team"; + set?: { + createdAt?: number; + memberCount?: number; + name?: string; + organizationId?: string; updatedAt?: null | number; - userId?: null | string; }; - where?: Array<{ + where: Array<{ connector?: "AND" | "OR"; field: - | "clientId" - | "userId" - | "scopes" + | "name" + | "memberCount" + | "organizationId" | "createdAt" | "updatedAt" - | "consentGiven" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -3260,20 +7608,21 @@ export type ComponentApi = }>; } | { - model: "jwks"; - update: { - createdAt?: number; - expiresAt?: null | number; - privateKey?: string; - publicKey?: string; + increment: Record; + model: "teamMember"; + set?: { + createdAt?: null | number; + membershipKey?: null | string; + teamId?: string; + userId?: string; }; - where?: Array<{ + where: Array<{ connector?: "AND" | "OR"; field: - | "publicKey" - | "privateKey" + | "teamId" + | "userId" + | "membershipKey" | "createdAt" - | "expiresAt" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -3298,15 +7647,32 @@ export type ComponentApi = }>; } | { - model: "rateLimit"; - update: { - count?: number; - key?: string; - lastRequest?: number; + increment: Record; + model: "invitation"; + set?: { + createdAt?: null | number; + email?: null | string; + expiresAt?: null | number; + inviterId?: null | string; + organizationId?: null | string; + role?: null | string; + status?: null | string; + teamId?: null | string; + updatedAt?: null | number; }; - where?: Array<{ + where: Array<{ connector?: "AND" | "OR"; - field: "key" | "count" | "lastRequest" | "_id"; + field: + | "email" + | "role" + | "status" + | "organizationId" + | "teamId" + | "inviterId" + | "expiresAt" + | "createdAt" + | "updatedAt" + | "_id"; mode?: "sensitive" | "insensitive"; operator?: | "lt" @@ -3330,19 +7696,11 @@ export type ComponentApi = }>; }; onUpdateHandle?: string; - paginationOpts: { - cursor: string | null; - endCursor?: string | null; - id?: number; - maximumBytesRead?: number; - maximumRowsRead?: number; - numItems: number; - }; }, any, Name >; - updateOne: FunctionReference< + updateMany: FunctionReference< "mutation", "internal", { @@ -3355,8 +7713,9 @@ export type ComponentApi = customField?: null | string; dateField?: null | number; displayUsername?: null | string; - email?: string; + email?: null | string; emailVerified?: boolean; + email_address?: null | string; image?: null | string; isAnonymous?: null | boolean; name?: string; @@ -3374,6 +7733,7 @@ export type ComponentApi = field: | "name" | "email" + | "email_address" | "emailVerified" | "image" | "createdAt" @@ -3385,10 +7745,10 @@ export type ComponentApi = | "phoneNumber" | "phoneNumberVerified" | "userId" - | "customField" - | "numericField" | "testField" | "cbDefaultValueField" + | "customField" + | "numericField" | "dateField" | "_id"; mode?: "sensitive" | "insensitive"; @@ -3465,6 +7825,7 @@ export type ComponentApi = accountId?: string; createdAt?: number; idToken?: null | string; + issuer?: null | string; password?: null | string; providerId?: string; refreshToken?: null | string; @@ -3476,6 +7837,7 @@ export type ComponentApi = where?: Array<{ connector?: "AND" | "OR"; field: + | "issuer" | "accountId" | "providerId" | "userId" @@ -3555,6 +7917,8 @@ export type ComponentApi = model: "twoFactor"; update: { backupCodes?: string; + failedVerificationCount?: null | number; + lockedUntil?: null | number; secret?: string; userId?: string; verified?: null | boolean; @@ -3566,6 +7930,8 @@ export type ComponentApi = | "backupCodes" | "userId" | "verified" + | "failedVerificationCount" + | "lockedUntil" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -3734,7 +8100,9 @@ export type ComponentApi = | { model: "jwks"; update: { + alg?: null | string; createdAt?: number; + crv?: null | string; expiresAt?: null | number; privateKey?: string; publicKey?: string; @@ -3746,6 +8114,8 @@ export type ComponentApi = | "privateKey" | "createdAt" | "expiresAt" + | "alg" + | "crv" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -3800,277 +8170,98 @@ export type ComponentApi = | Array | null; }>; - }; - onUpdateHandle?: string; - }, - any, - Name - >; - }; - adapterOrganizationJoins: { - create: FunctionReference< - "mutation", - "internal", - { - input: - | { - data: { - cbDefaultValueField?: null | string; - createdAt: number; - customField?: null | string; - dateField?: null | number; - displayUsername?: null | string; - email?: null | string; - emailVerified: boolean; - email_address?: null | string; - image?: null | string; - isAnonymous?: null | boolean; - name: string; - numericField?: null | number; - phoneNumber?: null | string; - phoneNumberVerified?: null | boolean; - testField?: null | string; - twoFactorEnabled?: null | boolean; - updatedAt: number; - userId?: null | string; - username?: null | string; - }; - model: "user"; - } - | { - data: { - createdAt: number; - expiresAt: number; - ipAddress?: null | string; - token: string; - updatedAt: number; - userAgent?: null | string; - userId: string; - }; - model: "session"; - } - | { - data: { - accessToken?: null | string; - accessTokenExpiresAt?: null | number; - accountId: string; - createdAt: number; - idToken?: null | string; - password?: null | string; - providerId: string; - refreshToken?: null | string; - refreshTokenExpiresAt?: null | number; - scope?: null | string; - updatedAt: number; - userId: string; - }; - model: "account"; - } - | { - data: { - createdAt: number; - expiresAt: number; - identifier: string; - updatedAt: number; - value: string; - }; - model: "verification"; - } - | { - data: { - backupCodes: string; - secret: string; - userId: string; - verified?: null | boolean; - }; - model: "twoFactor"; - } - | { - data: { - clientId?: null | string; - clientSecret?: null | string; - createdAt?: null | number; - disabled?: null | boolean; - icon?: null | string; - metadata?: null | string; - name?: null | string; - redirectUrls?: null | string; - type?: null | string; - updatedAt?: null | number; - userId?: null | string; - }; - model: "oauthApplication"; - } - | { - data: { - accessToken?: null | string; - accessTokenExpiresAt?: null | number; - clientId?: null | string; - createdAt?: null | number; - refreshToken?: null | string; - refreshTokenExpiresAt?: null | number; - scopes?: null | string; - updatedAt?: null | number; - userId?: null | string; - }; - model: "oauthAccessToken"; - } - | { - data: { - clientId?: null | string; - consentGiven?: null | boolean; - createdAt?: null | number; - scopes?: null | string; - updatedAt?: null | number; - userId?: null | string; - }; - model: "oauthConsent"; - } - | { - data: { - createdAt: number; - expiresAt?: null | number; - privateKey: string; - publicKey: string; - }; - model: "jwks"; - } - | { - data: { count: number; key: string; lastRequest: number }; - model: "rateLimit"; } | { - data: { + model: "user_custom"; + update: { cbDefaultValueField?: null | string; - createdAt: number; + createdAt?: number; customField?: null | string; dateField?: null | number; displayUsername?: null | string; email?: null | string; - emailVerified: boolean; + emailVerified?: boolean; email_address?: null | string; image?: null | string; isAnonymous?: null | boolean; - name: string; + name?: string; numericField?: null | number; phoneNumber?: null | string; phoneNumberVerified?: null | boolean; testField?: null | string; twoFactorEnabled?: null | boolean; - updatedAt: number; + updatedAt?: number; userId?: null | string; username?: null | string; }; - model: "user_custom"; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "name" + | "email" + | "email_address" + | "emailVerified" + | "image" + | "createdAt" + | "updatedAt" + | "twoFactorEnabled" + | "isAnonymous" + | "username" + | "displayUsername" + | "phoneNumber" + | "phoneNumberVerified" + | "userId" + | "testField" + | "cbDefaultValueField" + | "customField" + | "numericField" + | "dateField" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; } | { - data: { + model: "user_table"; + update: { cbDefaultValueField?: null | string; - createdAt: number; + createdAt?: number; customField?: null | string; dateField?: null | number; displayUsername?: null | string; email?: null | string; - emailVerified: boolean; + emailVerified?: boolean; email_address?: null | string; image?: null | string; isAnonymous?: null | boolean; - name: string; + name?: string; numericField?: null | number; phoneNumber?: null | string; phoneNumberVerified?: null | boolean; testField?: null | string; twoFactorEnabled?: null | boolean; - updatedAt: number; + updatedAt?: number; userId?: null | string; username?: null | string; }; - model: "user_table"; - } - | { data: { oneToOne: string }; model: "oneToOneTable" } - | { - data: { - oneToOne?: null | string; - one_to_one?: null | string; - }; - model: "one_to_one_table"; - } - | { - data: { - cbDefaultValueField?: null | string; - json?: any; - nullableReference?: null | string; - numberArray?: null | Array; - stringArray?: null | Array; - testField?: null | string; - }; - model: "testModel"; - } - | { - data: { - createdAt: number; - logo?: null | string; - metadata?: null | string; - name: string; - slug: string; - updatedAt?: null | number; - }; - model: "organization"; - } - | { - data: { - createdAt: number; - organizationId: string; - role: string; - updatedAt?: null | number; - userId: string; - }; - model: "member"; - } - | { - data: { - createdAt: number; - name: string; - organizationId: string; - updatedAt?: null | number; - }; - model: "team"; - } - | { - data: { - createdAt?: null | number; - teamId: string; - userId: string; - }; - model: "teamMember"; - } - | { - data: { - createdAt?: null | number; - email?: null | string; - expiresAt?: null | number; - inviterId?: null | string; - organizationId?: null | string; - role?: null | string; - status?: null | string; - teamId?: null | string; - updatedAt?: null | number; - }; - model: "invitation"; - }; - onCreateHandle?: string; - select?: Array; - }, - any, - Name - >; - deleteMany: FunctionReference< - "mutation", - "internal", - { - input: - | { - model: "user"; where?: Array<{ connector?: "AND" | "OR"; field: @@ -4117,17 +8308,125 @@ export type ComponentApi = }>; } | { - model: "session"; + model: "oneToOneTable"; + update: { oneToOne?: string }; + where?: Array<{ + connector?: "AND" | "OR"; + field: "oneToOne" | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "one_to_one_table"; + update: { + oneToOne?: null | string; + one_to_one?: null | string; + }; + where?: Array<{ + connector?: "AND" | "OR"; + field: "oneToOne" | "one_to_one" | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "testModel"; + update: { + cbDefaultValueField?: null | string; + json?: any; + nullableReference?: null | string; + numberArray?: null | Array; + stringArray?: null | Array; + testField?: null | string; + }; where?: Array<{ connector?: "AND" | "OR"; field: - | "expiresAt" - | "token" + | "nullableReference" + | "testField" + | "cbDefaultValueField" + | "stringArray" + | "numberArray" + | "json" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "organization"; + update: { + createdAt?: number; + logo?: null | string; + metadata?: null | string; + name?: string; + slug?: string; + updatedAt?: null | number; + }; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "name" + | "slug" + | "logo" + | "metadata" | "createdAt" | "updatedAt" - | "ipAddress" - | "userAgent" - | "userId" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -4152,20 +8451,20 @@ export type ComponentApi = }>; } | { - model: "account"; + model: "member"; + update: { + createdAt?: number; + organizationId?: string; + role?: string; + updatedAt?: null | number; + userId?: string; + }; where?: Array<{ connector?: "AND" | "OR"; field: - | "accountId" - | "providerId" + | "organizationId" | "userId" - | "accessToken" - | "refreshToken" - | "idToken" - | "accessTokenExpiresAt" - | "refreshTokenExpiresAt" - | "scope" - | "password" + | "role" | "createdAt" | "updatedAt" | "_id"; @@ -4192,13 +8491,20 @@ export type ComponentApi = }>; } | { - model: "verification"; + model: "team"; + update: { + createdAt?: number; + memberCount?: number; + name?: string; + organizationId?: string; + updatedAt?: null | number; + }; where?: Array<{ connector?: "AND" | "OR"; field: - | "identifier" - | "value" - | "expiresAt" + | "name" + | "memberCount" + | "organizationId" | "createdAt" | "updatedAt" | "_id"; @@ -4225,14 +8531,20 @@ export type ComponentApi = }>; } | { - model: "twoFactor"; + model: "teamMember"; + update: { + createdAt?: null | number; + membershipKey?: null | string; + teamId?: string; + userId?: string; + }; where?: Array<{ connector?: "AND" | "OR"; field: - | "secret" - | "backupCodes" + | "teamId" | "userId" - | "verified" + | "membershipKey" + | "createdAt" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -4257,19 +8569,28 @@ export type ComponentApi = }>; } | { - model: "oauthApplication"; + model: "invitation"; + update: { + createdAt?: null | number; + email?: null | string; + expiresAt?: null | number; + inviterId?: null | string; + organizationId?: null | string; + role?: null | string; + status?: null | string; + teamId?: null | string; + updatedAt?: null | number; + }; where?: Array<{ connector?: "AND" | "OR"; field: - | "name" - | "icon" - | "metadata" - | "clientId" - | "clientSecret" - | "redirectUrls" - | "type" - | "disabled" - | "userId" + | "email" + | "role" + | "status" + | "organizationId" + | "teamId" + | "inviterId" + | "expiresAt" | "createdAt" | "updatedAt" | "_id"; @@ -4294,21 +8615,70 @@ export type ComponentApi = | Array | null; }>; - } + }; + onUpdateHandle?: string; + paginationOpts: { + cursor: string | null; + endCursor?: string | null; + id?: number; + maximumBytesRead?: number; + maximumRowsRead?: number; + numItems: number; + }; + }, + any, + Name + >; + updateOne: FunctionReference< + "mutation", + "internal", + { + input: | { - model: "oauthAccessToken"; + model: "user"; + update: { + cbDefaultValueField?: null | string; + createdAt?: number; + customField?: null | string; + dateField?: null | number; + displayUsername?: null | string; + email?: null | string; + emailVerified?: boolean; + email_address?: null | string; + image?: null | string; + isAnonymous?: null | boolean; + name?: string; + numericField?: null | number; + phoneNumber?: null | string; + phoneNumberVerified?: null | boolean; + testField?: null | string; + twoFactorEnabled?: null | boolean; + updatedAt?: number; + userId?: null | string; + username?: null | string; + }; where?: Array<{ connector?: "AND" | "OR"; field: - | "accessToken" - | "refreshToken" - | "accessTokenExpiresAt" - | "refreshTokenExpiresAt" - | "clientId" - | "userId" - | "scopes" + | "name" + | "email" + | "email_address" + | "emailVerified" + | "image" | "createdAt" | "updatedAt" + | "twoFactorEnabled" + | "isAnonymous" + | "username" + | "displayUsername" + | "phoneNumber" + | "phoneNumberVerified" + | "userId" + | "testField" + | "cbDefaultValueField" + | "customField" + | "numericField" + | "dateField" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -4333,16 +8703,26 @@ export type ComponentApi = }>; } | { - model: "oauthConsent"; + model: "session"; + update: { + createdAt?: number; + expiresAt?: number; + ipAddress?: null | string; + token?: string; + updatedAt?: number; + userAgent?: null | string; + userId?: string; + }; where?: Array<{ connector?: "AND" | "OR"; field: - | "clientId" - | "userId" - | "scopes" + | "expiresAt" + | "token" | "createdAt" | "updatedAt" - | "consentGiven" + | "ipAddress" + | "userAgent" + | "userId" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -4367,14 +8747,38 @@ export type ComponentApi = }>; } | { - model: "jwks"; + model: "account"; + update: { + accessToken?: null | string; + accessTokenExpiresAt?: null | number; + accountId?: string; + createdAt?: number; + idToken?: null | string; + issuer?: null | string; + password?: null | string; + providerId?: string; + refreshToken?: null | string; + refreshTokenExpiresAt?: null | number; + scope?: null | string; + updatedAt?: number; + userId?: string; + }; where?: Array<{ connector?: "AND" | "OR"; field: - | "publicKey" - | "privateKey" + | "issuer" + | "accountId" + | "providerId" + | "userId" + | "accessToken" + | "refreshToken" + | "idToken" + | "accessTokenExpiresAt" + | "refreshTokenExpiresAt" + | "scope" + | "password" | "createdAt" - | "expiresAt" + | "updatedAt" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -4399,10 +8803,23 @@ export type ComponentApi = }>; } | { - model: "rateLimit"; + model: "verification"; + update: { + createdAt?: number; + expiresAt?: number; + identifier?: string; + updatedAt?: number; + value?: string; + }; where?: Array<{ connector?: "AND" | "OR"; - field: "key" | "count" | "lastRequest" | "_id"; + field: + | "identifier" + | "value" + | "expiresAt" + | "createdAt" + | "updatedAt" + | "_id"; mode?: "sensitive" | "insensitive"; operator?: | "lt" @@ -4426,29 +8843,24 @@ export type ComponentApi = }>; } | { - model: "user_custom"; + model: "twoFactor"; + update: { + backupCodes?: string; + failedVerificationCount?: null | number; + lockedUntil?: null | number; + secret?: string; + userId?: string; + verified?: null | boolean; + }; where?: Array<{ connector?: "AND" | "OR"; field: - | "name" - | "email" - | "email_address" - | "emailVerified" - | "image" - | "createdAt" - | "updatedAt" - | "twoFactorEnabled" - | "isAnonymous" - | "username" - | "displayUsername" - | "phoneNumber" - | "phoneNumberVerified" + | "secret" + | "backupCodes" | "userId" - | "testField" - | "cbDefaultValueField" - | "customField" - | "numericField" - | "dateField" + | "verified" + | "failedVerificationCount" + | "lockedUntil" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -4473,29 +8885,34 @@ export type ComponentApi = }>; } | { - model: "user_table"; + model: "oauthApplication"; + update: { + clientId?: null | string; + clientSecret?: null | string; + createdAt?: null | number; + disabled?: null | boolean; + icon?: null | string; + metadata?: null | string; + name?: null | string; + redirectUrls?: null | string; + type?: null | string; + updatedAt?: null | number; + userId?: null | string; + }; where?: Array<{ connector?: "AND" | "OR"; field: | "name" - | "email" - | "email_address" - | "emailVerified" - | "image" + | "icon" + | "metadata" + | "clientId" + | "clientSecret" + | "redirectUrls" + | "type" + | "disabled" + | "userId" | "createdAt" | "updatedAt" - | "twoFactorEnabled" - | "isAnonymous" - | "username" - | "displayUsername" - | "phoneNumber" - | "phoneNumberVerified" - | "userId" - | "testField" - | "cbDefaultValueField" - | "customField" - | "numericField" - | "dateField" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -4520,10 +8937,31 @@ export type ComponentApi = }>; } | { - model: "oneToOneTable"; + model: "oauthAccessToken"; + update: { + accessToken?: null | string; + accessTokenExpiresAt?: null | number; + clientId?: null | string; + createdAt?: null | number; + refreshToken?: null | string; + refreshTokenExpiresAt?: null | number; + scopes?: null | string; + updatedAt?: null | number; + userId?: null | string; + }; where?: Array<{ connector?: "AND" | "OR"; - field: "oneToOne" | "_id"; + field: + | "accessToken" + | "refreshToken" + | "accessTokenExpiresAt" + | "refreshTokenExpiresAt" + | "clientId" + | "userId" + | "scopes" + | "createdAt" + | "updatedAt" + | "_id"; mode?: "sensitive" | "insensitive"; operator?: | "lt" @@ -4547,10 +8985,25 @@ export type ComponentApi = }>; } | { - model: "one_to_one_table"; + model: "oauthConsent"; + update: { + clientId?: null | string; + consentGiven?: null | boolean; + createdAt?: null | number; + scopes?: null | string; + updatedAt?: null | number; + userId?: null | string; + }; where?: Array<{ connector?: "AND" | "OR"; - field: "oneToOne" | "one_to_one" | "_id"; + field: + | "clientId" + | "userId" + | "scopes" + | "createdAt" + | "updatedAt" + | "consentGiven" + | "_id"; mode?: "sensitive" | "insensitive"; operator?: | "lt" @@ -4574,16 +9027,24 @@ export type ComponentApi = }>; } | { - model: "testModel"; + model: "jwks"; + update: { + alg?: null | string; + createdAt?: number; + crv?: null | string; + expiresAt?: null | number; + privateKey?: string; + publicKey?: string; + }; where?: Array<{ connector?: "AND" | "OR"; field: - | "nullableReference" - | "testField" - | "cbDefaultValueField" - | "stringArray" - | "numberArray" - | "json" + | "publicKey" + | "privateKey" + | "createdAt" + | "expiresAt" + | "alg" + | "crv" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -4608,17 +9069,15 @@ export type ComponentApi = }>; } | { - model: "organization"; + model: "rateLimit"; + update: { + count?: number; + key?: string; + lastRequest?: number; + }; where?: Array<{ connector?: "AND" | "OR"; - field: - | "name" - | "slug" - | "logo" - | "metadata" - | "createdAt" - | "updatedAt" - | "_id"; + field: "key" | "count" | "lastRequest" | "_id"; mode?: "sensitive" | "insensitive"; operator?: | "lt" @@ -4642,15 +9101,50 @@ export type ComponentApi = }>; } | { - model: "member"; + model: "user_custom"; + update: { + cbDefaultValueField?: null | string; + createdAt?: number; + customField?: null | string; + dateField?: null | number; + displayUsername?: null | string; + email?: null | string; + emailVerified?: boolean; + email_address?: null | string; + image?: null | string; + isAnonymous?: null | boolean; + name?: string; + numericField?: null | number; + phoneNumber?: null | string; + phoneNumberVerified?: null | boolean; + testField?: null | string; + twoFactorEnabled?: null | boolean; + updatedAt?: number; + userId?: null | string; + username?: null | string; + }; where?: Array<{ connector?: "AND" | "OR"; field: - | "organizationId" - | "userId" - | "role" + | "name" + | "email" + | "email_address" + | "emailVerified" + | "image" | "createdAt" | "updatedAt" + | "twoFactorEnabled" + | "isAnonymous" + | "username" + | "displayUsername" + | "phoneNumber" + | "phoneNumberVerified" + | "userId" + | "testField" + | "cbDefaultValueField" + | "customField" + | "numericField" + | "dateField" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -4675,14 +9169,50 @@ export type ComponentApi = }>; } | { - model: "team"; + model: "user_table"; + update: { + cbDefaultValueField?: null | string; + createdAt?: number; + customField?: null | string; + dateField?: null | number; + displayUsername?: null | string; + email?: null | string; + emailVerified?: boolean; + email_address?: null | string; + image?: null | string; + isAnonymous?: null | boolean; + name?: string; + numericField?: null | number; + phoneNumber?: null | string; + phoneNumberVerified?: null | boolean; + testField?: null | string; + twoFactorEnabled?: null | boolean; + updatedAt?: number; + userId?: null | string; + username?: null | string; + }; where?: Array<{ connector?: "AND" | "OR"; field: | "name" - | "organizationId" + | "email" + | "email_address" + | "emailVerified" + | "image" | "createdAt" | "updatedAt" + | "twoFactorEnabled" + | "isAnonymous" + | "username" + | "displayUsername" + | "phoneNumber" + | "phoneNumberVerified" + | "userId" + | "testField" + | "cbDefaultValueField" + | "customField" + | "numericField" + | "dateField" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -4707,10 +9237,11 @@ export type ComponentApi = }>; } | { - model: "teamMember"; + model: "oneToOneTable"; + update: { oneToOne?: string }; where?: Array<{ connector?: "AND" | "OR"; - field: "teamId" | "userId" | "createdAt" | "_id"; + field: "oneToOne" | "_id"; mode?: "sensitive" | "insensitive"; operator?: | "lt" @@ -4734,20 +9265,14 @@ export type ComponentApi = }>; } | { - model: "invitation"; + model: "one_to_one_table"; + update: { + oneToOne?: null | string; + one_to_one?: null | string; + }; where?: Array<{ connector?: "AND" | "OR"; - field: - | "email" - | "role" - | "status" - | "organizationId" - | "teamId" - | "inviterId" - | "expiresAt" - | "createdAt" - | "updatedAt" - | "_id"; + field: "oneToOne" | "one_to_one" | "_id"; mode?: "sensitive" | "insensitive"; operator?: | "lt" @@ -4769,49 +9294,26 @@ export type ComponentApi = | Array | null; }>; - }; - onDeleteHandle?: string; - paginationOpts: { - cursor: string | null; - endCursor?: string | null; - id?: number; - maximumBytesRead?: number; - maximumRowsRead?: number; - numItems: number; - }; - }, - any, - Name - >; - deleteOne: FunctionReference< - "mutation", - "internal", - { - input: + } | { - model: "user"; + model: "testModel"; + update: { + cbDefaultValueField?: null | string; + json?: any; + nullableReference?: null | string; + numberArray?: null | Array; + stringArray?: null | Array; + testField?: null | string; + }; where?: Array<{ connector?: "AND" | "OR"; field: - | "name" - | "email" - | "email_address" - | "emailVerified" - | "image" - | "createdAt" - | "updatedAt" - | "twoFactorEnabled" - | "isAnonymous" - | "username" - | "displayUsername" - | "phoneNumber" - | "phoneNumberVerified" - | "userId" + | "nullableReference" | "testField" | "cbDefaultValueField" - | "customField" - | "numericField" - | "dateField" + | "stringArray" + | "numberArray" + | "json" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -4836,17 +9338,24 @@ export type ComponentApi = }>; } | { - model: "session"; + model: "organization"; + update: { + createdAt?: number; + logo?: null | string; + metadata?: null | string; + name?: string; + slug?: string; + updatedAt?: null | number; + }; where?: Array<{ connector?: "AND" | "OR"; field: - | "expiresAt" - | "token" + | "name" + | "slug" + | "logo" + | "metadata" | "createdAt" | "updatedAt" - | "ipAddress" - | "userAgent" - | "userId" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -4871,20 +9380,20 @@ export type ComponentApi = }>; } | { - model: "account"; + model: "member"; + update: { + createdAt?: number; + organizationId?: string; + role?: string; + updatedAt?: null | number; + userId?: string; + }; where?: Array<{ connector?: "AND" | "OR"; field: - | "accountId" - | "providerId" + | "organizationId" | "userId" - | "accessToken" - | "refreshToken" - | "idToken" - | "accessTokenExpiresAt" - | "refreshTokenExpiresAt" - | "scope" - | "password" + | "role" | "createdAt" | "updatedAt" | "_id"; @@ -4911,13 +9420,20 @@ export type ComponentApi = }>; } | { - model: "verification"; + model: "team"; + update: { + createdAt?: number; + memberCount?: number; + name?: string; + organizationId?: string; + updatedAt?: null | number; + }; where?: Array<{ connector?: "AND" | "OR"; field: - | "identifier" - | "value" - | "expiresAt" + | "name" + | "memberCount" + | "organizationId" | "createdAt" | "updatedAt" | "_id"; @@ -4944,14 +9460,20 @@ export type ComponentApi = }>; } | { - model: "twoFactor"; + model: "teamMember"; + update: { + createdAt?: null | number; + membershipKey?: null | string; + teamId?: string; + userId?: string; + }; where?: Array<{ connector?: "AND" | "OR"; field: - | "secret" - | "backupCodes" + | "teamId" | "userId" - | "verified" + | "membershipKey" + | "createdAt" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -4976,19 +9498,28 @@ export type ComponentApi = }>; } | { - model: "oauthApplication"; + model: "invitation"; + update: { + createdAt?: null | number; + email?: null | string; + expiresAt?: null | number; + inviterId?: null | string; + organizationId?: null | string; + role?: null | string; + status?: null | string; + teamId?: null | string; + updatedAt?: null | number; + }; where?: Array<{ connector?: "AND" | "OR"; field: - | "name" - | "icon" - | "metadata" - | "clientId" - | "clientSecret" - | "redirectUrls" - | "type" - | "disabled" - | "userId" + | "email" + | "role" + | "status" + | "organizationId" + | "teamId" + | "inviterId" + | "expiresAt" | "createdAt" | "updatedAt" | "_id"; @@ -5013,139 +9544,284 @@ export type ComponentApi = | Array | null; }>; + }; + onUpdateHandle?: string; + }, + any, + Name + >; + }; + adapterPluginTable: { + create: FunctionReference< + "mutation", + "internal", + { + input: + | { + data: { + cbDefaultValueField?: null | string; + createdAt: number; + customField?: null | string; + dateField?: null | number; + displayUsername?: null | string; + email?: null | string; + emailVerified: boolean; + email_address?: null | string; + image?: null | string; + isAnonymous?: null | boolean; + name: string; + numericField?: null | number; + phoneNumber?: null | string; + phoneNumberVerified?: null | boolean; + testField?: null | string; + twoFactorEnabled?: null | boolean; + updatedAt: number; + userId?: null | string; + username?: null | string; + }; + model: "user"; + } + | { + data: { + createdAt: number; + expiresAt: number; + ipAddress?: null | string; + token: string; + updatedAt: number; + userAgent?: null | string; + userId: string; + }; + model: "session"; + } + | { + data: { + accessToken?: null | string; + accessTokenExpiresAt?: null | number; + accountId: string; + createdAt: number; + idToken?: null | string; + issuer?: null | string; + password?: null | string; + providerId: string; + refreshToken?: null | string; + refreshTokenExpiresAt?: null | number; + scope?: null | string; + updatedAt: number; + userId: string; + }; + model: "account"; + } + | { + data: { + createdAt: number; + expiresAt: number; + identifier: string; + updatedAt: number; + value: string; + }; + model: "verification"; + } + | { + data: { + backupCodes: string; + failedVerificationCount?: null | number; + lockedUntil?: null | number; + secret: string; + userId: string; + verified?: null | boolean; + }; + model: "twoFactor"; + } + | { + data: { + clientId?: null | string; + clientSecret?: null | string; + createdAt?: null | number; + disabled?: null | boolean; + icon?: null | string; + metadata?: null | string; + name?: null | string; + redirectUrls?: null | string; + type?: null | string; + updatedAt?: null | number; + userId?: null | string; + }; + model: "oauthApplication"; + } + | { + data: { + accessToken?: null | string; + accessTokenExpiresAt?: null | number; + clientId?: null | string; + createdAt?: null | number; + refreshToken?: null | string; + refreshTokenExpiresAt?: null | number; + scopes?: null | string; + updatedAt?: null | number; + userId?: null | string; + }; + model: "oauthAccessToken"; + } + | { + data: { + clientId?: null | string; + consentGiven?: null | boolean; + createdAt?: null | number; + scopes?: null | string; + updatedAt?: null | number; + userId?: null | string; + }; + model: "oauthConsent"; + } + | { + data: { + alg?: null | string; + createdAt: number; + crv?: null | string; + expiresAt?: null | number; + privateKey: string; + publicKey: string; + }; + model: "jwks"; + } + | { + data: { count: number; key: string; lastRequest: number }; + model: "rateLimit"; + } + | { + data: { + cbDefaultValueField?: null | string; + createdAt: number; + customField?: null | string; + dateField?: null | number; + displayUsername?: null | string; + email?: null | string; + emailVerified: boolean; + email_address?: null | string; + image?: null | string; + isAnonymous?: null | boolean; + name: string; + numericField?: null | number; + phoneNumber?: null | string; + phoneNumberVerified?: null | boolean; + testField?: null | string; + twoFactorEnabled?: null | boolean; + updatedAt: number; + userId?: null | string; + username?: null | string; + }; + model: "user_custom"; + } + | { + data: { + cbDefaultValueField?: null | string; + createdAt: number; + customField?: null | string; + dateField?: null | number; + displayUsername?: null | string; + email?: null | string; + emailVerified: boolean; + email_address?: null | string; + image?: null | string; + isAnonymous?: null | boolean; + name: string; + numericField?: null | number; + phoneNumber?: null | string; + phoneNumberVerified?: null | boolean; + testField?: null | string; + twoFactorEnabled?: null | boolean; + updatedAt: number; + userId?: null | string; + username?: null | string; + }; + model: "user_table"; + } + | { data: { oneToOne: string }; model: "oneToOneTable" } + | { + data: { + oneToOne?: null | string; + one_to_one?: null | string; + }; + model: "one_to_one_table"; } | { - model: "oauthAccessToken"; - where?: Array<{ - connector?: "AND" | "OR"; - field: - | "accessToken" - | "refreshToken" - | "accessTokenExpiresAt" - | "refreshTokenExpiresAt" - | "clientId" - | "userId" - | "scopes" - | "createdAt" - | "updatedAt" - | "_id"; - mode?: "sensitive" | "insensitive"; - operator?: - | "lt" - | "lte" - | "gt" - | "gte" - | "eq" - | "in" - | "not_in" - | "ne" - | "contains" - | "starts_with" - | "ends_with"; - value: - | string - | number - | boolean - | Array - | Array - | null; - }>; + data: { + cbDefaultValueField?: null | string; + json?: any; + nullableReference?: null | string; + numberArray?: null | Array; + stringArray?: null | Array; + testField?: null | string; + }; + model: "testModel"; } | { - model: "oauthConsent"; - where?: Array<{ - connector?: "AND" | "OR"; - field: - | "clientId" - | "userId" - | "scopes" - | "createdAt" - | "updatedAt" - | "consentGiven" - | "_id"; - mode?: "sensitive" | "insensitive"; - operator?: - | "lt" - | "lte" - | "gt" - | "gte" - | "eq" - | "in" - | "not_in" - | "ne" - | "contains" - | "starts_with" - | "ends_with"; - value: - | string - | number - | boolean - | Array - | Array - | null; - }>; + data: { + createdAt: number; + logo?: null | string; + metadata?: null | string; + name: string; + slug: string; + updatedAt?: null | number; + }; + model: "organization"; } | { - model: "jwks"; - where?: Array<{ - connector?: "AND" | "OR"; - field: - | "publicKey" - | "privateKey" - | "createdAt" - | "expiresAt" - | "_id"; - mode?: "sensitive" | "insensitive"; - operator?: - | "lt" - | "lte" - | "gt" - | "gte" - | "eq" - | "in" - | "not_in" - | "ne" - | "contains" - | "starts_with" - | "ends_with"; - value: - | string - | number - | boolean - | Array - | Array - | null; - }>; + data: { + createdAt: number; + organizationId: string; + role: string; + updatedAt?: null | number; + userId: string; + }; + model: "member"; } | { - model: "rateLimit"; - where?: Array<{ - connector?: "AND" | "OR"; - field: "key" | "count" | "lastRequest" | "_id"; - mode?: "sensitive" | "insensitive"; - operator?: - | "lt" - | "lte" - | "gt" - | "gte" - | "eq" - | "in" - | "not_in" - | "ne" - | "contains" - | "starts_with" - | "ends_with"; - value: - | string - | number - | boolean - | Array - | Array - | null; - }>; + data: { + createdAt: number; + memberCount: number; + name: string; + organizationId: string; + updatedAt?: null | number; + }; + model: "team"; } | { - model: "user_custom"; + data: { + createdAt?: null | number; + membershipKey?: null | string; + teamId: string; + userId: string; + }; + model: "teamMember"; + } + | { + data: { + createdAt?: null | number; + email?: null | string; + expiresAt?: null | number; + inviterId?: null | string; + organizationId?: null | string; + role?: null | string; + status?: null | string; + teamId?: null | string; + updatedAt?: null | number; + }; + model: "invitation"; + }; + onCreateHandle?: string; + select?: Array; + }, + any, + Name + >; + deleteMany: FunctionReference< + "mutation", + "internal", + { + input: + | { + model: "user"; where?: Array<{ connector?: "AND" | "OR"; field: @@ -5192,29 +9868,17 @@ export type ComponentApi = }>; } | { - model: "user_table"; + model: "session"; where?: Array<{ connector?: "AND" | "OR"; field: - | "name" - | "email" - | "email_address" - | "emailVerified" - | "image" + | "expiresAt" + | "token" | "createdAt" | "updatedAt" - | "twoFactorEnabled" - | "isAnonymous" - | "username" - | "displayUsername" - | "phoneNumber" - | "phoneNumberVerified" + | "ipAddress" + | "userAgent" | "userId" - | "testField" - | "cbDefaultValueField" - | "customField" - | "numericField" - | "dateField" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -5239,37 +9903,24 @@ export type ComponentApi = }>; } | { - model: "oneToOneTable"; - where?: Array<{ - connector?: "AND" | "OR"; - field: "oneToOne" | "_id"; - mode?: "sensitive" | "insensitive"; - operator?: - | "lt" - | "lte" - | "gt" - | "gte" - | "eq" - | "in" - | "not_in" - | "ne" - | "contains" - | "starts_with" - | "ends_with"; - value: - | string - | number - | boolean - | Array - | Array - | null; - }>; - } - | { - model: "one_to_one_table"; + model: "account"; where?: Array<{ connector?: "AND" | "OR"; - field: "oneToOne" | "one_to_one" | "_id"; + field: + | "issuer" + | "accountId" + | "providerId" + | "userId" + | "accessToken" + | "refreshToken" + | "idToken" + | "accessTokenExpiresAt" + | "refreshTokenExpiresAt" + | "scope" + | "password" + | "createdAt" + | "updatedAt" + | "_id"; mode?: "sensitive" | "insensitive"; operator?: | "lt" @@ -5293,16 +9944,15 @@ export type ComponentApi = }>; } | { - model: "testModel"; + model: "verification"; where?: Array<{ connector?: "AND" | "OR"; field: - | "nullableReference" - | "testField" - | "cbDefaultValueField" - | "stringArray" - | "numberArray" - | "json" + | "identifier" + | "value" + | "expiresAt" + | "createdAt" + | "updatedAt" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -5327,16 +9977,16 @@ export type ComponentApi = }>; } | { - model: "organization"; + model: "twoFactor"; where?: Array<{ connector?: "AND" | "OR"; field: - | "name" - | "slug" - | "logo" - | "metadata" - | "createdAt" - | "updatedAt" + | "secret" + | "backupCodes" + | "userId" + | "verified" + | "failedVerificationCount" + | "lockedUntil" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -5361,13 +10011,19 @@ export type ComponentApi = }>; } | { - model: "member"; + model: "oauthApplication"; where?: Array<{ connector?: "AND" | "OR"; field: - | "organizationId" + | "name" + | "icon" + | "metadata" + | "clientId" + | "clientSecret" + | "redirectUrls" + | "type" + | "disabled" | "userId" - | "role" | "createdAt" | "updatedAt" | "_id"; @@ -5394,12 +10050,17 @@ export type ComponentApi = }>; } | { - model: "team"; + model: "oauthAccessToken"; where?: Array<{ connector?: "AND" | "OR"; field: - | "name" - | "organizationId" + | "accessToken" + | "refreshToken" + | "accessTokenExpiresAt" + | "refreshTokenExpiresAt" + | "clientId" + | "userId" + | "scopes" | "createdAt" | "updatedAt" | "_id"; @@ -5426,10 +10087,17 @@ export type ComponentApi = }>; } | { - model: "teamMember"; + model: "oauthConsent"; where?: Array<{ connector?: "AND" | "OR"; - field: "teamId" | "userId" | "createdAt" | "_id"; + field: + | "clientId" + | "userId" + | "scopes" + | "createdAt" + | "updatedAt" + | "consentGiven" + | "_id"; mode?: "sensitive" | "insensitive"; operator?: | "lt" @@ -5453,19 +10121,16 @@ export type ComponentApi = }>; } | { - model: "invitation"; + model: "jwks"; where?: Array<{ connector?: "AND" | "OR"; field: - | "email" - | "role" - | "status" - | "organizationId" - | "teamId" - | "inviterId" - | "expiresAt" + | "publicKey" + | "privateKey" | "createdAt" - | "updatedAt" + | "expiresAt" + | "alg" + | "crv" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -5486,186 +10151,14 @@ export type ComponentApi = | boolean | Array | Array - | null; - }>; - }; - onDeleteHandle?: string; - }, - any, - Name - >; - findMany: FunctionReference< - "query", - "internal", - { - join?: any; - limit?: number; - model: - | "user" - | "session" - | "account" - | "verification" - | "twoFactor" - | "oauthApplication" - | "oauthAccessToken" - | "oauthConsent" - | "jwks" - | "rateLimit" - | "user_custom" - | "user_table" - | "oneToOneTable" - | "one_to_one_table" - | "testModel" - | "organization" - | "member" - | "team" - | "teamMember" - | "invitation"; - offset?: number; - paginationOpts: { - cursor: string | null; - endCursor?: string | null; - id?: number; - maximumBytesRead?: number; - maximumRowsRead?: number; - numItems: number; - }; - select?: Array; - sortBy?: { direction: "asc" | "desc"; field: string }; - where?: Array<{ - connector?: "AND" | "OR"; - field: string; - mode?: "sensitive" | "insensitive"; - operator?: - | "lt" - | "lte" - | "gt" - | "gte" - | "eq" - | "in" - | "not_in" - | "ne" - | "contains" - | "starts_with" - | "ends_with"; - value: - | string - | number - | boolean - | Array - | Array - | null; - }>; - }, - any, - Name - >; - findOne: FunctionReference< - "query", - "internal", - { - join?: any; - model: - | "user" - | "session" - | "account" - | "verification" - | "twoFactor" - | "oauthApplication" - | "oauthAccessToken" - | "oauthConsent" - | "jwks" - | "rateLimit" - | "user_custom" - | "user_table" - | "oneToOneTable" - | "one_to_one_table" - | "testModel" - | "organization" - | "member" - | "team" - | "teamMember" - | "invitation"; - select?: Array; - where?: Array<{ - connector?: "AND" | "OR"; - field: string; - mode?: "sensitive" | "insensitive"; - operator?: - | "lt" - | "lte" - | "gt" - | "gte" - | "eq" - | "in" - | "not_in" - | "ne" - | "contains" - | "starts_with" - | "ends_with"; - value: - | string - | number - | boolean - | Array - | Array - | null; - }>; - }, - any, - Name - >; - updateMany: FunctionReference< - "mutation", - "internal", - { - input: - | { - model: "user"; - update: { - cbDefaultValueField?: null | string; - createdAt?: number; - customField?: null | string; - dateField?: null | number; - displayUsername?: null | string; - email?: null | string; - emailVerified?: boolean; - email_address?: null | string; - image?: null | string; - isAnonymous?: null | boolean; - name?: string; - numericField?: null | number; - phoneNumber?: null | string; - phoneNumberVerified?: null | boolean; - testField?: null | string; - twoFactorEnabled?: null | boolean; - updatedAt?: number; - userId?: null | string; - username?: null | string; - }; - where?: Array<{ - connector?: "AND" | "OR"; - field: - | "name" - | "email" - | "email_address" - | "emailVerified" - | "image" - | "createdAt" - | "updatedAt" - | "twoFactorEnabled" - | "isAnonymous" - | "username" - | "displayUsername" - | "phoneNumber" - | "phoneNumberVerified" - | "userId" - | "testField" - | "cbDefaultValueField" - | "customField" - | "numericField" - | "dateField" - | "_id"; + | null; + }>; + } + | { + model: "rateLimit"; + where?: Array<{ + connector?: "AND" | "OR"; + field: "key" | "count" | "lastRequest" | "_id"; mode?: "sensitive" | "insensitive"; operator?: | "lt" @@ -5689,26 +10182,29 @@ export type ComponentApi = }>; } | { - model: "session"; - update: { - createdAt?: number; - expiresAt?: number; - ipAddress?: null | string; - token?: string; - updatedAt?: number; - userAgent?: null | string; - userId?: string; - }; + model: "user_custom"; where?: Array<{ connector?: "AND" | "OR"; field: - | "expiresAt" - | "token" + | "name" + | "email" + | "email_address" + | "emailVerified" + | "image" | "createdAt" | "updatedAt" - | "ipAddress" - | "userAgent" + | "twoFactorEnabled" + | "isAnonymous" + | "username" + | "displayUsername" + | "phoneNumber" + | "phoneNumberVerified" | "userId" + | "testField" + | "cbDefaultValueField" + | "customField" + | "numericField" + | "dateField" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -5733,36 +10229,29 @@ export type ComponentApi = }>; } | { - model: "account"; - update: { - accessToken?: null | string; - accessTokenExpiresAt?: null | number; - accountId?: string; - createdAt?: number; - idToken?: null | string; - password?: null | string; - providerId?: string; - refreshToken?: null | string; - refreshTokenExpiresAt?: null | number; - scope?: null | string; - updatedAt?: number; - userId?: string; - }; + model: "user_table"; where?: Array<{ connector?: "AND" | "OR"; field: - | "accountId" - | "providerId" - | "userId" - | "accessToken" - | "refreshToken" - | "idToken" - | "accessTokenExpiresAt" - | "refreshTokenExpiresAt" - | "scope" - | "password" + | "name" + | "email" + | "email_address" + | "emailVerified" + | "image" | "createdAt" | "updatedAt" + | "twoFactorEnabled" + | "isAnonymous" + | "username" + | "displayUsername" + | "phoneNumber" + | "phoneNumberVerified" + | "userId" + | "testField" + | "cbDefaultValueField" + | "customField" + | "numericField" + | "dateField" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -5787,23 +10276,10 @@ export type ComponentApi = }>; } | { - model: "verification"; - update: { - createdAt?: number; - expiresAt?: number; - identifier?: string; - updatedAt?: number; - value?: string; - }; + model: "oneToOneTable"; where?: Array<{ connector?: "AND" | "OR"; - field: - | "identifier" - | "value" - | "expiresAt" - | "createdAt" - | "updatedAt" - | "_id"; + field: "oneToOne" | "_id"; mode?: "sensitive" | "insensitive"; operator?: | "lt" @@ -5827,21 +10303,10 @@ export type ComponentApi = }>; } | { - model: "twoFactor"; - update: { - backupCodes?: string; - secret?: string; - userId?: string; - verified?: null | boolean; - }; + model: "one_to_one_table"; where?: Array<{ connector?: "AND" | "OR"; - field: - | "secret" - | "backupCodes" - | "userId" - | "verified" - | "_id"; + field: "oneToOne" | "one_to_one" | "_id"; mode?: "sensitive" | "insensitive"; operator?: | "lt" @@ -5865,34 +10330,16 @@ export type ComponentApi = }>; } | { - model: "oauthApplication"; - update: { - clientId?: null | string; - clientSecret?: null | string; - createdAt?: null | number; - disabled?: null | boolean; - icon?: null | string; - metadata?: null | string; - name?: null | string; - redirectUrls?: null | string; - type?: null | string; - updatedAt?: null | number; - userId?: null | string; - }; + model: "testModel"; where?: Array<{ connector?: "AND" | "OR"; field: - | "name" - | "icon" - | "metadata" - | "clientId" - | "clientSecret" - | "redirectUrls" - | "type" - | "disabled" - | "userId" - | "createdAt" - | "updatedAt" + | "nullableReference" + | "testField" + | "cbDefaultValueField" + | "stringArray" + | "numberArray" + | "json" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -5917,28 +10364,14 @@ export type ComponentApi = }>; } | { - model: "oauthAccessToken"; - update: { - accessToken?: null | string; - accessTokenExpiresAt?: null | number; - clientId?: null | string; - createdAt?: null | number; - refreshToken?: null | string; - refreshTokenExpiresAt?: null | number; - scopes?: null | string; - updatedAt?: null | number; - userId?: null | string; - }; + model: "organization"; where?: Array<{ connector?: "AND" | "OR"; field: - | "accessToken" - | "refreshToken" - | "accessTokenExpiresAt" - | "refreshTokenExpiresAt" - | "clientId" - | "userId" - | "scopes" + | "name" + | "slug" + | "logo" + | "metadata" | "createdAt" | "updatedAt" | "_id"; @@ -5965,24 +10398,15 @@ export type ComponentApi = }>; } | { - model: "oauthConsent"; - update: { - clientId?: null | string; - consentGiven?: null | boolean; - createdAt?: null | number; - scopes?: null | string; - updatedAt?: null | number; - userId?: null | string; - }; + model: "member"; where?: Array<{ connector?: "AND" | "OR"; field: - | "clientId" + | "organizationId" | "userId" - | "scopes" + | "role" | "createdAt" | "updatedAt" - | "consentGiven" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -6007,20 +10431,15 @@ export type ComponentApi = }>; } | { - model: "jwks"; - update: { - createdAt?: number; - expiresAt?: null | number; - privateKey?: string; - publicKey?: string; - }; + model: "team"; where?: Array<{ connector?: "AND" | "OR"; field: - | "publicKey" - | "privateKey" + | "name" + | "memberCount" + | "organizationId" | "createdAt" - | "expiresAt" + | "updatedAt" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -6045,15 +10464,15 @@ export type ComponentApi = }>; } | { - model: "rateLimit"; - update: { - count?: number; - key?: string; - lastRequest?: number; - }; + model: "teamMember"; where?: Array<{ connector?: "AND" | "OR"; - field: "key" | "count" | "lastRequest" | "_id"; + field: + | "teamId" + | "userId" + | "membershipKey" + | "createdAt" + | "_id"; mode?: "sensitive" | "insensitive"; operator?: | "lt" @@ -6077,50 +10496,19 @@ export type ComponentApi = }>; } | { - model: "user_custom"; - update: { - cbDefaultValueField?: null | string; - createdAt?: number; - customField?: null | string; - dateField?: null | number; - displayUsername?: null | string; - email?: null | string; - emailVerified?: boolean; - email_address?: null | string; - image?: null | string; - isAnonymous?: null | boolean; - name?: string; - numericField?: null | number; - phoneNumber?: null | string; - phoneNumberVerified?: null | boolean; - testField?: null | string; - twoFactorEnabled?: null | boolean; - updatedAt?: number; - userId?: null | string; - username?: null | string; - }; + model: "invitation"; where?: Array<{ - connector?: "AND" | "OR"; - field: - | "name" - | "email" - | "email_address" - | "emailVerified" - | "image" - | "createdAt" - | "updatedAt" - | "twoFactorEnabled" - | "isAnonymous" - | "username" - | "displayUsername" - | "phoneNumber" - | "phoneNumberVerified" - | "userId" - | "testField" - | "cbDefaultValueField" - | "customField" - | "numericField" - | "dateField" + connector?: "AND" | "OR"; + field: + | "email" + | "role" + | "status" + | "organizationId" + | "teamId" + | "inviterId" + | "expiresAt" + | "createdAt" + | "updatedAt" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -6143,30 +10531,27 @@ export type ComponentApi = | Array | null; }>; - } + }; + onDeleteHandle?: string; + paginationOpts: { + cursor: string | null; + endCursor?: string | null; + id?: number; + maximumBytesRead?: number; + maximumRowsRead?: number; + numItems: number; + }; + }, + any, + Name + >; + deleteOne: FunctionReference< + "mutation", + "internal", + { + input: | { - model: "user_table"; - update: { - cbDefaultValueField?: null | string; - createdAt?: number; - customField?: null | string; - dateField?: null | number; - displayUsername?: null | string; - email?: null | string; - emailVerified?: boolean; - email_address?: null | string; - image?: null | string; - isAnonymous?: null | boolean; - name?: string; - numericField?: null | number; - phoneNumber?: null | string; - phoneNumberVerified?: null | boolean; - testField?: null | string; - twoFactorEnabled?: null | boolean; - updatedAt?: number; - userId?: null | string; - username?: null | string; - }; + model: "user"; where?: Array<{ connector?: "AND" | "OR"; field: @@ -6213,11 +10598,18 @@ export type ComponentApi = }>; } | { - model: "oneToOneTable"; - update: { oneToOne?: string }; + model: "session"; where?: Array<{ connector?: "AND" | "OR"; - field: "oneToOne" | "_id"; + field: + | "expiresAt" + | "token" + | "createdAt" + | "updatedAt" + | "ipAddress" + | "userAgent" + | "userId" + | "_id"; mode?: "sensitive" | "insensitive"; operator?: | "lt" @@ -6241,14 +10633,24 @@ export type ComponentApi = }>; } | { - model: "one_to_one_table"; - update: { - oneToOne?: null | string; - one_to_one?: null | string; - }; + model: "account"; where?: Array<{ connector?: "AND" | "OR"; - field: "oneToOne" | "one_to_one" | "_id"; + field: + | "issuer" + | "accountId" + | "providerId" + | "userId" + | "accessToken" + | "refreshToken" + | "idToken" + | "accessTokenExpiresAt" + | "refreshTokenExpiresAt" + | "scope" + | "password" + | "createdAt" + | "updatedAt" + | "_id"; mode?: "sensitive" | "insensitive"; operator?: | "lt" @@ -6272,24 +10674,15 @@ export type ComponentApi = }>; } | { - model: "testModel"; - update: { - cbDefaultValueField?: null | string; - json?: any; - nullableReference?: null | string; - numberArray?: null | Array; - stringArray?: null | Array; - testField?: null | string; - }; + model: "verification"; where?: Array<{ connector?: "AND" | "OR"; field: - | "nullableReference" - | "testField" - | "cbDefaultValueField" - | "stringArray" - | "numberArray" - | "json" + | "identifier" + | "value" + | "expiresAt" + | "createdAt" + | "updatedAt" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -6314,24 +10707,16 @@ export type ComponentApi = }>; } | { - model: "organization"; - update: { - createdAt?: number; - logo?: null | string; - metadata?: null | string; - name?: string; - slug?: string; - updatedAt?: null | number; - }; + model: "twoFactor"; where?: Array<{ connector?: "AND" | "OR"; field: - | "name" - | "slug" - | "logo" - | "metadata" - | "createdAt" - | "updatedAt" + | "secret" + | "backupCodes" + | "userId" + | "verified" + | "failedVerificationCount" + | "lockedUntil" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -6356,20 +10741,19 @@ export type ComponentApi = }>; } | { - model: "member"; - update: { - createdAt?: number; - organizationId?: string; - role?: string; - updatedAt?: null | number; - userId?: string; - }; + model: "oauthApplication"; where?: Array<{ connector?: "AND" | "OR"; field: - | "organizationId" + | "name" + | "icon" + | "metadata" + | "clientId" + | "clientSecret" + | "redirectUrls" + | "type" + | "disabled" | "userId" - | "role" | "createdAt" | "updatedAt" | "_id"; @@ -6396,18 +10780,17 @@ export type ComponentApi = }>; } | { - model: "team"; - update: { - createdAt?: number; - name?: string; - organizationId?: string; - updatedAt?: null | number; - }; + model: "oauthAccessToken"; where?: Array<{ connector?: "AND" | "OR"; field: - | "name" - | "organizationId" + | "accessToken" + | "refreshToken" + | "accessTokenExpiresAt" + | "refreshTokenExpiresAt" + | "clientId" + | "userId" + | "scopes" | "createdAt" | "updatedAt" | "_id"; @@ -6434,15 +10817,17 @@ export type ComponentApi = }>; } | { - model: "teamMember"; - update: { - createdAt?: null | number; - teamId?: string; - userId?: string; - }; + model: "oauthConsent"; where?: Array<{ connector?: "AND" | "OR"; - field: "teamId" | "userId" | "createdAt" | "_id"; + field: + | "clientId" + | "userId" + | "scopes" + | "createdAt" + | "updatedAt" + | "consentGiven" + | "_id"; mode?: "sensitive" | "insensitive"; operator?: | "lt" @@ -6466,30 +10851,16 @@ export type ComponentApi = }>; } | { - model: "invitation"; - update: { - createdAt?: null | number; - email?: null | string; - expiresAt?: null | number; - inviterId?: null | string; - organizationId?: null | string; - role?: null | string; - status?: null | string; - teamId?: null | string; - updatedAt?: null | number; - }; + model: "jwks"; where?: Array<{ connector?: "AND" | "OR"; field: - | "email" - | "role" - | "status" - | "organizationId" - | "teamId" - | "inviterId" - | "expiresAt" + | "publicKey" + | "privateKey" | "createdAt" - | "updatedAt" + | "expiresAt" + | "alg" + | "crv" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -6512,48 +10883,36 @@ export type ComponentApi = | Array | null; }>; - }; - onUpdateHandle?: string; - paginationOpts: { - cursor: string | null; - endCursor?: string | null; - id?: number; - maximumBytesRead?: number; - maximumRowsRead?: number; - numItems: number; - }; - }, - any, - Name - >; - updateOne: FunctionReference< - "mutation", - "internal", - { - input: + } | { - model: "user"; - update: { - cbDefaultValueField?: null | string; - createdAt?: number; - customField?: null | string; - dateField?: null | number; - displayUsername?: null | string; - email?: null | string; - emailVerified?: boolean; - email_address?: null | string; - image?: null | string; - isAnonymous?: null | boolean; - name?: string; - numericField?: null | number; - phoneNumber?: null | string; - phoneNumberVerified?: null | boolean; - testField?: null | string; - twoFactorEnabled?: null | boolean; - updatedAt?: number; - userId?: null | string; - username?: null | string; - }; + model: "rateLimit"; + where?: Array<{ + connector?: "AND" | "OR"; + field: "key" | "count" | "lastRequest" | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "user_custom"; where?: Array<{ connector?: "AND" | "OR"; field: @@ -6600,26 +10959,29 @@ export type ComponentApi = }>; } | { - model: "session"; - update: { - createdAt?: number; - expiresAt?: number; - ipAddress?: null | string; - token?: string; - updatedAt?: number; - userAgent?: null | string; - userId?: string; - }; + model: "user_table"; where?: Array<{ connector?: "AND" | "OR"; field: - | "expiresAt" - | "token" + | "name" + | "email" + | "email_address" + | "emailVerified" + | "image" | "createdAt" | "updatedAt" - | "ipAddress" - | "userAgent" + | "twoFactorEnabled" + | "isAnonymous" + | "username" + | "displayUsername" + | "phoneNumber" + | "phoneNumberVerified" | "userId" + | "testField" + | "cbDefaultValueField" + | "customField" + | "numericField" + | "dateField" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -6644,37 +11006,10 @@ export type ComponentApi = }>; } | { - model: "account"; - update: { - accessToken?: null | string; - accessTokenExpiresAt?: null | number; - accountId?: string; - createdAt?: number; - idToken?: null | string; - password?: null | string; - providerId?: string; - refreshToken?: null | string; - refreshTokenExpiresAt?: null | number; - scope?: null | string; - updatedAt?: number; - userId?: string; - }; + model: "oneToOneTable"; where?: Array<{ connector?: "AND" | "OR"; - field: - | "accountId" - | "providerId" - | "userId" - | "accessToken" - | "refreshToken" - | "idToken" - | "accessTokenExpiresAt" - | "refreshTokenExpiresAt" - | "scope" - | "password" - | "createdAt" - | "updatedAt" - | "_id"; + field: "oneToOne" | "_id"; mode?: "sensitive" | "insensitive"; operator?: | "lt" @@ -6698,23 +11033,10 @@ export type ComponentApi = }>; } | { - model: "verification"; - update: { - createdAt?: number; - expiresAt?: number; - identifier?: string; - updatedAt?: number; - value?: string; - }; + model: "one_to_one_table"; where?: Array<{ connector?: "AND" | "OR"; - field: - | "identifier" - | "value" - | "expiresAt" - | "createdAt" - | "updatedAt" - | "_id"; + field: "oneToOne" | "one_to_one" | "_id"; mode?: "sensitive" | "insensitive"; operator?: | "lt" @@ -6738,20 +11060,16 @@ export type ComponentApi = }>; } | { - model: "twoFactor"; - update: { - backupCodes?: string; - secret?: string; - userId?: string; - verified?: null | boolean; - }; + model: "testModel"; where?: Array<{ connector?: "AND" | "OR"; field: - | "secret" - | "backupCodes" - | "userId" - | "verified" + | "nullableReference" + | "testField" + | "cbDefaultValueField" + | "stringArray" + | "numberArray" + | "json" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -6776,32 +11094,14 @@ export type ComponentApi = }>; } | { - model: "oauthApplication"; - update: { - clientId?: null | string; - clientSecret?: null | string; - createdAt?: null | number; - disabled?: null | boolean; - icon?: null | string; - metadata?: null | string; - name?: null | string; - redirectUrls?: null | string; - type?: null | string; - updatedAt?: null | number; - userId?: null | string; - }; + model: "organization"; where?: Array<{ connector?: "AND" | "OR"; field: | "name" - | "icon" + | "slug" + | "logo" | "metadata" - | "clientId" - | "clientSecret" - | "redirectUrls" - | "type" - | "disabled" - | "userId" | "createdAt" | "updatedAt" | "_id"; @@ -6828,28 +11128,13 @@ export type ComponentApi = }>; } | { - model: "oauthAccessToken"; - update: { - accessToken?: null | string; - accessTokenExpiresAt?: null | number; - clientId?: null | string; - createdAt?: null | number; - refreshToken?: null | string; - refreshTokenExpiresAt?: null | number; - scopes?: null | string; - updatedAt?: null | number; - userId?: null | string; - }; + model: "member"; where?: Array<{ connector?: "AND" | "OR"; field: - | "accessToken" - | "refreshToken" - | "accessTokenExpiresAt" - | "refreshTokenExpiresAt" - | "clientId" + | "organizationId" | "userId" - | "scopes" + | "role" | "createdAt" | "updatedAt" | "_id"; @@ -6876,24 +11161,15 @@ export type ComponentApi = }>; } | { - model: "oauthConsent"; - update: { - clientId?: null | string; - consentGiven?: null | boolean; - createdAt?: null | number; - scopes?: null | string; - updatedAt?: null | number; - userId?: null | string; - }; + model: "team"; where?: Array<{ connector?: "AND" | "OR"; field: - | "clientId" - | "userId" - | "scopes" + | "name" + | "memberCount" + | "organizationId" | "createdAt" | "updatedAt" - | "consentGiven" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -6918,20 +11194,14 @@ export type ComponentApi = }>; } | { - model: "jwks"; - update: { - createdAt?: number; - expiresAt?: null | number; - privateKey?: string; - publicKey?: string; - }; + model: "teamMember"; where?: Array<{ connector?: "AND" | "OR"; field: - | "publicKey" - | "privateKey" + | "teamId" + | "userId" + | "membershipKey" | "createdAt" - | "expiresAt" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -6956,15 +11226,20 @@ export type ComponentApi = }>; } | { - model: "rateLimit"; - update: { - count?: number; - key?: string; - lastRequest?: number; - }; + model: "invitation"; where?: Array<{ connector?: "AND" | "OR"; - field: "key" | "count" | "lastRequest" | "_id"; + field: + | "email" + | "role" + | "status" + | "organizationId" + | "teamId" + | "inviterId" + | "expiresAt" + | "createdAt" + | "updatedAt" + | "_id"; mode?: "sensitive" | "insensitive"; operator?: | "lt" @@ -6986,10 +11261,142 @@ export type ComponentApi = | Array | null; }>; - } + }; + onDeleteHandle?: string; + }, + any, + Name + >; + findMany: FunctionReference< + "query", + "internal", + { + join?: any; + limit?: number; + model: + | "user" + | "session" + | "account" + | "verification" + | "twoFactor" + | "oauthApplication" + | "oauthAccessToken" + | "oauthConsent" + | "jwks" + | "rateLimit" + | "user_custom" + | "user_table" + | "oneToOneTable" + | "one_to_one_table" + | "testModel" + | "organization" + | "member" + | "team" + | "teamMember" + | "invitation"; + offset?: number; + paginationOpts: { + cursor: string | null; + endCursor?: string | null; + id?: number; + maximumBytesRead?: number; + maximumRowsRead?: number; + numItems: number; + }; + select?: Array; + sortBy?: { direction: "asc" | "desc"; field: string }; + where?: Array<{ + connector?: "AND" | "OR"; + field: string; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + }, + any, + Name + >; + findOne: FunctionReference< + "query", + "internal", + { + join?: any; + model: + | "user" + | "session" + | "account" + | "verification" + | "twoFactor" + | "oauthApplication" + | "oauthAccessToken" + | "oauthConsent" + | "jwks" + | "rateLimit" + | "user_custom" + | "user_table" + | "oneToOneTable" + | "one_to_one_table" + | "testModel" + | "organization" + | "member" + | "team" + | "teamMember" + | "invitation"; + select?: Array; + where?: Array<{ + connector?: "AND" | "OR"; + field: string; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + }, + any, + Name + >; + incrementOne: FunctionReference< + "mutation", + "internal", + { + input: | { - model: "user_custom"; - update: { + increment: Record; + model: "user"; + set?: { cbDefaultValueField?: null | string; createdAt?: number; customField?: null | string; @@ -7010,7 +11417,7 @@ export type ComponentApi = userId?: null | string; username?: null | string; }; - where?: Array<{ + where: Array<{ connector?: "AND" | "OR"; field: | "name" @@ -7056,50 +11463,27 @@ export type ComponentApi = }>; } | { - model: "user_table"; - update: { - cbDefaultValueField?: null | string; + increment: Record; + model: "session"; + set?: { createdAt?: number; - customField?: null | string; - dateField?: null | number; - displayUsername?: null | string; - email?: null | string; - emailVerified?: boolean; - email_address?: null | string; - image?: null | string; - isAnonymous?: null | boolean; - name?: string; - numericField?: null | number; - phoneNumber?: null | string; - phoneNumberVerified?: null | boolean; - testField?: null | string; - twoFactorEnabled?: null | boolean; + expiresAt?: number; + ipAddress?: null | string; + token?: string; updatedAt?: number; - userId?: null | string; - username?: null | string; + userAgent?: null | string; + userId?: string; }; - where?: Array<{ + where: Array<{ connector?: "AND" | "OR"; field: - | "name" - | "email" - | "email_address" - | "emailVerified" - | "image" + | "expiresAt" + | "token" | "createdAt" | "updatedAt" - | "twoFactorEnabled" - | "isAnonymous" - | "username" - | "displayUsername" - | "phoneNumber" - | "phoneNumberVerified" + | "ipAddress" + | "userAgent" | "userId" - | "testField" - | "cbDefaultValueField" - | "customField" - | "numericField" - | "dateField" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -7124,11 +11508,40 @@ export type ComponentApi = }>; } | { - model: "oneToOneTable"; - update: { oneToOne?: string }; - where?: Array<{ + increment: Record; + model: "account"; + set?: { + accessToken?: null | string; + accessTokenExpiresAt?: null | number; + accountId?: string; + createdAt?: number; + idToken?: null | string; + issuer?: null | string; + password?: null | string; + providerId?: string; + refreshToken?: null | string; + refreshTokenExpiresAt?: null | number; + scope?: null | string; + updatedAt?: number; + userId?: string; + }; + where: Array<{ connector?: "AND" | "OR"; - field: "oneToOne" | "_id"; + field: + | "issuer" + | "accountId" + | "providerId" + | "userId" + | "accessToken" + | "refreshToken" + | "idToken" + | "accessTokenExpiresAt" + | "refreshTokenExpiresAt" + | "scope" + | "password" + | "createdAt" + | "updatedAt" + | "_id"; mode?: "sensitive" | "insensitive"; operator?: | "lt" @@ -7152,14 +11565,24 @@ export type ComponentApi = }>; } | { - model: "one_to_one_table"; - update: { - oneToOne?: null | string; - one_to_one?: null | string; + increment: Record; + model: "verification"; + set?: { + createdAt?: number; + expiresAt?: number; + identifier?: string; + updatedAt?: number; + value?: string; }; - where?: Array<{ + where: Array<{ connector?: "AND" | "OR"; - field: "oneToOne" | "one_to_one" | "_id"; + field: + | "identifier" + | "value" + | "expiresAt" + | "createdAt" + | "updatedAt" + | "_id"; mode?: "sensitive" | "insensitive"; operator?: | "lt" @@ -7183,24 +11606,25 @@ export type ComponentApi = }>; } | { - model: "testModel"; - update: { - cbDefaultValueField?: null | string; - json?: any; - nullableReference?: null | string; - numberArray?: null | Array; - stringArray?: null | Array; - testField?: null | string; + increment: Record; + model: "twoFactor"; + set?: { + backupCodes?: string; + failedVerificationCount?: null | number; + lockedUntil?: null | number; + secret?: string; + userId?: string; + verified?: null | boolean; }; - where?: Array<{ + where: Array<{ connector?: "AND" | "OR"; field: - | "nullableReference" - | "testField" - | "cbDefaultValueField" - | "stringArray" - | "numberArray" - | "json" + | "secret" + | "backupCodes" + | "userId" + | "verified" + | "failedVerificationCount" + | "lockedUntil" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -7225,22 +11649,33 @@ export type ComponentApi = }>; } | { - model: "organization"; - update: { - createdAt?: number; - logo?: null | string; + increment: Record; + model: "oauthApplication"; + set?: { + clientId?: null | string; + clientSecret?: null | string; + createdAt?: null | number; + disabled?: null | boolean; + icon?: null | string; metadata?: null | string; - name?: string; - slug?: string; + name?: null | string; + redirectUrls?: null | string; + type?: null | string; updatedAt?: null | number; + userId?: null | string; }; - where?: Array<{ + where: Array<{ connector?: "AND" | "OR"; field: | "name" - | "slug" - | "logo" + | "icon" | "metadata" + | "clientId" + | "clientSecret" + | "redirectUrls" + | "type" + | "disabled" + | "userId" | "createdAt" | "updatedAt" | "_id"; @@ -7267,20 +11702,29 @@ export type ComponentApi = }>; } | { - model: "member"; - update: { - createdAt?: number; - organizationId?: string; - role?: string; + increment: Record; + model: "oauthAccessToken"; + set?: { + accessToken?: null | string; + accessTokenExpiresAt?: null | number; + clientId?: null | string; + createdAt?: null | number; + refreshToken?: null | string; + refreshTokenExpiresAt?: null | number; + scopes?: null | string; updatedAt?: null | number; - userId?: string; + userId?: null | string; }; - where?: Array<{ + where: Array<{ connector?: "AND" | "OR"; field: - | "organizationId" + | "accessToken" + | "refreshToken" + | "accessTokenExpiresAt" + | "refreshTokenExpiresAt" + | "clientId" | "userId" - | "role" + | "scopes" | "createdAt" | "updatedAt" | "_id"; @@ -7307,20 +11751,25 @@ export type ComponentApi = }>; } | { - model: "team"; - update: { - createdAt?: number; - name?: string; - organizationId?: string; + increment: Record; + model: "oauthConsent"; + set?: { + clientId?: null | string; + consentGiven?: null | boolean; + createdAt?: null | number; + scopes?: null | string; updatedAt?: null | number; + userId?: null | string; }; - where?: Array<{ + where: Array<{ connector?: "AND" | "OR"; field: - | "name" - | "organizationId" + | "clientId" + | "userId" + | "scopes" | "createdAt" | "updatedAt" + | "consentGiven" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -7345,15 +11794,26 @@ export type ComponentApi = }>; } | { - model: "teamMember"; - update: { - createdAt?: null | number; - teamId?: string; - userId?: string; + increment: Record; + model: "jwks"; + set?: { + alg?: null | string; + createdAt?: number; + crv?: null | string; + expiresAt?: null | number; + privateKey?: string; + publicKey?: string; }; - where?: Array<{ + where: Array<{ connector?: "AND" | "OR"; - field: "teamId" | "userId" | "createdAt" | "_id"; + field: + | "publicKey" + | "privateKey" + | "createdAt" + | "expiresAt" + | "alg" + | "crv" + | "_id"; mode?: "sensitive" | "insensitive"; operator?: | "lt" @@ -7377,31 +11837,12 @@ export type ComponentApi = }>; } | { - model: "invitation"; - update: { - createdAt?: null | number; - email?: null | string; - expiresAt?: null | number; - inviterId?: null | string; - organizationId?: null | string; - role?: null | string; - status?: null | string; - teamId?: null | string; - updatedAt?: null | number; - }; - where?: Array<{ + increment: Record; + model: "rateLimit"; + set?: { count?: number; key?: string; lastRequest?: number }; + where: Array<{ connector?: "AND" | "OR"; - field: - | "email" - | "role" - | "status" - | "organizationId" - | "teamId" - | "inviterId" - | "expiresAt" - | "createdAt" - | "updatedAt" - | "_id"; + field: "key" | "count" | "lastRequest" | "_id"; mode?: "sensitive" | "insensitive"; operator?: | "lt" @@ -7423,203 +11864,210 @@ export type ComponentApi = | Array | null; }>; - }; - onUpdateHandle?: string; - }, - any, - Name - >; - }; - adapterPluginTable: { - create: FunctionReference< - "mutation", - "internal", - { - input: - | { - data: { - cbDefaultValueField?: null | string; - createdAt: number; - customField?: null | string; - dateField?: null | number; - displayUsername?: null | string; - email?: null | string; - emailVerified: boolean; - email_address?: null | string; - image?: null | string; - isAnonymous?: null | boolean; - name: string; - numericField?: null | number; - phoneNumber?: null | string; - phoneNumberVerified?: null | boolean; - testField?: null | string; - twoFactorEnabled?: null | boolean; - updatedAt: number; - userId?: null | string; - username?: null | string; - }; - model: "user"; - } - | { - data: { - createdAt: number; - expiresAt: number; - ipAddress?: null | string; - token: string; - updatedAt: number; - userAgent?: null | string; - userId: string; - }; - model: "session"; - } - | { - data: { - accessToken?: null | string; - accessTokenExpiresAt?: null | number; - accountId: string; - createdAt: number; - idToken?: null | string; - password?: null | string; - providerId: string; - refreshToken?: null | string; - refreshTokenExpiresAt?: null | number; - scope?: null | string; - updatedAt: number; - userId: string; - }; - model: "account"; - } - | { - data: { - createdAt: number; - expiresAt: number; - identifier: string; - updatedAt: number; - value: string; - }; - model: "verification"; - } - | { - data: { - backupCodes: string; - secret: string; - userId: string; - verified?: null | boolean; - }; - model: "twoFactor"; - } - | { - data: { - clientId?: null | string; - clientSecret?: null | string; - createdAt?: null | number; - disabled?: null | boolean; - icon?: null | string; - metadata?: null | string; - name?: null | string; - redirectUrls?: null | string; - type?: null | string; - updatedAt?: null | number; - userId?: null | string; - }; - model: "oauthApplication"; - } - | { - data: { - accessToken?: null | string; - accessTokenExpiresAt?: null | number; - clientId?: null | string; - createdAt?: null | number; - refreshToken?: null | string; - refreshTokenExpiresAt?: null | number; - scopes?: null | string; - updatedAt?: null | number; - userId?: null | string; - }; - model: "oauthAccessToken"; - } - | { - data: { - clientId?: null | string; - consentGiven?: null | boolean; - createdAt?: null | number; - scopes?: null | string; - updatedAt?: null | number; - userId?: null | string; - }; - model: "oauthConsent"; - } - | { - data: { - createdAt: number; - expiresAt?: null | number; - privateKey: string; - publicKey: string; - }; - model: "jwks"; - } - | { - data: { count: number; key: string; lastRequest: number }; - model: "rateLimit"; } | { - data: { + increment: Record; + model: "user_custom"; + set?: { cbDefaultValueField?: null | string; - createdAt: number; + createdAt?: number; customField?: null | string; dateField?: null | number; displayUsername?: null | string; email?: null | string; - emailVerified: boolean; + emailVerified?: boolean; email_address?: null | string; image?: null | string; isAnonymous?: null | boolean; - name: string; + name?: string; numericField?: null | number; phoneNumber?: null | string; phoneNumberVerified?: null | boolean; testField?: null | string; twoFactorEnabled?: null | boolean; - updatedAt: number; + updatedAt?: number; userId?: null | string; username?: null | string; }; - model: "user_custom"; + where: Array<{ + connector?: "AND" | "OR"; + field: + | "name" + | "email" + | "email_address" + | "emailVerified" + | "image" + | "createdAt" + | "updatedAt" + | "twoFactorEnabled" + | "isAnonymous" + | "username" + | "displayUsername" + | "phoneNumber" + | "phoneNumberVerified" + | "userId" + | "testField" + | "cbDefaultValueField" + | "customField" + | "numericField" + | "dateField" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; } | { - data: { + increment: Record; + model: "user_table"; + set?: { cbDefaultValueField?: null | string; - createdAt: number; + createdAt?: number; customField?: null | string; dateField?: null | number; displayUsername?: null | string; email?: null | string; - emailVerified: boolean; + emailVerified?: boolean; email_address?: null | string; image?: null | string; isAnonymous?: null | boolean; - name: string; + name?: string; numericField?: null | number; phoneNumber?: null | string; phoneNumberVerified?: null | boolean; testField?: null | string; twoFactorEnabled?: null | boolean; - updatedAt: number; + updatedAt?: number; userId?: null | string; username?: null | string; }; - model: "user_table"; + where: Array<{ + connector?: "AND" | "OR"; + field: + | "name" + | "email" + | "email_address" + | "emailVerified" + | "image" + | "createdAt" + | "updatedAt" + | "twoFactorEnabled" + | "isAnonymous" + | "username" + | "displayUsername" + | "phoneNumber" + | "phoneNumberVerified" + | "userId" + | "testField" + | "cbDefaultValueField" + | "customField" + | "numericField" + | "dateField" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; } - | { data: { oneToOne: string }; model: "oneToOneTable" } | { - data: { + increment: Record; + model: "oneToOneTable"; + set?: { oneToOne?: string }; + where: Array<{ + connector?: "AND" | "OR"; + field: "oneToOne" | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + increment: Record; + model: "one_to_one_table"; + set?: { oneToOne?: null | string; one_to_one?: null | string; }; - model: "one_to_one_table"; + where: Array<{ + connector?: "AND" | "OR"; + field: "oneToOne" | "one_to_one" | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; } | { - data: { + increment: Record; + model: "testModel"; + set?: { cbDefaultValueField?: null | string; json?: any; nullableReference?: null | string; @@ -7627,48 +12075,206 @@ export type ComponentApi = stringArray?: null | Array; testField?: null | string; }; - model: "testModel"; + where: Array<{ + connector?: "AND" | "OR"; + field: + | "nullableReference" + | "testField" + | "cbDefaultValueField" + | "stringArray" + | "numberArray" + | "json" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; } | { - data: { - createdAt: number; + increment: Record; + model: "organization"; + set?: { + createdAt?: number; logo?: null | string; metadata?: null | string; - name: string; - slug: string; + name?: string; + slug?: string; updatedAt?: null | number; }; - model: "organization"; + where: Array<{ + connector?: "AND" | "OR"; + field: + | "name" + | "slug" + | "logo" + | "metadata" + | "createdAt" + | "updatedAt" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; } | { - data: { - createdAt: number; - organizationId: string; - role: string; + increment: Record; + model: "member"; + set?: { + createdAt?: number; + organizationId?: string; + role?: string; updatedAt?: null | number; - userId: string; + userId?: string; }; - model: "member"; + where: Array<{ + connector?: "AND" | "OR"; + field: + | "organizationId" + | "userId" + | "role" + | "createdAt" + | "updatedAt" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; } | { - data: { - createdAt: number; - name: string; - organizationId: string; + increment: Record; + model: "team"; + set?: { + createdAt?: number; + memberCount?: number; + name?: string; + organizationId?: string; updatedAt?: null | number; }; - model: "team"; + where: Array<{ + connector?: "AND" | "OR"; + field: + | "name" + | "memberCount" + | "organizationId" + | "createdAt" + | "updatedAt" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; } | { - data: { + increment: Record; + model: "teamMember"; + set?: { createdAt?: null | number; - teamId: string; - userId: string; + membershipKey?: null | string; + teamId?: string; + userId?: string; }; - model: "teamMember"; + where: Array<{ + connector?: "AND" | "OR"; + field: + | "teamId" + | "userId" + | "membershipKey" + | "createdAt" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; } | { - data: { + increment: Record; + model: "invitation"; + set?: { createdAt?: null | number; email?: null | string; expiresAt?: null | number; @@ -7679,21 +12285,74 @@ export type ComponentApi = teamId?: null | string; updatedAt?: null | number; }; - model: "invitation"; + where: Array<{ + connector?: "AND" | "OR"; + field: + | "email" + | "role" + | "status" + | "organizationId" + | "teamId" + | "inviterId" + | "expiresAt" + | "createdAt" + | "updatedAt" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; }; - onCreateHandle?: string; - select?: Array; + onUpdateHandle?: string; }, any, Name >; - deleteMany: FunctionReference< + updateMany: FunctionReference< "mutation", "internal", { input: | { model: "user"; + update: { + cbDefaultValueField?: null | string; + createdAt?: number; + customField?: null | string; + dateField?: null | number; + displayUsername?: null | string; + email?: null | string; + emailVerified?: boolean; + email_address?: null | string; + image?: null | string; + isAnonymous?: null | boolean; + name?: string; + numericField?: null | number; + phoneNumber?: null | string; + phoneNumberVerified?: null | boolean; + testField?: null | string; + twoFactorEnabled?: null | boolean; + updatedAt?: number; + userId?: null | string; + username?: null | string; + }; where?: Array<{ connector?: "AND" | "OR"; field: @@ -7741,6 +12400,15 @@ export type ComponentApi = } | { model: "session"; + update: { + createdAt?: number; + expiresAt?: number; + ipAddress?: null | string; + token?: string; + updatedAt?: number; + userAgent?: null | string; + userId?: string; + }; where?: Array<{ connector?: "AND" | "OR"; field: @@ -7776,9 +12444,25 @@ export type ComponentApi = } | { model: "account"; + update: { + accessToken?: null | string; + accessTokenExpiresAt?: null | number; + accountId?: string; + createdAt?: number; + idToken?: null | string; + issuer?: null | string; + password?: null | string; + providerId?: string; + refreshToken?: null | string; + refreshTokenExpiresAt?: null | number; + scope?: null | string; + updatedAt?: number; + userId?: string; + }; where?: Array<{ connector?: "AND" | "OR"; field: + | "issuer" | "accountId" | "providerId" | "userId" @@ -7816,6 +12500,13 @@ export type ComponentApi = } | { model: "verification"; + update: { + createdAt?: number; + expiresAt?: number; + identifier?: string; + updatedAt?: number; + value?: string; + }; where?: Array<{ connector?: "AND" | "OR"; field: @@ -7849,6 +12540,14 @@ export type ComponentApi = } | { model: "twoFactor"; + update: { + backupCodes?: string; + failedVerificationCount?: null | number; + lockedUntil?: null | number; + secret?: string; + userId?: string; + verified?: null | boolean; + }; where?: Array<{ connector?: "AND" | "OR"; field: @@ -7856,6 +12555,8 @@ export type ComponentApi = | "backupCodes" | "userId" | "verified" + | "failedVerificationCount" + | "lockedUntil" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -7881,6 +12582,19 @@ export type ComponentApi = } | { model: "oauthApplication"; + update: { + clientId?: null | string; + clientSecret?: null | string; + createdAt?: null | number; + disabled?: null | boolean; + icon?: null | string; + metadata?: null | string; + name?: null | string; + redirectUrls?: null | string; + type?: null | string; + updatedAt?: null | number; + userId?: null | string; + }; where?: Array<{ connector?: "AND" | "OR"; field: @@ -7920,6 +12634,17 @@ export type ComponentApi = } | { model: "oauthAccessToken"; + update: { + accessToken?: null | string; + accessTokenExpiresAt?: null | number; + clientId?: null | string; + createdAt?: null | number; + refreshToken?: null | string; + refreshTokenExpiresAt?: null | number; + scopes?: null | string; + updatedAt?: null | number; + userId?: null | string; + }; where?: Array<{ connector?: "AND" | "OR"; field: @@ -7957,6 +12682,14 @@ export type ComponentApi = } | { model: "oauthConsent"; + update: { + clientId?: null | string; + consentGiven?: null | boolean; + createdAt?: null | number; + scopes?: null | string; + updatedAt?: null | number; + userId?: null | string; + }; where?: Array<{ connector?: "AND" | "OR"; field: @@ -7991,6 +12724,14 @@ export type ComponentApi = } | { model: "jwks"; + update: { + alg?: null | string; + createdAt?: number; + crv?: null | string; + expiresAt?: null | number; + privateKey?: string; + publicKey?: string; + }; where?: Array<{ connector?: "AND" | "OR"; field: @@ -7998,6 +12739,8 @@ export type ComponentApi = | "privateKey" | "createdAt" | "expiresAt" + | "alg" + | "crv" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -8023,6 +12766,11 @@ export type ComponentApi = } | { model: "rateLimit"; + update: { + count?: number; + key?: string; + lastRequest?: number; + }; where?: Array<{ connector?: "AND" | "OR"; field: "key" | "count" | "lastRequest" | "_id"; @@ -8050,6 +12798,27 @@ export type ComponentApi = } | { model: "user_custom"; + update: { + cbDefaultValueField?: null | string; + createdAt?: number; + customField?: null | string; + dateField?: null | number; + displayUsername?: null | string; + email?: null | string; + emailVerified?: boolean; + email_address?: null | string; + image?: null | string; + isAnonymous?: null | boolean; + name?: string; + numericField?: null | number; + phoneNumber?: null | string; + phoneNumberVerified?: null | boolean; + testField?: null | string; + twoFactorEnabled?: null | boolean; + updatedAt?: number; + userId?: null | string; + username?: null | string; + }; where?: Array<{ connector?: "AND" | "OR"; field: @@ -8097,6 +12866,27 @@ export type ComponentApi = } | { model: "user_table"; + update: { + cbDefaultValueField?: null | string; + createdAt?: number; + customField?: null | string; + dateField?: null | number; + displayUsername?: null | string; + email?: null | string; + emailVerified?: boolean; + email_address?: null | string; + image?: null | string; + isAnonymous?: null | boolean; + name?: string; + numericField?: null | number; + phoneNumber?: null | string; + phoneNumberVerified?: null | boolean; + testField?: null | string; + twoFactorEnabled?: null | boolean; + updatedAt?: number; + userId?: null | string; + username?: null | string; + }; where?: Array<{ connector?: "AND" | "OR"; field: @@ -8144,6 +12934,7 @@ export type ComponentApi = } | { model: "oneToOneTable"; + update: { oneToOne?: string }; where?: Array<{ connector?: "AND" | "OR"; field: "oneToOne" | "_id"; @@ -8171,6 +12962,10 @@ export type ComponentApi = } | { model: "one_to_one_table"; + update: { + oneToOne?: null | string; + one_to_one?: null | string; + }; where?: Array<{ connector?: "AND" | "OR"; field: "oneToOne" | "one_to_one" | "_id"; @@ -8198,6 +12993,14 @@ export type ComponentApi = } | { model: "testModel"; + update: { + cbDefaultValueField?: null | string; + json?: any; + nullableReference?: null | string; + numberArray?: null | Array; + stringArray?: null | Array; + testField?: null | string; + }; where?: Array<{ connector?: "AND" | "OR"; field: @@ -8232,6 +13035,14 @@ export type ComponentApi = } | { model: "organization"; + update: { + createdAt?: number; + logo?: null | string; + metadata?: null | string; + name?: string; + slug?: string; + updatedAt?: null | number; + }; where?: Array<{ connector?: "AND" | "OR"; field: @@ -8266,6 +13077,13 @@ export type ComponentApi = } | { model: "member"; + update: { + createdAt?: number; + organizationId?: string; + role?: string; + updatedAt?: null | number; + userId?: string; + }; where?: Array<{ connector?: "AND" | "OR"; field: @@ -8299,10 +13117,18 @@ export type ComponentApi = } | { model: "team"; + update: { + createdAt?: number; + memberCount?: number; + name?: string; + organizationId?: string; + updatedAt?: null | number; + }; where?: Array<{ connector?: "AND" | "OR"; field: | "name" + | "memberCount" | "organizationId" | "createdAt" | "updatedAt" @@ -8331,9 +13157,20 @@ export type ComponentApi = } | { model: "teamMember"; + update: { + createdAt?: null | number; + membershipKey?: null | string; + teamId?: string; + userId?: string; + }; where?: Array<{ connector?: "AND" | "OR"; - field: "teamId" | "userId" | "createdAt" | "_id"; + field: + | "teamId" + | "userId" + | "membershipKey" + | "createdAt" + | "_id"; mode?: "sensitive" | "insensitive"; operator?: | "lt" @@ -8358,6 +13195,17 @@ export type ComponentApi = } | { model: "invitation"; + update: { + createdAt?: null | number; + email?: null | string; + expiresAt?: null | number; + inviterId?: null | string; + organizationId?: null | string; + role?: null | string; + status?: null | string; + teamId?: null | string; + updatedAt?: null | number; + }; where?: Array<{ connector?: "AND" | "OR"; field: @@ -8393,7 +13241,7 @@ export type ComponentApi = | null; }>; }; - onDeleteHandle?: string; + onUpdateHandle?: string; paginationOpts: { cursor: string | null; endCursor?: string | null; @@ -8406,13 +13254,34 @@ export type ComponentApi = any, Name >; - deleteOne: FunctionReference< + updateOne: FunctionReference< "mutation", "internal", { input: | { model: "user"; + update: { + cbDefaultValueField?: null | string; + createdAt?: number; + customField?: null | string; + dateField?: null | number; + displayUsername?: null | string; + email?: null | string; + emailVerified?: boolean; + email_address?: null | string; + image?: null | string; + isAnonymous?: null | boolean; + name?: string; + numericField?: null | number; + phoneNumber?: null | string; + phoneNumberVerified?: null | boolean; + testField?: null | string; + twoFactorEnabled?: null | boolean; + updatedAt?: number; + userId?: null | string; + username?: null | string; + }; where?: Array<{ connector?: "AND" | "OR"; field: @@ -8460,6 +13329,15 @@ export type ComponentApi = } | { model: "session"; + update: { + createdAt?: number; + expiresAt?: number; + ipAddress?: null | string; + token?: string; + updatedAt?: number; + userAgent?: null | string; + userId?: string; + }; where?: Array<{ connector?: "AND" | "OR"; field: @@ -8495,9 +13373,25 @@ export type ComponentApi = } | { model: "account"; + update: { + accessToken?: null | string; + accessTokenExpiresAt?: null | number; + accountId?: string; + createdAt?: number; + idToken?: null | string; + issuer?: null | string; + password?: null | string; + providerId?: string; + refreshToken?: null | string; + refreshTokenExpiresAt?: null | number; + scope?: null | string; + updatedAt?: number; + userId?: string; + }; where?: Array<{ connector?: "AND" | "OR"; field: + | "issuer" | "accountId" | "providerId" | "userId" @@ -8535,6 +13429,13 @@ export type ComponentApi = } | { model: "verification"; + update: { + createdAt?: number; + expiresAt?: number; + identifier?: string; + updatedAt?: number; + value?: string; + }; where?: Array<{ connector?: "AND" | "OR"; field: @@ -8568,6 +13469,14 @@ export type ComponentApi = } | { model: "twoFactor"; + update: { + backupCodes?: string; + failedVerificationCount?: null | number; + lockedUntil?: null | number; + secret?: string; + userId?: string; + verified?: null | boolean; + }; where?: Array<{ connector?: "AND" | "OR"; field: @@ -8575,6 +13484,8 @@ export type ComponentApi = | "backupCodes" | "userId" | "verified" + | "failedVerificationCount" + | "lockedUntil" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -8600,6 +13511,19 @@ export type ComponentApi = } | { model: "oauthApplication"; + update: { + clientId?: null | string; + clientSecret?: null | string; + createdAt?: null | number; + disabled?: null | boolean; + icon?: null | string; + metadata?: null | string; + name?: null | string; + redirectUrls?: null | string; + type?: null | string; + updatedAt?: null | number; + userId?: null | string; + }; where?: Array<{ connector?: "AND" | "OR"; field: @@ -8639,6 +13563,17 @@ export type ComponentApi = } | { model: "oauthAccessToken"; + update: { + accessToken?: null | string; + accessTokenExpiresAt?: null | number; + clientId?: null | string; + createdAt?: null | number; + refreshToken?: null | string; + refreshTokenExpiresAt?: null | number; + scopes?: null | string; + updatedAt?: null | number; + userId?: null | string; + }; where?: Array<{ connector?: "AND" | "OR"; field: @@ -8676,6 +13611,14 @@ export type ComponentApi = } | { model: "oauthConsent"; + update: { + clientId?: null | string; + consentGiven?: null | boolean; + createdAt?: null | number; + scopes?: null | string; + updatedAt?: null | number; + userId?: null | string; + }; where?: Array<{ connector?: "AND" | "OR"; field: @@ -8710,6 +13653,14 @@ export type ComponentApi = } | { model: "jwks"; + update: { + alg?: null | string; + createdAt?: number; + crv?: null | string; + expiresAt?: null | number; + privateKey?: string; + publicKey?: string; + }; where?: Array<{ connector?: "AND" | "OR"; field: @@ -8717,6 +13668,8 @@ export type ComponentApi = | "privateKey" | "createdAt" | "expiresAt" + | "alg" + | "crv" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -8742,6 +13695,11 @@ export type ComponentApi = } | { model: "rateLimit"; + update: { + count?: number; + key?: string; + lastRequest?: number; + }; where?: Array<{ connector?: "AND" | "OR"; field: "key" | "count" | "lastRequest" | "_id"; @@ -8769,6 +13727,27 @@ export type ComponentApi = } | { model: "user_custom"; + update: { + cbDefaultValueField?: null | string; + createdAt?: number; + customField?: null | string; + dateField?: null | number; + displayUsername?: null | string; + email?: null | string; + emailVerified?: boolean; + email_address?: null | string; + image?: null | string; + isAnonymous?: null | boolean; + name?: string; + numericField?: null | number; + phoneNumber?: null | string; + phoneNumberVerified?: null | boolean; + testField?: null | string; + twoFactorEnabled?: null | boolean; + updatedAt?: number; + userId?: null | string; + username?: null | string; + }; where?: Array<{ connector?: "AND" | "OR"; field: @@ -8816,6 +13795,27 @@ export type ComponentApi = } | { model: "user_table"; + update: { + cbDefaultValueField?: null | string; + createdAt?: number; + customField?: null | string; + dateField?: null | number; + displayUsername?: null | string; + email?: null | string; + emailVerified?: boolean; + email_address?: null | string; + image?: null | string; + isAnonymous?: null | boolean; + name?: string; + numericField?: null | number; + phoneNumber?: null | string; + phoneNumberVerified?: null | boolean; + testField?: null | string; + twoFactorEnabled?: null | boolean; + updatedAt?: number; + userId?: null | string; + username?: null | string; + }; where?: Array<{ connector?: "AND" | "OR"; field: @@ -8863,6 +13863,7 @@ export type ComponentApi = } | { model: "oneToOneTable"; + update: { oneToOne?: string }; where?: Array<{ connector?: "AND" | "OR"; field: "oneToOne" | "_id"; @@ -8890,6 +13891,10 @@ export type ComponentApi = } | { model: "one_to_one_table"; + update: { + oneToOne?: null | string; + one_to_one?: null | string; + }; where?: Array<{ connector?: "AND" | "OR"; field: "oneToOne" | "one_to_one" | "_id"; @@ -8917,6 +13922,14 @@ export type ComponentApi = } | { model: "testModel"; + update: { + cbDefaultValueField?: null | string; + json?: any; + nullableReference?: null | string; + numberArray?: null | Array; + stringArray?: null | Array; + testField?: null | string; + }; where?: Array<{ connector?: "AND" | "OR"; field: @@ -8950,14 +13963,102 @@ export type ComponentApi = }>; } | { - model: "organization"; + model: "organization"; + update: { + createdAt?: number; + logo?: null | string; + metadata?: null | string; + name?: string; + slug?: string; + updatedAt?: null | number; + }; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "name" + | "slug" + | "logo" + | "metadata" + | "createdAt" + | "updatedAt" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "member"; + update: { + createdAt?: number; + organizationId?: string; + role?: string; + updatedAt?: null | number; + userId?: string; + }; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "organizationId" + | "userId" + | "role" + | "createdAt" + | "updatedAt" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "team"; + update: { + createdAt?: number; + memberCount?: number; + name?: string; + organizationId?: string; + updatedAt?: null | number; + }; where?: Array<{ connector?: "AND" | "OR"; field: | "name" - | "slug" - | "logo" - | "metadata" + | "memberCount" + | "organizationId" | "createdAt" | "updatedAt" | "_id"; @@ -8984,15 +14085,20 @@ export type ComponentApi = }>; } | { - model: "member"; + model: "teamMember"; + update: { + createdAt?: null | number; + membershipKey?: null | string; + teamId?: string; + userId?: string; + }; where?: Array<{ connector?: "AND" | "OR"; field: - | "organizationId" + | "teamId" | "userId" - | "role" + | "membershipKey" | "createdAt" - | "updatedAt" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -9017,12 +14123,28 @@ export type ComponentApi = }>; } | { - model: "team"; + model: "invitation"; + update: { + createdAt?: null | number; + email?: null | string; + expiresAt?: null | number; + inviterId?: null | string; + organizationId?: null | string; + role?: null | string; + status?: null | string; + teamId?: null | string; + updatedAt?: null | number; + }; where?: Array<{ connector?: "AND" | "OR"; field: - | "name" + | "email" + | "role" + | "status" | "organizationId" + | "teamId" + | "inviterId" + | "expiresAt" | "createdAt" | "updatedAt" | "_id"; @@ -9047,225 +14169,284 @@ export type ComponentApi = | Array | null; }>; + }; + onUpdateHandle?: string; + }, + any, + Name + >; + }; + adapterRenameField: { + create: FunctionReference< + "mutation", + "internal", + { + input: + | { + data: { + cbDefaultValueField?: null | string; + createdAt: number; + customField?: null | string; + dateField?: null | number; + displayUsername?: null | string; + email?: null | string; + emailVerified: boolean; + email_address?: null | string; + image?: null | string; + isAnonymous?: null | boolean; + name: string; + numericField?: null | number; + phoneNumber?: null | string; + phoneNumberVerified?: null | boolean; + testField?: null | string; + twoFactorEnabled?: null | boolean; + updatedAt: number; + userId?: null | string; + username?: null | string; + }; + model: "user"; + } + | { + data: { + createdAt: number; + expiresAt: number; + ipAddress?: null | string; + token: string; + updatedAt: number; + userAgent?: null | string; + userId: string; + }; + model: "session"; + } + | { + data: { + accessToken?: null | string; + accessTokenExpiresAt?: null | number; + accountId: string; + createdAt: number; + idToken?: null | string; + issuer?: null | string; + password?: null | string; + providerId: string; + refreshToken?: null | string; + refreshTokenExpiresAt?: null | number; + scope?: null | string; + updatedAt: number; + userId: string; + }; + model: "account"; + } + | { + data: { + createdAt: number; + expiresAt: number; + identifier: string; + updatedAt: number; + value: string; + }; + model: "verification"; + } + | { + data: { + backupCodes: string; + failedVerificationCount?: null | number; + lockedUntil?: null | number; + secret: string; + userId: string; + verified?: null | boolean; + }; + model: "twoFactor"; + } + | { + data: { + clientId?: null | string; + clientSecret?: null | string; + createdAt?: null | number; + disabled?: null | boolean; + icon?: null | string; + metadata?: null | string; + name?: null | string; + redirectUrls?: null | string; + type?: null | string; + updatedAt?: null | number; + userId?: null | string; + }; + model: "oauthApplication"; + } + | { + data: { + accessToken?: null | string; + accessTokenExpiresAt?: null | number; + clientId?: null | string; + createdAt?: null | number; + refreshToken?: null | string; + refreshTokenExpiresAt?: null | number; + scopes?: null | string; + updatedAt?: null | number; + userId?: null | string; + }; + model: "oauthAccessToken"; + } + | { + data: { + clientId?: null | string; + consentGiven?: null | boolean; + createdAt?: null | number; + scopes?: null | string; + updatedAt?: null | number; + userId?: null | string; + }; + model: "oauthConsent"; + } + | { + data: { + alg?: null | string; + createdAt: number; + crv?: null | string; + expiresAt?: null | number; + privateKey: string; + publicKey: string; + }; + model: "jwks"; + } + | { + data: { count: number; key: string; lastRequest: number }; + model: "rateLimit"; + } + | { + data: { + cbDefaultValueField?: null | string; + createdAt: number; + customField?: null | string; + dateField?: null | number; + displayUsername?: null | string; + email?: null | string; + emailVerified: boolean; + email_address?: null | string; + image?: null | string; + isAnonymous?: null | boolean; + name: string; + numericField?: null | number; + phoneNumber?: null | string; + phoneNumberVerified?: null | boolean; + testField?: null | string; + twoFactorEnabled?: null | boolean; + updatedAt: number; + userId?: null | string; + username?: null | string; + }; + model: "user_custom"; + } + | { + data: { + cbDefaultValueField?: null | string; + createdAt: number; + customField?: null | string; + dateField?: null | number; + displayUsername?: null | string; + email?: null | string; + emailVerified: boolean; + email_address?: null | string; + image?: null | string; + isAnonymous?: null | boolean; + name: string; + numericField?: null | number; + phoneNumber?: null | string; + phoneNumberVerified?: null | boolean; + testField?: null | string; + twoFactorEnabled?: null | boolean; + updatedAt: number; + userId?: null | string; + username?: null | string; + }; + model: "user_table"; + } + | { data: { oneToOne: string }; model: "oneToOneTable" } + | { + data: { + oneToOne?: null | string; + one_to_one?: null | string; + }; + model: "one_to_one_table"; + } + | { + data: { + cbDefaultValueField?: null | string; + json?: any; + nullableReference?: null | string; + numberArray?: null | Array; + stringArray?: null | Array; + testField?: null | string; + }; + model: "testModel"; + } + | { + data: { + createdAt: number; + logo?: null | string; + metadata?: null | string; + name: string; + slug: string; + updatedAt?: null | number; + }; + model: "organization"; + } + | { + data: { + createdAt: number; + organizationId: string; + role: string; + updatedAt?: null | number; + userId: string; + }; + model: "member"; + } + | { + data: { + createdAt: number; + memberCount: number; + name: string; + organizationId: string; + updatedAt?: null | number; + }; + model: "team"; } | { + data: { + createdAt?: null | number; + membershipKey?: null | string; + teamId: string; + userId: string; + }; model: "teamMember"; - where?: Array<{ - connector?: "AND" | "OR"; - field: "teamId" | "userId" | "createdAt" | "_id"; - mode?: "sensitive" | "insensitive"; - operator?: - | "lt" - | "lte" - | "gt" - | "gte" - | "eq" - | "in" - | "not_in" - | "ne" - | "contains" - | "starts_with" - | "ends_with"; - value: - | string - | number - | boolean - | Array - | Array - | null; - }>; } | { + data: { + createdAt?: null | number; + email?: null | string; + expiresAt?: null | number; + inviterId?: null | string; + organizationId?: null | string; + role?: null | string; + status?: null | string; + teamId?: null | string; + updatedAt?: null | number; + }; model: "invitation"; - where?: Array<{ - connector?: "AND" | "OR"; - field: - | "email" - | "role" - | "status" - | "organizationId" - | "teamId" - | "inviterId" - | "expiresAt" - | "createdAt" - | "updatedAt" - | "_id"; - mode?: "sensitive" | "insensitive"; - operator?: - | "lt" - | "lte" - | "gt" - | "gte" - | "eq" - | "in" - | "not_in" - | "ne" - | "contains" - | "starts_with" - | "ends_with"; - value: - | string - | number - | boolean - | Array - | Array - | null; - }>; }; - onDeleteHandle?: string; - }, - any, - Name - >; - findMany: FunctionReference< - "query", - "internal", - { - join?: any; - limit?: number; - model: - | "user" - | "session" - | "account" - | "verification" - | "twoFactor" - | "oauthApplication" - | "oauthAccessToken" - | "oauthConsent" - | "jwks" - | "rateLimit" - | "user_custom" - | "user_table" - | "oneToOneTable" - | "one_to_one_table" - | "testModel" - | "organization" - | "member" - | "team" - | "teamMember" - | "invitation"; - offset?: number; - paginationOpts: { - cursor: string | null; - endCursor?: string | null; - id?: number; - maximumBytesRead?: number; - maximumRowsRead?: number; - numItems: number; - }; - select?: Array; - sortBy?: { direction: "asc" | "desc"; field: string }; - where?: Array<{ - connector?: "AND" | "OR"; - field: string; - mode?: "sensitive" | "insensitive"; - operator?: - | "lt" - | "lte" - | "gt" - | "gte" - | "eq" - | "in" - | "not_in" - | "ne" - | "contains" - | "starts_with" - | "ends_with"; - value: - | string - | number - | boolean - | Array - | Array - | null; - }>; - }, - any, - Name - >; - findOne: FunctionReference< - "query", - "internal", - { - join?: any; - model: - | "user" - | "session" - | "account" - | "verification" - | "twoFactor" - | "oauthApplication" - | "oauthAccessToken" - | "oauthConsent" - | "jwks" - | "rateLimit" - | "user_custom" - | "user_table" - | "oneToOneTable" - | "one_to_one_table" - | "testModel" - | "organization" - | "member" - | "team" - | "teamMember" - | "invitation"; + onCreateHandle?: string; select?: Array; - where?: Array<{ - connector?: "AND" | "OR"; - field: string; - mode?: "sensitive" | "insensitive"; - operator?: - | "lt" - | "lte" - | "gt" - | "gte" - | "eq" - | "in" - | "not_in" - | "ne" - | "contains" - | "starts_with" - | "ends_with"; - value: - | string - | number - | boolean - | Array - | Array - | null; - }>; }, any, Name >; - updateMany: FunctionReference< + deleteMany: FunctionReference< "mutation", "internal", { input: | { model: "user"; - update: { - cbDefaultValueField?: null | string; - createdAt?: number; - customField?: null | string; - dateField?: null | number; - displayUsername?: null | string; - email?: null | string; - emailVerified?: boolean; - email_address?: null | string; - image?: null | string; - isAnonymous?: null | boolean; - name?: string; - numericField?: null | number; - phoneNumber?: null | string; - phoneNumberVerified?: null | boolean; - testField?: null | string; - twoFactorEnabled?: null | boolean; - updatedAt?: number; - userId?: null | string; - username?: null | string; - }; where?: Array<{ connector?: "AND" | "OR"; field: @@ -9307,21 +14488,12 @@ export type ComponentApi = | number | boolean | Array - | Array - | null; - }>; - } - | { - model: "session"; - update: { - createdAt?: number; - expiresAt?: number; - ipAddress?: null | string; - token?: string; - updatedAt?: number; - userAgent?: null | string; - userId?: string; - }; + | Array + | null; + }>; + } + | { + model: "session"; where?: Array<{ connector?: "AND" | "OR"; field: @@ -9357,23 +14529,10 @@ export type ComponentApi = } | { model: "account"; - update: { - accessToken?: null | string; - accessTokenExpiresAt?: null | number; - accountId?: string; - createdAt?: number; - idToken?: null | string; - password?: null | string; - providerId?: string; - refreshToken?: null | string; - refreshTokenExpiresAt?: null | number; - scope?: null | string; - updatedAt?: number; - userId?: string; - }; where?: Array<{ connector?: "AND" | "OR"; field: + | "issuer" | "accountId" | "providerId" | "userId" @@ -9411,13 +14570,6 @@ export type ComponentApi = } | { model: "verification"; - update: { - createdAt?: number; - expiresAt?: number; - identifier?: string; - updatedAt?: number; - value?: string; - }; where?: Array<{ connector?: "AND" | "OR"; field: @@ -9451,12 +14603,6 @@ export type ComponentApi = } | { model: "twoFactor"; - update: { - backupCodes?: string; - secret?: string; - userId?: string; - verified?: null | boolean; - }; where?: Array<{ connector?: "AND" | "OR"; field: @@ -9464,6 +14610,8 @@ export type ComponentApi = | "backupCodes" | "userId" | "verified" + | "failedVerificationCount" + | "lockedUntil" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -9489,19 +14637,6 @@ export type ComponentApi = } | { model: "oauthApplication"; - update: { - clientId?: null | string; - clientSecret?: null | string; - createdAt?: null | number; - disabled?: null | boolean; - icon?: null | string; - metadata?: null | string; - name?: null | string; - redirectUrls?: null | string; - type?: null | string; - updatedAt?: null | number; - userId?: null | string; - }; where?: Array<{ connector?: "AND" | "OR"; field: @@ -9541,17 +14676,6 @@ export type ComponentApi = } | { model: "oauthAccessToken"; - update: { - accessToken?: null | string; - accessTokenExpiresAt?: null | number; - clientId?: null | string; - createdAt?: null | number; - refreshToken?: null | string; - refreshTokenExpiresAt?: null | number; - scopes?: null | string; - updatedAt?: null | number; - userId?: null | string; - }; where?: Array<{ connector?: "AND" | "OR"; field: @@ -9589,14 +14713,6 @@ export type ComponentApi = } | { model: "oauthConsent"; - update: { - clientId?: null | string; - consentGiven?: null | boolean; - createdAt?: null | number; - scopes?: null | string; - updatedAt?: null | number; - userId?: null | string; - }; where?: Array<{ connector?: "AND" | "OR"; field: @@ -9631,12 +14747,6 @@ export type ComponentApi = } | { model: "jwks"; - update: { - createdAt?: number; - expiresAt?: null | number; - privateKey?: string; - publicKey?: string; - }; where?: Array<{ connector?: "AND" | "OR"; field: @@ -9644,6 +14754,8 @@ export type ComponentApi = | "privateKey" | "createdAt" | "expiresAt" + | "alg" + | "crv" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -9669,11 +14781,6 @@ export type ComponentApi = } | { model: "rateLimit"; - update: { - count?: number; - key?: string; - lastRequest?: number; - }; where?: Array<{ connector?: "AND" | "OR"; field: "key" | "count" | "lastRequest" | "_id"; @@ -9701,27 +14808,6 @@ export type ComponentApi = } | { model: "user_custom"; - update: { - cbDefaultValueField?: null | string; - createdAt?: number; - customField?: null | string; - dateField?: null | number; - displayUsername?: null | string; - email?: null | string; - emailVerified?: boolean; - email_address?: null | string; - image?: null | string; - isAnonymous?: null | boolean; - name?: string; - numericField?: null | number; - phoneNumber?: null | string; - phoneNumberVerified?: null | boolean; - testField?: null | string; - twoFactorEnabled?: null | boolean; - updatedAt?: number; - userId?: null | string; - username?: null | string; - }; where?: Array<{ connector?: "AND" | "OR"; field: @@ -9769,27 +14855,6 @@ export type ComponentApi = } | { model: "user_table"; - update: { - cbDefaultValueField?: null | string; - createdAt?: number; - customField?: null | string; - dateField?: null | number; - displayUsername?: null | string; - email?: null | string; - emailVerified?: boolean; - email_address?: null | string; - image?: null | string; - isAnonymous?: null | boolean; - name?: string; - numericField?: null | number; - phoneNumber?: null | string; - phoneNumberVerified?: null | boolean; - testField?: null | string; - twoFactorEnabled?: null | boolean; - updatedAt?: number; - userId?: null | string; - username?: null | string; - }; where?: Array<{ connector?: "AND" | "OR"; field: @@ -9837,7 +14902,6 @@ export type ComponentApi = } | { model: "oneToOneTable"; - update: { oneToOne?: string }; where?: Array<{ connector?: "AND" | "OR"; field: "oneToOne" | "_id"; @@ -9865,10 +14929,6 @@ export type ComponentApi = } | { model: "one_to_one_table"; - update: { - oneToOne?: null | string; - one_to_one?: null | string; - }; where?: Array<{ connector?: "AND" | "OR"; field: "oneToOne" | "one_to_one" | "_id"; @@ -9896,14 +14956,6 @@ export type ComponentApi = } | { model: "testModel"; - update: { - cbDefaultValueField?: null | string; - json?: any; - nullableReference?: null | string; - numberArray?: null | Array; - stringArray?: null | Array; - testField?: null | string; - }; where?: Array<{ connector?: "AND" | "OR"; field: @@ -9938,14 +14990,6 @@ export type ComponentApi = } | { model: "organization"; - update: { - createdAt?: number; - logo?: null | string; - metadata?: null | string; - name?: string; - slug?: string; - updatedAt?: null | number; - }; where?: Array<{ connector?: "AND" | "OR"; field: @@ -9980,13 +15024,6 @@ export type ComponentApi = } | { model: "member"; - update: { - createdAt?: number; - organizationId?: string; - role?: string; - updatedAt?: null | number; - userId?: string; - }; where?: Array<{ connector?: "AND" | "OR"; field: @@ -10020,16 +15057,11 @@ export type ComponentApi = } | { model: "team"; - update: { - createdAt?: number; - name?: string; - organizationId?: string; - updatedAt?: null | number; - }; where?: Array<{ connector?: "AND" | "OR"; field: | "name" + | "memberCount" | "organizationId" | "createdAt" | "updatedAt" @@ -10058,14 +15090,14 @@ export type ComponentApi = } | { model: "teamMember"; - update: { - createdAt?: null | number; - teamId?: string; - userId?: string; - }; where?: Array<{ connector?: "AND" | "OR"; - field: "teamId" | "userId" | "createdAt" | "_id"; + field: + | "teamId" + | "userId" + | "membershipKey" + | "createdAt" + | "_id"; mode?: "sensitive" | "insensitive"; operator?: | "lt" @@ -10090,17 +15122,6 @@ export type ComponentApi = } | { model: "invitation"; - update: { - createdAt?: null | number; - email?: null | string; - expiresAt?: null | number; - inviterId?: null | string; - organizationId?: null | string; - role?: null | string; - status?: null | string; - teamId?: null | string; - updatedAt?: null | number; - }; where?: Array<{ connector?: "AND" | "OR"; field: @@ -10136,7 +15157,7 @@ export type ComponentApi = | null; }>; }; - onUpdateHandle?: string; + onDeleteHandle?: string; paginationOpts: { cursor: string | null; endCursor?: string | null; @@ -10149,34 +15170,13 @@ export type ComponentApi = any, Name >; - updateOne: FunctionReference< + deleteOne: FunctionReference< "mutation", "internal", { input: | { model: "user"; - update: { - cbDefaultValueField?: null | string; - createdAt?: number; - customField?: null | string; - dateField?: null | number; - displayUsername?: null | string; - email?: null | string; - emailVerified?: boolean; - email_address?: null | string; - image?: null | string; - isAnonymous?: null | boolean; - name?: string; - numericField?: null | number; - phoneNumber?: null | string; - phoneNumberVerified?: null | boolean; - testField?: null | string; - twoFactorEnabled?: null | boolean; - updatedAt?: number; - userId?: null | string; - username?: null | string; - }; where?: Array<{ connector?: "AND" | "OR"; field: @@ -10224,15 +15224,6 @@ export type ComponentApi = } | { model: "session"; - update: { - createdAt?: number; - expiresAt?: number; - ipAddress?: null | string; - token?: string; - updatedAt?: number; - userAgent?: null | string; - userId?: string; - }; where?: Array<{ connector?: "AND" | "OR"; field: @@ -10268,23 +15259,10 @@ export type ComponentApi = } | { model: "account"; - update: { - accessToken?: null | string; - accessTokenExpiresAt?: null | number; - accountId?: string; - createdAt?: number; - idToken?: null | string; - password?: null | string; - providerId?: string; - refreshToken?: null | string; - refreshTokenExpiresAt?: null | number; - scope?: null | string; - updatedAt?: number; - userId?: string; - }; where?: Array<{ connector?: "AND" | "OR"; field: + | "issuer" | "accountId" | "providerId" | "userId" @@ -10322,13 +15300,6 @@ export type ComponentApi = } | { model: "verification"; - update: { - createdAt?: number; - expiresAt?: number; - identifier?: string; - updatedAt?: number; - value?: string; - }; where?: Array<{ connector?: "AND" | "OR"; field: @@ -10362,12 +15333,6 @@ export type ComponentApi = } | { model: "twoFactor"; - update: { - backupCodes?: string; - secret?: string; - userId?: string; - verified?: null | boolean; - }; where?: Array<{ connector?: "AND" | "OR"; field: @@ -10375,6 +15340,8 @@ export type ComponentApi = | "backupCodes" | "userId" | "verified" + | "failedVerificationCount" + | "lockedUntil" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -10391,28 +15358,15 @@ export type ComponentApi = | "ends_with"; value: | string - | number - | boolean - | Array - | Array - | null; - }>; - } - | { - model: "oauthApplication"; - update: { - clientId?: null | string; - clientSecret?: null | string; - createdAt?: null | number; - disabled?: null | boolean; - icon?: null | string; - metadata?: null | string; - name?: null | string; - redirectUrls?: null | string; - type?: null | string; - updatedAt?: null | number; - userId?: null | string; - }; + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "oauthApplication"; where?: Array<{ connector?: "AND" | "OR"; field: @@ -10452,17 +15406,6 @@ export type ComponentApi = } | { model: "oauthAccessToken"; - update: { - accessToken?: null | string; - accessTokenExpiresAt?: null | number; - clientId?: null | string; - createdAt?: null | number; - refreshToken?: null | string; - refreshTokenExpiresAt?: null | number; - scopes?: null | string; - updatedAt?: null | number; - userId?: null | string; - }; where?: Array<{ connector?: "AND" | "OR"; field: @@ -10500,14 +15443,6 @@ export type ComponentApi = } | { model: "oauthConsent"; - update: { - clientId?: null | string; - consentGiven?: null | boolean; - createdAt?: null | number; - scopes?: null | string; - updatedAt?: null | number; - userId?: null | string; - }; where?: Array<{ connector?: "AND" | "OR"; field: @@ -10542,12 +15477,6 @@ export type ComponentApi = } | { model: "jwks"; - update: { - createdAt?: number; - expiresAt?: null | number; - privateKey?: string; - publicKey?: string; - }; where?: Array<{ connector?: "AND" | "OR"; field: @@ -10555,6 +15484,8 @@ export type ComponentApi = | "privateKey" | "createdAt" | "expiresAt" + | "alg" + | "crv" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -10580,11 +15511,6 @@ export type ComponentApi = } | { model: "rateLimit"; - update: { - count?: number; - key?: string; - lastRequest?: number; - }; where?: Array<{ connector?: "AND" | "OR"; field: "key" | "count" | "lastRequest" | "_id"; @@ -10612,27 +15538,6 @@ export type ComponentApi = } | { model: "user_custom"; - update: { - cbDefaultValueField?: null | string; - createdAt?: number; - customField?: null | string; - dateField?: null | number; - displayUsername?: null | string; - email?: null | string; - emailVerified?: boolean; - email_address?: null | string; - image?: null | string; - isAnonymous?: null | boolean; - name?: string; - numericField?: null | number; - phoneNumber?: null | string; - phoneNumberVerified?: null | boolean; - testField?: null | string; - twoFactorEnabled?: null | boolean; - updatedAt?: number; - userId?: null | string; - username?: null | string; - }; where?: Array<{ connector?: "AND" | "OR"; field: @@ -10680,27 +15585,6 @@ export type ComponentApi = } | { model: "user_table"; - update: { - cbDefaultValueField?: null | string; - createdAt?: number; - customField?: null | string; - dateField?: null | number; - displayUsername?: null | string; - email?: null | string; - emailVerified?: boolean; - email_address?: null | string; - image?: null | string; - isAnonymous?: null | boolean; - name?: string; - numericField?: null | number; - phoneNumber?: null | string; - phoneNumberVerified?: null | boolean; - testField?: null | string; - twoFactorEnabled?: null | boolean; - updatedAt?: number; - userId?: null | string; - username?: null | string; - }; where?: Array<{ connector?: "AND" | "OR"; field: @@ -10748,7 +15632,6 @@ export type ComponentApi = } | { model: "oneToOneTable"; - update: { oneToOne?: string }; where?: Array<{ connector?: "AND" | "OR"; field: "oneToOne" | "_id"; @@ -10776,10 +15659,6 @@ export type ComponentApi = } | { model: "one_to_one_table"; - update: { - oneToOne?: null | string; - one_to_one?: null | string; - }; where?: Array<{ connector?: "AND" | "OR"; field: "oneToOne" | "one_to_one" | "_id"; @@ -10807,14 +15686,6 @@ export type ComponentApi = } | { model: "testModel"; - update: { - cbDefaultValueField?: null | string; - json?: any; - nullableReference?: null | string; - numberArray?: null | Array; - stringArray?: null | Array; - testField?: null | string; - }; where?: Array<{ connector?: "AND" | "OR"; field: @@ -10849,14 +15720,6 @@ export type ComponentApi = } | { model: "organization"; - update: { - createdAt?: number; - logo?: null | string; - metadata?: null | string; - name?: string; - slug?: string; - updatedAt?: null | number; - }; where?: Array<{ connector?: "AND" | "OR"; field: @@ -10891,13 +15754,6 @@ export type ComponentApi = } | { model: "member"; - update: { - createdAt?: number; - organizationId?: string; - role?: string; - updatedAt?: null | number; - userId?: string; - }; where?: Array<{ connector?: "AND" | "OR"; field: @@ -10931,16 +15787,11 @@ export type ComponentApi = } | { model: "team"; - update: { - createdAt?: number; - name?: string; - organizationId?: string; - updatedAt?: null | number; - }; where?: Array<{ connector?: "AND" | "OR"; field: | "name" + | "memberCount" | "organizationId" | "createdAt" | "updatedAt" @@ -10969,14 +15820,14 @@ export type ComponentApi = } | { model: "teamMember"; - update: { - createdAt?: null | number; - teamId?: string; - userId?: string; - }; where?: Array<{ connector?: "AND" | "OR"; - field: "teamId" | "userId" | "createdAt" | "_id"; + field: + | "teamId" + | "userId" + | "membershipKey" + | "createdAt" + | "_id"; mode?: "sensitive" | "insensitive"; operator?: | "lt" @@ -11001,17 +15852,6 @@ export type ComponentApi = } | { model: "invitation"; - update: { - createdAt?: null | number; - email?: null | string; - expiresAt?: null | number; - inviterId?: null | string; - organizationId?: null | string; - role?: null | string; - status?: null | string; - teamId?: null | string; - updatedAt?: null | number; - }; where?: Array<{ connector?: "AND" | "OR"; field: @@ -11047,277 +15887,162 @@ export type ComponentApi = | null; }>; }; - onUpdateHandle?: string; + onDeleteHandle?: string; + }, + any, + Name + >; + findMany: FunctionReference< + "query", + "internal", + { + join?: any; + limit?: number; + model: + | "user" + | "session" + | "account" + | "verification" + | "twoFactor" + | "oauthApplication" + | "oauthAccessToken" + | "oauthConsent" + | "jwks" + | "rateLimit" + | "user_custom" + | "user_table" + | "oneToOneTable" + | "one_to_one_table" + | "testModel" + | "organization" + | "member" + | "team" + | "teamMember" + | "invitation"; + offset?: number; + paginationOpts: { + cursor: string | null; + endCursor?: string | null; + id?: number; + maximumBytesRead?: number; + maximumRowsRead?: number; + numItems: number; + }; + select?: Array; + sortBy?: { direction: "asc" | "desc"; field: string }; + where?: Array<{ + connector?: "AND" | "OR"; + field: string; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + }, + any, + Name + >; + findOne: FunctionReference< + "query", + "internal", + { + join?: any; + model: + | "user" + | "session" + | "account" + | "verification" + | "twoFactor" + | "oauthApplication" + | "oauthAccessToken" + | "oauthConsent" + | "jwks" + | "rateLimit" + | "user_custom" + | "user_table" + | "oneToOneTable" + | "one_to_one_table" + | "testModel" + | "organization" + | "member" + | "team" + | "teamMember" + | "invitation"; + select?: Array; + where?: Array<{ + connector?: "AND" | "OR"; + field: string; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; }, any, Name >; - }; - adapterRenameField: { - create: FunctionReference< + incrementOne: FunctionReference< "mutation", "internal", { input: | { - data: { - cbDefaultValueField?: null | string; - createdAt: number; - customField?: null | string; - dateField?: null | number; - displayUsername?: null | string; - email?: null | string; - emailVerified: boolean; - email_address?: null | string; - image?: null | string; - isAnonymous?: null | boolean; - name: string; - numericField?: null | number; - phoneNumber?: null | string; - phoneNumberVerified?: null | boolean; - testField?: null | string; - twoFactorEnabled?: null | boolean; - updatedAt: number; - userId?: null | string; - username?: null | string; - }; + increment: Record; model: "user"; - } - | { - data: { - createdAt: number; - expiresAt: number; - ipAddress?: null | string; - token: string; - updatedAt: number; - userAgent?: null | string; - userId: string; - }; - model: "session"; - } - | { - data: { - accessToken?: null | string; - accessTokenExpiresAt?: null | number; - accountId: string; - createdAt: number; - idToken?: null | string; - password?: null | string; - providerId: string; - refreshToken?: null | string; - refreshTokenExpiresAt?: null | number; - scope?: null | string; - updatedAt: number; - userId: string; - }; - model: "account"; - } - | { - data: { - createdAt: number; - expiresAt: number; - identifier: string; - updatedAt: number; - value: string; - }; - model: "verification"; - } - | { - data: { - backupCodes: string; - secret: string; - userId: string; - verified?: null | boolean; - }; - model: "twoFactor"; - } - | { - data: { - clientId?: null | string; - clientSecret?: null | string; - createdAt?: null | number; - disabled?: null | boolean; - icon?: null | string; - metadata?: null | string; - name?: null | string; - redirectUrls?: null | string; - type?: null | string; - updatedAt?: null | number; - userId?: null | string; - }; - model: "oauthApplication"; - } - | { - data: { - accessToken?: null | string; - accessTokenExpiresAt?: null | number; - clientId?: null | string; - createdAt?: null | number; - refreshToken?: null | string; - refreshTokenExpiresAt?: null | number; - scopes?: null | string; - updatedAt?: null | number; - userId?: null | string; - }; - model: "oauthAccessToken"; - } - | { - data: { - clientId?: null | string; - consentGiven?: null | boolean; - createdAt?: null | number; - scopes?: null | string; - updatedAt?: null | number; - userId?: null | string; - }; - model: "oauthConsent"; - } - | { - data: { - createdAt: number; - expiresAt?: null | number; - privateKey: string; - publicKey: string; - }; - model: "jwks"; - } - | { - data: { count: number; key: string; lastRequest: number }; - model: "rateLimit"; - } - | { - data: { - cbDefaultValueField?: null | string; - createdAt: number; - customField?: null | string; - dateField?: null | number; - displayUsername?: null | string; - email?: null | string; - emailVerified: boolean; - email_address?: null | string; - image?: null | string; - isAnonymous?: null | boolean; - name: string; - numericField?: null | number; - phoneNumber?: null | string; - phoneNumberVerified?: null | boolean; - testField?: null | string; - twoFactorEnabled?: null | boolean; - updatedAt: number; - userId?: null | string; - username?: null | string; - }; - model: "user_custom"; - } - | { - data: { + set?: { cbDefaultValueField?: null | string; - createdAt: number; + createdAt?: number; customField?: null | string; dateField?: null | number; displayUsername?: null | string; email?: null | string; - emailVerified: boolean; + emailVerified?: boolean; email_address?: null | string; image?: null | string; isAnonymous?: null | boolean; - name: string; + name?: string; numericField?: null | number; phoneNumber?: null | string; phoneNumberVerified?: null | boolean; testField?: null | string; twoFactorEnabled?: null | boolean; - updatedAt: number; + updatedAt?: number; userId?: null | string; username?: null | string; }; - model: "user_table"; - } - | { data: { oneToOne: string }; model: "oneToOneTable" } - | { - data: { - oneToOne?: null | string; - one_to_one?: null | string; - }; - model: "one_to_one_table"; - } - | { - data: { - cbDefaultValueField?: null | string; - json?: any; - nullableReference?: null | string; - numberArray?: null | Array; - stringArray?: null | Array; - testField?: null | string; - }; - model: "testModel"; - } - | { - data: { - createdAt: number; - logo?: null | string; - metadata?: null | string; - name: string; - slug: string; - updatedAt?: null | number; - }; - model: "organization"; - } - | { - data: { - createdAt: number; - organizationId: string; - role: string; - updatedAt?: null | number; - userId: string; - }; - model: "member"; - } - | { - data: { - createdAt: number; - name: string; - organizationId: string; - updatedAt?: null | number; - }; - model: "team"; - } - | { - data: { - createdAt?: null | number; - teamId: string; - userId: string; - }; - model: "teamMember"; - } - | { - data: { - createdAt?: null | number; - email?: null | string; - expiresAt?: null | number; - inviterId?: null | string; - organizationId?: null | string; - role?: null | string; - status?: null | string; - teamId?: null | string; - updatedAt?: null | number; - }; - model: "invitation"; - }; - onCreateHandle?: string; - select?: Array; - }, - any, - Name - >; - deleteMany: FunctionReference< - "mutation", - "internal", - { - input: - | { - model: "user"; - where?: Array<{ + where: Array<{ connector?: "AND" | "OR"; field: | "name" @@ -11363,8 +16088,18 @@ export type ComponentApi = }>; } | { + increment: Record; model: "session"; - where?: Array<{ + set?: { + createdAt?: number; + expiresAt?: number; + ipAddress?: null | string; + token?: string; + updatedAt?: number; + userAgent?: null | string; + userId?: string; + }; + where: Array<{ connector?: "AND" | "OR"; field: | "expiresAt" @@ -11398,10 +16133,27 @@ export type ComponentApi = }>; } | { + increment: Record; model: "account"; - where?: Array<{ + set?: { + accessToken?: null | string; + accessTokenExpiresAt?: null | number; + accountId?: string; + createdAt?: number; + idToken?: null | string; + issuer?: null | string; + password?: null | string; + providerId?: string; + refreshToken?: null | string; + refreshTokenExpiresAt?: null | number; + scope?: null | string; + updatedAt?: number; + userId?: string; + }; + where: Array<{ connector?: "AND" | "OR"; field: + | "issuer" | "accountId" | "providerId" | "userId" @@ -11438,8 +16190,16 @@ export type ComponentApi = }>; } | { + increment: Record; model: "verification"; - where?: Array<{ + set?: { + createdAt?: number; + expiresAt?: number; + identifier?: string; + updatedAt?: number; + value?: string; + }; + where: Array<{ connector?: "AND" | "OR"; field: | "identifier" @@ -11471,14 +16231,25 @@ export type ComponentApi = }>; } | { + increment: Record; model: "twoFactor"; - where?: Array<{ + set?: { + backupCodes?: string; + failedVerificationCount?: null | number; + lockedUntil?: null | number; + secret?: string; + userId?: string; + verified?: null | boolean; + }; + where: Array<{ connector?: "AND" | "OR"; field: | "secret" | "backupCodes" | "userId" | "verified" + | "failedVerificationCount" + | "lockedUntil" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -11503,8 +16274,22 @@ export type ComponentApi = }>; } | { + increment: Record; model: "oauthApplication"; - where?: Array<{ + set?: { + clientId?: null | string; + clientSecret?: null | string; + createdAt?: null | number; + disabled?: null | boolean; + icon?: null | string; + metadata?: null | string; + name?: null | string; + redirectUrls?: null | string; + type?: null | string; + updatedAt?: null | number; + userId?: null | string; + }; + where: Array<{ connector?: "AND" | "OR"; field: | "name" @@ -11542,8 +16327,20 @@ export type ComponentApi = }>; } | { + increment: Record; model: "oauthAccessToken"; - where?: Array<{ + set?: { + accessToken?: null | string; + accessTokenExpiresAt?: null | number; + clientId?: null | string; + createdAt?: null | number; + refreshToken?: null | string; + refreshTokenExpiresAt?: null | number; + scopes?: null | string; + updatedAt?: null | number; + userId?: null | string; + }; + where: Array<{ connector?: "AND" | "OR"; field: | "accessToken" @@ -11579,8 +16376,17 @@ export type ComponentApi = }>; } | { + increment: Record; model: "oauthConsent"; - where?: Array<{ + set?: { + clientId?: null | string; + consentGiven?: null | boolean; + createdAt?: null | number; + scopes?: null | string; + updatedAt?: null | number; + userId?: null | string; + }; + where: Array<{ connector?: "AND" | "OR"; field: | "clientId" @@ -11613,14 +16419,25 @@ export type ComponentApi = }>; } | { + increment: Record; model: "jwks"; - where?: Array<{ + set?: { + alg?: null | string; + createdAt?: number; + crv?: null | string; + expiresAt?: null | number; + privateKey?: string; + publicKey?: string; + }; + where: Array<{ connector?: "AND" | "OR"; field: | "publicKey" | "privateKey" | "createdAt" | "expiresAt" + | "alg" + | "crv" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -11645,8 +16462,10 @@ export type ComponentApi = }>; } | { + increment: Record; model: "rateLimit"; - where?: Array<{ + set?: { count?: number; key?: string; lastRequest?: number }; + where: Array<{ connector?: "AND" | "OR"; field: "key" | "count" | "lastRequest" | "_id"; mode?: "sensitive" | "insensitive"; @@ -11672,8 +16491,30 @@ export type ComponentApi = }>; } | { + increment: Record; model: "user_custom"; - where?: Array<{ + set?: { + cbDefaultValueField?: null | string; + createdAt?: number; + customField?: null | string; + dateField?: null | number; + displayUsername?: null | string; + email?: null | string; + emailVerified?: boolean; + email_address?: null | string; + image?: null | string; + isAnonymous?: null | boolean; + name?: string; + numericField?: null | number; + phoneNumber?: null | string; + phoneNumberVerified?: null | boolean; + testField?: null | string; + twoFactorEnabled?: null | boolean; + updatedAt?: number; + userId?: null | string; + username?: null | string; + }; + where: Array<{ connector?: "AND" | "OR"; field: | "name" @@ -11719,8 +16560,30 @@ export type ComponentApi = }>; } | { + increment: Record; model: "user_table"; - where?: Array<{ + set?: { + cbDefaultValueField?: null | string; + createdAt?: number; + customField?: null | string; + dateField?: null | number; + displayUsername?: null | string; + email?: null | string; + emailVerified?: boolean; + email_address?: null | string; + image?: null | string; + isAnonymous?: null | boolean; + name?: string; + numericField?: null | number; + phoneNumber?: null | string; + phoneNumberVerified?: null | boolean; + testField?: null | string; + twoFactorEnabled?: null | boolean; + updatedAt?: number; + userId?: null | string; + username?: null | string; + }; + where: Array<{ connector?: "AND" | "OR"; field: | "name" @@ -11766,8 +16629,10 @@ export type ComponentApi = }>; } | { + increment: Record; model: "oneToOneTable"; - where?: Array<{ + set?: { oneToOne?: string }; + where: Array<{ connector?: "AND" | "OR"; field: "oneToOne" | "_id"; mode?: "sensitive" | "insensitive"; @@ -11793,8 +16658,13 @@ export type ComponentApi = }>; } | { + increment: Record; model: "one_to_one_table"; - where?: Array<{ + set?: { + oneToOne?: null | string; + one_to_one?: null | string; + }; + where: Array<{ connector?: "AND" | "OR"; field: "oneToOne" | "one_to_one" | "_id"; mode?: "sensitive" | "insensitive"; @@ -11820,8 +16690,17 @@ export type ComponentApi = }>; } | { + increment: Record; model: "testModel"; - where?: Array<{ + set?: { + cbDefaultValueField?: null | string; + json?: any; + nullableReference?: null | string; + numberArray?: null | Array; + stringArray?: null | Array; + testField?: null | string; + }; + where: Array<{ connector?: "AND" | "OR"; field: | "nullableReference" @@ -11854,8 +16733,17 @@ export type ComponentApi = }>; } | { + increment: Record; model: "organization"; - where?: Array<{ + set?: { + createdAt?: number; + logo?: null | string; + metadata?: null | string; + name?: string; + slug?: string; + updatedAt?: null | number; + }; + where: Array<{ connector?: "AND" | "OR"; field: | "name" @@ -11888,8 +16776,16 @@ export type ComponentApi = }>; } | { + increment: Record; model: "member"; - where?: Array<{ + set?: { + createdAt?: number; + organizationId?: string; + role?: string; + updatedAt?: null | number; + userId?: string; + }; + where: Array<{ connector?: "AND" | "OR"; field: | "organizationId" @@ -11921,11 +16817,20 @@ export type ComponentApi = }>; } | { + increment: Record; model: "team"; - where?: Array<{ + set?: { + createdAt?: number; + memberCount?: number; + name?: string; + organizationId?: string; + updatedAt?: null | number; + }; + where: Array<{ connector?: "AND" | "OR"; field: | "name" + | "memberCount" | "organizationId" | "createdAt" | "updatedAt" @@ -11953,10 +16858,22 @@ export type ComponentApi = }>; } | { + increment: Record; model: "teamMember"; - where?: Array<{ + set?: { + createdAt?: null | number; + membershipKey?: null | string; + teamId?: string; + userId?: string; + }; + where: Array<{ connector?: "AND" | "OR"; - field: "teamId" | "userId" | "createdAt" | "_id"; + field: + | "teamId" + | "userId" + | "membershipKey" + | "createdAt" + | "_id"; mode?: "sensitive" | "insensitive"; operator?: | "lt" @@ -11980,8 +16897,20 @@ export type ComponentApi = }>; } | { + increment: Record; model: "invitation"; - where?: Array<{ + set?: { + createdAt?: null | number; + email?: null | string; + expiresAt?: null | number; + inviterId?: null | string; + organizationId?: null | string; + role?: null | string; + status?: null | string; + teamId?: null | string; + updatedAt?: null | number; + }; + where: Array<{ connector?: "AND" | "OR"; field: | "email" @@ -12016,26 +16945,39 @@ export type ComponentApi = | null; }>; }; - onDeleteHandle?: string; - paginationOpts: { - cursor: string | null; - endCursor?: string | null; - id?: number; - maximumBytesRead?: number; - maximumRowsRead?: number; - numItems: number; - }; + onUpdateHandle?: string; }, any, Name >; - deleteOne: FunctionReference< + updateMany: FunctionReference< "mutation", "internal", { input: | { model: "user"; + update: { + cbDefaultValueField?: null | string; + createdAt?: number; + customField?: null | string; + dateField?: null | number; + displayUsername?: null | string; + email?: null | string; + emailVerified?: boolean; + email_address?: null | string; + image?: null | string; + isAnonymous?: null | boolean; + name?: string; + numericField?: null | number; + phoneNumber?: null | string; + phoneNumberVerified?: null | boolean; + testField?: null | string; + twoFactorEnabled?: null | boolean; + updatedAt?: number; + userId?: null | string; + username?: null | string; + }; where?: Array<{ connector?: "AND" | "OR"; field: @@ -12083,6 +17025,15 @@ export type ComponentApi = } | { model: "session"; + update: { + createdAt?: number; + expiresAt?: number; + ipAddress?: null | string; + token?: string; + updatedAt?: number; + userAgent?: null | string; + userId?: string; + }; where?: Array<{ connector?: "AND" | "OR"; field: @@ -12118,9 +17069,25 @@ export type ComponentApi = } | { model: "account"; + update: { + accessToken?: null | string; + accessTokenExpiresAt?: null | number; + accountId?: string; + createdAt?: number; + idToken?: null | string; + issuer?: null | string; + password?: null | string; + providerId?: string; + refreshToken?: null | string; + refreshTokenExpiresAt?: null | number; + scope?: null | string; + updatedAt?: number; + userId?: string; + }; where?: Array<{ connector?: "AND" | "OR"; field: + | "issuer" | "accountId" | "providerId" | "userId" @@ -12158,6 +17125,13 @@ export type ComponentApi = } | { model: "verification"; + update: { + createdAt?: number; + expiresAt?: number; + identifier?: string; + updatedAt?: number; + value?: string; + }; where?: Array<{ connector?: "AND" | "OR"; field: @@ -12191,6 +17165,14 @@ export type ComponentApi = } | { model: "twoFactor"; + update: { + backupCodes?: string; + failedVerificationCount?: null | number; + lockedUntil?: null | number; + secret?: string; + userId?: string; + verified?: null | boolean; + }; where?: Array<{ connector?: "AND" | "OR"; field: @@ -12198,6 +17180,8 @@ export type ComponentApi = | "backupCodes" | "userId" | "verified" + | "failedVerificationCount" + | "lockedUntil" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -12223,6 +17207,19 @@ export type ComponentApi = } | { model: "oauthApplication"; + update: { + clientId?: null | string; + clientSecret?: null | string; + createdAt?: null | number; + disabled?: null | boolean; + icon?: null | string; + metadata?: null | string; + name?: null | string; + redirectUrls?: null | string; + type?: null | string; + updatedAt?: null | number; + userId?: null | string; + }; where?: Array<{ connector?: "AND" | "OR"; field: @@ -12262,6 +17259,17 @@ export type ComponentApi = } | { model: "oauthAccessToken"; + update: { + accessToken?: null | string; + accessTokenExpiresAt?: null | number; + clientId?: null | string; + createdAt?: null | number; + refreshToken?: null | string; + refreshTokenExpiresAt?: null | number; + scopes?: null | string; + updatedAt?: null | number; + userId?: null | string; + }; where?: Array<{ connector?: "AND" | "OR"; field: @@ -12299,6 +17307,14 @@ export type ComponentApi = } | { model: "oauthConsent"; + update: { + clientId?: null | string; + consentGiven?: null | boolean; + createdAt?: null | number; + scopes?: null | string; + updatedAt?: null | number; + userId?: null | string; + }; where?: Array<{ connector?: "AND" | "OR"; field: @@ -12333,6 +17349,14 @@ export type ComponentApi = } | { model: "jwks"; + update: { + alg?: null | string; + createdAt?: number; + crv?: null | string; + expiresAt?: null | number; + privateKey?: string; + publicKey?: string; + }; where?: Array<{ connector?: "AND" | "OR"; field: @@ -12340,6 +17364,8 @@ export type ComponentApi = | "privateKey" | "createdAt" | "expiresAt" + | "alg" + | "crv" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -12365,6 +17391,11 @@ export type ComponentApi = } | { model: "rateLimit"; + update: { + count?: number; + key?: string; + lastRequest?: number; + }; where?: Array<{ connector?: "AND" | "OR"; field: "key" | "count" | "lastRequest" | "_id"; @@ -12392,6 +17423,27 @@ export type ComponentApi = } | { model: "user_custom"; + update: { + cbDefaultValueField?: null | string; + createdAt?: number; + customField?: null | string; + dateField?: null | number; + displayUsername?: null | string; + email?: null | string; + emailVerified?: boolean; + email_address?: null | string; + image?: null | string; + isAnonymous?: null | boolean; + name?: string; + numericField?: null | number; + phoneNumber?: null | string; + phoneNumberVerified?: null | boolean; + testField?: null | string; + twoFactorEnabled?: null | boolean; + updatedAt?: number; + userId?: null | string; + username?: null | string; + }; where?: Array<{ connector?: "AND" | "OR"; field: @@ -12439,6 +17491,27 @@ export type ComponentApi = } | { model: "user_table"; + update: { + cbDefaultValueField?: null | string; + createdAt?: number; + customField?: null | string; + dateField?: null | number; + displayUsername?: null | string; + email?: null | string; + emailVerified?: boolean; + email_address?: null | string; + image?: null | string; + isAnonymous?: null | boolean; + name?: string; + numericField?: null | number; + phoneNumber?: null | string; + phoneNumberVerified?: null | boolean; + testField?: null | string; + twoFactorEnabled?: null | boolean; + updatedAt?: number; + userId?: null | string; + username?: null | string; + }; where?: Array<{ connector?: "AND" | "OR"; field: @@ -12486,6 +17559,7 @@ export type ComponentApi = } | { model: "oneToOneTable"; + update: { oneToOne?: string }; where?: Array<{ connector?: "AND" | "OR"; field: "oneToOne" | "_id"; @@ -12513,6 +17587,10 @@ export type ComponentApi = } | { model: "one_to_one_table"; + update: { + oneToOne?: null | string; + one_to_one?: null | string; + }; where?: Array<{ connector?: "AND" | "OR"; field: "oneToOne" | "one_to_one" | "_id"; @@ -12540,6 +17618,14 @@ export type ComponentApi = } | { model: "testModel"; + update: { + cbDefaultValueField?: null | string; + json?: any; + nullableReference?: null | string; + numberArray?: null | Array; + stringArray?: null | Array; + testField?: null | string; + }; where?: Array<{ connector?: "AND" | "OR"; field: @@ -12574,6 +17660,14 @@ export type ComponentApi = } | { model: "organization"; + update: { + createdAt?: number; + logo?: null | string; + metadata?: null | string; + name?: string; + slug?: string; + updatedAt?: null | number; + }; where?: Array<{ connector?: "AND" | "OR"; field: @@ -12608,6 +17702,13 @@ export type ComponentApi = } | { model: "member"; + update: { + createdAt?: number; + organizationId?: string; + role?: string; + updatedAt?: null | number; + userId?: string; + }; where?: Array<{ connector?: "AND" | "OR"; field: @@ -12641,10 +17742,18 @@ export type ComponentApi = } | { model: "team"; + update: { + createdAt?: number; + memberCount?: number; + name?: string; + organizationId?: string; + updatedAt?: null | number; + }; where?: Array<{ connector?: "AND" | "OR"; field: | "name" + | "memberCount" | "organizationId" | "createdAt" | "updatedAt" @@ -12673,9 +17782,20 @@ export type ComponentApi = } | { model: "teamMember"; + update: { + createdAt?: null | number; + membershipKey?: null | string; + teamId?: string; + userId?: string; + }; where?: Array<{ connector?: "AND" | "OR"; - field: "teamId" | "userId" | "createdAt" | "_id"; + field: + | "teamId" + | "userId" + | "membershipKey" + | "createdAt" + | "_id"; mode?: "sensitive" | "insensitive"; operator?: | "lt" @@ -12700,6 +17820,17 @@ export type ComponentApi = } | { model: "invitation"; + update: { + createdAt?: null | number; + email?: null | string; + expiresAt?: null | number; + inviterId?: null | string; + organizationId?: null | string; + role?: null | string; + status?: null | string; + teamId?: null | string; + updatedAt?: null | number; + }; where?: Array<{ connector?: "AND" | "OR"; field: @@ -12721,147 +17852,34 @@ export type ComponentApi = | "gte" | "eq" | "in" - | "not_in" - | "ne" - | "contains" - | "starts_with" - | "ends_with"; - value: - | string - | number - | boolean - | Array - | Array - | null; - }>; - }; - onDeleteHandle?: string; - }, - any, - Name - >; - findMany: FunctionReference< - "query", - "internal", - { - join?: any; - limit?: number; - model: - | "user" - | "session" - | "account" - | "verification" - | "twoFactor" - | "oauthApplication" - | "oauthAccessToken" - | "oauthConsent" - | "jwks" - | "rateLimit" - | "user_custom" - | "user_table" - | "oneToOneTable" - | "one_to_one_table" - | "testModel" - | "organization" - | "member" - | "team" - | "teamMember" - | "invitation"; - offset?: number; - paginationOpts: { - cursor: string | null; - endCursor?: string | null; - id?: number; - maximumBytesRead?: number; - maximumRowsRead?: number; - numItems: number; - }; - select?: Array; - sortBy?: { direction: "asc" | "desc"; field: string }; - where?: Array<{ - connector?: "AND" | "OR"; - field: string; - mode?: "sensitive" | "insensitive"; - operator?: - | "lt" - | "lte" - | "gt" - | "gte" - | "eq" - | "in" - | "not_in" - | "ne" - | "contains" - | "starts_with" - | "ends_with"; - value: - | string - | number - | boolean - | Array - | Array - | null; - }>; - }, - any, - Name - >; - findOne: FunctionReference< - "query", - "internal", - { - join?: any; - model: - | "user" - | "session" - | "account" - | "verification" - | "twoFactor" - | "oauthApplication" - | "oauthAccessToken" - | "oauthConsent" - | "jwks" - | "rateLimit" - | "user_custom" - | "user_table" - | "oneToOneTable" - | "one_to_one_table" - | "testModel" - | "organization" - | "member" - | "team" - | "teamMember" - | "invitation"; - select?: Array; - where?: Array<{ - connector?: "AND" | "OR"; - field: string; - mode?: "sensitive" | "insensitive"; - operator?: - | "lt" - | "lte" - | "gt" - | "gte" - | "eq" - | "in" - | "not_in" - | "ne" - | "contains" - | "starts_with" - | "ends_with"; - value: - | string - | number - | boolean - | Array - | Array - | null; - }>; + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + }; + onUpdateHandle?: string; + paginationOpts: { + cursor: string | null; + endCursor?: string | null; + id?: number; + maximumBytesRead?: number; + maximumRowsRead?: number; + numItems: number; + }; }, any, Name >; - updateMany: FunctionReference< + updateOne: FunctionReference< "mutation", "internal", { @@ -12986,6 +18004,7 @@ export type ComponentApi = accountId?: string; createdAt?: number; idToken?: null | string; + issuer?: null | string; password?: null | string; providerId?: string; refreshToken?: null | string; @@ -12997,6 +18016,7 @@ export type ComponentApi = where?: Array<{ connector?: "AND" | "OR"; field: + | "issuer" | "accountId" | "providerId" | "userId" @@ -13076,6 +18096,8 @@ export type ComponentApi = model: "twoFactor"; update: { backupCodes?: string; + failedVerificationCount?: null | number; + lockedUntil?: null | number; secret?: string; userId?: string; verified?: null | boolean; @@ -13087,6 +18109,8 @@ export type ComponentApi = | "backupCodes" | "userId" | "verified" + | "failedVerificationCount" + | "lockedUntil" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -13255,7 +18279,9 @@ export type ComponentApi = | { model: "jwks"; update: { + alg?: null | string; createdAt?: number; + crv?: null | string; expiresAt?: null | number; privateKey?: string; publicKey?: string; @@ -13267,6 +18293,8 @@ export type ComponentApi = | "privateKey" | "createdAt" | "expiresAt" + | "alg" + | "crv" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -13417,24 +18445,247 @@ export type ComponentApi = connector?: "AND" | "OR"; field: | "name" - | "email" - | "email_address" - | "emailVerified" - | "image" + | "email" + | "email_address" + | "emailVerified" + | "image" + | "createdAt" + | "updatedAt" + | "twoFactorEnabled" + | "isAnonymous" + | "username" + | "displayUsername" + | "phoneNumber" + | "phoneNumberVerified" + | "userId" + | "testField" + | "cbDefaultValueField" + | "customField" + | "numericField" + | "dateField" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "oneToOneTable"; + update: { oneToOne?: string }; + where?: Array<{ + connector?: "AND" | "OR"; + field: "oneToOne" | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "one_to_one_table"; + update: { + oneToOne?: null | string; + one_to_one?: null | string; + }; + where?: Array<{ + connector?: "AND" | "OR"; + field: "oneToOne" | "one_to_one" | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "testModel"; + update: { + cbDefaultValueField?: null | string; + json?: any; + nullableReference?: null | string; + numberArray?: null | Array; + stringArray?: null | Array; + testField?: null | string; + }; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "nullableReference" + | "testField" + | "cbDefaultValueField" + | "stringArray" + | "numberArray" + | "json" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "organization"; + update: { + createdAt?: number; + logo?: null | string; + metadata?: null | string; + name?: string; + slug?: string; + updatedAt?: null | number; + }; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "name" + | "slug" + | "logo" + | "metadata" + | "createdAt" + | "updatedAt" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "member"; + update: { + createdAt?: number; + organizationId?: string; + role?: string; + updatedAt?: null | number; + userId?: string; + }; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "organizationId" + | "userId" + | "role" + | "createdAt" + | "updatedAt" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "team"; + update: { + createdAt?: number; + memberCount?: number; + name?: string; + organizationId?: string; + updatedAt?: null | number; + }; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "name" + | "memberCount" + | "organizationId" | "createdAt" | "updatedAt" - | "twoFactorEnabled" - | "isAnonymous" - | "username" - | "displayUsername" - | "phoneNumber" - | "phoneNumberVerified" - | "userId" - | "testField" - | "cbDefaultValueField" - | "customField" - | "numericField" - | "dateField" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -13459,11 +18710,21 @@ export type ComponentApi = }>; } | { - model: "oneToOneTable"; - update: { oneToOne?: string }; + model: "teamMember"; + update: { + createdAt?: null | number; + membershipKey?: null | string; + teamId?: string; + userId?: string; + }; where?: Array<{ connector?: "AND" | "OR"; - field: "oneToOne" | "_id"; + field: + | "teamId" + | "userId" + | "membershipKey" + | "createdAt" + | "_id"; mode?: "sensitive" | "insensitive"; operator?: | "lt" @@ -13487,14 +18748,31 @@ export type ComponentApi = }>; } | { - model: "one_to_one_table"; + model: "invitation"; update: { - oneToOne?: null | string; - one_to_one?: null | string; + createdAt?: null | number; + email?: null | string; + expiresAt?: null | number; + inviterId?: null | string; + organizationId?: null | string; + role?: null | string; + status?: null | string; + teamId?: null | string; + updatedAt?: null | number; }; where?: Array<{ connector?: "AND" | "OR"; - field: "oneToOne" | "one_to_one" | "_id"; + field: + | "email" + | "role" + | "status" + | "organizationId" + | "teamId" + | "inviterId" + | "expiresAt" + | "createdAt" + | "updatedAt" + | "_id"; mode?: "sensitive" | "insensitive"; operator?: | "lt" @@ -13516,10 +18794,208 @@ export type ComponentApi = | Array | null; }>; + }; + onUpdateHandle?: string; + }, + any, + Name + >; + }; + adapterRenameUserCustom: { + create: FunctionReference< + "mutation", + "internal", + { + input: + | { + data: { + cbDefaultValueField?: null | string; + createdAt: number; + customField?: null | string; + dateField?: null | number; + displayUsername?: null | string; + email?: null | string; + emailVerified: boolean; + email_address?: null | string; + image?: null | string; + isAnonymous?: null | boolean; + name: string; + numericField?: null | number; + phoneNumber?: null | string; + phoneNumberVerified?: null | boolean; + testField?: null | string; + twoFactorEnabled?: null | boolean; + updatedAt: number; + userId?: null | string; + username?: null | string; + }; + model: "user"; } | { - model: "testModel"; - update: { + data: { + createdAt: number; + expiresAt: number; + ipAddress?: null | string; + token: string; + updatedAt: number; + userAgent?: null | string; + userId: string; + }; + model: "session"; + } + | { + data: { + accessToken?: null | string; + accessTokenExpiresAt?: null | number; + accountId: string; + createdAt: number; + idToken?: null | string; + issuer?: null | string; + password?: null | string; + providerId: string; + refreshToken?: null | string; + refreshTokenExpiresAt?: null | number; + scope?: null | string; + updatedAt: number; + userId: string; + }; + model: "account"; + } + | { + data: { + createdAt: number; + expiresAt: number; + identifier: string; + updatedAt: number; + value: string; + }; + model: "verification"; + } + | { + data: { + backupCodes: string; + failedVerificationCount?: null | number; + lockedUntil?: null | number; + secret: string; + userId: string; + verified?: null | boolean; + }; + model: "twoFactor"; + } + | { + data: { + clientId?: null | string; + clientSecret?: null | string; + createdAt?: null | number; + disabled?: null | boolean; + icon?: null | string; + metadata?: null | string; + name?: null | string; + redirectUrls?: null | string; + type?: null | string; + updatedAt?: null | number; + userId?: null | string; + }; + model: "oauthApplication"; + } + | { + data: { + accessToken?: null | string; + accessTokenExpiresAt?: null | number; + clientId?: null | string; + createdAt?: null | number; + refreshToken?: null | string; + refreshTokenExpiresAt?: null | number; + scopes?: null | string; + updatedAt?: null | number; + userId?: null | string; + }; + model: "oauthAccessToken"; + } + | { + data: { + clientId?: null | string; + consentGiven?: null | boolean; + createdAt?: null | number; + scopes?: null | string; + updatedAt?: null | number; + userId?: null | string; + }; + model: "oauthConsent"; + } + | { + data: { + alg?: null | string; + createdAt: number; + crv?: null | string; + expiresAt?: null | number; + privateKey: string; + publicKey: string; + }; + model: "jwks"; + } + | { + data: { count: number; key: string; lastRequest: number }; + model: "rateLimit"; + } + | { + data: { + cbDefaultValueField?: null | string; + createdAt: number; + customField?: null | string; + dateField?: null | number; + displayUsername?: null | string; + email?: null | string; + emailVerified: boolean; + email_address?: null | string; + image?: null | string; + isAnonymous?: null | boolean; + name: string; + numericField?: null | number; + phoneNumber?: null | string; + phoneNumberVerified?: null | boolean; + testField?: null | string; + twoFactorEnabled?: null | boolean; + updatedAt: number; + userId?: null | string; + username?: null | string; + }; + model: "user_custom"; + } + | { + data: { + cbDefaultValueField?: null | string; + createdAt: number; + customField?: null | string; + dateField?: null | number; + displayUsername?: null | string; + email?: null | string; + emailVerified: boolean; + email_address?: null | string; + image?: null | string; + isAnonymous?: null | boolean; + name: string; + numericField?: null | number; + phoneNumber?: null | string; + phoneNumberVerified?: null | boolean; + testField?: null | string; + twoFactorEnabled?: null | boolean; + updatedAt: number; + userId?: null | string; + username?: null | string; + }; + model: "user_table"; + } + | { data: { oneToOne: string }; model: "oneToOneTable" } + | { + data: { + oneToOne?: null | string; + one_to_one?: null | string; + }; + model: "one_to_one_table"; + } + | { + data: { cbDefaultValueField?: null | string; json?: any; nullableReference?: null | string; @@ -13527,57 +19003,97 @@ export type ComponentApi = stringArray?: null | Array; testField?: null | string; }; - where?: Array<{ - connector?: "AND" | "OR"; - field: - | "nullableReference" - | "testField" - | "cbDefaultValueField" - | "stringArray" - | "numberArray" - | "json" - | "_id"; - mode?: "sensitive" | "insensitive"; - operator?: - | "lt" - | "lte" - | "gt" - | "gte" - | "eq" - | "in" - | "not_in" - | "ne" - | "contains" - | "starts_with" - | "ends_with"; - value: - | string - | number - | boolean - | Array - | Array - | null; - }>; + model: "testModel"; } | { - model: "organization"; - update: { - createdAt?: number; + data: { + createdAt: number; logo?: null | string; metadata?: null | string; - name?: string; - slug?: string; + name: string; + slug: string; + updatedAt?: null | number; + }; + model: "organization"; + } + | { + data: { + createdAt: number; + organizationId: string; + role: string; + updatedAt?: null | number; + userId: string; + }; + model: "member"; + } + | { + data: { + createdAt: number; + memberCount: number; + name: string; + organizationId: string; + updatedAt?: null | number; + }; + model: "team"; + } + | { + data: { + createdAt?: null | number; + membershipKey?: null | string; + teamId: string; + userId: string; + }; + model: "teamMember"; + } + | { + data: { + createdAt?: null | number; + email?: null | string; + expiresAt?: null | number; + inviterId?: null | string; + organizationId?: null | string; + role?: null | string; + status?: null | string; + teamId?: null | string; updatedAt?: null | number; }; + model: "invitation"; + }; + onCreateHandle?: string; + select?: Array; + }, + any, + Name + >; + deleteMany: FunctionReference< + "mutation", + "internal", + { + input: + | { + model: "user"; where?: Array<{ connector?: "AND" | "OR"; field: | "name" - | "slug" - | "logo" - | "metadata" + | "email" + | "email_address" + | "emailVerified" + | "image" | "createdAt" | "updatedAt" + | "twoFactorEnabled" + | "isAnonymous" + | "username" + | "displayUsername" + | "phoneNumber" + | "phoneNumberVerified" + | "userId" + | "testField" + | "cbDefaultValueField" + | "customField" + | "numericField" + | "dateField" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -13602,22 +19118,17 @@ export type ComponentApi = }>; } | { - model: "member"; - update: { - createdAt?: number; - organizationId?: string; - role?: string; - updatedAt?: null | number; - userId?: string; - }; + model: "session"; where?: Array<{ connector?: "AND" | "OR"; field: - | "organizationId" - | "userId" - | "role" + | "expiresAt" + | "token" | "createdAt" | "updatedAt" + | "ipAddress" + | "userAgent" + | "userId" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -13642,18 +19153,21 @@ export type ComponentApi = }>; } | { - model: "team"; - update: { - createdAt?: number; - name?: string; - organizationId?: string; - updatedAt?: null | number; - }; + model: "account"; where?: Array<{ connector?: "AND" | "OR"; field: - | "name" - | "organizationId" + | "issuer" + | "accountId" + | "providerId" + | "userId" + | "accessToken" + | "refreshToken" + | "idToken" + | "accessTokenExpiresAt" + | "refreshTokenExpiresAt" + | "scope" + | "password" | "createdAt" | "updatedAt" | "_id"; @@ -13680,15 +19194,16 @@ export type ComponentApi = }>; } | { - model: "teamMember"; - update: { - createdAt?: null | number; - teamId?: string; - userId?: string; - }; + model: "verification"; where?: Array<{ connector?: "AND" | "OR"; - field: "teamId" | "userId" | "createdAt" | "_id"; + field: + | "identifier" + | "value" + | "expiresAt" + | "createdAt" + | "updatedAt" + | "_id"; mode?: "sensitive" | "insensitive"; operator?: | "lt" @@ -13712,30 +19227,16 @@ export type ComponentApi = }>; } | { - model: "invitation"; - update: { - createdAt?: null | number; - email?: null | string; - expiresAt?: null | number; - inviterId?: null | string; - organizationId?: null | string; - role?: null | string; - status?: null | string; - teamId?: null | string; - updatedAt?: null | number; - }; + model: "twoFactor"; where?: Array<{ connector?: "AND" | "OR"; field: - | "email" - | "role" - | "status" - | "organizationId" - | "teamId" - | "inviterId" - | "expiresAt" - | "createdAt" - | "updatedAt" + | "secret" + | "backupCodes" + | "userId" + | "verified" + | "failedVerificationCount" + | "lockedUntil" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -13758,70 +19259,23 @@ export type ComponentApi = | Array | null; }>; - }; - onUpdateHandle?: string; - paginationOpts: { - cursor: string | null; - endCursor?: string | null; - id?: number; - maximumBytesRead?: number; - maximumRowsRead?: number; - numItems: number; - }; - }, - any, - Name - >; - updateOne: FunctionReference< - "mutation", - "internal", - { - input: + } | { - model: "user"; - update: { - cbDefaultValueField?: null | string; - createdAt?: number; - customField?: null | string; - dateField?: null | number; - displayUsername?: null | string; - email?: null | string; - emailVerified?: boolean; - email_address?: null | string; - image?: null | string; - isAnonymous?: null | boolean; - name?: string; - numericField?: null | number; - phoneNumber?: null | string; - phoneNumberVerified?: null | boolean; - testField?: null | string; - twoFactorEnabled?: null | boolean; - updatedAt?: number; - userId?: null | string; - username?: null | string; - }; + model: "oauthApplication"; where?: Array<{ connector?: "AND" | "OR"; field: | "name" - | "email" - | "email_address" - | "emailVerified" - | "image" - | "createdAt" - | "updatedAt" - | "twoFactorEnabled" - | "isAnonymous" - | "username" - | "displayUsername" - | "phoneNumber" - | "phoneNumberVerified" + | "icon" + | "metadata" + | "clientId" + | "clientSecret" + | "redirectUrls" + | "type" + | "disabled" | "userId" - | "testField" - | "cbDefaultValueField" - | "customField" - | "numericField" - | "dateField" + | "createdAt" + | "updatedAt" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -13846,26 +19300,19 @@ export type ComponentApi = }>; } | { - model: "session"; - update: { - createdAt?: number; - expiresAt?: number; - ipAddress?: null | string; - token?: string; - updatedAt?: number; - userAgent?: null | string; - userId?: string; - }; + model: "oauthAccessToken"; where?: Array<{ connector?: "AND" | "OR"; field: - | "expiresAt" - | "token" + | "accessToken" + | "refreshToken" + | "accessTokenExpiresAt" + | "refreshTokenExpiresAt" + | "clientId" + | "userId" + | "scopes" | "createdAt" | "updatedAt" - | "ipAddress" - | "userAgent" - | "userId" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -13890,36 +19337,16 @@ export type ComponentApi = }>; } | { - model: "account"; - update: { - accessToken?: null | string; - accessTokenExpiresAt?: null | number; - accountId?: string; - createdAt?: number; - idToken?: null | string; - password?: null | string; - providerId?: string; - refreshToken?: null | string; - refreshTokenExpiresAt?: null | number; - scope?: null | string; - updatedAt?: number; - userId?: string; - }; + model: "oauthConsent"; where?: Array<{ connector?: "AND" | "OR"; field: - | "accountId" - | "providerId" + | "clientId" | "userId" - | "accessToken" - | "refreshToken" - | "idToken" - | "accessTokenExpiresAt" - | "refreshTokenExpiresAt" - | "scope" - | "password" + | "scopes" | "createdAt" | "updatedAt" + | "consentGiven" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -13944,22 +19371,16 @@ export type ComponentApi = }>; } | { - model: "verification"; - update: { - createdAt?: number; - expiresAt?: number; - identifier?: string; - updatedAt?: number; - value?: string; - }; + model: "jwks"; where?: Array<{ connector?: "AND" | "OR"; field: - | "identifier" - | "value" - | "expiresAt" + | "publicKey" + | "privateKey" | "createdAt" - | "updatedAt" + | "expiresAt" + | "alg" + | "crv" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -13984,21 +19405,10 @@ export type ComponentApi = }>; } | { - model: "twoFactor"; - update: { - backupCodes?: string; - secret?: string; - userId?: string; - verified?: null | boolean; - }; + model: "rateLimit"; where?: Array<{ connector?: "AND" | "OR"; - field: - | "secret" - | "backupCodes" - | "userId" - | "verified" - | "_id"; + field: "key" | "count" | "lastRequest" | "_id"; mode?: "sensitive" | "insensitive"; operator?: | "lt" @@ -14022,34 +19432,29 @@ export type ComponentApi = }>; } | { - model: "oauthApplication"; - update: { - clientId?: null | string; - clientSecret?: null | string; - createdAt?: null | number; - disabled?: null | boolean; - icon?: null | string; - metadata?: null | string; - name?: null | string; - redirectUrls?: null | string; - type?: null | string; - updatedAt?: null | number; - userId?: null | string; - }; + model: "user_custom"; where?: Array<{ connector?: "AND" | "OR"; field: | "name" - | "icon" - | "metadata" - | "clientId" - | "clientSecret" - | "redirectUrls" - | "type" - | "disabled" - | "userId" + | "email" + | "email_address" + | "emailVerified" + | "image" | "createdAt" | "updatedAt" + | "twoFactorEnabled" + | "isAnonymous" + | "username" + | "displayUsername" + | "phoneNumber" + | "phoneNumberVerified" + | "userId" + | "testField" + | "cbDefaultValueField" + | "customField" + | "numericField" + | "dateField" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -14074,30 +19479,29 @@ export type ComponentApi = }>; } | { - model: "oauthAccessToken"; - update: { - accessToken?: null | string; - accessTokenExpiresAt?: null | number; - clientId?: null | string; - createdAt?: null | number; - refreshToken?: null | string; - refreshTokenExpiresAt?: null | number; - scopes?: null | string; - updatedAt?: null | number; - userId?: null | string; - }; + model: "user_table"; where?: Array<{ connector?: "AND" | "OR"; field: - | "accessToken" - | "refreshToken" - | "accessTokenExpiresAt" - | "refreshTokenExpiresAt" - | "clientId" - | "userId" - | "scopes" + | "name" + | "email" + | "email_address" + | "emailVerified" + | "image" | "createdAt" | "updatedAt" + | "twoFactorEnabled" + | "isAnonymous" + | "username" + | "displayUsername" + | "phoneNumber" + | "phoneNumberVerified" + | "userId" + | "testField" + | "cbDefaultValueField" + | "customField" + | "numericField" + | "dateField" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -14122,24 +19526,70 @@ export type ComponentApi = }>; } | { - model: "oauthConsent"; - update: { - clientId?: null | string; - consentGiven?: null | boolean; - createdAt?: null | number; - scopes?: null | string; - updatedAt?: null | number; - userId?: null | string; - }; + model: "oneToOneTable"; + where?: Array<{ + connector?: "AND" | "OR"; + field: "oneToOne" | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "one_to_one_table"; + where?: Array<{ + connector?: "AND" | "OR"; + field: "oneToOne" | "one_to_one" | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "testModel"; where?: Array<{ connector?: "AND" | "OR"; field: - | "clientId" - | "userId" - | "scopes" - | "createdAt" - | "updatedAt" - | "consentGiven" + | "nullableReference" + | "testField" + | "cbDefaultValueField" + | "stringArray" + | "numberArray" + | "json" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -14164,20 +19614,16 @@ export type ComponentApi = }>; } | { - model: "jwks"; - update: { - createdAt?: number; - expiresAt?: null | number; - privateKey?: string; - publicKey?: string; - }; + model: "organization"; where?: Array<{ connector?: "AND" | "OR"; field: - | "publicKey" - | "privateKey" + | "name" + | "slug" + | "logo" + | "metadata" | "createdAt" - | "expiresAt" + | "updatedAt" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -14202,15 +19648,16 @@ export type ComponentApi = }>; } | { - model: "rateLimit"; - update: { - count?: number; - key?: string; - lastRequest?: number; - }; + model: "member"; where?: Array<{ connector?: "AND" | "OR"; - field: "key" | "count" | "lastRequest" | "_id"; + field: + | "organizationId" + | "userId" + | "role" + | "createdAt" + | "updatedAt" + | "_id"; mode?: "sensitive" | "insensitive"; operator?: | "lt" @@ -14234,50 +19681,15 @@ export type ComponentApi = }>; } | { - model: "user_custom"; - update: { - cbDefaultValueField?: null | string; - createdAt?: number; - customField?: null | string; - dateField?: null | number; - displayUsername?: null | string; - email?: null | string; - emailVerified?: boolean; - email_address?: null | string; - image?: null | string; - isAnonymous?: null | boolean; - name?: string; - numericField?: null | number; - phoneNumber?: null | string; - phoneNumberVerified?: null | boolean; - testField?: null | string; - twoFactorEnabled?: null | boolean; - updatedAt?: number; - userId?: null | string; - username?: null | string; - }; + model: "team"; where?: Array<{ connector?: "AND" | "OR"; field: | "name" - | "email" - | "email_address" - | "emailVerified" - | "image" + | "memberCount" + | "organizationId" | "createdAt" | "updatedAt" - | "twoFactorEnabled" - | "isAnonymous" - | "username" - | "displayUsername" - | "phoneNumber" - | "phoneNumberVerified" - | "userId" - | "testField" - | "cbDefaultValueField" - | "customField" - | "numericField" - | "dateField" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -14302,50 +19714,14 @@ export type ComponentApi = }>; } | { - model: "user_table"; - update: { - cbDefaultValueField?: null | string; - createdAt?: number; - customField?: null | string; - dateField?: null | number; - displayUsername?: null | string; - email?: null | string; - emailVerified?: boolean; - email_address?: null | string; - image?: null | string; - isAnonymous?: null | boolean; - name?: string; - numericField?: null | number; - phoneNumber?: null | string; - phoneNumberVerified?: null | boolean; - testField?: null | string; - twoFactorEnabled?: null | boolean; - updatedAt?: number; - userId?: null | string; - username?: null | string; - }; + model: "teamMember"; where?: Array<{ connector?: "AND" | "OR"; field: - | "name" - | "email" - | "email_address" - | "emailVerified" - | "image" - | "createdAt" - | "updatedAt" - | "twoFactorEnabled" - | "isAnonymous" - | "username" - | "displayUsername" - | "phoneNumber" - | "phoneNumberVerified" + | "teamId" | "userId" - | "testField" - | "cbDefaultValueField" - | "customField" - | "numericField" - | "dateField" + | "membershipKey" + | "createdAt" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -14370,11 +19746,20 @@ export type ComponentApi = }>; } | { - model: "oneToOneTable"; - update: { oneToOne?: string }; + model: "invitation"; where?: Array<{ connector?: "AND" | "OR"; - field: "oneToOne" | "_id"; + field: + | "email" + | "role" + | "status" + | "organizationId" + | "teamId" + | "inviterId" + | "expiresAt" + | "createdAt" + | "updatedAt" + | "_id"; mode?: "sensitive" | "insensitive"; operator?: | "lt" @@ -14396,16 +19781,50 @@ export type ComponentApi = | Array | null; }>; - } + }; + onDeleteHandle?: string; + paginationOpts: { + cursor: string | null; + endCursor?: string | null; + id?: number; + maximumBytesRead?: number; + maximumRowsRead?: number; + numItems: number; + }; + }, + any, + Name + >; + deleteOne: FunctionReference< + "mutation", + "internal", + { + input: | { - model: "one_to_one_table"; - update: { - oneToOne?: null | string; - one_to_one?: null | string; - }; + model: "user"; where?: Array<{ connector?: "AND" | "OR"; - field: "oneToOne" | "one_to_one" | "_id"; + field: + | "name" + | "email" + | "email_address" + | "emailVerified" + | "image" + | "createdAt" + | "updatedAt" + | "twoFactorEnabled" + | "isAnonymous" + | "username" + | "displayUsername" + | "phoneNumber" + | "phoneNumberVerified" + | "userId" + | "testField" + | "cbDefaultValueField" + | "customField" + | "numericField" + | "dateField" + | "_id"; mode?: "sensitive" | "insensitive"; operator?: | "lt" @@ -14429,24 +19848,17 @@ export type ComponentApi = }>; } | { - model: "testModel"; - update: { - cbDefaultValueField?: null | string; - json?: any; - nullableReference?: null | string; - numberArray?: null | Array; - stringArray?: null | Array; - testField?: null | string; - }; + model: "session"; where?: Array<{ connector?: "AND" | "OR"; field: - | "nullableReference" - | "testField" - | "cbDefaultValueField" - | "stringArray" - | "numberArray" - | "json" + | "expiresAt" + | "token" + | "createdAt" + | "updatedAt" + | "ipAddress" + | "userAgent" + | "userId" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -14471,22 +19883,21 @@ export type ComponentApi = }>; } | { - model: "organization"; - update: { - createdAt?: number; - logo?: null | string; - metadata?: null | string; - name?: string; - slug?: string; - updatedAt?: null | number; - }; + model: "account"; where?: Array<{ connector?: "AND" | "OR"; field: - | "name" - | "slug" - | "logo" - | "metadata" + | "issuer" + | "accountId" + | "providerId" + | "userId" + | "accessToken" + | "refreshToken" + | "idToken" + | "accessTokenExpiresAt" + | "refreshTokenExpiresAt" + | "scope" + | "password" | "createdAt" | "updatedAt" | "_id"; @@ -14513,20 +19924,13 @@ export type ComponentApi = }>; } | { - model: "member"; - update: { - createdAt?: number; - organizationId?: string; - role?: string; - updatedAt?: null | number; - userId?: string; - }; + model: "verification"; where?: Array<{ connector?: "AND" | "OR"; field: - | "organizationId" - | "userId" - | "role" + | "identifier" + | "value" + | "expiresAt" | "createdAt" | "updatedAt" | "_id"; @@ -14553,20 +19957,16 @@ export type ComponentApi = }>; } | { - model: "team"; - update: { - createdAt?: number; - name?: string; - organizationId?: string; - updatedAt?: null | number; - }; + model: "twoFactor"; where?: Array<{ connector?: "AND" | "OR"; field: - | "name" - | "organizationId" - | "createdAt" - | "updatedAt" + | "secret" + | "backupCodes" + | "userId" + | "verified" + | "failedVerificationCount" + | "lockedUntil" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -14591,15 +19991,22 @@ export type ComponentApi = }>; } | { - model: "teamMember"; - update: { - createdAt?: null | number; - teamId?: string; - userId?: string; - }; + model: "oauthApplication"; where?: Array<{ connector?: "AND" | "OR"; - field: "teamId" | "userId" | "createdAt" | "_id"; + field: + | "name" + | "icon" + | "metadata" + | "clientId" + | "clientSecret" + | "redirectUrls" + | "type" + | "disabled" + | "userId" + | "createdAt" + | "updatedAt" + | "_id"; mode?: "sensitive" | "insensitive"; operator?: | "lt" @@ -14623,28 +20030,17 @@ export type ComponentApi = }>; } | { - model: "invitation"; - update: { - createdAt?: null | number; - email?: null | string; - expiresAt?: null | number; - inviterId?: null | string; - organizationId?: null | string; - role?: null | string; - status?: null | string; - teamId?: null | string; - updatedAt?: null | number; - }; + model: "oauthAccessToken"; where?: Array<{ connector?: "AND" | "OR"; field: - | "email" - | "role" - | "status" - | "organizationId" - | "teamId" - | "inviterId" - | "expiresAt" + | "accessToken" + | "refreshToken" + | "accessTokenExpiresAt" + | "refreshTokenExpiresAt" + | "clientId" + | "userId" + | "scopes" | "createdAt" | "updatedAt" | "_id"; @@ -14661,285 +20057,112 @@ export type ComponentApi = | "contains" | "starts_with" | "ends_with"; - value: - | string - | number - | boolean - | Array - | Array - | null; - }>; - }; - onUpdateHandle?: string; - }, - any, - Name - >; - }; - adapterRenameUserCustom: { - create: FunctionReference< - "mutation", - "internal", - { - input: - | { - data: { - cbDefaultValueField?: null | string; - createdAt: number; - customField?: null | string; - dateField?: null | number; - displayUsername?: null | string; - email?: null | string; - emailVerified: boolean; - email_address?: null | string; - image?: null | string; - isAnonymous?: null | boolean; - name: string; - numericField?: null | number; - phoneNumber?: null | string; - phoneNumberVerified?: null | boolean; - testField?: null | string; - twoFactorEnabled?: null | boolean; - updatedAt: number; - userId?: null | string; - username?: null | string; - }; - model: "user"; - } - | { - data: { - createdAt: number; - expiresAt: number; - ipAddress?: null | string; - token: string; - updatedAt: number; - userAgent?: null | string; - userId: string; - }; - model: "session"; - } - | { - data: { - accessToken?: null | string; - accessTokenExpiresAt?: null | number; - accountId: string; - createdAt: number; - idToken?: null | string; - password?: null | string; - providerId: string; - refreshToken?: null | string; - refreshTokenExpiresAt?: null | number; - scope?: null | string; - updatedAt: number; - userId: string; - }; - model: "account"; - } - | { - data: { - createdAt: number; - expiresAt: number; - identifier: string; - updatedAt: number; - value: string; - }; - model: "verification"; - } - | { - data: { - backupCodes: string; - secret: string; - userId: string; - verified?: null | boolean; - }; - model: "twoFactor"; - } - | { - data: { - clientId?: null | string; - clientSecret?: null | string; - createdAt?: null | number; - disabled?: null | boolean; - icon?: null | string; - metadata?: null | string; - name?: null | string; - redirectUrls?: null | string; - type?: null | string; - updatedAt?: null | number; - userId?: null | string; - }; - model: "oauthApplication"; - } - | { - data: { - accessToken?: null | string; - accessTokenExpiresAt?: null | number; - clientId?: null | string; - createdAt?: null | number; - refreshToken?: null | string; - refreshTokenExpiresAt?: null | number; - scopes?: null | string; - updatedAt?: null | number; - userId?: null | string; - }; - model: "oauthAccessToken"; - } - | { - data: { - clientId?: null | string; - consentGiven?: null | boolean; - createdAt?: null | number; - scopes?: null | string; - updatedAt?: null | number; - userId?: null | string; - }; - model: "oauthConsent"; - } - | { - data: { - createdAt: number; - expiresAt?: null | number; - privateKey: string; - publicKey: string; - }; - model: "jwks"; - } - | { - data: { count: number; key: string; lastRequest: number }; - model: "rateLimit"; - } - | { - data: { - cbDefaultValueField?: null | string; - createdAt: number; - customField?: null | string; - dateField?: null | number; - displayUsername?: null | string; - email?: null | string; - emailVerified: boolean; - email_address?: null | string; - image?: null | string; - isAnonymous?: null | boolean; - name: string; - numericField?: null | number; - phoneNumber?: null | string; - phoneNumberVerified?: null | boolean; - testField?: null | string; - twoFactorEnabled?: null | boolean; - updatedAt: number; - userId?: null | string; - username?: null | string; - }; - model: "user_custom"; - } - | { - data: { - cbDefaultValueField?: null | string; - createdAt: number; - customField?: null | string; - dateField?: null | number; - displayUsername?: null | string; - email?: null | string; - emailVerified: boolean; - email_address?: null | string; - image?: null | string; - isAnonymous?: null | boolean; - name: string; - numericField?: null | number; - phoneNumber?: null | string; - phoneNumberVerified?: null | boolean; - testField?: null | string; - twoFactorEnabled?: null | boolean; - updatedAt: number; - userId?: null | string; - username?: null | string; - }; - model: "user_table"; - } - | { data: { oneToOne: string }; model: "oneToOneTable" } - | { - data: { - oneToOne?: null | string; - one_to_one?: null | string; - }; - model: "one_to_one_table"; - } - | { - data: { - cbDefaultValueField?: null | string; - json?: any; - nullableReference?: null | string; - numberArray?: null | Array; - stringArray?: null | Array; - testField?: null | string; - }; - model: "testModel"; - } - | { - data: { - createdAt: number; - logo?: null | string; - metadata?: null | string; - name: string; - slug: string; - updatedAt?: null | number; - }; - model: "organization"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; } | { - data: { - createdAt: number; - organizationId: string; - role: string; - updatedAt?: null | number; - userId: string; - }; - model: "member"; + model: "oauthConsent"; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "clientId" + | "userId" + | "scopes" + | "createdAt" + | "updatedAt" + | "consentGiven" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; } | { - data: { - createdAt: number; - name: string; - organizationId: string; - updatedAt?: null | number; - }; - model: "team"; + model: "jwks"; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "publicKey" + | "privateKey" + | "createdAt" + | "expiresAt" + | "alg" + | "crv" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; } | { - data: { - createdAt?: null | number; - teamId: string; - userId: string; - }; - model: "teamMember"; + model: "rateLimit"; + where?: Array<{ + connector?: "AND" | "OR"; + field: "key" | "count" | "lastRequest" | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; } | { - data: { - createdAt?: null | number; - email?: null | string; - expiresAt?: null | number; - inviterId?: null | string; - organizationId?: null | string; - role?: null | string; - status?: null | string; - teamId?: null | string; - updatedAt?: null | number; - }; - model: "invitation"; - }; - onCreateHandle?: string; - select?: Array; - }, - any, - Name - >; - deleteMany: FunctionReference< - "mutation", - "internal", - { - input: - | { - model: "user"; + model: "user_custom"; where?: Array<{ connector?: "AND" | "OR"; field: @@ -14986,17 +20209,29 @@ export type ComponentApi = }>; } | { - model: "session"; + model: "user_table"; where?: Array<{ connector?: "AND" | "OR"; field: - | "expiresAt" - | "token" + | "name" + | "email" + | "email_address" + | "emailVerified" + | "image" | "createdAt" | "updatedAt" - | "ipAddress" - | "userAgent" + | "twoFactorEnabled" + | "isAnonymous" + | "username" + | "displayUsername" + | "phoneNumber" + | "phoneNumberVerified" | "userId" + | "testField" + | "cbDefaultValueField" + | "customField" + | "numericField" + | "dateField" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -15021,23 +20256,10 @@ export type ComponentApi = }>; } | { - model: "account"; + model: "oneToOneTable"; where?: Array<{ connector?: "AND" | "OR"; - field: - | "accountId" - | "providerId" - | "userId" - | "accessToken" - | "refreshToken" - | "idToken" - | "accessTokenExpiresAt" - | "refreshTokenExpiresAt" - | "scope" - | "password" - | "createdAt" - | "updatedAt" - | "_id"; + field: "oneToOne" | "_id"; mode?: "sensitive" | "insensitive"; operator?: | "lt" @@ -15061,16 +20283,10 @@ export type ComponentApi = }>; } | { - model: "verification"; + model: "one_to_one_table"; where?: Array<{ connector?: "AND" | "OR"; - field: - | "identifier" - | "value" - | "expiresAt" - | "createdAt" - | "updatedAt" - | "_id"; + field: "oneToOne" | "one_to_one" | "_id"; mode?: "sensitive" | "insensitive"; operator?: | "lt" @@ -15094,14 +20310,16 @@ export type ComponentApi = }>; } | { - model: "twoFactor"; + model: "testModel"; where?: Array<{ connector?: "AND" | "OR"; field: - | "secret" - | "backupCodes" - | "userId" - | "verified" + | "nullableReference" + | "testField" + | "cbDefaultValueField" + | "stringArray" + | "numberArray" + | "json" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -15126,19 +20344,14 @@ export type ComponentApi = }>; } | { - model: "oauthApplication"; + model: "organization"; where?: Array<{ connector?: "AND" | "OR"; field: | "name" - | "icon" + | "slug" + | "logo" | "metadata" - | "clientId" - | "clientSecret" - | "redirectUrls" - | "type" - | "disabled" - | "userId" | "createdAt" | "updatedAt" | "_id"; @@ -15165,17 +20378,13 @@ export type ComponentApi = }>; } | { - model: "oauthAccessToken"; + model: "member"; where?: Array<{ connector?: "AND" | "OR"; field: - | "accessToken" - | "refreshToken" - | "accessTokenExpiresAt" - | "refreshTokenExpiresAt" - | "clientId" + | "organizationId" | "userId" - | "scopes" + | "role" | "createdAt" | "updatedAt" | "_id"; @@ -15202,16 +20411,15 @@ export type ComponentApi = }>; } | { - model: "oauthConsent"; + model: "team"; where?: Array<{ connector?: "AND" | "OR"; field: - | "clientId" - | "userId" - | "scopes" + | "name" + | "memberCount" + | "organizationId" | "createdAt" | "updatedAt" - | "consentGiven" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -15236,14 +20444,14 @@ export type ComponentApi = }>; } | { - model: "jwks"; + model: "teamMember"; where?: Array<{ connector?: "AND" | "OR"; field: - | "publicKey" - | "privateKey" + | "teamId" + | "userId" + | "membershipKey" | "createdAt" - | "expiresAt" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -15268,10 +20476,20 @@ export type ComponentApi = }>; } | { - model: "rateLimit"; + model: "invitation"; where?: Array<{ connector?: "AND" | "OR"; - field: "key" | "count" | "lastRequest" | "_id"; + field: + | "email" + | "role" + | "status" + | "organizationId" + | "teamId" + | "inviterId" + | "expiresAt" + | "createdAt" + | "updatedAt" + | "_id"; mode?: "sensitive" | "insensitive"; operator?: | "lt" @@ -15293,10 +20511,163 @@ export type ComponentApi = | Array | null; }>; - } + }; + onDeleteHandle?: string; + }, + any, + Name + >; + findMany: FunctionReference< + "query", + "internal", + { + join?: any; + limit?: number; + model: + | "user" + | "session" + | "account" + | "verification" + | "twoFactor" + | "oauthApplication" + | "oauthAccessToken" + | "oauthConsent" + | "jwks" + | "rateLimit" + | "user_custom" + | "user_table" + | "oneToOneTable" + | "one_to_one_table" + | "testModel" + | "organization" + | "member" + | "team" + | "teamMember" + | "invitation"; + offset?: number; + paginationOpts: { + cursor: string | null; + endCursor?: string | null; + id?: number; + maximumBytesRead?: number; + maximumRowsRead?: number; + numItems: number; + }; + select?: Array; + sortBy?: { direction: "asc" | "desc"; field: string }; + where?: Array<{ + connector?: "AND" | "OR"; + field: string; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + }, + any, + Name + >; + findOne: FunctionReference< + "query", + "internal", + { + join?: any; + model: + | "user" + | "session" + | "account" + | "verification" + | "twoFactor" + | "oauthApplication" + | "oauthAccessToken" + | "oauthConsent" + | "jwks" + | "rateLimit" + | "user_custom" + | "user_table" + | "oneToOneTable" + | "one_to_one_table" + | "testModel" + | "organization" + | "member" + | "team" + | "teamMember" + | "invitation"; + select?: Array; + where?: Array<{ + connector?: "AND" | "OR"; + field: string; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + }, + any, + Name + >; + incrementOne: FunctionReference< + "mutation", + "internal", + { + input: | { - model: "user_custom"; - where?: Array<{ + increment: Record; + model: "user"; + set?: { + cbDefaultValueField?: null | string; + createdAt?: number; + customField?: null | string; + dateField?: null | number; + displayUsername?: null | string; + email?: null | string; + emailVerified?: boolean; + email_address?: null | string; + image?: null | string; + isAnonymous?: null | boolean; + name?: string; + numericField?: null | number; + phoneNumber?: null | string; + phoneNumberVerified?: null | boolean; + testField?: null | string; + twoFactorEnabled?: null | boolean; + updatedAt?: number; + userId?: null | string; + username?: null | string; + }; + where: Array<{ connector?: "AND" | "OR"; field: | "name" @@ -15342,29 +20713,27 @@ export type ComponentApi = }>; } | { - model: "user_table"; - where?: Array<{ + increment: Record; + model: "session"; + set?: { + createdAt?: number; + expiresAt?: number; + ipAddress?: null | string; + token?: string; + updatedAt?: number; + userAgent?: null | string; + userId?: string; + }; + where: Array<{ connector?: "AND" | "OR"; field: - | "name" - | "email" - | "email_address" - | "emailVerified" - | "image" + | "expiresAt" + | "token" | "createdAt" | "updatedAt" - | "twoFactorEnabled" - | "isAnonymous" - | "username" - | "displayUsername" - | "phoneNumber" - | "phoneNumberVerified" + | "ipAddress" + | "userAgent" | "userId" - | "testField" - | "cbDefaultValueField" - | "customField" - | "numericField" - | "dateField" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -15389,10 +20758,40 @@ export type ComponentApi = }>; } | { - model: "oneToOneTable"; - where?: Array<{ + increment: Record; + model: "account"; + set?: { + accessToken?: null | string; + accessTokenExpiresAt?: null | number; + accountId?: string; + createdAt?: number; + idToken?: null | string; + issuer?: null | string; + password?: null | string; + providerId?: string; + refreshToken?: null | string; + refreshTokenExpiresAt?: null | number; + scope?: null | string; + updatedAt?: number; + userId?: string; + }; + where: Array<{ connector?: "AND" | "OR"; - field: "oneToOne" | "_id"; + field: + | "issuer" + | "accountId" + | "providerId" + | "userId" + | "accessToken" + | "refreshToken" + | "idToken" + | "accessTokenExpiresAt" + | "refreshTokenExpiresAt" + | "scope" + | "password" + | "createdAt" + | "updatedAt" + | "_id"; mode?: "sensitive" | "insensitive"; operator?: | "lt" @@ -15416,10 +20815,24 @@ export type ComponentApi = }>; } | { - model: "one_to_one_table"; - where?: Array<{ + increment: Record; + model: "verification"; + set?: { + createdAt?: number; + expiresAt?: number; + identifier?: string; + updatedAt?: number; + value?: string; + }; + where: Array<{ connector?: "AND" | "OR"; - field: "oneToOne" | "one_to_one" | "_id"; + field: + | "identifier" + | "value" + | "expiresAt" + | "createdAt" + | "updatedAt" + | "_id"; mode?: "sensitive" | "insensitive"; operator?: | "lt" @@ -15443,16 +20856,25 @@ export type ComponentApi = }>; } | { - model: "testModel"; - where?: Array<{ + increment: Record; + model: "twoFactor"; + set?: { + backupCodes?: string; + failedVerificationCount?: null | number; + lockedUntil?: null | number; + secret?: string; + userId?: string; + verified?: null | boolean; + }; + where: Array<{ connector?: "AND" | "OR"; field: - | "nullableReference" - | "testField" - | "cbDefaultValueField" - | "stringArray" - | "numberArray" - | "json" + | "secret" + | "backupCodes" + | "userId" + | "verified" + | "failedVerificationCount" + | "lockedUntil" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -15477,14 +20899,33 @@ export type ComponentApi = }>; } | { - model: "organization"; - where?: Array<{ + increment: Record; + model: "oauthApplication"; + set?: { + clientId?: null | string; + clientSecret?: null | string; + createdAt?: null | number; + disabled?: null | boolean; + icon?: null | string; + metadata?: null | string; + name?: null | string; + redirectUrls?: null | string; + type?: null | string; + updatedAt?: null | number; + userId?: null | string; + }; + where: Array<{ connector?: "AND" | "OR"; field: | "name" - | "slug" - | "logo" + | "icon" | "metadata" + | "clientId" + | "clientSecret" + | "redirectUrls" + | "type" + | "disabled" + | "userId" | "createdAt" | "updatedAt" | "_id"; @@ -15511,13 +20952,29 @@ export type ComponentApi = }>; } | { - model: "member"; - where?: Array<{ + increment: Record; + model: "oauthAccessToken"; + set?: { + accessToken?: null | string; + accessTokenExpiresAt?: null | number; + clientId?: null | string; + createdAt?: null | number; + refreshToken?: null | string; + refreshTokenExpiresAt?: null | number; + scopes?: null | string; + updatedAt?: null | number; + userId?: null | string; + }; + where: Array<{ connector?: "AND" | "OR"; field: - | "organizationId" + | "accessToken" + | "refreshToken" + | "accessTokenExpiresAt" + | "refreshTokenExpiresAt" + | "clientId" | "userId" - | "role" + | "scopes" | "createdAt" | "updatedAt" | "_id"; @@ -15544,14 +21001,25 @@ export type ComponentApi = }>; } | { - model: "team"; - where?: Array<{ + increment: Record; + model: "oauthConsent"; + set?: { + clientId?: null | string; + consentGiven?: null | boolean; + createdAt?: null | number; + scopes?: null | string; + updatedAt?: null | number; + userId?: null | string; + }; + where: Array<{ connector?: "AND" | "OR"; field: - | "name" - | "organizationId" + | "clientId" + | "userId" + | "scopes" | "createdAt" | "updatedAt" + | "consentGiven" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -15576,10 +21044,26 @@ export type ComponentApi = }>; } | { - model: "teamMember"; - where?: Array<{ + increment: Record; + model: "jwks"; + set?: { + alg?: null | string; + createdAt?: number; + crv?: null | string; + expiresAt?: null | number; + privateKey?: string; + publicKey?: string; + }; + where: Array<{ connector?: "AND" | "OR"; - field: "teamId" | "userId" | "createdAt" | "_id"; + field: + | "publicKey" + | "privateKey" + | "createdAt" + | "expiresAt" + | "alg" + | "crv" + | "_id"; mode?: "sensitive" | "insensitive"; operator?: | "lt" @@ -15603,20 +21087,12 @@ export type ComponentApi = }>; } | { - model: "invitation"; - where?: Array<{ + increment: Record; + model: "rateLimit"; + set?: { count?: number; key?: string; lastRequest?: number }; + where: Array<{ connector?: "AND" | "OR"; - field: - | "email" - | "role" - | "status" - | "organizationId" - | "teamId" - | "inviterId" - | "expiresAt" - | "createdAt" - | "updatedAt" - | "_id"; + field: "key" | "count" | "lastRequest" | "_id"; mode?: "sensitive" | "insensitive"; operator?: | "lt" @@ -15638,28 +21114,32 @@ export type ComponentApi = | Array | null; }>; - }; - onDeleteHandle?: string; - paginationOpts: { - cursor: string | null; - endCursor?: string | null; - id?: number; - maximumBytesRead?: number; - maximumRowsRead?: number; - numItems: number; - }; - }, - any, - Name - >; - deleteOne: FunctionReference< - "mutation", - "internal", - { - input: + } | { - model: "user"; - where?: Array<{ + increment: Record; + model: "user_custom"; + set?: { + cbDefaultValueField?: null | string; + createdAt?: number; + customField?: null | string; + dateField?: null | number; + displayUsername?: null | string; + email?: null | string; + emailVerified?: boolean; + email_address?: null | string; + image?: null | string; + isAnonymous?: null | boolean; + name?: string; + numericField?: null | number; + phoneNumber?: null | string; + phoneNumberVerified?: null | boolean; + testField?: null | string; + twoFactorEnabled?: null | boolean; + updatedAt?: number; + userId?: null | string; + username?: null | string; + }; + where: Array<{ connector?: "AND" | "OR"; field: | "name" @@ -15705,17 +21185,51 @@ export type ComponentApi = }>; } | { - model: "session"; - where?: Array<{ + increment: Record; + model: "user_table"; + set?: { + cbDefaultValueField?: null | string; + createdAt?: number; + customField?: null | string; + dateField?: null | number; + displayUsername?: null | string; + email?: null | string; + emailVerified?: boolean; + email_address?: null | string; + image?: null | string; + isAnonymous?: null | boolean; + name?: string; + numericField?: null | number; + phoneNumber?: null | string; + phoneNumberVerified?: null | boolean; + testField?: null | string; + twoFactorEnabled?: null | boolean; + updatedAt?: number; + userId?: null | string; + username?: null | string; + }; + where: Array<{ connector?: "AND" | "OR"; field: - | "expiresAt" - | "token" + | "name" + | "email" + | "email_address" + | "emailVerified" + | "image" | "createdAt" | "updatedAt" - | "ipAddress" - | "userAgent" + | "twoFactorEnabled" + | "isAnonymous" + | "username" + | "displayUsername" + | "phoneNumber" + | "phoneNumberVerified" | "userId" + | "testField" + | "cbDefaultValueField" + | "customField" + | "numericField" + | "dateField" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -15740,23 +21254,12 @@ export type ComponentApi = }>; } | { - model: "account"; - where?: Array<{ + increment: Record; + model: "oneToOneTable"; + set?: { oneToOne?: string }; + where: Array<{ connector?: "AND" | "OR"; - field: - | "accountId" - | "providerId" - | "userId" - | "accessToken" - | "refreshToken" - | "idToken" - | "accessTokenExpiresAt" - | "refreshTokenExpiresAt" - | "scope" - | "password" - | "createdAt" - | "updatedAt" - | "_id"; + field: "oneToOne" | "_id"; mode?: "sensitive" | "insensitive"; operator?: | "lt" @@ -15780,16 +21283,15 @@ export type ComponentApi = }>; } | { - model: "verification"; - where?: Array<{ + increment: Record; + model: "one_to_one_table"; + set?: { + oneToOne?: null | string; + one_to_one?: null | string; + }; + where: Array<{ connector?: "AND" | "OR"; - field: - | "identifier" - | "value" - | "expiresAt" - | "createdAt" - | "updatedAt" - | "_id"; + field: "oneToOne" | "one_to_one" | "_id"; mode?: "sensitive" | "insensitive"; operator?: | "lt" @@ -15813,14 +21315,25 @@ export type ComponentApi = }>; } | { - model: "twoFactor"; - where?: Array<{ + increment: Record; + model: "testModel"; + set?: { + cbDefaultValueField?: null | string; + json?: any; + nullableReference?: null | string; + numberArray?: null | Array; + stringArray?: null | Array; + testField?: null | string; + }; + where: Array<{ connector?: "AND" | "OR"; field: - | "secret" - | "backupCodes" - | "userId" - | "verified" + | "nullableReference" + | "testField" + | "cbDefaultValueField" + | "stringArray" + | "numberArray" + | "json" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -15845,19 +21358,23 @@ export type ComponentApi = }>; } | { - model: "oauthApplication"; - where?: Array<{ + increment: Record; + model: "organization"; + set?: { + createdAt?: number; + logo?: null | string; + metadata?: null | string; + name?: string; + slug?: string; + updatedAt?: null | number; + }; + where: Array<{ connector?: "AND" | "OR"; field: | "name" - | "icon" + | "slug" + | "logo" | "metadata" - | "clientId" - | "clientSecret" - | "redirectUrls" - | "type" - | "disabled" - | "userId" | "createdAt" | "updatedAt" | "_id"; @@ -15884,17 +21401,21 @@ export type ComponentApi = }>; } | { - model: "oauthAccessToken"; - where?: Array<{ - connector?: "AND" | "OR"; - field: - | "accessToken" - | "refreshToken" - | "accessTokenExpiresAt" - | "refreshTokenExpiresAt" - | "clientId" + increment: Record; + model: "member"; + set?: { + createdAt?: number; + organizationId?: string; + role?: string; + updatedAt?: null | number; + userId?: string; + }; + where: Array<{ + connector?: "AND" | "OR"; + field: + | "organizationId" | "userId" - | "scopes" + | "role" | "createdAt" | "updatedAt" | "_id"; @@ -15921,16 +21442,23 @@ export type ComponentApi = }>; } | { - model: "oauthConsent"; - where?: Array<{ + increment: Record; + model: "team"; + set?: { + createdAt?: number; + memberCount?: number; + name?: string; + organizationId?: string; + updatedAt?: null | number; + }; + where: Array<{ connector?: "AND" | "OR"; field: - | "clientId" - | "userId" - | "scopes" + | "name" + | "memberCount" + | "organizationId" | "createdAt" | "updatedAt" - | "consentGiven" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -15955,14 +21483,21 @@ export type ComponentApi = }>; } | { - model: "jwks"; - where?: Array<{ + increment: Record; + model: "teamMember"; + set?: { + createdAt?: null | number; + membershipKey?: null | string; + teamId?: string; + userId?: string; + }; + where: Array<{ connector?: "AND" | "OR"; field: - | "publicKey" - | "privateKey" + | "teamId" + | "userId" + | "membershipKey" | "createdAt" - | "expiresAt" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -15987,10 +21522,32 @@ export type ComponentApi = }>; } | { - model: "rateLimit"; - where?: Array<{ + increment: Record; + model: "invitation"; + set?: { + createdAt?: null | number; + email?: null | string; + expiresAt?: null | number; + inviterId?: null | string; + organizationId?: null | string; + role?: null | string; + status?: null | string; + teamId?: null | string; + updatedAt?: null | number; + }; + where: Array<{ connector?: "AND" | "OR"; - field: "key" | "count" | "lastRequest" | "_id"; + field: + | "email" + | "role" + | "status" + | "organizationId" + | "teamId" + | "inviterId" + | "expiresAt" + | "createdAt" + | "updatedAt" + | "_id"; mode?: "sensitive" | "insensitive"; operator?: | "lt" @@ -16012,9 +21569,40 @@ export type ComponentApi = | Array | null; }>; - } + }; + onUpdateHandle?: string; + }, + any, + Name + >; + updateMany: FunctionReference< + "mutation", + "internal", + { + input: | { - model: "user_custom"; + model: "user"; + update: { + cbDefaultValueField?: null | string; + createdAt?: number; + customField?: null | string; + dateField?: null | number; + displayUsername?: null | string; + email?: null | string; + emailVerified?: boolean; + email_address?: null | string; + image?: null | string; + isAnonymous?: null | boolean; + name?: string; + numericField?: null | number; + phoneNumber?: null | string; + phoneNumberVerified?: null | boolean; + testField?: null | string; + twoFactorEnabled?: null | boolean; + updatedAt?: number; + userId?: null | string; + username?: null | string; + }; where?: Array<{ connector?: "AND" | "OR"; field: @@ -16061,29 +21649,26 @@ export type ComponentApi = }>; } | { - model: "user_table"; + model: "session"; + update: { + createdAt?: number; + expiresAt?: number; + ipAddress?: null | string; + token?: string; + updatedAt?: number; + userAgent?: null | string; + userId?: string; + }; where?: Array<{ connector?: "AND" | "OR"; field: - | "name" - | "email" - | "email_address" - | "emailVerified" - | "image" + | "expiresAt" + | "token" | "createdAt" | "updatedAt" - | "twoFactorEnabled" - | "isAnonymous" - | "username" - | "displayUsername" - | "phoneNumber" - | "phoneNumberVerified" + | "ipAddress" + | "userAgent" | "userId" - | "testField" - | "cbDefaultValueField" - | "customField" - | "numericField" - | "dateField" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -16108,10 +21693,39 @@ export type ComponentApi = }>; } | { - model: "oneToOneTable"; + model: "account"; + update: { + accessToken?: null | string; + accessTokenExpiresAt?: null | number; + accountId?: string; + createdAt?: number; + idToken?: null | string; + issuer?: null | string; + password?: null | string; + providerId?: string; + refreshToken?: null | string; + refreshTokenExpiresAt?: null | number; + scope?: null | string; + updatedAt?: number; + userId?: string; + }; where?: Array<{ connector?: "AND" | "OR"; - field: "oneToOne" | "_id"; + field: + | "issuer" + | "accountId" + | "providerId" + | "userId" + | "accessToken" + | "refreshToken" + | "idToken" + | "accessTokenExpiresAt" + | "refreshTokenExpiresAt" + | "scope" + | "password" + | "createdAt" + | "updatedAt" + | "_id"; mode?: "sensitive" | "insensitive"; operator?: | "lt" @@ -16135,10 +21749,23 @@ export type ComponentApi = }>; } | { - model: "one_to_one_table"; + model: "verification"; + update: { + createdAt?: number; + expiresAt?: number; + identifier?: string; + updatedAt?: number; + value?: string; + }; where?: Array<{ connector?: "AND" | "OR"; - field: "oneToOne" | "one_to_one" | "_id"; + field: + | "identifier" + | "value" + | "expiresAt" + | "createdAt" + | "updatedAt" + | "_id"; mode?: "sensitive" | "insensitive"; operator?: | "lt" @@ -16162,16 +21789,24 @@ export type ComponentApi = }>; } | { - model: "testModel"; + model: "twoFactor"; + update: { + backupCodes?: string; + failedVerificationCount?: null | number; + lockedUntil?: null | number; + secret?: string; + userId?: string; + verified?: null | boolean; + }; where?: Array<{ connector?: "AND" | "OR"; field: - | "nullableReference" - | "testField" - | "cbDefaultValueField" - | "stringArray" - | "numberArray" - | "json" + | "secret" + | "backupCodes" + | "userId" + | "verified" + | "failedVerificationCount" + | "lockedUntil" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -16196,14 +21831,32 @@ export type ComponentApi = }>; } | { - model: "organization"; + model: "oauthApplication"; + update: { + clientId?: null | string; + clientSecret?: null | string; + createdAt?: null | number; + disabled?: null | boolean; + icon?: null | string; + metadata?: null | string; + name?: null | string; + redirectUrls?: null | string; + type?: null | string; + updatedAt?: null | number; + userId?: null | string; + }; where?: Array<{ connector?: "AND" | "OR"; field: | "name" - | "slug" - | "logo" + | "icon" | "metadata" + | "clientId" + | "clientSecret" + | "redirectUrls" + | "type" + | "disabled" + | "userId" | "createdAt" | "updatedAt" | "_id"; @@ -16230,13 +21883,28 @@ export type ComponentApi = }>; } | { - model: "member"; + model: "oauthAccessToken"; + update: { + accessToken?: null | string; + accessTokenExpiresAt?: null | number; + clientId?: null | string; + createdAt?: null | number; + refreshToken?: null | string; + refreshTokenExpiresAt?: null | number; + scopes?: null | string; + updatedAt?: null | number; + userId?: null | string; + }; where?: Array<{ connector?: "AND" | "OR"; field: - | "organizationId" + | "accessToken" + | "refreshToken" + | "accessTokenExpiresAt" + | "refreshTokenExpiresAt" + | "clientId" | "userId" - | "role" + | "scopes" | "createdAt" | "updatedAt" | "_id"; @@ -16263,14 +21931,24 @@ export type ComponentApi = }>; } | { - model: "team"; + model: "oauthConsent"; + update: { + clientId?: null | string; + consentGiven?: null | boolean; + createdAt?: null | number; + scopes?: null | string; + updatedAt?: null | number; + userId?: null | string; + }; where?: Array<{ connector?: "AND" | "OR"; field: - | "name" - | "organizationId" + | "clientId" + | "userId" + | "scopes" | "createdAt" | "updatedAt" + | "consentGiven" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -16295,10 +21973,25 @@ export type ComponentApi = }>; } | { - model: "teamMember"; + model: "jwks"; + update: { + alg?: null | string; + createdAt?: number; + crv?: null | string; + expiresAt?: null | number; + privateKey?: string; + publicKey?: string; + }; where?: Array<{ connector?: "AND" | "OR"; - field: "teamId" | "userId" | "createdAt" | "_id"; + field: + | "publicKey" + | "privateKey" + | "createdAt" + | "expiresAt" + | "alg" + | "crv" + | "_id"; mode?: "sensitive" | "insensitive"; operator?: | "lt" @@ -16322,20 +22015,15 @@ export type ComponentApi = }>; } | { - model: "invitation"; + model: "rateLimit"; + update: { + count?: number; + key?: string; + lastRequest?: number; + }; where?: Array<{ connector?: "AND" | "OR"; - field: - | "email" - | "role" - | "status" - | "organizationId" - | "teamId" - | "inviterId" - | "expiresAt" - | "createdAt" - | "updatedAt" - | "_id"; + field: "key" | "count" | "lastRequest" | "_id"; mode?: "sensitive" | "insensitive"; operator?: | "lt" @@ -16357,140 +22045,9 @@ export type ComponentApi = | Array | null; }>; - }; - onDeleteHandle?: string; - }, - any, - Name - >; - findMany: FunctionReference< - "query", - "internal", - { - join?: any; - limit?: number; - model: - | "user" - | "session" - | "account" - | "verification" - | "twoFactor" - | "oauthApplication" - | "oauthAccessToken" - | "oauthConsent" - | "jwks" - | "rateLimit" - | "user_custom" - | "user_table" - | "oneToOneTable" - | "one_to_one_table" - | "testModel" - | "organization" - | "member" - | "team" - | "teamMember" - | "invitation"; - offset?: number; - paginationOpts: { - cursor: string | null; - endCursor?: string | null; - id?: number; - maximumBytesRead?: number; - maximumRowsRead?: number; - numItems: number; - }; - select?: Array; - sortBy?: { direction: "asc" | "desc"; field: string }; - where?: Array<{ - connector?: "AND" | "OR"; - field: string; - mode?: "sensitive" | "insensitive"; - operator?: - | "lt" - | "lte" - | "gt" - | "gte" - | "eq" - | "in" - | "not_in" - | "ne" - | "contains" - | "starts_with" - | "ends_with"; - value: - | string - | number - | boolean - | Array - | Array - | null; - }>; - }, - any, - Name - >; - findOne: FunctionReference< - "query", - "internal", - { - join?: any; - model: - | "user" - | "session" - | "account" - | "verification" - | "twoFactor" - | "oauthApplication" - | "oauthAccessToken" - | "oauthConsent" - | "jwks" - | "rateLimit" - | "user_custom" - | "user_table" - | "oneToOneTable" - | "one_to_one_table" - | "testModel" - | "organization" - | "member" - | "team" - | "teamMember" - | "invitation"; - select?: Array; - where?: Array<{ - connector?: "AND" | "OR"; - field: string; - mode?: "sensitive" | "insensitive"; - operator?: - | "lt" - | "lte" - | "gt" - | "gte" - | "eq" - | "in" - | "not_in" - | "ne" - | "contains" - | "starts_with" - | "ends_with"; - value: - | string - | number - | boolean - | Array - | Array - | null; - }>; - }, - any, - Name - >; - updateMany: FunctionReference< - "mutation", - "internal", - { - input: + } | { - model: "user"; + model: "user_custom"; update: { cbDefaultValueField?: null | string; createdAt?: number; @@ -16558,26 +22115,50 @@ export type ComponentApi = }>; } | { - model: "session"; + model: "user_table"; update: { + cbDefaultValueField?: null | string; createdAt?: number; - expiresAt?: number; - ipAddress?: null | string; - token?: string; + customField?: null | string; + dateField?: null | number; + displayUsername?: null | string; + email?: null | string; + emailVerified?: boolean; + email_address?: null | string; + image?: null | string; + isAnonymous?: null | boolean; + name?: string; + numericField?: null | number; + phoneNumber?: null | string; + phoneNumberVerified?: null | boolean; + testField?: null | string; + twoFactorEnabled?: null | boolean; updatedAt?: number; - userAgent?: null | string; - userId?: string; + userId?: null | string; + username?: null | string; }; where?: Array<{ connector?: "AND" | "OR"; field: - | "expiresAt" - | "token" + | "name" + | "email" + | "email_address" + | "emailVerified" + | "image" | "createdAt" | "updatedAt" - | "ipAddress" - | "userAgent" + | "twoFactorEnabled" + | "isAnonymous" + | "username" + | "displayUsername" + | "phoneNumber" + | "phoneNumberVerified" | "userId" + | "testField" + | "cbDefaultValueField" + | "customField" + | "numericField" + | "dateField" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -16602,37 +22183,11 @@ export type ComponentApi = }>; } | { - model: "account"; - update: { - accessToken?: null | string; - accessTokenExpiresAt?: null | number; - accountId?: string; - createdAt?: number; - idToken?: null | string; - password?: null | string; - providerId?: string; - refreshToken?: null | string; - refreshTokenExpiresAt?: null | number; - scope?: null | string; - updatedAt?: number; - userId?: string; - }; + model: "oneToOneTable"; + update: { oneToOne?: string }; where?: Array<{ connector?: "AND" | "OR"; - field: - | "accountId" - | "providerId" - | "userId" - | "accessToken" - | "refreshToken" - | "idToken" - | "accessTokenExpiresAt" - | "refreshTokenExpiresAt" - | "scope" - | "password" - | "createdAt" - | "updatedAt" - | "_id"; + field: "oneToOne" | "_id"; mode?: "sensitive" | "insensitive"; operator?: | "lt" @@ -16656,23 +22211,14 @@ export type ComponentApi = }>; } | { - model: "verification"; + model: "one_to_one_table"; update: { - createdAt?: number; - expiresAt?: number; - identifier?: string; - updatedAt?: number; - value?: string; + oneToOne?: null | string; + one_to_one?: null | string; }; where?: Array<{ connector?: "AND" | "OR"; - field: - | "identifier" - | "value" - | "expiresAt" - | "createdAt" - | "updatedAt" - | "_id"; + field: "oneToOne" | "one_to_one" | "_id"; mode?: "sensitive" | "insensitive"; operator?: | "lt" @@ -16696,20 +22242,24 @@ export type ComponentApi = }>; } | { - model: "twoFactor"; + model: "testModel"; update: { - backupCodes?: string; - secret?: string; - userId?: string; - verified?: null | boolean; + cbDefaultValueField?: null | string; + json?: any; + nullableReference?: null | string; + numberArray?: null | Array; + stringArray?: null | Array; + testField?: null | string; }; where?: Array<{ connector?: "AND" | "OR"; field: - | "secret" - | "backupCodes" - | "userId" - | "verified" + | "nullableReference" + | "testField" + | "cbDefaultValueField" + | "stringArray" + | "numberArray" + | "json" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -16734,32 +22284,22 @@ export type ComponentApi = }>; } | { - model: "oauthApplication"; + model: "organization"; update: { - clientId?: null | string; - clientSecret?: null | string; - createdAt?: null | number; - disabled?: null | boolean; - icon?: null | string; + createdAt?: number; + logo?: null | string; metadata?: null | string; - name?: null | string; - redirectUrls?: null | string; - type?: null | string; + name?: string; + slug?: string; updatedAt?: null | number; - userId?: null | string; }; where?: Array<{ connector?: "AND" | "OR"; field: | "name" - | "icon" + | "slug" + | "logo" | "metadata" - | "clientId" - | "clientSecret" - | "redirectUrls" - | "type" - | "disabled" - | "userId" | "createdAt" | "updatedAt" | "_id"; @@ -16786,28 +22326,20 @@ export type ComponentApi = }>; } | { - model: "oauthAccessToken"; + model: "member"; update: { - accessToken?: null | string; - accessTokenExpiresAt?: null | number; - clientId?: null | string; - createdAt?: null | number; - refreshToken?: null | string; - refreshTokenExpiresAt?: null | number; - scopes?: null | string; + createdAt?: number; + organizationId?: string; + role?: string; updatedAt?: null | number; - userId?: null | string; + userId?: string; }; where?: Array<{ connector?: "AND" | "OR"; field: - | "accessToken" - | "refreshToken" - | "accessTokenExpiresAt" - | "refreshTokenExpiresAt" - | "clientId" + | "organizationId" | "userId" - | "scopes" + | "role" | "createdAt" | "updatedAt" | "_id"; @@ -16834,24 +22366,22 @@ export type ComponentApi = }>; } | { - model: "oauthConsent"; + model: "team"; update: { - clientId?: null | string; - consentGiven?: null | boolean; - createdAt?: null | number; - scopes?: null | string; + createdAt?: number; + memberCount?: number; + name?: string; + organizationId?: string; updatedAt?: null | number; - userId?: null | string; }; where?: Array<{ connector?: "AND" | "OR"; field: - | "clientId" - | "userId" - | "scopes" + | "name" + | "memberCount" + | "organizationId" | "createdAt" | "updatedAt" - | "consentGiven" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -16876,20 +22406,20 @@ export type ComponentApi = }>; } | { - model: "jwks"; + model: "teamMember"; update: { - createdAt?: number; - expiresAt?: null | number; - privateKey?: string; - publicKey?: string; + createdAt?: null | number; + membershipKey?: null | string; + teamId?: string; + userId?: string; }; where?: Array<{ connector?: "AND" | "OR"; field: - | "publicKey" - | "privateKey" + | "teamId" + | "userId" + | "membershipKey" | "createdAt" - | "expiresAt" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -16914,15 +22444,31 @@ export type ComponentApi = }>; } | { - model: "rateLimit"; + model: "invitation"; update: { - count?: number; - key?: string; - lastRequest?: number; + createdAt?: null | number; + email?: null | string; + expiresAt?: null | number; + inviterId?: null | string; + organizationId?: null | string; + role?: null | string; + status?: null | string; + teamId?: null | string; + updatedAt?: null | number; }; where?: Array<{ connector?: "AND" | "OR"; - field: "key" | "count" | "lastRequest" | "_id"; + field: + | "email" + | "role" + | "status" + | "organizationId" + | "teamId" + | "inviterId" + | "expiresAt" + | "createdAt" + | "updatedAt" + | "_id"; mode?: "sensitive" | "insensitive"; operator?: | "lt" @@ -16944,9 +22490,27 @@ export type ComponentApi = | Array | null; }>; - } + }; + onUpdateHandle?: string; + paginationOpts: { + cursor: string | null; + endCursor?: string | null; + id?: number; + maximumBytesRead?: number; + maximumRowsRead?: number; + numItems: number; + }; + }, + any, + Name + >; + updateOne: FunctionReference< + "mutation", + "internal", + { + input: | { - model: "user_custom"; + model: "user"; update: { cbDefaultValueField?: null | string; createdAt?: number; @@ -17014,50 +22578,26 @@ export type ComponentApi = }>; } | { - model: "user_table"; + model: "session"; update: { - cbDefaultValueField?: null | string; createdAt?: number; - customField?: null | string; - dateField?: null | number; - displayUsername?: null | string; - email?: null | string; - emailVerified?: boolean; - email_address?: null | string; - image?: null | string; - isAnonymous?: null | boolean; - name?: string; - numericField?: null | number; - phoneNumber?: null | string; - phoneNumberVerified?: null | boolean; - testField?: null | string; - twoFactorEnabled?: null | boolean; + expiresAt?: number; + ipAddress?: null | string; + token?: string; updatedAt?: number; - userId?: null | string; - username?: null | string; + userAgent?: null | string; + userId?: string; }; where?: Array<{ connector?: "AND" | "OR"; field: - | "name" - | "email" - | "email_address" - | "emailVerified" - | "image" + | "expiresAt" + | "token" | "createdAt" | "updatedAt" - | "twoFactorEnabled" - | "isAnonymous" - | "username" - | "displayUsername" - | "phoneNumber" - | "phoneNumberVerified" + | "ipAddress" + | "userAgent" | "userId" - | "testField" - | "cbDefaultValueField" - | "customField" - | "numericField" - | "dateField" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -17082,11 +22622,39 @@ export type ComponentApi = }>; } | { - model: "oneToOneTable"; - update: { oneToOne?: string }; + model: "account"; + update: { + accessToken?: null | string; + accessTokenExpiresAt?: null | number; + accountId?: string; + createdAt?: number; + idToken?: null | string; + issuer?: null | string; + password?: null | string; + providerId?: string; + refreshToken?: null | string; + refreshTokenExpiresAt?: null | number; + scope?: null | string; + updatedAt?: number; + userId?: string; + }; where?: Array<{ connector?: "AND" | "OR"; - field: "oneToOne" | "_id"; + field: + | "issuer" + | "accountId" + | "providerId" + | "userId" + | "accessToken" + | "refreshToken" + | "idToken" + | "accessTokenExpiresAt" + | "refreshTokenExpiresAt" + | "scope" + | "password" + | "createdAt" + | "updatedAt" + | "_id"; mode?: "sensitive" | "insensitive"; operator?: | "lt" @@ -17110,14 +22678,23 @@ export type ComponentApi = }>; } | { - model: "one_to_one_table"; + model: "verification"; update: { - oneToOne?: null | string; - one_to_one?: null | string; + createdAt?: number; + expiresAt?: number; + identifier?: string; + updatedAt?: number; + value?: string; }; where?: Array<{ connector?: "AND" | "OR"; - field: "oneToOne" | "one_to_one" | "_id"; + field: + | "identifier" + | "value" + | "expiresAt" + | "createdAt" + | "updatedAt" + | "_id"; mode?: "sensitive" | "insensitive"; operator?: | "lt" @@ -17141,24 +22718,24 @@ export type ComponentApi = }>; } | { - model: "testModel"; + model: "twoFactor"; update: { - cbDefaultValueField?: null | string; - json?: any; - nullableReference?: null | string; - numberArray?: null | Array; - stringArray?: null | Array; - testField?: null | string; + backupCodes?: string; + failedVerificationCount?: null | number; + lockedUntil?: null | number; + secret?: string; + userId?: string; + verified?: null | boolean; }; where?: Array<{ connector?: "AND" | "OR"; field: - | "nullableReference" - | "testField" - | "cbDefaultValueField" - | "stringArray" - | "numberArray" - | "json" + | "secret" + | "backupCodes" + | "userId" + | "verified" + | "failedVerificationCount" + | "lockedUntil" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -17183,22 +22760,32 @@ export type ComponentApi = }>; } | { - model: "organization"; + model: "oauthApplication"; update: { - createdAt?: number; - logo?: null | string; + clientId?: null | string; + clientSecret?: null | string; + createdAt?: null | number; + disabled?: null | boolean; + icon?: null | string; metadata?: null | string; - name?: string; - slug?: string; + name?: null | string; + redirectUrls?: null | string; + type?: null | string; updatedAt?: null | number; + userId?: null | string; }; where?: Array<{ connector?: "AND" | "OR"; field: | "name" - | "slug" - | "logo" + | "icon" | "metadata" + | "clientId" + | "clientSecret" + | "redirectUrls" + | "type" + | "disabled" + | "userId" | "createdAt" | "updatedAt" | "_id"; @@ -17225,20 +22812,28 @@ export type ComponentApi = }>; } | { - model: "member"; + model: "oauthAccessToken"; update: { - createdAt?: number; - organizationId?: string; - role?: string; + accessToken?: null | string; + accessTokenExpiresAt?: null | number; + clientId?: null | string; + createdAt?: null | number; + refreshToken?: null | string; + refreshTokenExpiresAt?: null | number; + scopes?: null | string; updatedAt?: null | number; - userId?: string; + userId?: null | string; }; where?: Array<{ connector?: "AND" | "OR"; field: - | "organizationId" + | "accessToken" + | "refreshToken" + | "accessTokenExpiresAt" + | "refreshTokenExpiresAt" + | "clientId" | "userId" - | "role" + | "scopes" | "createdAt" | "updatedAt" | "_id"; @@ -17265,20 +22860,24 @@ export type ComponentApi = }>; } | { - model: "team"; + model: "oauthConsent"; update: { - createdAt?: number; - name?: string; - organizationId?: string; + clientId?: null | string; + consentGiven?: null | boolean; + createdAt?: null | number; + scopes?: null | string; updatedAt?: null | number; + userId?: null | string; }; where?: Array<{ connector?: "AND" | "OR"; field: - | "name" - | "organizationId" + | "clientId" + | "userId" + | "scopes" | "createdAt" | "updatedAt" + | "consentGiven" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -17303,15 +22902,25 @@ export type ComponentApi = }>; } | { - model: "teamMember"; + model: "jwks"; update: { - createdAt?: null | number; - teamId?: string; - userId?: string; + alg?: null | string; + createdAt?: number; + crv?: null | string; + expiresAt?: null | number; + privateKey?: string; + publicKey?: string; }; where?: Array<{ connector?: "AND" | "OR"; - field: "teamId" | "userId" | "createdAt" | "_id"; + field: + | "publicKey" + | "privateKey" + | "createdAt" + | "expiresAt" + | "alg" + | "crv" + | "_id"; mode?: "sensitive" | "insensitive"; operator?: | "lt" @@ -17335,31 +22944,15 @@ export type ComponentApi = }>; } | { - model: "invitation"; + model: "rateLimit"; update: { - createdAt?: null | number; - email?: null | string; - expiresAt?: null | number; - inviterId?: null | string; - organizationId?: null | string; - role?: null | string; - status?: null | string; - teamId?: null | string; - updatedAt?: null | number; + count?: number; + key?: string; + lastRequest?: number; }; where?: Array<{ connector?: "AND" | "OR"; - field: - | "email" - | "role" - | "status" - | "organizationId" - | "teamId" - | "inviterId" - | "expiresAt" - | "createdAt" - | "updatedAt" - | "_id"; + field: "key" | "count" | "lastRequest" | "_id"; mode?: "sensitive" | "insensitive"; operator?: | "lt" @@ -17381,27 +22974,9 @@ export type ComponentApi = | Array | null; }>; - }; - onUpdateHandle?: string; - paginationOpts: { - cursor: string | null; - endCursor?: string | null; - id?: number; - maximumBytesRead?: number; - maximumRowsRead?: number; - numItems: number; - }; - }, - any, - Name - >; - updateOne: FunctionReference< - "mutation", - "internal", - { - input: + } | { - model: "user"; + model: "user_custom"; update: { cbDefaultValueField?: null | string; createdAt?: number; @@ -17469,26 +23044,50 @@ export type ComponentApi = }>; } | { - model: "session"; + model: "user_table"; update: { + cbDefaultValueField?: null | string; createdAt?: number; - expiresAt?: number; - ipAddress?: null | string; - token?: string; + customField?: null | string; + dateField?: null | number; + displayUsername?: null | string; + email?: null | string; + emailVerified?: boolean; + email_address?: null | string; + image?: null | string; + isAnonymous?: null | boolean; + name?: string; + numericField?: null | number; + phoneNumber?: null | string; + phoneNumberVerified?: null | boolean; + testField?: null | string; + twoFactorEnabled?: null | boolean; updatedAt?: number; - userAgent?: null | string; - userId?: string; + userId?: null | string; + username?: null | string; }; where?: Array<{ connector?: "AND" | "OR"; field: - | "expiresAt" - | "token" + | "name" + | "email" + | "email_address" + | "emailVerified" + | "image" | "createdAt" | "updatedAt" - | "ipAddress" - | "userAgent" + | "twoFactorEnabled" + | "isAnonymous" + | "username" + | "displayUsername" + | "phoneNumber" + | "phoneNumberVerified" | "userId" + | "testField" + | "cbDefaultValueField" + | "customField" + | "numericField" + | "dateField" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -17513,37 +23112,11 @@ export type ComponentApi = }>; } | { - model: "account"; - update: { - accessToken?: null | string; - accessTokenExpiresAt?: null | number; - accountId?: string; - createdAt?: number; - idToken?: null | string; - password?: null | string; - providerId?: string; - refreshToken?: null | string; - refreshTokenExpiresAt?: null | number; - scope?: null | string; - updatedAt?: number; - userId?: string; - }; + model: "oneToOneTable"; + update: { oneToOne?: string }; where?: Array<{ connector?: "AND" | "OR"; - field: - | "accountId" - | "providerId" - | "userId" - | "accessToken" - | "refreshToken" - | "idToken" - | "accessTokenExpiresAt" - | "refreshTokenExpiresAt" - | "scope" - | "password" - | "createdAt" - | "updatedAt" - | "_id"; + field: "oneToOne" | "_id"; mode?: "sensitive" | "insensitive"; operator?: | "lt" @@ -17567,23 +23140,14 @@ export type ComponentApi = }>; } | { - model: "verification"; + model: "one_to_one_table"; update: { - createdAt?: number; - expiresAt?: number; - identifier?: string; - updatedAt?: number; - value?: string; + oneToOne?: null | string; + one_to_one?: null | string; }; where?: Array<{ connector?: "AND" | "OR"; - field: - | "identifier" - | "value" - | "expiresAt" - | "createdAt" - | "updatedAt" - | "_id"; + field: "oneToOne" | "one_to_one" | "_id"; mode?: "sensitive" | "insensitive"; operator?: | "lt" @@ -17607,20 +23171,24 @@ export type ComponentApi = }>; } | { - model: "twoFactor"; + model: "testModel"; update: { - backupCodes?: string; - secret?: string; - userId?: string; - verified?: null | boolean; + cbDefaultValueField?: null | string; + json?: any; + nullableReference?: null | string; + numberArray?: null | Array; + stringArray?: null | Array; + testField?: null | string; }; where?: Array<{ connector?: "AND" | "OR"; field: - | "secret" - | "backupCodes" - | "userId" - | "verified" + | "nullableReference" + | "testField" + | "cbDefaultValueField" + | "stringArray" + | "numberArray" + | "json" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -17645,32 +23213,22 @@ export type ComponentApi = }>; } | { - model: "oauthApplication"; + model: "organization"; update: { - clientId?: null | string; - clientSecret?: null | string; - createdAt?: null | number; - disabled?: null | boolean; - icon?: null | string; + createdAt?: number; + logo?: null | string; metadata?: null | string; - name?: null | string; - redirectUrls?: null | string; - type?: null | string; + name?: string; + slug?: string; updatedAt?: null | number; - userId?: null | string; }; where?: Array<{ connector?: "AND" | "OR"; field: | "name" - | "icon" + | "slug" + | "logo" | "metadata" - | "clientId" - | "clientSecret" - | "redirectUrls" - | "type" - | "disabled" - | "userId" | "createdAt" | "updatedAt" | "_id"; @@ -17697,28 +23255,20 @@ export type ComponentApi = }>; } | { - model: "oauthAccessToken"; + model: "member"; update: { - accessToken?: null | string; - accessTokenExpiresAt?: null | number; - clientId?: null | string; - createdAt?: null | number; - refreshToken?: null | string; - refreshTokenExpiresAt?: null | number; - scopes?: null | string; + createdAt?: number; + organizationId?: string; + role?: string; updatedAt?: null | number; - userId?: null | string; + userId?: string; }; where?: Array<{ connector?: "AND" | "OR"; field: - | "accessToken" - | "refreshToken" - | "accessTokenExpiresAt" - | "refreshTokenExpiresAt" - | "clientId" + | "organizationId" | "userId" - | "scopes" + | "role" | "createdAt" | "updatedAt" | "_id"; @@ -17745,24 +23295,22 @@ export type ComponentApi = }>; } | { - model: "oauthConsent"; + model: "team"; update: { - clientId?: null | string; - consentGiven?: null | boolean; - createdAt?: null | number; - scopes?: null | string; + createdAt?: number; + memberCount?: number; + name?: string; + organizationId?: string; updatedAt?: null | number; - userId?: null | string; }; where?: Array<{ connector?: "AND" | "OR"; field: - | "clientId" - | "userId" - | "scopes" + | "name" + | "memberCount" + | "organizationId" | "createdAt" | "updatedAt" - | "consentGiven" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -17787,20 +23335,20 @@ export type ComponentApi = }>; } | { - model: "jwks"; + model: "teamMember"; update: { - createdAt?: number; - expiresAt?: null | number; - privateKey?: string; - publicKey?: string; + createdAt?: null | number; + membershipKey?: null | string; + teamId?: string; + userId?: string; }; where?: Array<{ connector?: "AND" | "OR"; field: - | "publicKey" - | "privateKey" + | "teamId" + | "userId" + | "membershipKey" | "createdAt" - | "expiresAt" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -17825,15 +23373,31 @@ export type ComponentApi = }>; } | { - model: "rateLimit"; + model: "invitation"; update: { - count?: number; - key?: string; - lastRequest?: number; + createdAt?: null | number; + email?: null | string; + expiresAt?: null | number; + inviterId?: null | string; + organizationId?: null | string; + role?: null | string; + status?: null | string; + teamId?: null | string; + updatedAt?: null | number; }; where?: Array<{ connector?: "AND" | "OR"; - field: "key" | "count" | "lastRequest" | "_id"; + field: + | "email" + | "role" + | "status" + | "organizationId" + | "teamId" + | "inviterId" + | "expiresAt" + | "createdAt" + | "updatedAt" + | "_id"; mode?: "sensitive" | "insensitive"; operator?: | "lt" @@ -17855,30 +23419,284 @@ export type ComponentApi = | Array | null; }>; + }; + onUpdateHandle?: string; + }, + any, + Name + >; + }; + adapterRenameUserTable: { + create: FunctionReference< + "mutation", + "internal", + { + input: + | { + data: { + cbDefaultValueField?: null | string; + createdAt: number; + customField?: null | string; + dateField?: null | number; + displayUsername?: null | string; + email?: null | string; + emailVerified: boolean; + email_address?: null | string; + image?: null | string; + isAnonymous?: null | boolean; + name: string; + numericField?: null | number; + phoneNumber?: null | string; + phoneNumberVerified?: null | boolean; + testField?: null | string; + twoFactorEnabled?: null | boolean; + updatedAt: number; + userId?: null | string; + username?: null | string; + }; + model: "user"; + } + | { + data: { + createdAt: number; + expiresAt: number; + ipAddress?: null | string; + token: string; + updatedAt: number; + userAgent?: null | string; + userId: string; + }; + model: "session"; + } + | { + data: { + accessToken?: null | string; + accessTokenExpiresAt?: null | number; + accountId: string; + createdAt: number; + idToken?: null | string; + issuer?: null | string; + password?: null | string; + providerId: string; + refreshToken?: null | string; + refreshTokenExpiresAt?: null | number; + scope?: null | string; + updatedAt: number; + userId: string; + }; + model: "account"; + } + | { + data: { + createdAt: number; + expiresAt: number; + identifier: string; + updatedAt: number; + value: string; + }; + model: "verification"; + } + | { + data: { + backupCodes: string; + failedVerificationCount?: null | number; + lockedUntil?: null | number; + secret: string; + userId: string; + verified?: null | boolean; + }; + model: "twoFactor"; + } + | { + data: { + clientId?: null | string; + clientSecret?: null | string; + createdAt?: null | number; + disabled?: null | boolean; + icon?: null | string; + metadata?: null | string; + name?: null | string; + redirectUrls?: null | string; + type?: null | string; + updatedAt?: null | number; + userId?: null | string; + }; + model: "oauthApplication"; + } + | { + data: { + accessToken?: null | string; + accessTokenExpiresAt?: null | number; + clientId?: null | string; + createdAt?: null | number; + refreshToken?: null | string; + refreshTokenExpiresAt?: null | number; + scopes?: null | string; + updatedAt?: null | number; + userId?: null | string; + }; + model: "oauthAccessToken"; + } + | { + data: { + clientId?: null | string; + consentGiven?: null | boolean; + createdAt?: null | number; + scopes?: null | string; + updatedAt?: null | number; + userId?: null | string; + }; + model: "oauthConsent"; + } + | { + data: { + alg?: null | string; + createdAt: number; + crv?: null | string; + expiresAt?: null | number; + privateKey: string; + publicKey: string; + }; + model: "jwks"; } | { + data: { count: number; key: string; lastRequest: number }; + model: "rateLimit"; + } + | { + data: { + cbDefaultValueField?: null | string; + createdAt: number; + customField?: null | string; + dateField?: null | number; + displayUsername?: null | string; + email?: null | string; + emailVerified: boolean; + email_address?: null | string; + image?: null | string; + isAnonymous?: null | boolean; + name: string; + numericField?: null | number; + phoneNumber?: null | string; + phoneNumberVerified?: null | boolean; + testField?: null | string; + twoFactorEnabled?: null | boolean; + updatedAt: number; + userId?: null | string; + username?: null | string; + }; model: "user_custom"; - update: { + } + | { + data: { cbDefaultValueField?: null | string; - createdAt?: number; + createdAt: number; customField?: null | string; dateField?: null | number; displayUsername?: null | string; email?: null | string; - emailVerified?: boolean; + emailVerified: boolean; email_address?: null | string; image?: null | string; isAnonymous?: null | boolean; - name?: string; + name: string; numericField?: null | number; phoneNumber?: null | string; phoneNumberVerified?: null | boolean; testField?: null | string; twoFactorEnabled?: null | boolean; - updatedAt?: number; + updatedAt: number; userId?: null | string; username?: null | string; }; + model: "user_table"; + } + | { data: { oneToOne: string }; model: "oneToOneTable" } + | { + data: { + oneToOne?: null | string; + one_to_one?: null | string; + }; + model: "one_to_one_table"; + } + | { + data: { + cbDefaultValueField?: null | string; + json?: any; + nullableReference?: null | string; + numberArray?: null | Array; + stringArray?: null | Array; + testField?: null | string; + }; + model: "testModel"; + } + | { + data: { + createdAt: number; + logo?: null | string; + metadata?: null | string; + name: string; + slug: string; + updatedAt?: null | number; + }; + model: "organization"; + } + | { + data: { + createdAt: number; + organizationId: string; + role: string; + updatedAt?: null | number; + userId: string; + }; + model: "member"; + } + | { + data: { + createdAt: number; + memberCount: number; + name: string; + organizationId: string; + updatedAt?: null | number; + }; + model: "team"; + } + | { + data: { + createdAt?: null | number; + membershipKey?: null | string; + teamId: string; + userId: string; + }; + model: "teamMember"; + } + | { + data: { + createdAt?: null | number; + email?: null | string; + expiresAt?: null | number; + inviterId?: null | string; + organizationId?: null | string; + role?: null | string; + status?: null | string; + teamId?: null | string; + updatedAt?: null | number; + }; + model: "invitation"; + }; + onCreateHandle?: string; + select?: Array; + }, + any, + Name + >; + deleteMany: FunctionReference< + "mutation", + "internal", + { + input: + | { + model: "user"; where?: Array<{ connector?: "AND" | "OR"; field: @@ -17925,50 +23743,17 @@ export type ComponentApi = }>; } | { - model: "user_table"; - update: { - cbDefaultValueField?: null | string; - createdAt?: number; - customField?: null | string; - dateField?: null | number; - displayUsername?: null | string; - email?: null | string; - emailVerified?: boolean; - email_address?: null | string; - image?: null | string; - isAnonymous?: null | boolean; - name?: string; - numericField?: null | number; - phoneNumber?: null | string; - phoneNumberVerified?: null | boolean; - testField?: null | string; - twoFactorEnabled?: null | boolean; - updatedAt?: number; - userId?: null | string; - username?: null | string; - }; + model: "session"; where?: Array<{ connector?: "AND" | "OR"; field: - | "name" - | "email" - | "email_address" - | "emailVerified" - | "image" + | "expiresAt" + | "token" | "createdAt" | "updatedAt" - | "twoFactorEnabled" - | "isAnonymous" - | "username" - | "displayUsername" - | "phoneNumber" - | "phoneNumberVerified" + | "ipAddress" + | "userAgent" | "userId" - | "testField" - | "cbDefaultValueField" - | "customField" - | "numericField" - | "dateField" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -17993,11 +23778,24 @@ export type ComponentApi = }>; } | { - model: "oneToOneTable"; - update: { oneToOne?: string }; + model: "account"; where?: Array<{ connector?: "AND" | "OR"; - field: "oneToOne" | "_id"; + field: + | "issuer" + | "accountId" + | "providerId" + | "userId" + | "accessToken" + | "refreshToken" + | "idToken" + | "accessTokenExpiresAt" + | "refreshTokenExpiresAt" + | "scope" + | "password" + | "createdAt" + | "updatedAt" + | "_id"; mode?: "sensitive" | "insensitive"; operator?: | "lt" @@ -18021,14 +23819,16 @@ export type ComponentApi = }>; } | { - model: "one_to_one_table"; - update: { - oneToOne?: null | string; - one_to_one?: null | string; - }; + model: "verification"; where?: Array<{ connector?: "AND" | "OR"; - field: "oneToOne" | "one_to_one" | "_id"; + field: + | "identifier" + | "value" + | "expiresAt" + | "createdAt" + | "updatedAt" + | "_id"; mode?: "sensitive" | "insensitive"; operator?: | "lt" @@ -18052,24 +23852,16 @@ export type ComponentApi = }>; } | { - model: "testModel"; - update: { - cbDefaultValueField?: null | string; - json?: any; - nullableReference?: null | string; - numberArray?: null | Array; - stringArray?: null | Array; - testField?: null | string; - }; + model: "twoFactor"; where?: Array<{ connector?: "AND" | "OR"; field: - | "nullableReference" - | "testField" - | "cbDefaultValueField" - | "stringArray" - | "numberArray" - | "json" + | "secret" + | "backupCodes" + | "userId" + | "verified" + | "failedVerificationCount" + | "lockedUntil" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -18094,22 +23886,19 @@ export type ComponentApi = }>; } | { - model: "organization"; - update: { - createdAt?: number; - logo?: null | string; - metadata?: null | string; - name?: string; - slug?: string; - updatedAt?: null | number; - }; + model: "oauthApplication"; where?: Array<{ connector?: "AND" | "OR"; field: | "name" - | "slug" - | "logo" + | "icon" | "metadata" + | "clientId" + | "clientSecret" + | "redirectUrls" + | "type" + | "disabled" + | "userId" | "createdAt" | "updatedAt" | "_id"; @@ -18136,22 +23925,53 @@ export type ComponentApi = }>; } | { - model: "member"; - update: { - createdAt?: number; - organizationId?: string; - role?: string; - updatedAt?: null | number; - userId?: string; - }; + model: "oauthAccessToken"; where?: Array<{ connector?: "AND" | "OR"; field: - | "organizationId" + | "accessToken" + | "refreshToken" + | "accessTokenExpiresAt" + | "refreshTokenExpiresAt" + | "clientId" | "userId" - | "role" + | "scopes" + | "createdAt" + | "updatedAt" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "oauthConsent"; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "clientId" + | "userId" + | "scopes" | "createdAt" | "updatedAt" + | "consentGiven" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -18176,20 +23996,16 @@ export type ComponentApi = }>; } | { - model: "team"; - update: { - createdAt?: number; - name?: string; - organizationId?: string; - updatedAt?: null | number; - }; + model: "jwks"; where?: Array<{ connector?: "AND" | "OR"; field: - | "name" - | "organizationId" + | "publicKey" + | "privateKey" | "createdAt" - | "updatedAt" + | "expiresAt" + | "alg" + | "crv" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -18214,15 +24030,10 @@ export type ComponentApi = }>; } | { - model: "teamMember"; - update: { - createdAt?: null | number; - teamId?: string; - userId?: string; - }; + model: "rateLimit"; where?: Array<{ connector?: "AND" | "OR"; - field: "teamId" | "userId" | "createdAt" | "_id"; + field: "key" | "count" | "lastRequest" | "_id"; mode?: "sensitive" | "insensitive"; operator?: | "lt" @@ -18246,30 +24057,29 @@ export type ComponentApi = }>; } | { - model: "invitation"; - update: { - createdAt?: null | number; - email?: null | string; - expiresAt?: null | number; - inviterId?: null | string; - organizationId?: null | string; - role?: null | string; - status?: null | string; - teamId?: null | string; - updatedAt?: null | number; - }; + model: "user_custom"; where?: Array<{ connector?: "AND" | "OR"; field: + | "name" | "email" - | "role" - | "status" - | "organizationId" - | "teamId" - | "inviterId" - | "expiresAt" + | "email_address" + | "emailVerified" + | "image" | "createdAt" | "updatedAt" + | "twoFactorEnabled" + | "isAnonymous" + | "username" + | "displayUsername" + | "phoneNumber" + | "phoneNumberVerified" + | "userId" + | "testField" + | "cbDefaultValueField" + | "customField" + | "numericField" + | "dateField" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -18292,271 +24102,325 @@ export type ComponentApi = | Array | null; }>; - }; - onUpdateHandle?: string; - }, - any, - Name - >; - }; - adapterRenameUserTable: { - create: FunctionReference< - "mutation", - "internal", - { - input: - | { - data: { - cbDefaultValueField?: null | string; - createdAt: number; - customField?: null | string; - dateField?: null | number; - displayUsername?: null | string; - email?: null | string; - emailVerified: boolean; - email_address?: null | string; - image?: null | string; - isAnonymous?: null | boolean; - name: string; - numericField?: null | number; - phoneNumber?: null | string; - phoneNumberVerified?: null | boolean; - testField?: null | string; - twoFactorEnabled?: null | boolean; - updatedAt: number; - userId?: null | string; - username?: null | string; - }; - model: "user"; - } - | { - data: { - createdAt: number; - expiresAt: number; - ipAddress?: null | string; - token: string; - updatedAt: number; - userAgent?: null | string; - userId: string; - }; - model: "session"; - } - | { - data: { - accessToken?: null | string; - accessTokenExpiresAt?: null | number; - accountId: string; - createdAt: number; - idToken?: null | string; - password?: null | string; - providerId: string; - refreshToken?: null | string; - refreshTokenExpiresAt?: null | number; - scope?: null | string; - updatedAt: number; - userId: string; - }; - model: "account"; - } - | { - data: { - createdAt: number; - expiresAt: number; - identifier: string; - updatedAt: number; - value: string; - }; - model: "verification"; - } - | { - data: { - backupCodes: string; - secret: string; - userId: string; - verified?: null | boolean; - }; - model: "twoFactor"; - } - | { - data: { - clientId?: null | string; - clientSecret?: null | string; - createdAt?: null | number; - disabled?: null | boolean; - icon?: null | string; - metadata?: null | string; - name?: null | string; - redirectUrls?: null | string; - type?: null | string; - updatedAt?: null | number; - userId?: null | string; - }; - model: "oauthApplication"; - } - | { - data: { - accessToken?: null | string; - accessTokenExpiresAt?: null | number; - clientId?: null | string; - createdAt?: null | number; - refreshToken?: null | string; - refreshTokenExpiresAt?: null | number; - scopes?: null | string; - updatedAt?: null | number; - userId?: null | string; - }; - model: "oauthAccessToken"; - } - | { - data: { - clientId?: null | string; - consentGiven?: null | boolean; - createdAt?: null | number; - scopes?: null | string; - updatedAt?: null | number; - userId?: null | string; - }; - model: "oauthConsent"; - } - | { - data: { - createdAt: number; - expiresAt?: null | number; - privateKey: string; - publicKey: string; - }; - model: "jwks"; - } - | { - data: { count: number; key: string; lastRequest: number }; - model: "rateLimit"; } | { - data: { - cbDefaultValueField?: null | string; - createdAt: number; - customField?: null | string; - dateField?: null | number; - displayUsername?: null | string; - email?: null | string; - emailVerified: boolean; - email_address?: null | string; - image?: null | string; - isAnonymous?: null | boolean; - name: string; - numericField?: null | number; - phoneNumber?: null | string; - phoneNumberVerified?: null | boolean; - testField?: null | string; - twoFactorEnabled?: null | boolean; - updatedAt: number; - userId?: null | string; - username?: null | string; - }; - model: "user_custom"; + model: "user_table"; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "name" + | "email" + | "email_address" + | "emailVerified" + | "image" + | "createdAt" + | "updatedAt" + | "twoFactorEnabled" + | "isAnonymous" + | "username" + | "displayUsername" + | "phoneNumber" + | "phoneNumberVerified" + | "userId" + | "testField" + | "cbDefaultValueField" + | "customField" + | "numericField" + | "dateField" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; } | { - data: { - cbDefaultValueField?: null | string; - createdAt: number; - customField?: null | string; - dateField?: null | number; - displayUsername?: null | string; - email?: null | string; - emailVerified: boolean; - email_address?: null | string; - image?: null | string; - isAnonymous?: null | boolean; - name: string; - numericField?: null | number; - phoneNumber?: null | string; - phoneNumberVerified?: null | boolean; - testField?: null | string; - twoFactorEnabled?: null | boolean; - updatedAt: number; - userId?: null | string; - username?: null | string; - }; - model: "user_table"; + model: "oneToOneTable"; + where?: Array<{ + connector?: "AND" | "OR"; + field: "oneToOne" | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; } - | { data: { oneToOne: string }; model: "oneToOneTable" } | { - data: { - oneToOne?: null | string; - one_to_one?: null | string; - }; model: "one_to_one_table"; + where?: Array<{ + connector?: "AND" | "OR"; + field: "oneToOne" | "one_to_one" | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; } | { - data: { - cbDefaultValueField?: null | string; - json?: any; - nullableReference?: null | string; - numberArray?: null | Array; - stringArray?: null | Array; - testField?: null | string; - }; model: "testModel"; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "nullableReference" + | "testField" + | "cbDefaultValueField" + | "stringArray" + | "numberArray" + | "json" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; } | { - data: { - createdAt: number; - logo?: null | string; - metadata?: null | string; - name: string; - slug: string; - updatedAt?: null | number; - }; model: "organization"; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "name" + | "slug" + | "logo" + | "metadata" + | "createdAt" + | "updatedAt" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; } | { - data: { - createdAt: number; - organizationId: string; - role: string; - updatedAt?: null | number; - userId: string; - }; model: "member"; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "organizationId" + | "userId" + | "role" + | "createdAt" + | "updatedAt" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; } | { - data: { - createdAt: number; - name: string; - organizationId: string; - updatedAt?: null | number; - }; model: "team"; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "name" + | "memberCount" + | "organizationId" + | "createdAt" + | "updatedAt" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; } | { - data: { - createdAt?: null | number; - teamId: string; - userId: string; - }; model: "teamMember"; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "teamId" + | "userId" + | "membershipKey" + | "createdAt" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; } | { - data: { - createdAt?: null | number; - email?: null | string; - expiresAt?: null | number; - inviterId?: null | string; - organizationId?: null | string; - role?: null | string; - status?: null | string; - teamId?: null | string; - updatedAt?: null | number; - }; model: "invitation"; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "email" + | "role" + | "status" + | "organizationId" + | "teamId" + | "inviterId" + | "expiresAt" + | "createdAt" + | "updatedAt" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; }; - onCreateHandle?: string; - select?: Array; + onDeleteHandle?: string; + paginationOpts: { + cursor: string | null; + endCursor?: string | null; + id?: number; + maximumBytesRead?: number; + maximumRowsRead?: number; + numItems: number; + }; }, any, Name >; - deleteMany: FunctionReference< + deleteOne: FunctionReference< "mutation", "internal", { @@ -18648,6 +24512,7 @@ export type ComponentApi = where?: Array<{ connector?: "AND" | "OR"; field: + | "issuer" | "accountId" | "providerId" | "userId" @@ -18725,6 +24590,8 @@ export type ComponentApi = | "backupCodes" | "userId" | "verified" + | "failedVerificationCount" + | "lockedUntil" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -18867,6 +24734,8 @@ export type ComponentApi = | "privateKey" | "createdAt" | "expiresAt" + | "alg" + | "crv" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -19172,6 +25041,7 @@ export type ComponentApi = connector?: "AND" | "OR"; field: | "name" + | "memberCount" | "organizationId" | "createdAt" | "updatedAt" @@ -19202,7 +25072,12 @@ export type ComponentApi = model: "teamMember"; where?: Array<{ connector?: "AND" | "OR"; - field: "teamId" | "userId" | "createdAt" | "_id"; + field: + | "teamId" + | "userId" + | "membershipKey" + | "createdAt" + | "_id"; mode?: "sensitive" | "insensitive"; operator?: | "lt" @@ -19263,6 +25138,38 @@ export type ComponentApi = }>; }; onDeleteHandle?: string; + }, + any, + Name + >; + findMany: FunctionReference< + "query", + "internal", + { + join?: any; + limit?: number; + model: + | "user" + | "session" + | "account" + | "verification" + | "twoFactor" + | "oauthApplication" + | "oauthAccessToken" + | "oauthConsent" + | "jwks" + | "rateLimit" + | "user_custom" + | "user_table" + | "oneToOneTable" + | "one_to_one_table" + | "testModel" + | "organization" + | "member" + | "team" + | "teamMember" + | "invitation"; + offset?: number; paginationOpts: { cursor: string | null; endCursor?: string | null; @@ -19271,18 +25178,121 @@ export type ComponentApi = maximumRowsRead?: number; numItems: number; }; + select?: Array; + sortBy?: { direction: "asc" | "desc"; field: string }; + where?: Array<{ + connector?: "AND" | "OR"; + field: string; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; }, any, Name >; - deleteOne: FunctionReference< + findOne: FunctionReference< + "query", + "internal", + { + join?: any; + model: + | "user" + | "session" + | "account" + | "verification" + | "twoFactor" + | "oauthApplication" + | "oauthAccessToken" + | "oauthConsent" + | "jwks" + | "rateLimit" + | "user_custom" + | "user_table" + | "oneToOneTable" + | "one_to_one_table" + | "testModel" + | "organization" + | "member" + | "team" + | "teamMember" + | "invitation"; + select?: Array; + where?: Array<{ + connector?: "AND" | "OR"; + field: string; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + }, + any, + Name + >; + incrementOne: FunctionReference< "mutation", "internal", { input: | { + increment: Record; model: "user"; - where?: Array<{ + set?: { + cbDefaultValueField?: null | string; + createdAt?: number; + customField?: null | string; + dateField?: null | number; + displayUsername?: null | string; + email?: null | string; + emailVerified?: boolean; + email_address?: null | string; + image?: null | string; + isAnonymous?: null | boolean; + name?: string; + numericField?: null | number; + phoneNumber?: null | string; + phoneNumberVerified?: null | boolean; + testField?: null | string; + twoFactorEnabled?: null | boolean; + updatedAt?: number; + userId?: null | string; + username?: null | string; + }; + where: Array<{ connector?: "AND" | "OR"; field: | "name" @@ -19328,8 +25338,18 @@ export type ComponentApi = }>; } | { + increment: Record; model: "session"; - where?: Array<{ + set?: { + createdAt?: number; + expiresAt?: number; + ipAddress?: null | string; + token?: string; + updatedAt?: number; + userAgent?: null | string; + userId?: string; + }; + where: Array<{ connector?: "AND" | "OR"; field: | "expiresAt" @@ -19363,10 +25383,27 @@ export type ComponentApi = }>; } | { + increment: Record; model: "account"; - where?: Array<{ + set?: { + accessToken?: null | string; + accessTokenExpiresAt?: null | number; + accountId?: string; + createdAt?: number; + idToken?: null | string; + issuer?: null | string; + password?: null | string; + providerId?: string; + refreshToken?: null | string; + refreshTokenExpiresAt?: null | number; + scope?: null | string; + updatedAt?: number; + userId?: string; + }; + where: Array<{ connector?: "AND" | "OR"; field: + | "issuer" | "accountId" | "providerId" | "userId" @@ -19403,8 +25440,16 @@ export type ComponentApi = }>; } | { + increment: Record; model: "verification"; - where?: Array<{ + set?: { + createdAt?: number; + expiresAt?: number; + identifier?: string; + updatedAt?: number; + value?: string; + }; + where: Array<{ connector?: "AND" | "OR"; field: | "identifier" @@ -19436,14 +25481,25 @@ export type ComponentApi = }>; } | { + increment: Record; model: "twoFactor"; - where?: Array<{ + set?: { + backupCodes?: string; + failedVerificationCount?: null | number; + lockedUntil?: null | number; + secret?: string; + userId?: string; + verified?: null | boolean; + }; + where: Array<{ connector?: "AND" | "OR"; field: | "secret" | "backupCodes" | "userId" | "verified" + | "failedVerificationCount" + | "lockedUntil" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -19468,8 +25524,22 @@ export type ComponentApi = }>; } | { + increment: Record; model: "oauthApplication"; - where?: Array<{ + set?: { + clientId?: null | string; + clientSecret?: null | string; + createdAt?: null | number; + disabled?: null | boolean; + icon?: null | string; + metadata?: null | string; + name?: null | string; + redirectUrls?: null | string; + type?: null | string; + updatedAt?: null | number; + userId?: null | string; + }; + where: Array<{ connector?: "AND" | "OR"; field: | "name" @@ -19507,8 +25577,20 @@ export type ComponentApi = }>; } | { + increment: Record; model: "oauthAccessToken"; - where?: Array<{ + set?: { + accessToken?: null | string; + accessTokenExpiresAt?: null | number; + clientId?: null | string; + createdAt?: null | number; + refreshToken?: null | string; + refreshTokenExpiresAt?: null | number; + scopes?: null | string; + updatedAt?: null | number; + userId?: null | string; + }; + where: Array<{ connector?: "AND" | "OR"; field: | "accessToken" @@ -19544,8 +25626,17 @@ export type ComponentApi = }>; } | { + increment: Record; model: "oauthConsent"; - where?: Array<{ + set?: { + clientId?: null | string; + consentGiven?: null | boolean; + createdAt?: null | number; + scopes?: null | string; + updatedAt?: null | number; + userId?: null | string; + }; + where: Array<{ connector?: "AND" | "OR"; field: | "clientId" @@ -19578,14 +25669,25 @@ export type ComponentApi = }>; } | { + increment: Record; model: "jwks"; - where?: Array<{ + set?: { + alg?: null | string; + createdAt?: number; + crv?: null | string; + expiresAt?: null | number; + privateKey?: string; + publicKey?: string; + }; + where: Array<{ connector?: "AND" | "OR"; field: | "publicKey" | "privateKey" | "createdAt" | "expiresAt" + | "alg" + | "crv" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -19610,8 +25712,10 @@ export type ComponentApi = }>; } | { + increment: Record; model: "rateLimit"; - where?: Array<{ + set?: { count?: number; key?: string; lastRequest?: number }; + where: Array<{ connector?: "AND" | "OR"; field: "key" | "count" | "lastRequest" | "_id"; mode?: "sensitive" | "insensitive"; @@ -19637,8 +25741,30 @@ export type ComponentApi = }>; } | { + increment: Record; model: "user_custom"; - where?: Array<{ + set?: { + cbDefaultValueField?: null | string; + createdAt?: number; + customField?: null | string; + dateField?: null | number; + displayUsername?: null | string; + email?: null | string; + emailVerified?: boolean; + email_address?: null | string; + image?: null | string; + isAnonymous?: null | boolean; + name?: string; + numericField?: null | number; + phoneNumber?: null | string; + phoneNumberVerified?: null | boolean; + testField?: null | string; + twoFactorEnabled?: null | boolean; + updatedAt?: number; + userId?: null | string; + username?: null | string; + }; + where: Array<{ connector?: "AND" | "OR"; field: | "name" @@ -19684,8 +25810,30 @@ export type ComponentApi = }>; } | { + increment: Record; model: "user_table"; - where?: Array<{ + set?: { + cbDefaultValueField?: null | string; + createdAt?: number; + customField?: null | string; + dateField?: null | number; + displayUsername?: null | string; + email?: null | string; + emailVerified?: boolean; + email_address?: null | string; + image?: null | string; + isAnonymous?: null | boolean; + name?: string; + numericField?: null | number; + phoneNumber?: null | string; + phoneNumberVerified?: null | boolean; + testField?: null | string; + twoFactorEnabled?: null | boolean; + updatedAt?: number; + userId?: null | string; + username?: null | string; + }; + where: Array<{ connector?: "AND" | "OR"; field: | "name" @@ -19731,8 +25879,10 @@ export type ComponentApi = }>; } | { + increment: Record; model: "oneToOneTable"; - where?: Array<{ + set?: { oneToOne?: string }; + where: Array<{ connector?: "AND" | "OR"; field: "oneToOne" | "_id"; mode?: "sensitive" | "insensitive"; @@ -19758,8 +25908,13 @@ export type ComponentApi = }>; } | { + increment: Record; model: "one_to_one_table"; - where?: Array<{ + set?: { + oneToOne?: null | string; + one_to_one?: null | string; + }; + where: Array<{ connector?: "AND" | "OR"; field: "oneToOne" | "one_to_one" | "_id"; mode?: "sensitive" | "insensitive"; @@ -19785,8 +25940,17 @@ export type ComponentApi = }>; } | { + increment: Record; model: "testModel"; - where?: Array<{ + set?: { + cbDefaultValueField?: null | string; + json?: any; + nullableReference?: null | string; + numberArray?: null | Array; + stringArray?: null | Array; + testField?: null | string; + }; + where: Array<{ connector?: "AND" | "OR"; field: | "nullableReference" @@ -19819,8 +25983,17 @@ export type ComponentApi = }>; } | { + increment: Record; model: "organization"; - where?: Array<{ + set?: { + createdAt?: number; + logo?: null | string; + metadata?: null | string; + name?: string; + slug?: string; + updatedAt?: null | number; + }; + where: Array<{ connector?: "AND" | "OR"; field: | "name" @@ -19853,8 +26026,16 @@ export type ComponentApi = }>; } | { + increment: Record; model: "member"; - where?: Array<{ + set?: { + createdAt?: number; + organizationId?: string; + role?: string; + updatedAt?: null | number; + userId?: string; + }; + where: Array<{ connector?: "AND" | "OR"; field: | "organizationId" @@ -19886,11 +26067,20 @@ export type ComponentApi = }>; } | { + increment: Record; model: "team"; - where?: Array<{ + set?: { + createdAt?: number; + memberCount?: number; + name?: string; + organizationId?: string; + updatedAt?: null | number; + }; + where: Array<{ connector?: "AND" | "OR"; field: | "name" + | "memberCount" | "organizationId" | "createdAt" | "updatedAt" @@ -19918,10 +26108,22 @@ export type ComponentApi = }>; } | { + increment: Record; model: "teamMember"; - where?: Array<{ + set?: { + createdAt?: null | number; + membershipKey?: null | string; + teamId?: string; + userId?: string; + }; + where: Array<{ connector?: "AND" | "OR"; - field: "teamId" | "userId" | "createdAt" | "_id"; + field: + | "teamId" + | "userId" + | "membershipKey" + | "createdAt" + | "_id"; mode?: "sensitive" | "insensitive"; operator?: | "lt" @@ -19945,8 +26147,20 @@ export type ComponentApi = }>; } | { + increment: Record; model: "invitation"; - where?: Array<{ + set?: { + createdAt?: null | number; + email?: null | string; + expiresAt?: null | number; + inviterId?: null | string; + organizationId?: null | string; + role?: null | string; + status?: null | string; + teamId?: null | string; + updatedAt?: null | number; + }; + where: Array<{ connector?: "AND" | "OR"; field: | "email" @@ -19981,128 +26195,7 @@ export type ComponentApi = | null; }>; }; - onDeleteHandle?: string; - }, - any, - Name - >; - findMany: FunctionReference< - "query", - "internal", - { - join?: any; - limit?: number; - model: - | "user" - | "session" - | "account" - | "verification" - | "twoFactor" - | "oauthApplication" - | "oauthAccessToken" - | "oauthConsent" - | "jwks" - | "rateLimit" - | "user_custom" - | "user_table" - | "oneToOneTable" - | "one_to_one_table" - | "testModel" - | "organization" - | "member" - | "team" - | "teamMember" - | "invitation"; - offset?: number; - paginationOpts: { - cursor: string | null; - endCursor?: string | null; - id?: number; - maximumBytesRead?: number; - maximumRowsRead?: number; - numItems: number; - }; - select?: Array; - sortBy?: { direction: "asc" | "desc"; field: string }; - where?: Array<{ - connector?: "AND" | "OR"; - field: string; - mode?: "sensitive" | "insensitive"; - operator?: - | "lt" - | "lte" - | "gt" - | "gte" - | "eq" - | "in" - | "not_in" - | "ne" - | "contains" - | "starts_with" - | "ends_with"; - value: - | string - | number - | boolean - | Array - | Array - | null; - }>; - }, - any, - Name - >; - findOne: FunctionReference< - "query", - "internal", - { - join?: any; - model: - | "user" - | "session" - | "account" - | "verification" - | "twoFactor" - | "oauthApplication" - | "oauthAccessToken" - | "oauthConsent" - | "jwks" - | "rateLimit" - | "user_custom" - | "user_table" - | "oneToOneTable" - | "one_to_one_table" - | "testModel" - | "organization" - | "member" - | "team" - | "teamMember" - | "invitation"; - select?: Array; - where?: Array<{ - connector?: "AND" | "OR"; - field: string; - mode?: "sensitive" | "insensitive"; - operator?: - | "lt" - | "lte" - | "gt" - | "gte" - | "eq" - | "in" - | "not_in" - | "ne" - | "contains" - | "starts_with" - | "ends_with"; - value: - | string - | number - | boolean - | Array - | Array - | null; - }>; + onUpdateHandle?: string; }, any, Name @@ -20232,6 +26325,7 @@ export type ComponentApi = accountId?: string; createdAt?: number; idToken?: null | string; + issuer?: null | string; password?: null | string; providerId?: string; refreshToken?: null | string; @@ -20243,6 +26337,7 @@ export type ComponentApi = where?: Array<{ connector?: "AND" | "OR"; field: + | "issuer" | "accountId" | "providerId" | "userId" @@ -20322,6 +26417,8 @@ export type ComponentApi = model: "twoFactor"; update: { backupCodes?: string; + failedVerificationCount?: null | number; + lockedUntil?: null | number; secret?: string; userId?: string; verified?: null | boolean; @@ -20333,6 +26430,8 @@ export type ComponentApi = | "backupCodes" | "userId" | "verified" + | "failedVerificationCount" + | "lockedUntil" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -20501,7 +26600,9 @@ export type ComponentApi = | { model: "jwks"; update: { + alg?: null | string; createdAt?: number; + crv?: null | string; expiresAt?: null | number; privateKey?: string; publicKey?: string; @@ -20513,6 +26614,8 @@ export type ComponentApi = | "privateKey" | "createdAt" | "expiresAt" + | "alg" + | "crv" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -20891,6 +26994,7 @@ export type ComponentApi = model: "team"; update: { createdAt?: number; + memberCount?: number; name?: string; organizationId?: string; updatedAt?: null | number; @@ -20899,6 +27003,7 @@ export type ComponentApi = connector?: "AND" | "OR"; field: | "name" + | "memberCount" | "organizationId" | "createdAt" | "updatedAt" @@ -20929,12 +27034,18 @@ export type ComponentApi = model: "teamMember"; update: { createdAt?: null | number; + membershipKey?: null | string; teamId?: string; userId?: string; }; where?: Array<{ connector?: "AND" | "OR"; - field: "teamId" | "userId" | "createdAt" | "_id"; + field: + | "teamId" + | "userId" + | "membershipKey" + | "createdAt" + | "_id"; mode?: "sensitive" | "insensitive"; operator?: | "lt" @@ -21143,6 +27254,7 @@ export type ComponentApi = accountId?: string; createdAt?: number; idToken?: null | string; + issuer?: null | string; password?: null | string; providerId?: string; refreshToken?: null | string; @@ -21154,6 +27266,7 @@ export type ComponentApi = where?: Array<{ connector?: "AND" | "OR"; field: + | "issuer" | "accountId" | "providerId" | "userId" @@ -21233,6 +27346,8 @@ export type ComponentApi = model: "twoFactor"; update: { backupCodes?: string; + failedVerificationCount?: null | number; + lockedUntil?: null | number; secret?: string; userId?: string; verified?: null | boolean; @@ -21244,6 +27359,8 @@ export type ComponentApi = | "backupCodes" | "userId" | "verified" + | "failedVerificationCount" + | "lockedUntil" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -21412,7 +27529,9 @@ export type ComponentApi = | { model: "jwks"; update: { + alg?: null | string; createdAt?: number; + crv?: null | string; expiresAt?: null | number; privateKey?: string; publicKey?: string; @@ -21424,6 +27543,8 @@ export type ComponentApi = | "privateKey" | "createdAt" | "expiresAt" + | "alg" + | "crv" | "_id"; mode?: "sensitive" | "insensitive"; operator?: @@ -21802,6 +27923,7 @@ export type ComponentApi = model: "team"; update: { createdAt?: number; + memberCount?: number; name?: string; organizationId?: string; updatedAt?: null | number; @@ -21810,6 +27932,7 @@ export type ComponentApi = connector?: "AND" | "OR"; field: | "name" + | "memberCount" | "organizationId" | "createdAt" | "updatedAt" @@ -21840,12 +27963,18 @@ export type ComponentApi = model: "teamMember"; update: { createdAt?: null | number; + membershipKey?: null | string; teamId?: string; userId?: string; }; where?: Array<{ connector?: "AND" | "OR"; - field: "teamId" | "userId" | "createdAt" | "_id"; + field: + | "teamId" + | "userId" + | "membershipKey" + | "createdAt" + | "_id"; mode?: "sensitive" | "insensitive"; operator?: | "lt" diff --git a/src/component/adapter.ts b/src/component/adapter.ts index 41ee5cbc..4d4c86b9 100644 --- a/src/component/adapter.ts +++ b/src/component/adapter.ts @@ -7,6 +7,7 @@ export const { findOne, findMany, updateOne, + incrementOne, updateMany, deleteOne, deleteMany, diff --git a/src/component/migrations.test.ts b/src/component/migrations.test.ts new file mode 100644 index 00000000..fc4cbc09 --- /dev/null +++ b/src/component/migrations.test.ts @@ -0,0 +1,516 @@ +/// + +import { describe, expect, it } from "vitest"; +import { convexTest } from "convex-test"; +import { api } from "./_generated/api.js"; +import schema from "./schema.js"; + +const modules = import.meta.glob("./**/*.*s"); +const backfillAccountIssuers = api.migrations.backfillAccountIssuers; +const validateAccountIssuerBackfill = + api.migrations.validateAccountIssuerBackfill; +const listLegacyOAuthApplications = api.migrations.listLegacyOAuthApplications; +const clearLegacyOAuthProviderRecords = + api.migrations.clearLegacyOAuthProviderRecords; + +describe("backfillAccountIssuers", () => { + it("backfills trusted issuers and normalizes credential account IDs", async () => { + const t = convexTest(schema, modules); + await t.run(async (ctx) => { + await ctx.db.insert("account", { + accountId: "legacy-credential-id", + providerId: "credential", + userId: "user-1", + createdAt: 1, + updatedAt: 1, + }); + await ctx.db.insert("account", { + accountId: "github-subject", + providerId: "github", + userId: "user-2", + createdAt: 1, + updatedAt: 1, + }); + }); + + await t.mutation(backfillAccountIssuers, { + providerIssuers: { github: "local:oauth:github" }, + paginationOpts: { cursor: null, numItems: 10 }, + }); + + const accounts = await t.run((ctx) => ctx.db.query("account").collect()); + expect(accounts).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + issuer: "local:credential", + accountId: "user-1", + }), + expect.objectContaining({ + issuer: "local:oauth:github", + accountId: "github-subject", + }), + ]) + ); + }); + + it("rolls back a page when a provider mapping is missing", async () => { + const t = convexTest(schema, modules); + await t.run((ctx) => + ctx.db.insert("account", { + accountId: "subject", + providerId: "custom-provider", + userId: "user-1", + createdAt: 1, + updatedAt: 1, + }) + ); + + await expect( + t.mutation(backfillAccountIssuers, { + providerIssuers: {}, + paginationOpts: { cursor: null, numItems: 10 }, + }) + ).rejects.toThrow("Missing trusted issuer mapping"); + const [account] = await t.run((ctx) => ctx.db.query("account").collect()); + expect(account?.issuer).toBeUndefined(); + }); + + it("rejects issuer mappings with surrounding whitespace", async () => { + const t = convexTest(schema, modules); + await t.run((ctx) => + ctx.db.insert("account", { + accountId: "subject", + providerId: "custom-provider", + userId: "user-1", + createdAt: 1, + updatedAt: 1, + }) + ); + + await expect( + t.query(validateAccountIssuerBackfill, { + providerIssuers: { + "custom-provider": " https://issuer.example.com ", + }, + paginationOpts: { cursor: null, numItems: 10 }, + }) + ).rejects.toThrow("contains leading or trailing whitespace"); + }); + + it("validates supplied external issuer mappings before reading a page", async () => { + const t = convexTest(schema, modules); + + await expect( + t.query(validateAccountIssuerBackfill, { + providerIssuers: { + "unused-provider": " https://issuer.example.com ", + }, + paginationOpts: { cursor: null, numItems: 10 }, + }) + ).rejects.toThrow( + "Trusted issuer mapping for providerId unused-provider contains leading or trailing whitespace" + ); + }); + + it("ignores issuer mappings for fixed local authentication methods", async () => { + const t = convexTest(schema, modules); + await t.run((ctx) => + ctx.db.insert("account", { + accountId: "legacy-credential-id", + providerId: "credential", + userId: "user-1", + createdAt: 1, + updatedAt: 1, + }) + ); + + await expect( + t.query(validateAccountIssuerBackfill, { + providerIssuers: { credential: "ignored" }, + paginationOpts: { cursor: null, numItems: 10 }, + }) + ).resolves.toMatchObject({ pending: 1, isDone: true }); + }); + + it("distinguishes an invalid stored issuer from an invalid mapping", async () => { + const t = convexTest(schema, modules); + await t.run((ctx) => + ctx.db.insert("account", { + accountId: "subject", + issuer: " https://issuer.example.com ", + providerId: "custom-provider", + userId: "user-1", + createdAt: 1, + updatedAt: 1, + }) + ); + + await expect( + t.query(validateAccountIssuerBackfill, { + providerIssuers: {}, + paginationOpts: { cursor: null, numItems: 10 }, + }) + ).rejects.toThrow( + "Existing issuer for providerId custom-provider contains leading or trailing whitespace" + ); + }); + + it("rejects non-positive page sizes on every migration entry point", async () => { + const t = convexTest(schema, modules); + const paginationOpts = { cursor: null, numItems: 0 }; + + await expect( + t.query(validateAccountIssuerBackfill, { + providerIssuers: {}, + paginationOpts, + }) + ).rejects.toThrow("paginationOpts.numItems must be a positive integer"); + await expect( + t.mutation(backfillAccountIssuers, { + providerIssuers: {}, + paginationOpts, + }) + ).rejects.toThrow("paginationOpts.numItems must be a positive integer"); + await expect( + t.query(listLegacyOAuthApplications, { paginationOpts }) + ).rejects.toThrow("paginationOpts.numItems must be a positive integer"); + await expect( + t.mutation(clearLegacyOAuthProviderRecords, { + table: "oauthApplication", + paginationOpts, + }) + ).rejects.toThrow("paginationOpts.numItems must be a positive integer"); + }); + + it("rolls back a page when trusted issuer mappings reveal a collision", async () => { + const t = convexTest(schema, modules); + await t.run(async (ctx) => { + await ctx.db.insert("account", { + accountId: "same-subject", + providerId: "oidc-primary", + userId: "user-1", + createdAt: 1, + updatedAt: 1, + }); + await ctx.db.insert("account", { + accountId: "same-subject", + providerId: "oidc-secondary", + userId: "user-2", + createdAt: 1, + updatedAt: 1, + }); + }); + + await expect( + t.mutation(backfillAccountIssuers, { + providerIssuers: { + "oidc-primary": "https://issuer.example.com", + "oidc-secondary": "https://issuer.example.com", + }, + paginationOpts: { cursor: null, numItems: 10 }, + }) + ).rejects.toThrow("Account identity collision"); + const accounts = await t.run((ctx) => ctx.db.query("account").collect()); + expect(accounts.every((account) => account.issuer === undefined)).toBe( + true + ); + }); + + it("detects collisions beyond the current page before writing", async () => { + const t = convexTest(schema, modules); + await t.run(async (ctx) => { + await ctx.db.insert("account", { + accountId: "same-subject", + providerId: "oidc-primary", + userId: "user-1", + createdAt: 1, + updatedAt: 1, + }); + await ctx.db.insert("account", { + accountId: "same-subject", + providerId: "oidc-secondary", + userId: "user-2", + createdAt: 1, + updatedAt: 1, + }); + }); + + const args = { + providerIssuers: { + "oidc-primary": "https://issuer.example.com", + "oidc-secondary": "https://issuer.example.com", + }, + paginationOpts: { cursor: null, numItems: 1 }, + }; + await expect(t.query(validateAccountIssuerBackfill, args)).rejects.toThrow( + "Account identity collision" + ); + await expect(t.mutation(backfillAccountIssuers, args)).rejects.toThrow( + "Account identity collision" + ); + + const accounts = await t.run((ctx) => ctx.db.query("account").collect()); + expect(accounts.every((account) => account.issuer === undefined)).toBe( + true + ); + }); + + it("detects credential collisions after account ID normalization", async () => { + const t = convexTest(schema, modules); + await t.run(async (ctx) => { + await ctx.db.insert("account", { + accountId: "legacy-credential-1", + providerId: "credential", + userId: "same-user", + createdAt: 1, + updatedAt: 1, + }); + await ctx.db.insert("account", { + accountId: "legacy-credential-2", + providerId: "credential", + userId: "same-user", + createdAt: 1, + updatedAt: 1, + }); + }); + + await expect( + t.mutation(backfillAccountIssuers, { + providerIssuers: {}, + paginationOpts: { cursor: null, numItems: 1 }, + }) + ).rejects.toThrow("Account identity collision"); + const accounts = await t.run((ctx) => ctx.db.query("account").collect()); + expect(accounts.every((account) => account.issuer === undefined)).toBe( + true + ); + }); + + it("is idempotent for accounts already written by Better Auth 1.7", async () => { + const t = convexTest(schema, modules); + await t.run((ctx) => + ctx.db.insert("account", { + issuer: "local:oauth:github", + accountId: "github-subject", + providerId: "github", + userId: "user-1", + createdAt: 1, + updatedAt: 1, + }) + ); + + const result = await t.mutation(backfillAccountIssuers, { + providerIssuers: {}, + paginationOpts: { cursor: null, numItems: 10 }, + }); + expect(result).toMatchObject({ + isDone: true, + migrated: 0, + alreadyMigrated: 1, + }); + }); + + it("validates and migrates every cursor page", async () => { + const t = convexTest(schema, modules); + await t.run(async (ctx) => { + for (let index = 0; index < 3; index += 1) { + await ctx.db.insert("account", { + accountId: `subject-${index}`, + providerId: "github", + userId: `user-${index}`, + createdAt: 1, + updatedAt: 1, + }); + } + }); + + let cursor: string | null = null; + let validated = 0; + do { + const result: { + pending: number; + isDone: boolean; + continueCursor: string; + } = await t.query(validateAccountIssuerBackfill, { + providerIssuers: { github: "local:oauth:github" }, + paginationOpts: { cursor, numItems: 1 }, + }); + validated += result.pending; + cursor = result.isDone ? null : result.continueCursor; + if (result.isDone) break; + } while (cursor); + expect(validated).toBe(3); + + cursor = null; + let migrated = 0; + do { + const result: { + migrated: number; + isDone: boolean; + continueCursor: string; + } = await t.mutation(backfillAccountIssuers, { + providerIssuers: { github: "local:oauth:github" }, + paginationOpts: { cursor, numItems: 1 }, + }); + migrated += result.migrated; + cursor = result.isDone ? null : result.continueCursor; + if (result.isDone) break; + } while (cursor); + expect(migrated).toBe(3); + const accounts = await t.run((ctx) => ctx.db.query("account").collect()); + expect( + accounts.every((account) => account.issuer === "local:oauth:github") + ).toBe(true); + }); + + it("treats completed cursors as terminal instead of restarting", async () => { + const t = convexTest(schema, modules); + await t.run(async (ctx) => { + await ctx.db.insert("account", { + accountId: "github-subject", + providerId: "github", + userId: "user-1", + createdAt: 1, + updatedAt: 1, + }); + await ctx.db.insert("oauthApplication", { + clientId: "legacy-client", + clientSecret: "legacy-secret", + }); + }); + const providerIssuers = { github: "local:oauth:github" }; + const initialPage = { cursor: null, numItems: 10 }; + + const validation = await t.query(validateAccountIssuerBackfill, { + providerIssuers, + paginationOpts: initialPage, + }); + expect(validation.isDone).toBe(true); + await expect( + t.query(validateAccountIssuerBackfill, { + providerIssuers, + paginationOpts: { + cursor: validation.continueCursor, + numItems: 10, + }, + }) + ).resolves.toMatchObject({ + isDone: true, + pending: 0, + alreadyMigrated: 0, + }); + + const backfill = await t.mutation(backfillAccountIssuers, { + providerIssuers, + paginationOpts: initialPage, + }); + expect(backfill.isDone).toBe(true); + await expect( + t.mutation(backfillAccountIssuers, { + providerIssuers, + paginationOpts: { cursor: backfill.continueCursor, numItems: 10 }, + }) + ).resolves.toMatchObject({ + isDone: true, + migrated: 0, + alreadyMigrated: 0, + }); + + const exported = await t.query(listLegacyOAuthApplications, { + paginationOpts: initialPage, + }); + expect(exported.isDone).toBe(true); + await expect( + t.query(listLegacyOAuthApplications, { + paginationOpts: { cursor: exported.continueCursor, numItems: 10 }, + }) + ).resolves.toMatchObject({ isDone: true, page: [] }); + + const cleared = await t.mutation(clearLegacyOAuthProviderRecords, { + table: "oauthApplication", + paginationOpts: initialPage, + }); + expect(cleared.isDone).toBe(true); + await expect( + t.mutation(clearLegacyOAuthProviderRecords, { + table: "oauthApplication", + paginationOpts: { cursor: cleared.continueCursor, numItems: 10 }, + }) + ).resolves.toMatchObject({ isDone: true, deleted: 0 }); + }); + + it("accepts, exports, and clears legacy OAuth provider records", async () => { + const t = convexTest(schema, modules); + await t.run(async (ctx) => { + await ctx.db.insert("oauthApplication", { + clientId: "legacy-client", + clientSecret: "legacy-secret", + redirectUrls: "https://example.com/callback", + }); + await ctx.db.insert("oauthAccessToken", { + accessToken: "legacy-access-token", + refreshToken: "legacy-refresh-token", + clientId: "legacy-client", + scopes: "openid profile", + }); + await ctx.db.insert("oauthAccessToken", { + accessToken: "second-legacy-access-token", + clientId: "legacy-client", + scopes: "openid", + }); + await ctx.db.insert("oauthConsent", { + clientId: "legacy-client", + userId: "user-1", + scopes: "openid profile", + consentGiven: true, + }); + await ctx.db.insert("oauthConsent", { + clientId: "legacy-client", + userId: "user-2", + scopes: "openid", + consentGiven: true, + }); + }); + + const clients = await t.query(listLegacyOAuthApplications, { + paginationOpts: { cursor: null, numItems: 10 }, + }); + expect(clients.page).toEqual([ + expect.objectContaining({ + clientId: "legacy-client", + redirectUrls: "https://example.com/callback", + hadClientSecret: true, + }), + ]); + expect(clients.page[0]).not.toHaveProperty("clientSecret"); + + for (const table of [ + "oauthApplication", + "oauthAccessToken", + "oauthConsent", + ] as const) { + let cursor: string | null = null; + let deleted = 0; + do { + const result: { + deleted: number; + isDone: boolean; + continueCursor: string; + } = await t.mutation(clearLegacyOAuthProviderRecords, { + table, + paginationOpts: { cursor, numItems: 1 }, + }); + deleted += result.deleted; + cursor = result.isDone ? null : result.continueCursor; + if (result.isDone) break; + } while (cursor); + expect(deleted).toBe(table === "oauthApplication" ? 1 : 2); + } + const remaining = await t.run(async (ctx) => ({ + applications: await ctx.db.query("oauthApplication").collect(), + tokens: await ctx.db.query("oauthAccessToken").collect(), + consents: await ctx.db.query("oauthConsent").collect(), + })); + expect(remaining).toEqual({ applications: [], tokens: [], consents: [] }); + }); +}); diff --git a/src/component/migrations.ts b/src/component/migrations.ts new file mode 100644 index 00000000..edf172d8 --- /dev/null +++ b/src/component/migrations.ts @@ -0,0 +1,349 @@ +import { + paginationOptsValidator, + paginationResultValidator, +} from "convex/server"; +import { v } from "convex/values"; +import { omit } from "convex-helpers"; +import { paginator } from "convex-helpers/server/pagination"; +import type { Doc } from "./_generated/dataModel.js"; +import { mutation, query } from "./_generated/server.js"; +import type { QueryCtx } from "./_generated/server.js"; +import schema from "./schema.js"; + +type ProviderIssuers = Record; + +const migrationCompleteCursor = "convex-better-auth:migration-complete"; + +const assertValidPaginationOpts = (paginationOpts: { + cursor: string | null; + numItems: number; +}) => { + if ( + !Number.isInteger(paginationOpts.numItems) || + paginationOpts.numItems <= 0 + ) { + throw new Error("paginationOpts.numItems must be a positive integer"); + } +}; + +const assertValidProviderIssuers = (providerIssuers: ProviderIssuers) => { + for (const [providerId, issuer] of Object.entries(providerIssuers)) { + if (providerId === "credential" || providerId === "siwe") { + continue; + } + if (!issuer.trim()) { + throw new Error( + `Trusted issuer mapping for providerId ${providerId} must not be empty` + ); + } + if (issuer !== issuer.trim()) { + throw new Error( + `Trusted issuer mapping for providerId ${providerId} contains leading or trailing whitespace` + ); + } + if (issuer === "local:credential" || issuer === "local:siwe") { + throw new Error( + `Issuer ${issuer} is reserved for a local authentication method` + ); + } + } +}; + +const targetAccountIdentity = ( + account: Doc<"account">, + providerIssuers: ProviderIssuers +) => { + const accountId = + account.providerId === "credential" ? account.userId : account.accountId; + const fixedIssuer = + account.providerId === "credential" + ? "local:credential" + : account.providerId === "siwe" + ? "local:siwe" + : undefined; + const mappedIssuer = providerIssuers[account.providerId]; + const existingIssuer = account.issuer ?? undefined; + + if (existingIssuer !== undefined && !existingIssuer.trim()) { + throw new Error( + `Existing issuer for providerId ${account.providerId} must not be empty` + ); + } + if ( + existingIssuer !== undefined && + existingIssuer !== existingIssuer.trim() + ) { + throw new Error( + `Existing issuer for providerId ${account.providerId} contains leading or trailing whitespace` + ); + } + + const issuer = fixedIssuer ?? mappedIssuer ?? existingIssuer; + + if (!issuer) { + throw new Error( + `Missing trusted issuer mapping for providerId ${account.providerId}` + ); + } + if ( + !fixedIssuer && + (issuer === "local:credential" || issuer === "local:siwe") + ) { + throw new Error( + `Issuer ${issuer} is reserved for a local authentication method` + ); + } + if (existingIssuer && existingIssuer !== issuer) { + throw new Error( + `Existing issuer ${existingIssuer} does not match trusted issuer ${issuer} for providerId ${account.providerId}` + ); + } + + return { accountId, issuer }; +}; + +const assertNoAccountIdentityCollision = async ( + ctx: Pick, + account: Doc<"account">, + providerIssuers: ProviderIssuers +) => { + const target = targetAccountIdentity(account, providerIssuers); + const candidates = await ctx.db + .query("account") + .withIndex("accountId_issuer", (index) => + index.eq("accountId", target.accountId) + ) + .collect(); + + if (account.providerId === "credential") { + const credentialCandidates = await ctx.db + .query("account") + .withIndex("providerId_userId", (index) => + index.eq("providerId", "credential").eq("userId", account.userId) + ) + .collect(); + const candidateIds = new Set(candidates.map((candidate) => candidate._id)); + candidates.push( + ...credentialCandidates.filter( + (candidate) => !candidateIds.has(candidate._id) + ) + ); + } + + for (const candidate of candidates) { + if (candidate._id === account._id) { + continue; + } + const candidateTarget = targetAccountIdentity(candidate, providerIssuers); + if ( + candidateTarget.issuer === target.issuer && + candidateTarget.accountId === target.accountId + ) { + throw new Error( + `Account identity collision for issuer ${target.issuer} and accountId ${target.accountId}` + ); + } + } + + return target; +}; + +const migrationArgs = { + providerIssuers: v.record(v.string(), v.string()), + paginationOpts: paginationOptsValidator, +}; + +const legacyOAuthApplicationExportValidator = v.object({ + ...omit(schema.tables.oauthApplication.validator.fields, ["clientSecret"]), + _id: v.id("oauthApplication"), + _creationTime: v.number(), + hadClientSecret: v.boolean(), +}); + +const clearedLegacyRecordsValidator = v.object({ + continueCursor: v.string(), + isDone: v.boolean(), + deleted: v.number(), +}); + +const accountValidationValidator = v.object({ + continueCursor: v.string(), + isDone: v.boolean(), + pending: v.number(), + alreadyMigrated: v.number(), +}); + +const accountBackfillValidator = v.object({ + continueCursor: v.string(), + isDone: v.boolean(), + migrated: v.number(), + alreadyMigrated: v.number(), +}); + +export const listLegacyOAuthApplications = query({ + args: { paginationOpts: paginationOptsValidator }, + returns: paginationResultValidator(legacyOAuthApplicationExportValidator), + handler: async (ctx, args) => { + assertValidPaginationOpts(args.paginationOpts); + if (args.paginationOpts.cursor === migrationCompleteCursor) { + return { + page: [], + continueCursor: migrationCompleteCursor, + isDone: true, + }; + } + const result = await paginator(ctx.db, schema) + .query("oauthApplication") + .paginate(args.paginationOpts); + return { + ...result, + continueCursor: result.isDone + ? migrationCompleteCursor + : result.continueCursor, + page: result.page.map(({ clientSecret, ...application }) => ({ + ...application, + hadClientSecret: Boolean(clientSecret), + })), + }; + }, +}); + +export const clearLegacyOAuthProviderRecords = mutation({ + args: { + table: v.union( + v.literal("oauthApplication"), + v.literal("oauthAccessToken"), + v.literal("oauthConsent") + ), + paginationOpts: paginationOptsValidator, + }, + returns: clearedLegacyRecordsValidator, + handler: async (ctx, args) => { + assertValidPaginationOpts(args.paginationOpts); + if (args.paginationOpts.cursor === migrationCompleteCursor) { + return { + continueCursor: migrationCompleteCursor, + isDone: true, + deleted: 0, + }; + } + const result = + args.table === "oauthApplication" + ? await paginator(ctx.db, schema) + .query("oauthApplication") + .paginate(args.paginationOpts) + : args.table === "oauthAccessToken" + ? await paginator(ctx.db, schema) + .query("oauthAccessToken") + .paginate(args.paginationOpts) + : await paginator(ctx.db, schema) + .query("oauthConsent") + .paginate(args.paginationOpts); + for (const record of result.page) { + await ctx.db.delete(args.table, record._id); + } + return { + continueCursor: result.isDone + ? migrationCompleteCursor + : result.continueCursor, + isDone: result.isDone, + deleted: result.page.length, + }; + }, +}); + +export const validateAccountIssuerBackfill = query({ + args: migrationArgs, + returns: accountValidationValidator, + handler: async (ctx, args) => { + assertValidPaginationOpts(args.paginationOpts); + assertValidProviderIssuers(args.providerIssuers); + if (args.paginationOpts.cursor === migrationCompleteCursor) { + return { + continueCursor: migrationCompleteCursor, + isDone: true, + pending: 0, + alreadyMigrated: 0, + }; + } + const result = await paginator(ctx.db, schema) + .query("account") + .paginate(args.paginationOpts); + let pending = 0; + let alreadyMigrated = 0; + + for (const account of result.page) { + const target = await assertNoAccountIdentityCollision( + ctx, + account, + args.providerIssuers + ); + if ( + account.issuer === target.issuer && + account.accountId === target.accountId + ) { + alreadyMigrated += 1; + } else { + pending += 1; + } + } + + return { + continueCursor: result.isDone + ? migrationCompleteCursor + : result.continueCursor, + isDone: result.isDone, + pending, + alreadyMigrated, + }; + }, +}); + +export const backfillAccountIssuers = mutation({ + args: migrationArgs, + returns: accountBackfillValidator, + handler: async (ctx, args) => { + assertValidPaginationOpts(args.paginationOpts); + assertValidProviderIssuers(args.providerIssuers); + if (args.paginationOpts.cursor === migrationCompleteCursor) { + return { + continueCursor: migrationCompleteCursor, + isDone: true, + migrated: 0, + alreadyMigrated: 0, + }; + } + const result = await paginator(ctx.db, schema) + .query("account") + .paginate(args.paginationOpts); + let migrated = 0; + let alreadyMigrated = 0; + + for (const account of result.page) { + const target = await assertNoAccountIdentityCollision( + ctx, + account, + args.providerIssuers + ); + if ( + account.issuer === target.issuer && + account.accountId === target.accountId + ) { + alreadyMigrated += 1; + continue; + } + + await ctx.db.patch("account", account._id, target); + migrated += 1; + } + + return { + continueCursor: result.isDone + ? migrationCompleteCursor + : result.continueCursor, + isDone: result.isDone, + migrated, + alreadyMigrated, + }; + }, +}); diff --git a/src/component/schema.ts b/src/component/schema.ts index 8b5ecc1c..ec144b39 100644 --- a/src/component/schema.ts +++ b/src/component/schema.ts @@ -1,5 +1,8 @@ /** - * This file is auto-generated. Do not edit this file manually. + * Generated from Better Auth 1.7, then intentionally staged for the 0.13 + * migration. Do not regenerate it without preserving the documented migration + * exceptions: optional account.issuer and the three legacy OAuth provider + * tables retained below. * To regenerate the schema, from your project root: * * npx auth generate --output src/component/schema.ts @@ -28,7 +31,7 @@ export const tables = { phoneNumberVerified: v.optional(v.union(v.null(), v.boolean())), userId: v.optional(v.union(v.null(), v.string())), }) - .index("email_name", ["email","name"]) + .index("email_name", ["email", "name"]) .index("name", ["name"]) .index("userId", ["userId"]) .index("username", ["username"]) @@ -43,10 +46,13 @@ export const tables = { userId: v.string(), }) .index("expiresAt", ["expiresAt"]) - .index("expiresAt_userId", ["expiresAt","userId"]) + .index("expiresAt_userId", ["expiresAt", "userId"]) .index("token", ["token"]) .index("userId", ["userId"]), account: defineTable({ + // Temporarily optional so existing deployments can run the 1.7 backfill. + // Better Auth supplies this field for all new accounts. + issuer: v.optional(v.union(v.null(), v.string())), accountId: v.string(), providerId: v.string(), userId: v.string(), @@ -60,9 +66,10 @@ export const tables = { createdAt: v.number(), updatedAt: v.number(), }) + .index("accountId_issuer", ["accountId", "issuer"]) .index("accountId", ["accountId"]) - .index("accountId_providerId", ["accountId","providerId"]) - .index("providerId_userId", ["providerId","userId"]) + .index("accountId_providerId", ["accountId", "providerId"]) + .index("providerId_userId", ["providerId", "userId"]) .index("userId", ["userId"]), verification: defineTable({ identifier: v.string(), @@ -78,8 +85,11 @@ export const tables = { backupCodes: v.string(), userId: v.string(), verified: v.optional(v.union(v.null(), v.boolean())), - }) - .index("userId", ["userId"]), + failedVerificationCount: v.optional(v.union(v.null(), v.number())), + lockedUntil: v.optional(v.union(v.null(), v.number())), + }).index("userId", ["userId"]), + // Kept during the 0.13 migration so existing OIDC provider clients can be + // exported before moving provider support to a Local Install. oauthApplication: defineTable({ name: v.optional(v.union(v.null(), v.string())), icon: v.optional(v.union(v.null(), v.string())), @@ -118,20 +128,21 @@ export const tables = { updatedAt: v.optional(v.union(v.null(), v.number())), consentGiven: v.optional(v.union(v.null(), v.boolean())), }) - .index("clientId_userId", ["clientId","userId"]) + .index("clientId_userId", ["clientId", "userId"]) .index("userId", ["userId"]), jwks: defineTable({ publicKey: v.string(), privateKey: v.string(), createdAt: v.number(), expiresAt: v.optional(v.union(v.null(), v.number())), + alg: v.optional(v.union(v.null(), v.string())), + crv: v.optional(v.union(v.null(), v.string())), }), rateLimit: defineTable({ key: v.string(), count: v.number(), lastRequest: v.number(), - }) - .index("key", ["key"]), + }).index("key", ["key"]), }; const schema = defineSchema(tables); diff --git a/src/component/testProfiles/adapterAdditionalFields.ts b/src/component/testProfiles/adapterAdditionalFields.ts index 9df286c5..638fcae6 100644 --- a/src/component/testProfiles/adapterAdditionalFields.ts +++ b/src/component/testProfiles/adapterAdditionalFields.ts @@ -7,6 +7,7 @@ export const { findOne, findMany, updateOne, + incrementOne, updateMany, deleteOne, deleteMany, diff --git a/src/component/testProfiles/adapterOrganizationJoins.ts b/src/component/testProfiles/adapterOrganizationJoins.ts index 526670be..cb6cb296 100644 --- a/src/component/testProfiles/adapterOrganizationJoins.ts +++ b/src/component/testProfiles/adapterOrganizationJoins.ts @@ -7,6 +7,7 @@ export const { findOne, findMany, updateOne, + incrementOne, updateMany, deleteOne, deleteMany, diff --git a/src/component/testProfiles/adapterPluginTable.ts b/src/component/testProfiles/adapterPluginTable.ts index 1c1d2435..577996f4 100644 --- a/src/component/testProfiles/adapterPluginTable.ts +++ b/src/component/testProfiles/adapterPluginTable.ts @@ -7,6 +7,7 @@ export const { findOne, findMany, updateOne, + incrementOne, updateMany, deleteOne, deleteMany, diff --git a/src/component/testProfiles/adapterRenameField.ts b/src/component/testProfiles/adapterRenameField.ts index bb79625b..cd2d85f6 100644 --- a/src/component/testProfiles/adapterRenameField.ts +++ b/src/component/testProfiles/adapterRenameField.ts @@ -7,6 +7,7 @@ export const { findOne, findMany, updateOne, + incrementOne, updateMany, deleteOne, deleteMany, diff --git a/src/component/testProfiles/adapterRenameUserCustom.ts b/src/component/testProfiles/adapterRenameUserCustom.ts index 45071851..f36daebd 100644 --- a/src/component/testProfiles/adapterRenameUserCustom.ts +++ b/src/component/testProfiles/adapterRenameUserCustom.ts @@ -7,6 +7,7 @@ export const { findOne, findMany, updateOne, + incrementOne, updateMany, deleteOne, deleteMany, diff --git a/src/component/testProfiles/adapterRenameUserTable.ts b/src/component/testProfiles/adapterRenameUserTable.ts index 84731f68..da6f2741 100644 --- a/src/component/testProfiles/adapterRenameUserTable.ts +++ b/src/component/testProfiles/adapterRenameUserTable.ts @@ -7,6 +7,7 @@ export const { findOne, findMany, updateOne, + incrementOne, updateMany, deleteOne, deleteMany, diff --git a/src/component/testProfiles/schema.profile-plugin-table.ts b/src/component/testProfiles/schema.profile-plugin-table.ts index cf0d790f..ac83f2d5 100644 --- a/src/component/testProfiles/schema.profile-plugin-table.ts +++ b/src/component/testProfiles/schema.profile-plugin-table.ts @@ -83,6 +83,7 @@ const memberTable = defineTable({ const teamTable = defineTable({ name: v.string(), + memberCount: v.number(), organizationId: v.string(), createdAt: v.number(), updatedAt: v.optional(v.union(v.null(), v.number())), @@ -93,9 +94,11 @@ const teamTable = defineTable({ const teamMemberTable = defineTable({ teamId: v.string(), userId: v.string(), + membershipKey: v.optional(v.union(v.null(), v.string())), createdAt: v.optional(v.union(v.null(), v.number())), }) .index("teamId", ["teamId"]) + .index("membershipKey", ["membershipKey"]) .index("userId", ["userId"]); const invitationTable = defineTable({ diff --git a/src/plugins/convex/index.test.ts b/src/plugins/convex/index.test.ts index 8c7cb984..a51c6b0d 100644 --- a/src/plugins/convex/index.test.ts +++ b/src/plugins/convex/index.test.ts @@ -53,3 +53,47 @@ describe("convex plugin JWT cookie refresh matcher", () => { expect(matcher(withoutSessionCtx as unknown as MatcherContext)).toBe(false); }); }); + +describe("convex plugin OpenID configuration", () => { + it("publishes only the Convex JWT metadata", async () => { + const originalConvexSiteUrl = process.env.CONVEX_SITE_URL; + process.env.CONVEX_SITE_URL = "https://deployment.convex.site"; + try { + const plugin = convex({ + authConfig: { + providers: [ + { + type: "customJwt", + applicationID: "convex", + issuer: "https://deployment.convex.site", + algorithm: "RS256", + jwks: "https://deployment.convex.site/custom/auth/convex/jwks", + }, + ], + }, + options: { basePath: "/custom/auth" }, + }); + + const response = await plugin.endpoints!.getOpenIdConfig!({ + context: {}, + asResponse: false, + returnHeaders: false, + returnStatus: false, + }); + + expect(response).toMatchObject({ + issuer: "https://deployment.convex.site", + jwks_uri: "https://deployment.convex.site/custom/auth/convex/jwks", + id_token_signing_alg_values_supported: ["RS256"], + }); + expect(response).not.toHaveProperty("authorization_endpoint"); + expect(response).not.toHaveProperty("token_endpoint"); + } finally { + if (originalConvexSiteUrl === undefined) { + delete process.env.CONVEX_SITE_URL; + } else { + process.env.CONVEX_SITE_URL = originalConvexSiteUrl; + } + } + }); +}); diff --git a/src/plugins/convex/index.ts b/src/plugins/convex/index.ts index db4cce2c..af18b791 100644 --- a/src/plugins/convex/index.ts +++ b/src/plugins/convex/index.ts @@ -8,28 +8,12 @@ import { import { bearer as bearerPlugin } from "better-auth/plugins/bearer"; import { jwt as jwtPlugin } from "better-auth/plugins/jwt"; import type { JwtOptions, Jwk } from "better-auth/plugins/jwt"; -import { oidcProvider as oidcProviderPlugin } from "better-auth/plugins/oidc-provider"; import { omit } from "convex-helpers"; import type { AuthConfig, AuthProvider } from "convex/server"; import { VERSION } from "../../version.js"; export const JWT_COOKIE_NAME = "convex_jwt"; -type BetterAuthAfterHooks = NonNullable< - NonNullable["after"] ->; -type BetterAuthAfterHook = BetterAuthAfterHooks[number]; -type BetterAuthHookContext = Parameters[0]; - -const normalizeAfterHooks = ( - hooks: THook[] -): BetterAuthAfterHooks => { - return hooks.map((hook) => ({ - ...hook, - matcher: (ctx: BetterAuthHookContext) => Boolean(hook.matcher(ctx)), - })); -}; - const getJwksAlg = (authProvider: AuthProvider) => { const isCustomJwt = "type" in authProvider && authProvider.type === "customJwt"; @@ -170,20 +154,12 @@ export const convex = (opts: { jwksRotateOnTokenGenerationError?: boolean; /** * @param {BetterAuthOptions} options - Better Auth options. Not required, - * currently used to pass the basePath to the oidcProvider plugin. + * currently used to keep discovery metadata aligned with the basePath. */ options?: BetterAuthOptions; }) => { const jwtExpirationSeconds = opts.jwt?.expirationSeconds ?? opts.jwtExpirationSeconds ?? 60 * 15; - const oidcProvider = oidcProviderPlugin({ - loginPage: "/not-used", - metadata: { - issuer: `${process.env.CONVEX_SITE_URL}`, - jwks_uri: `${process.env.CONVEX_SITE_URL}${opts.options?.basePath ?? "/api/auth"}/convex/jwks`, - }, - __skipDeprecationWarning: true, - }); const providerConfig = parseAuthConfig(opts.authConfig, opts); const jwtOptions = { @@ -304,24 +280,31 @@ export const convex = (opts: { return 0; }; }; + const noopAtomicWrite = (method: string) => { + const warn = noopWrite(method); + return async (...args: any[]) => { + await warn(...args); + return null; + }; + }; ctx.context.adapter.create = noopWrite("create") as any; ctx.context.adapter.update = noopWrite("update") as any; ctx.context.adapter.updateMany = noopWrite("updateMany") as any; ctx.context.adapter.delete = noopWrite("delete") as any; ctx.context.adapter.deleteMany = noopWrite("deleteMany") as any; + ctx.context.adapter.consumeOne = noopAtomicWrite("consumeOne"); + ctx.context.adapter.incrementOne = noopAtomicWrite("incrementOne"); return { context: ctx }; }), }, ], after: [ - ...normalizeAfterHooks(oidcProvider.hooks.after), { matcher: (ctx) => { return Boolean( ctx.path?.startsWith("/sign-in") || ctx.path?.startsWith("/sign-up") || ctx.path?.startsWith("/callback") || - ctx.path?.startsWith("/oauth2/callback") || ctx.path?.startsWith("/magic-link/verify") || ctx.path?.startsWith("/email-otp/verify-email") || ctx.path?.startsWith("/phone-number/verify") || @@ -383,14 +366,29 @@ export const convex = (opts: { }, // TODO: properly type this }, - async (ctx) => { - const response = await oidcProvider.endpoints.getOpenIdConfig({ - ...ctx, - asResponse: false, - returnHeaders: false, - returnStatus: false, - }); - return response; + async (_ctx) => { + const issuer = process.env.CONVEX_SITE_URL; + if (!issuer) { + throw new Error("CONVEX_SITE_URL is not set"); + } + const basePath = opts.options?.basePath ?? "/api/auth"; + const baseURL = `${issuer}${basePath === "/" ? "" : basePath}`; + return { + issuer, + jwks_uri: `${baseURL}/convex/jwks`, + id_token_signing_alg_values_supported: [getJwksAlg(providerConfig)], + claims_supported: [ + "sub", + "iss", + "aud", + "exp", + "iat", + "email", + "email_verified", + "name", + "picture", + ], + }; } ), getJwks: createAuthEndpoint( diff --git a/src/plugins/cross-domain/index.test.ts b/src/plugins/cross-domain/index.test.ts index 1527b542..f602cca4 100644 --- a/src/plugins/cross-domain/index.test.ts +++ b/src/plugins/cross-domain/index.test.ts @@ -100,10 +100,10 @@ describe("crossDomain plugin", async () => { }); }); - describe("callbackURL defaulting for oauth2", () => { + describe("callbackURL defaulting for generic OAuth", () => { it("injects siteUrl when callbackURL is absent", async () => { - const response = await post("/sign-in/oauth2", { - providerId: "example-oauth", + const response = await post("/sign-in/social", { + provider: "example-oauth", disableRedirect: true, }); const { url } = (await response.json()) as { url: string }; @@ -116,8 +116,8 @@ describe("crossDomain plugin", async () => { }); it("rewrites relative callbackURL to absolute using siteUrl", async () => { - const response = await post("/sign-in/oauth2", { - providerId: "example-oauth", + const response = await post("/sign-in/social", { + provider: "example-oauth", callbackURL: "/dashboard", disableRedirect: true, }); diff --git a/src/plugins/cross-domain/index.ts b/src/plugins/cross-domain/index.ts index be57aabf..13470312 100644 --- a/src/plugins/cross-domain/index.ts +++ b/src/plugins/cross-domain/index.ts @@ -108,7 +108,6 @@ export const crossDomain = ({ siteUrl }: { siteUrl: string }) => { ctx.body && !ctx.body.callbackURL && (ctx.path?.startsWith("/sign-in/social") || - ctx.path?.startsWith("/sign-in/oauth2") || ctx.path?.startsWith("/sign-in/magic-link") || ctx.path?.startsWith("/send-verification-email")) ) { @@ -154,7 +153,6 @@ export const crossDomain = ({ siteUrl }: { siteUrl: string }) => { matcher: (ctx) => { return Boolean( (ctx.path?.startsWith("/callback") || - ctx.path?.startsWith("/oauth2/callback") || ctx.path?.startsWith("/magic-link/verify")) && !isExpoNative(ctx) ); diff --git a/src/react/index.tsx b/src/react/index.tsx index dc10909a..6fc07a16 100644 --- a/src/react/index.tsx +++ b/src/react/index.tsx @@ -8,34 +8,40 @@ import { useQuery, } from "convex/react"; import type { FunctionReference } from "convex/server"; -import type { BetterAuthClientPlugin } from "better-auth"; -import type { createAuthClient } from "better-auth/react"; -import type { - convexClient, - crossDomainClient, -} from "../client/plugins/index.js"; import type { EmptyObject } from "convex-helpers"; +import type { BetterAuthClientOptions } from "better-auth"; +import type { ReactAuthClient } from "better-auth/react"; -type CrossDomainClient = ReturnType; -type ConvexClient = ReturnType; -type PluginsWithCrossDomain = ( - | CrossDomainClient - | ConvexClient - | BetterAuthClientPlugin -)[]; -type PluginsWithoutCrossDomain = (ConvexClient | BetterAuthClientPlugin)[]; -type AuthClientWithPlugins< - Plugins extends PluginsWithCrossDomain | PluginsWithoutCrossDomain, -> = ReturnType< - typeof createAuthClient< - BetterAuthClientPlugin & { - plugins: Plugins; - } - > ->; -export type AuthClient = - | AuthClientWithPlugins - | AuthClientWithPlugins; +type RequiredAuthClient = { + useSession: () => { + data: { session?: { id: string } } | null; + isPending: boolean; + }; + getSession: (args?: { + fetchOptions: { headers: Record }; + }) => Promise; + convex: { + token: (args: { + fetchOptions: { throw: boolean }; + }) => Promise<{ data?: { token?: string } | null }>; + }; +}; + +export type AuthClient< + Client extends RequiredAuthClient = ReactAuthClient & + RequiredAuthClient, +> = Client & RequiredAuthClient; + +type AuthClientWithCrossDomain = Client & { + crossDomain: { + oneTimeToken: { + verify: (args: { token: string }) => Promise<{ + data?: { session?: { token: string } } | null; + }>; + }; + }; + updateSession: () => void; +}; // Until we can import from our own entry points (requires TypeScript 4.7), // just describe the interface enough to help users pass the right type. @@ -50,7 +56,7 @@ type IConvexReactClient = { * * @public */ -export function ConvexBetterAuthProvider({ +export function ConvexBetterAuthProvider({ children, client, authClient, @@ -58,7 +64,7 @@ export function ConvexBetterAuthProvider({ }: { children: ReactNode; client: IConvexReactClient; - authClient: AuthClient; + authClient: Client; initialToken?: string | null; }) { const useBetterAuth = useUseAuthFromBetterAuth(authClient, initialToken); @@ -71,7 +77,7 @@ export function ConvexBetterAuthProvider({ const token = url.searchParams.get("ott"); if (token) { const authClientWithCrossDomain = - authClient as AuthClientWithPlugins; + authClient as AuthClientWithCrossDomain; url.searchParams.delete("ott"); window.history.replaceState({}, "", url); const result = @@ -101,8 +107,8 @@ export function ConvexBetterAuthProvider({ let initialTokenUsed = false; -function useUseAuthFromBetterAuth( - authClient: AuthClient, +function useUseAuthFromBetterAuth( + authClient: Client, initialToken?: string | null ) { const [cachedToken, setCachedToken] = useState( @@ -256,7 +262,7 @@ const UserSubscription = ({ * The component provides a query for this via `export const { getAuthUser } = authComponent.clientApi()`. * @param props.isAuthError - Function to check if the error is auth related. */ -export const AuthBoundary = ({ +export const AuthBoundary = ({ children, /** * The function to call when the user is unauthenticated. Typically a redirect @@ -282,7 +288,7 @@ export const AuthBoundary = ({ isAuthError, }: PropsWithChildren<{ onUnauth: () => void | Promise; - authClient: AuthClient; + authClient: Client; renderFallback?: () => React.ReactNode; getAuthUserFn: FunctionReference<"query", "public", EmptyObject>; isAuthError: (error: unknown) => boolean; diff --git a/src/react/index.types.test.ts b/src/react/index.types.test.ts new file mode 100644 index 00000000..6095ddb6 --- /dev/null +++ b/src/react/index.types.test.ts @@ -0,0 +1,9 @@ +import { expectTypeOf, it } from "vitest"; +import type { AuthClient } from "./index.js"; + +it("preserves the core Better Auth client API", () => { + expectTypeOf().not.toBeNever(); + expectTypeOf().toBeFunction(); + expectTypeOf().toBeFunction(); + expectTypeOf().toBeFunction(); +}); diff --git a/src/test/adapter-factory/convex-custom.ts b/src/test/adapter-factory/convex-custom.ts index 06bb5e7d..7d19c312 100644 --- a/src/test/adapter-factory/convex-custom.ts +++ b/src/test/adapter-factory/convex-custom.ts @@ -1,10 +1,11 @@ import { createTestSuite } from "@better-auth/test-utils/adapter"; +import { twoFactor } from "better-auth/plugins/two-factor"; import { expect } from "vitest"; export const convexCustomTestSuite = createTestSuite( "convex-custom", {}, - ({ adapter }) => ({ + ({ adapter, modifyBetterAuthOptions }) => ({ "should handle lone range operators": async () => { const user = await adapter.create({ model: "user", @@ -589,6 +590,7 @@ export const convexCustomTestSuite = createTestSuite( await adapter.create({ model: "account", data: { + issuer: "local:oauth:bar", accountId: "foo", providerId: "bar", userId: "baz", @@ -626,6 +628,7 @@ export const convexCustomTestSuite = createTestSuite( const nullRangeAccount = await adapter.create({ model: "account", data: { + issuer: "local:oauth:null-range-provider", accountId: nullRangeAccountId, providerId: "null-range-provider", userId: `null-range-user-${now}`, @@ -638,6 +641,7 @@ export const convexCustomTestSuite = createTestSuite( const nonNullRangeAccount = await adapter.create({ model: "account", data: { + issuer: "local:oauth:non-null-range-provider", accountId: nonNullRangeAccountId, providerId: "non-null-range-provider", userId: `non-null-range-user-${now}`, @@ -737,11 +741,89 @@ export const convexCustomTestSuite = createTestSuite( await expect( adapter.create({ model: "user", - data: { name: "foo", email: "foo@bar.com" }, + data: { name: "different name", email: "foo@bar.com" }, }), ).rejects.toThrow("user email already exists"); }, + "should consume a matching record only once": async () => { + const user = await adapter.create({ + model: "user", + data: { name: "consume", email: "consume@example.com" }, + }); + const where = [{ field: "id", value: user.id }]; + + const consumed = await Promise.all([ + adapter.consumeOne({ model: "user", where }), + adapter.consumeOne({ model: "user", where }), + ]); + expect(consumed.filter(Boolean)).toEqual([user]); + await expect( + adapter.consumeOne({ model: "user", where }), + ).resolves.toBeNull(); + }, + + "should apply a guarded increment only once": async () => { + await modifyBetterAuthOptions( + { rateLimit: { enabled: true, storage: "database" } }, + true, + ); + const rateLimit = await adapter.create({ + model: "rateLimit", + data: { key: "guarded-counter", count: 0, lastRequest: 0 }, + }); + const where = [ + { field: "id", value: rateLimit.id }, + { field: "count", operator: "lt" as const, value: 1 }, + ]; + + const increments = await Promise.all([ + adapter.incrementOne({ + model: "rateLimit", + where, + increment: { count: 1 }, + set: { lastRequest: 42 }, + }), + adapter.incrementOne({ + model: "rateLimit", + where, + increment: { count: 1 }, + set: { lastRequest: 42 }, + }), + ]); + expect(increments.filter(Boolean)).toEqual([ + expect.objectContaining({ count: 1, lastRequest: 42 }), + ]); + await expect( + adapter.incrementOne({ + model: "rateLimit", + where, + increment: { count: 1 }, + }), + ).resolves.toBeNull(); + }, + + "should increment a nullable legacy counter from zero": async () => { + await modifyBetterAuthOptions({ plugins: [twoFactor()] }, true); + const factor = await adapter.create({ + model: "twoFactor", + data: { + secret: "secret", + backupCodes: "[]", + userId: "legacy-counter-user", + failedVerificationCount: null, + }, + }); + + await expect( + adapter.incrementOne({ + model: "twoFactor", + where: [{ field: "id", value: factor.id }], + increment: { failedVerificationCount: 1 }, + }), + ).resolves.toMatchObject({ failedVerificationCount: 1 }); + }, + "should be able to compare against a date": async () => { const now = new Date().toISOString(); const user = await adapter.create({ From 764076844a017981ac3d583cb00495740c4aea8d Mon Sep 17 00:00:00 2001 From: Stephen Shkeda Date: Thu, 20 Aug 2026 23:41:46 -0400 Subject: [PATCH 02/29] fix: preserve declared Better Auth index order --- .../docs/migrations/migrate-to-0-13.mdx | 15 ++- .../next/convex/betterAuth/generatedSchema.ts | 2 +- examples/tanstack/convex/betterAuth/schema.ts | 2 +- src/client/adapter-utils.test.ts | 82 +++++++++++++++++ src/client/adapter-utils.ts | 91 ++++++++++++------- src/client/create-schema.test.ts | 27 +++++- src/client/create-schema.ts | 10 +- src/component/migrations.ts | 4 +- src/component/schema.ts | 5 +- 9 files changed, 188 insertions(+), 50 deletions(-) create mode 100644 src/client/adapter-utils.test.ts diff --git a/docs/content/docs/migrations/migrate-to-0-13.mdx b/docs/content/docs/migrations/migrate-to-0-13.mdx index 1ecef37f..59630232 100644 --- a/docs/content/docs/migrations/migrate-to-0-13.mdx +++ b/docs/content/docs/migrations/migrate-to-0-13.mdx @@ -406,8 +406,10 @@ npx auth generate --output generatedSchema.next.ts Before deploying, copy the new definitions into your active schema with these temporary changes: -1. Make `account.issuer` optional, but add the `accountId_issuer` compound - index. +1. Make `account.issuer` optional. Add the generated `issuer_accountId` index + and retain the existing `accountId_issuer` index until the new index finishes + backfilling. Keep the generated `accountId` index for migration collision + checks. 2. If you used `oidcProvider`, retain `oauthApplication` and merge the legacy and new `oauthAccessToken` and `oauthConsent` fields as optional unions. 3. Add the new OAuth tables without removing legacy records. @@ -422,6 +424,9 @@ If the local deployment used `oidcProvider`, also implement local equivalents of the export and clear functions above. Redact `clientSecret` from the export, save the client metadata, and clear `oauthAccessToken`, `oauthConsent`, and `oauthApplication` before replacing the temporary table definitions with the -final generated definitions. Run Convex code generation and deploy, then -re-register clients and complete the provider smoke tests before resuming -authentication writes. +final generated definitions. Retain the old `accountId_issuer` index through +this deployment. After `issuer_accountId` is active and the smoke tests pass, +remove the old index in a separate deployment; Convex may require interactive +confirmation when removing an index from a large table. Run Convex code +generation and deploy, then re-register clients and complete the provider smoke +tests before resuming authentication writes. diff --git a/examples/next/convex/betterAuth/generatedSchema.ts b/examples/next/convex/betterAuth/generatedSchema.ts index 5c1024ac..b672cdc4 100644 --- a/examples/next/convex/betterAuth/generatedSchema.ts +++ b/examples/next/convex/betterAuth/generatedSchema.ts @@ -60,7 +60,7 @@ export const tables = { createdAt: v.number(), updatedAt: v.number(), }) - .index("accountId_issuer", ["accountId", "issuer"]) + .index("issuer_accountId", ["issuer", "accountId"]) .index("accountId", ["accountId"]) .index("accountId_providerId", ["accountId", "providerId"]) .index("providerId_userId", ["providerId", "userId"]) diff --git a/examples/tanstack/convex/betterAuth/schema.ts b/examples/tanstack/convex/betterAuth/schema.ts index 3f246602..27ce3ac4 100644 --- a/examples/tanstack/convex/betterAuth/schema.ts +++ b/examples/tanstack/convex/betterAuth/schema.ts @@ -57,7 +57,7 @@ export const tables = { createdAt: v.number(), updatedAt: v.number(), }) - .index('accountId_issuer', ['accountId', 'issuer']) + .index('issuer_accountId', ['issuer', 'accountId']) .index('accountId', ['accountId']) .index('accountId_providerId', ['accountId', 'providerId']) .index('providerId_userId', ['providerId', 'userId']) diff --git a/src/client/adapter-utils.test.ts b/src/client/adapter-utils.test.ts new file mode 100644 index 00000000..0974e9cb --- /dev/null +++ b/src/client/adapter-utils.test.ts @@ -0,0 +1,82 @@ +/// + +import { describe, expect, it } from "vitest"; +import { convexTest } from "convex-test"; +import { defineSchema, defineTable } from "convex/server"; +import { v } from "convex/values"; +import type { BetterAuthDBSchema } from "better-auth/db"; +import { checkUniqueFields, paginate } from "./adapter-utils.js"; + +const schema = defineSchema({ + account: defineTable({ + issuer: v.string(), + accountId: v.string(), + }).index("issuer_accountId", ["issuer", "accountId"]), +}); + +const betterAuthSchema = { + account: { + modelName: "account", + fields: { + issuer: { type: "string", required: true }, + accountId: { type: "string", required: true }, + }, + indexes: [{ fields: ["issuer", "accountId"], unique: true }], + }, +} satisfies BetterAuthDBSchema; + +const modules = import.meta.glob("../component/**/*.*s"); + +describe("ordered compound indexes", () => { + it("pairs equality values with fields in the selected index order", async () => { + const t = convexTest(schema, modules); + await t.run(async (ctx) => { + await ctx.db.insert("account", { + issuer: "issuer-value", + accountId: "account-value", + }); + await ctx.db.insert("account", { + issuer: "account-value", + accountId: "issuer-value", + }); + }); + + const result = await t.run((ctx) => + paginate(ctx, schema, betterAuthSchema, { + model: "account", + where: [ + { field: "accountId", value: "account-value" }, + { field: "issuer", value: "issuer-value" }, + ], + paginationOpts: { cursor: null, numItems: 10 }, + }) + ); + + expect(result.page).toHaveLength(1); + expect(result.page[0]).toMatchObject({ + issuer: "issuer-value", + accountId: "account-value", + }); + }); + + it("checks compound uniqueness against the declared index order", async () => { + const t = convexTest(schema, modules); + await t.run((ctx) => + ctx.db.insert("account", { + issuer: "issuer-value", + accountId: "account-value", + }) + ); + + await expect( + t.run((ctx) => + checkUniqueFields(ctx, schema, betterAuthSchema, "account", { + issuer: "issuer-value", + accountId: "account-value", + }) + ) + ).rejects.toThrow( + "account unique constraint issuer+accountId already exists" + ); + }); +}); diff --git a/src/client/adapter-utils.ts b/src/client/adapter-utils.ts index 1e2cec8f..d4c857a6 100644 --- a/src/client/adapter-utils.ts +++ b/src/client/adapter-utils.ts @@ -181,10 +181,7 @@ const findIndex = ( const indexEqFields = where ?.filter((w) => !w.operator || w.operator === "eq") - .sort((a, b) => { - return a.field.localeCompare(b.field); - }) - .map((w) => [w.field, w.value]) ?? []; + .map((w) => [w.field, w.value] as const) ?? []; if (!indexEqFields?.length && !boundField && !args.sortBy) { return; } @@ -196,8 +193,16 @@ const findIndex = ( const sortField = args.sortBy?.field; // We internally use _creationTime in place of Better Auth's createdAt - const indexFields = indexEqFields - .map(([field]) => field) + const eqFields = indexEqFields.map(([field]) => field); + const eqValues = new Map(indexEqFields); + const suffixFields = [ + boundField && boundField !== "createdAt" ? boundField : "", + sortField && sortField !== "createdAt" && boundField !== sortField + ? sortField + : "", + ].filter(Boolean); + const indexFields = [...eqFields] + .sort((a, b) => a.localeCompare(b)) .concat(boundField && boundField !== "createdAt" ? boundField : "") .concat( sortField && sortField !== "createdAt" && boundField !== sortField @@ -215,18 +220,35 @@ const findIndex = ( indexDescriptor: "by_creation_time", fields: [], } - : indexes.find(({ fields }: { fields: string[] }) => { - const fieldsMatch = indexFields.every( - (field, idx) => field === fields[idx] - ); - // If sorting by createdAt, no intermediate fields can be on the index - // as they may override the createdAt sort order. - const boundFieldMatch = - boundField === "createdAt" || sortField === "createdAt" - ? indexFields.length === fields.length - : true; - return fieldsMatch && boundFieldMatch; - }); + : (indexes.find(({ fields }: { fields: string[] }) => + matchesIndex(fields, true) + ) ?? + indexes.find(({ fields }: { fields: string[] }) => + matchesIndex(fields, false) + )); + + function matchesIndex(fields: string[], requireExactEqOrder: boolean) { + if ( + eqValues.size !== eqFields.length || + fields.length < eqFields.length + suffixFields.length + ) { + return false; + } + const eqPrefix = fields.slice(0, eqFields.length); + const eqFieldsMatch = requireExactEqOrder + ? eqPrefix.every((field, index) => field === eqFields[index]) + : eqPrefix.every((field) => eqValues.has(field)); + const suffixFieldsMatch = suffixFields.every( + (field, index) => field === fields[eqFields.length + index] + ); + // If sorting by createdAt, no intermediate fields can be on the index + // as they may override the createdAt sort order. + const createdAtFieldMatch = + boundField === "createdAt" || sortField === "createdAt" + ? eqFields.length + suffixFields.length === fields.length + : true; + return eqFieldsMatch && suffixFieldsMatch && createdAtFieldMatch; + } if (!index) { return { indexFields }; } @@ -238,7 +260,9 @@ const findIndex = ( boundField, sortField, values: { - eq: indexEqFields.map(([, value]) => value), + eq: index.fields + .slice(0, eqFields.length) + .map((field: string) => [field, eqValues.get(field)!] as const), lt: lowerBound?.operator === "lt" ? lowerBound.value : undefined, lte: lowerBound?.operator === "lte" ? lowerBound.value : undefined, gt: upperBound?.operator === "gt" ? upperBound.value : undefined, @@ -278,15 +302,22 @@ export const checkUniqueFields = async < `No index found for unique constraint ${table}.${constraint.join("+")}` ); } + const indexConstraintFields = index.fields.slice(0, constraint.length); + if ( + indexConstraintFields.length !== constraint.length || + !indexConstraintFields.every((field) => constraint.includes(field)) + ) { + throw new Error( + `Index ${index.indexDescriptor} does not match unique constraint ${table}.${constraint.join("+")}` + ); + } const existingDocs = await ctx.db .query(table as any) .withIndex(index.indexDescriptor, (q) => - index.fields - .slice(0, constraint.length) - .reduce( - (query: any, field: string) => query.eq(field, mergedDoc[field]), - q - ) + indexConstraintFields.reduce( + (query: any, field: string) => query.eq(field, mergedDoc[field]), + q + ) ) .take(2); if (existingDocs.some((existingDoc) => existingDoc._id !== doc?._id)) { @@ -433,8 +464,8 @@ const generateQuery = ( usableIndex.indexDescriptor, hasValues ? (q: any) => { - for (const [idx, value] of (values?.eq ?? []).entries()) { - q = q.eq(usableIndex.fields[idx], value); + for (const [field, value] of values?.eq ?? []) { + q = q.eq(field, value); } if (values?.lt !== undefined) { q = q.lt(boundField, values.lt); @@ -542,9 +573,7 @@ export const paginate = async < where: [uniqueWhere], }) || {}; if (uniqueWhere.field !== "_id" && !index) { - throw new Error( - `No index found for ${args.model}.${uniqueWhere.field}` - ); + throw new Error(`No index found for ${args.model}.${uniqueWhere.field}`); } const doc = uniqueWhere.field === "_id" @@ -554,7 +583,7 @@ export const paginate = async < : await ctx.db .query(args.model as any) .withIndex(index?.indexDescriptor as any, (q) => - q.eq(index?.fields[0], uniqueWhere.value) + q.eq(uniqueWhere.field, uniqueWhere.value) ) .unique(); diff --git a/src/client/create-schema.test.ts b/src/client/create-schema.test.ts index 18956bb0..d36be1e1 100644 --- a/src/client/create-schema.test.ts +++ b/src/client/create-schema.test.ts @@ -19,8 +19,33 @@ describe("createSchema", () => { const { code } = await createSchema({ tables }); expect(code).toContain( - '.index("accountId_issuer", ["accountId","issuer"])' + '.index("issuer_accountId", ["issuer","accountId"])' ); expect(indexFields).toEqual(["issuer", "accountId"]); }); + + it("keeps reversed compound indexes distinct", async () => { + const tables = { + account: { + modelName: "account", + fields: { + issuer: { type: "string", required: true }, + accountId: { type: "string", required: true }, + }, + indexes: [ + { fields: ["issuer", "accountId"], unique: true }, + { fields: ["accountId", "issuer"], unique: false }, + ], + }, + } satisfies BetterAuthDBSchema; + + const { code } = await createSchema({ tables }); + + expect(code).toContain( + '.index("issuer_accountId", ["issuer","accountId"])' + ); + expect(code).toContain( + '.index("accountId_issuer", ["accountId","issuer"])' + ); + }); }); diff --git a/src/client/create-schema.ts b/src/client/create-schema.ts index 0c465c74..2b3d925a 100644 --- a/src/client/create-schema.ts +++ b/src/client/create-schema.ts @@ -75,14 +75,12 @@ const mergedIndexFields = (tables: BetterAuthDBSchema) => specialFieldIndexes.map((field) => [field]) ); const deduplicated = indexes.filter((index, indexPosition) => { - const fields = [...index].sort(); return ( indexes.findIndex((candidate) => { - const candidateFields = [...candidate].sort(); return ( - candidateFields.length === fields.length && - candidateFields.every( - (field, fieldPosition) => field === fields[fieldPosition] + candidate.length === index.length && + candidate.every( + (field, fieldPosition) => field === index[fieldPosition] ) ); }) === indexPosition @@ -190,7 +188,7 @@ export const tables = { mergedIndexFields(tables)[ tableKey as keyof typeof mergedIndexFields ]?.map((index) => { - const indexArray = Array.isArray(index) ? [...index].sort() : [index]; + const indexArray = Array.isArray(index) ? index : [index]; const indexName = indexArray.join("_"); return `.index("${indexName}", ${JSON.stringify(indexArray)})`; }) || []; diff --git a/src/component/migrations.ts b/src/component/migrations.ts index edf172d8..80418a8b 100644 --- a/src/component/migrations.ts +++ b/src/component/migrations.ts @@ -110,9 +110,7 @@ const assertNoAccountIdentityCollision = async ( const target = targetAccountIdentity(account, providerIssuers); const candidates = await ctx.db .query("account") - .withIndex("accountId_issuer", (index) => - index.eq("accountId", target.accountId) - ) + .withIndex("accountId", (index) => index.eq("accountId", target.accountId)) .collect(); if (account.providerId === "credential") { diff --git a/src/component/schema.ts b/src/component/schema.ts index ec144b39..4eca1b0d 100644 --- a/src/component/schema.ts +++ b/src/component/schema.ts @@ -1,8 +1,8 @@ /** * Generated from Better Auth 1.7, then intentionally staged for the 0.13 * migration. Do not regenerate it without preserving the documented migration - * exceptions: optional account.issuer and the three legacy OAuth provider - * tables retained below. + * exceptions: optional account.issuer, the previous accountId_issuer index, + * and the three legacy OAuth provider tables retained below. * To regenerate the schema, from your project root: * * npx auth generate --output src/component/schema.ts @@ -66,6 +66,7 @@ export const tables = { createdAt: v.number(), updatedAt: v.number(), }) + .index("issuer_accountId", ["issuer", "accountId"]) .index("accountId_issuer", ["accountId", "issuer"]) .index("accountId", ["accountId"]) .index("accountId_providerId", ["accountId", "providerId"]) From 57007e582a1578ba063ef38c9592ada7eceb0cd2 Mon Sep 17 00:00:00 2001 From: Stephen Shkeda Date: Thu, 20 Aug 2026 23:41:56 -0400 Subject: [PATCH 03/29] fix: guard optional cross-domain auth methods --- src/react/cross-domain.test.ts | 62 ++++++++++++++++++++++++++++++++ src/react/cross-domain.ts | 63 ++++++++++++++++++++++++++++++++ src/react/index.tsx | 65 ++++++++-------------------------- 3 files changed, 139 insertions(+), 51 deletions(-) create mode 100644 src/react/cross-domain.test.ts create mode 100644 src/react/cross-domain.ts diff --git a/src/react/cross-domain.test.ts b/src/react/cross-domain.test.ts new file mode 100644 index 00000000..d39a68f3 --- /dev/null +++ b/src/react/cross-domain.test.ts @@ -0,0 +1,62 @@ +import { describe, expect, it, vi } from "vitest"; +import { handleCrossDomainCallback } from "./cross-domain.js"; +import type { RequiredAuthClient } from "./cross-domain.js"; + +const coreClient = (): RequiredAuthClient => ({ + useSession: () => ({ data: null, isPending: false }), + getSession: vi.fn(), + convex: { + token: vi.fn(), + }, +}); + +describe("handleCrossDomainCallback", () => { + it("preserves the token when the client lacks cross-domain support", async () => { + const authClient = coreClient(); + const replaceUrl = vi.fn(); + + await handleCrossDomainCallback( + authClient, + "https://example.com/callback?ott=one-time-token&next=%2Fsettings", + replaceUrl + ); + + expect(replaceUrl).not.toHaveBeenCalled(); + expect(authClient.getSession).not.toHaveBeenCalled(); + }); + + it("verifies supported callbacks and refreshes the session", async () => { + const authClient = { + ...coreClient(), + crossDomain: { + oneTimeToken: { + verify: vi.fn().mockResolvedValue({ + data: { session: { token: "session-token" } }, + }), + }, + }, + updateSession: vi.fn(), + }; + const replaceUrl = vi.fn(); + + await handleCrossDomainCallback( + authClient, + "https://example.com/callback?ott=one-time-token&next=%2Fsettings", + replaceUrl + ); + + expect(authClient.crossDomain.oneTimeToken.verify).toHaveBeenCalledWith({ + token: "one-time-token", + }); + expect(replaceUrl).toHaveBeenCalledOnce(); + const replacedUrl = replaceUrl.mock.calls[0]?.[0] as URL; + expect(replacedUrl.searchParams.has("ott")).toBe(false); + expect(replacedUrl.searchParams.get("next")).toBe("/settings"); + expect(authClient.getSession).toHaveBeenCalledWith({ + fetchOptions: { + headers: { Authorization: "Bearer session-token" }, + }, + }); + expect(authClient.updateSession).toHaveBeenCalledOnce(); + }); +}); diff --git a/src/react/cross-domain.ts b/src/react/cross-domain.ts new file mode 100644 index 00000000..da6f84d1 --- /dev/null +++ b/src/react/cross-domain.ts @@ -0,0 +1,63 @@ +export type RequiredAuthClient = { + useSession: () => { + data: { session?: { id: string } } | null; + isPending: boolean; + }; + getSession: (args?: { + fetchOptions: { headers: Record }; + }) => Promise; + convex: { + token: (args: { + fetchOptions: { throw: boolean }; + }) => Promise<{ data?: { token?: string } | null }>; + }; +}; + +type AuthClientWithCrossDomain = RequiredAuthClient & { + crossDomain: { + oneTimeToken: { + verify: (args: { token: string }) => Promise<{ + data?: { session?: { token: string } } | null; + }>; + }; + }; + updateSession: () => void; +}; + +const hasCrossDomain = ( + authClient: RequiredAuthClient +): authClient is AuthClientWithCrossDomain => { + const candidate = authClient as Partial; + return ( + typeof candidate.crossDomain?.oneTimeToken?.verify === "function" && + typeof candidate.updateSession === "function" + ); +}; + +export const handleCrossDomainCallback = async ( + authClient: RequiredAuthClient, + href: string, + replaceUrl: (url: URL) => void +) => { + const url = new URL(href); + const token = url.searchParams.get("ott"); + if (!token || !hasCrossDomain(authClient)) { + return; + } + + url.searchParams.delete("ott"); + replaceUrl(url); + const result = await authClient.crossDomain.oneTimeToken.verify({ token }); + const session = result.data?.session; + if (!session) { + return; + } + await authClient.getSession({ + fetchOptions: { + headers: { + Authorization: `Bearer ${session.token}`, + }, + }, + }); + authClient.updateSession(); +}; diff --git a/src/react/index.tsx b/src/react/index.tsx index 6fc07a16..7865bab4 100644 --- a/src/react/index.tsx +++ b/src/react/index.tsx @@ -1,5 +1,12 @@ import type { PropsWithChildren, ReactNode } from "react"; -import { Component, useCallback, useEffect, useMemo, useRef, useState } from "react"; +import { + Component, + useCallback, + useEffect, + useMemo, + useRef, + useState, +} from "react"; import type { AuthTokenFetcher } from "convex/browser"; import { Authenticated, @@ -11,38 +18,14 @@ import type { FunctionReference } from "convex/server"; import type { EmptyObject } from "convex-helpers"; import type { BetterAuthClientOptions } from "better-auth"; import type { ReactAuthClient } from "better-auth/react"; - -type RequiredAuthClient = { - useSession: () => { - data: { session?: { id: string } } | null; - isPending: boolean; - }; - getSession: (args?: { - fetchOptions: { headers: Record }; - }) => Promise; - convex: { - token: (args: { - fetchOptions: { throw: boolean }; - }) => Promise<{ data?: { token?: string } | null }>; - }; -}; +import { handleCrossDomainCallback } from "./cross-domain.js"; +import type { RequiredAuthClient } from "./cross-domain.js"; export type AuthClient< Client extends RequiredAuthClient = ReactAuthClient & RequiredAuthClient, > = Client & RequiredAuthClient; -type AuthClientWithCrossDomain = Client & { - crossDomain: { - oneTimeToken: { - verify: (args: { token: string }) => Promise<{ - data?: { session?: { token: string } } | null; - }>; - }; - }; - updateSession: () => void; -}; - // Until we can import from our own entry points (requires TypeScript 4.7), // just describe the interface enough to help users pass the right type. type IConvexReactClient = { @@ -73,29 +56,9 @@ export function ConvexBetterAuthProvider({ if (typeof window === "undefined" || !window.location?.href) { return; } - const url = new URL(window.location.href); - const token = url.searchParams.get("ott"); - if (token) { - const authClientWithCrossDomain = - authClient as AuthClientWithCrossDomain; - url.searchParams.delete("ott"); - window.history.replaceState({}, "", url); - const result = - await authClientWithCrossDomain.crossDomain.oneTimeToken.verify({ - token, - }); - const session = result.data?.session; - if (session) { - await authClient.getSession({ - fetchOptions: { - headers: { - Authorization: `Bearer ${session.token}`, - }, - }, - }); - authClientWithCrossDomain.updateSession(); - } - } + await handleCrossDomainCallback(authClient, window.location.href, (url) => + window.history.replaceState({}, "", url) + ); })(); }, [authClient]); return ( @@ -262,7 +225,7 @@ const UserSubscription = ({ * The component provides a query for this via `export const { getAuthUser } = authComponent.clientApi()`. * @param props.isAuthError - Function to check if the error is auth related. */ -export const AuthBoundary = ({ +export const AuthBoundary = ({ children, /** * The function to call when the user is unauthenticated. Typically a redirect From 1d0d46c1c49c70aace5a3caaecfc684612b8ba93 Mon Sep 17 00:00:00 2001 From: Stephen Shkeda Date: Thu, 20 Aug 2026 23:42:06 -0400 Subject: [PATCH 04/29] test: isolate Better Auth adapter options --- src/test/adapter-factory/convex-custom.ts | 141 +++++++++++++--------- 1 file changed, 84 insertions(+), 57 deletions(-) diff --git a/src/test/adapter-factory/convex-custom.ts b/src/test/adapter-factory/convex-custom.ts index 7d19c312..f8584e65 100644 --- a/src/test/adapter-factory/convex-custom.ts +++ b/src/test/adapter-factory/convex-custom.ts @@ -1,11 +1,29 @@ import { createTestSuite } from "@better-auth/test-utils/adapter"; +import type { BetterAuthOptions } from "better-auth"; import { twoFactor } from "better-auth/plugins/two-factor"; import { expect } from "vitest"; +const withBetterAuthOptions = ( + modifyBetterAuthOptions: ( + options: BetterAuthOptions, + shouldRunMigrations: boolean, + ) => Promise, + options: BetterAuthOptions, + test: () => Promise, +) => + async () => { + await modifyBetterAuthOptions(options, true); + try { + await test(); + } finally { + await modifyBetterAuthOptions({}, true); + } + }; + export const convexCustomTestSuite = createTestSuite( "convex-custom", {}, - ({ adapter, modifyBetterAuthOptions }) => ({ + ({ adapter, getBetterAuthOptions, modifyBetterAuthOptions }) => ({ "should handle lone range operators": async () => { const user = await adapter.create({ model: "user", @@ -763,65 +781,74 @@ export const convexCustomTestSuite = createTestSuite( ).resolves.toBeNull(); }, - "should apply a guarded increment only once": async () => { - await modifyBetterAuthOptions( - { rateLimit: { enabled: true, storage: "database" } }, - true, - ); - const rateLimit = await adapter.create({ - model: "rateLimit", - data: { key: "guarded-counter", count: 0, lastRequest: 0 }, - }); - const where = [ - { field: "id", value: rateLimit.id }, - { field: "count", operator: "lt" as const, value: 1 }, - ]; - - const increments = await Promise.all([ - adapter.incrementOne({ - model: "rateLimit", - where, - increment: { count: 1 }, - set: { lastRequest: 42 }, - }), - adapter.incrementOne({ - model: "rateLimit", - where, - increment: { count: 1 }, - set: { lastRequest: 42 }, - }), - ]); - expect(increments.filter(Boolean)).toEqual([ - expect.objectContaining({ count: 1, lastRequest: 42 }), - ]); - await expect( - adapter.incrementOne({ + "should apply a guarded increment only once": withBetterAuthOptions( + modifyBetterAuthOptions, + { rateLimit: { enabled: true, storage: "database" } }, + async () => { + const rateLimit = await adapter.create({ model: "rateLimit", - where, - increment: { count: 1 }, - }), - ).resolves.toBeNull(); - }, - - "should increment a nullable legacy counter from zero": async () => { - await modifyBetterAuthOptions({ plugins: [twoFactor()] }, true); - const factor = await adapter.create({ - model: "twoFactor", - data: { - secret: "secret", - backupCodes: "[]", - userId: "legacy-counter-user", - failedVerificationCount: null, + data: { key: "guarded-counter", count: 0, lastRequest: 0 }, + }); + const where = [ + { field: "id", value: rateLimit.id }, + { field: "count", operator: "lt" as const, value: 1 }, + ]; + + const increments = await Promise.all([ + adapter.incrementOne({ + model: "rateLimit", + where, + increment: { count: 1 }, + set: { lastRequest: 42 }, + }), + adapter.incrementOne({ + model: "rateLimit", + where, + increment: { count: 1 }, + set: { lastRequest: 42 }, + }), + ]); + expect(increments.filter(Boolean)).toEqual([ + expect.objectContaining({ count: 1, lastRequest: 42 }), + ]); + await expect( + adapter.incrementOne({ + model: "rateLimit", + where, + increment: { count: 1 }, + }), + ).resolves.toBeNull(); + }, + ), + + "should increment a nullable legacy counter from zero": + withBetterAuthOptions( + modifyBetterAuthOptions, + { plugins: [twoFactor()] }, + async () => { + const factor = await adapter.create({ + model: "twoFactor", + data: { + secret: "secret", + backupCodes: "[]", + userId: "legacy-counter-user", + failedVerificationCount: null, + }, + }); + + await expect( + adapter.incrementOne({ + model: "twoFactor", + where: [{ field: "id", value: factor.id }], + increment: { failedVerificationCount: 1 }, + }), + ).resolves.toMatchObject({ failedVerificationCount: 1 }); }, - }); + ), - await expect( - adapter.incrementOne({ - model: "twoFactor", - where: [{ field: "id", value: factor.id }], - increment: { failedVerificationCount: 1 }, - }), - ).resolves.toMatchObject({ failedVerificationCount: 1 }); + "should restore Better Auth options after a custom test": async () => { + expect(getBetterAuthOptions().plugins).toBeUndefined(); + expect(getBetterAuthOptions().rateLimit).toBeUndefined(); }, "should be able to compare against a date": async () => { From 0598e92762c38149de5e773f664aa06fbda69ec1 Mon Sep 17 00:00:00 2001 From: Stephen Shkeda Date: Thu, 20 Aug 2026 23:42:18 -0400 Subject: [PATCH 05/29] docs: fix migration guide list formatting --- .../docs/migrations/migrate-to-0-13.mdx | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/docs/content/docs/migrations/migrate-to-0-13.mdx b/docs/content/docs/migrations/migrate-to-0-13.mdx index 59630232..dea96b0e 100644 --- a/docs/content/docs/migrations/migrate-to-0-13.mdx +++ b/docs/content/docs/migrations/migrate-to-0-13.mdx @@ -353,24 +353,24 @@ migration functions are not available after the switch. A cursor returned with 2. Add the replacement package and review Better Auth's full OAuth provider migration requirements: -```npm -npm install @better-auth/oauth-provider@~1.7.1 -``` - -```ts -import { oauthProvider } from "@better-auth/oauth-provider"; -import { jwt } from "better-auth/plugins/jwt"; - -betterAuth({ - plugins: [ - jwt(), - oauthProvider({ - loginPage: "/sign-in", - consentPage: "/consent", - }), - ], -}); -``` + ```npm + npm install @better-auth/oauth-provider@~1.7.1 + ``` + + ```ts + import { oauthProvider } from "@better-auth/oauth-provider"; + import { jwt } from "better-auth/plugins/jwt"; + + betterAuth({ + plugins: [ + jwt(), + oauthProvider({ + loginPage: "/sign-in", + consentPage: "/consent", + }), + ], + }); + ``` 3. Generate the Local Install schema only after the static Better Auth options include `oauthProvider()`. The account backfill is complete and all three From dd5a8cd72266942faef9d1464cbdfc5c555a84d9 Mon Sep 17 00:00:00 2001 From: Stephen Shkeda Date: Fri, 21 Aug 2026 00:26:07 -0400 Subject: [PATCH 06/29] fix: document multi-tenant Microsoft migration --- .../docs/migrations/migrate-to-0-13.mdx | 25 +++-- src/component/migrations.test.ts | 98 +++++++++++++++++++ 2 files changed, 116 insertions(+), 7 deletions(-) diff --git a/docs/content/docs/migrations/migrate-to-0-13.mdx b/docs/content/docs/migrations/migrate-to-0-13.mdx index dea96b0e..b243d063 100644 --- a/docs/content/docs/migrations/migrate-to-0-13.mdx +++ b/docs/content/docs/migrations/migrate-to-0-13.mdx @@ -34,9 +34,11 @@ request value. | OAuth provider without an issuer | `local:oauth:` | The component migration handles `credential` and `siwe` automatically. Pass an -explicit mapping for every other legacy provider. For an OAuth provider without -an `accountIssuer`, use Better Auth's helper rather than constructing the value -by hand: +explicit mapping for every other legacy provider whose rows all use the same +issuer. If one `providerId` can contain rows from multiple issuers, populate +each row's trusted issuer before running the component migration and omit that +provider from the mapping. For an OAuth provider without an `accountIssuer`, use +Better Auth's helper rather than constructing the value by hand: ```ts import { createOAuthAccountIssuer } from "better-auth/db"; @@ -54,10 +56,18 @@ Auth 1.7 retain their stored issuer and do not need a mapping on a rerun. Better Auth 1.7 changes Microsoft identities from the app-specific `sub` claim to the stable directory `oid` claim. Before running this component migration, - update `accountId` for `microsoft` and `microsoft-entra-id` rows using - verified ID tokens or a trusted Microsoft Entra export. The old account row - cannot safely supply this mapping by itself. See Better Auth's upgrade guide - for the full provider-specific procedure. + populate both the trusted `issuer` and the `oid`-based `accountId` on every + `microsoft` and `microsoft-entra-id` row using verified ID tokens or a trusted + Microsoft Entra export. The old account row cannot safely supply either value + by itself. + +The built-in `microsoft` provider derives the issuer from each verified token, +so accounts from different tenants can have different issuers under the same +`providerId`. Do not add one `microsoft` entry to `providerIssuers` unless every +row uses that exact issuer. After populating per-row issuers, omit `microsoft` +from the mapping. See Better Auth's upgrade guide for the full provider-specific +procedure. + Also audit custom OAuth providers whose immutable identifier is not their @@ -145,6 +155,7 @@ const providerIssuers = { github: createOAuthAccountIssuer("github"), google: "https://accounts.google.com", // "my-oidc-provider": "https://issuer.example.com", + // Do not add one Microsoft issuer when accounts span multiple tenants. }; export const validateAccountIssuerBackfill = internalQuery({ diff --git a/src/component/migrations.test.ts b/src/component/migrations.test.ts index fc4cbc09..6202ccc8 100644 --- a/src/component/migrations.test.ts +++ b/src/component/migrations.test.ts @@ -310,6 +310,104 @@ describe("backfillAccountIssuers", () => { }); }); + it("preserves per-row issuers for Microsoft accounts from different tenants", async () => { + const t = convexTest(schema, modules); + await t.run(async (ctx) => { + await ctx.db.insert("account", { + issuer: "https://login.microsoftonline.com/tenant-a/v2.0", + accountId: "directory-object-id", + providerId: "microsoft", + userId: "user-1", + createdAt: 1, + updatedAt: 1, + }); + await ctx.db.insert("account", { + issuer: "https://login.microsoftonline.com/tenant-b/v2.0", + accountId: "directory-object-id", + providerId: "microsoft", + userId: "user-2", + createdAt: 1, + updatedAt: 1, + }); + }); + + const args = { + providerIssuers: {}, + paginationOpts: { cursor: null, numItems: 10 }, + }; + await expect( + t.query(validateAccountIssuerBackfill, args) + ).resolves.toMatchObject({ + isDone: true, + pending: 0, + alreadyMigrated: 2, + }); + await expect( + t.mutation(backfillAccountIssuers, args) + ).resolves.toMatchObject({ + isDone: true, + migrated: 0, + alreadyMigrated: 2, + }); + + const accounts = await t.run((ctx) => ctx.db.query("account").collect()); + expect(accounts.map(({ issuer }) => issuer).sort()).toEqual([ + "https://login.microsoftonline.com/tenant-a/v2.0", + "https://login.microsoftonline.com/tenant-b/v2.0", + ]); + }); + + it("rejects one Microsoft issuer for accounts from different tenants", async () => { + const t = convexTest(schema, modules); + await t.run(async (ctx) => { + await ctx.db.insert("account", { + issuer: "https://login.microsoftonline.com/tenant-a/v2.0", + accountId: "directory-object-id", + providerId: "microsoft", + userId: "user-1", + createdAt: 1, + updatedAt: 1, + }); + await ctx.db.insert("account", { + issuer: "https://login.microsoftonline.com/tenant-b/v2.0", + accountId: "directory-object-id", + providerId: "microsoft", + userId: "user-2", + createdAt: 1, + updatedAt: 1, + }); + }); + + const args = { + providerIssuers: { + microsoft: "https://login.microsoftonline.com/tenant-a/v2.0", + }, + paginationOpts: { cursor: null, numItems: 10 }, + }; + await expect(t.query(validateAccountIssuerBackfill, args)).rejects.toThrow( + "does not match trusted issuer" + ); + await expect(t.mutation(backfillAccountIssuers, args)).rejects.toThrow( + "does not match trusted issuer" + ); + + const accounts = await t.run((ctx) => ctx.db.query("account").collect()); + expect( + accounts + .map(({ issuer, accountId }) => ({ issuer, accountId })) + .sort((left, right) => left.issuer!.localeCompare(right.issuer!)) + ).toEqual([ + { + issuer: "https://login.microsoftonline.com/tenant-a/v2.0", + accountId: "directory-object-id", + }, + { + issuer: "https://login.microsoftonline.com/tenant-b/v2.0", + accountId: "directory-object-id", + }, + ]); + }); + it("validates and migrates every cursor page", async () => { const t = convexTest(schema, modules); await t.run(async (ctx) => { From 9257a1cff79374aee25645b410f6c996a085b11d Mon Sep 17 00:00:00 2001 From: Stephen Shkeda Date: Fri, 21 Aug 2026 00:59:55 -0400 Subject: [PATCH 07/29] fix: harden account identity enforcement --- src/client/adapter-utils.ts | 10 + src/client/create-api.test.ts | 29 +++ src/client/create-api.ts | 124 +++++++++- src/client/required-fields.test.ts | 268 ++++++++++++++++++++++ src/client/triggers.test.ts | 33 +++ src/component/migrations.test.ts | 27 +++ src/component/migrations.ts | 7 +- src/component/testTriggerHandlers.ts | 23 ++ src/test/adapter-factory/convex-custom.ts | 71 ++++++ 9 files changed, 585 insertions(+), 7 deletions(-) create mode 100644 src/client/create-api.test.ts create mode 100644 src/client/required-fields.test.ts diff --git a/src/client/adapter-utils.ts b/src/client/adapter-utils.ts index d4c857a6..dcdf4b45 100644 --- a/src/client/adapter-utils.ts +++ b/src/client/adapter-utils.ts @@ -97,6 +97,16 @@ export const hasUniqueFields = ( constraint.every((field) => inputFields.has(field)) ); }; +export const touchesUniqueFields = ( + betterAuthSchema: BetterAuthDBSchema, + model: string, + input: Record +) => { + const inputFields = new Set(Object.keys(input)); + return getUniqueConstraints(betterAuthSchema, model).some((constraint) => + constraint.some((field) => inputFields.has(field)) + ); +}; const findIndex = ( schema: SchemaDefinition, diff --git a/src/client/create-api.test.ts b/src/client/create-api.test.ts new file mode 100644 index 00000000..e03370c8 --- /dev/null +++ b/src/client/create-api.test.ts @@ -0,0 +1,29 @@ +import { describe, expect, it } from "vitest"; +import type { BetterAuthDBSchema } from "better-auth/db"; +import { assertRequiredFields } from "./create-api.js"; + +describe("assertRequiredFields", () => { + it("resolves renamed models and stored field names", () => { + const schema = { + account: { + modelName: "customAccount", + fields: { + issuer: { + type: "string", + required: true, + fieldName: "customIssuer", + }, + }, + }, + } satisfies BetterAuthDBSchema; + + expect(() => assertRequiredFields(schema, "customAccount", {})).toThrow( + "Missing required field customAccount.customIssuer" + ); + expect(() => + assertRequiredFields(schema, "customAccount", { + customIssuer: "https://issuer.example.com", + }) + ).not.toThrow(); + }); +}); diff --git a/src/client/create-api.ts b/src/client/create-api.ts index 0cec263c..42d50638 100644 --- a/src/client/create-api.ts +++ b/src/client/create-api.ts @@ -20,8 +20,10 @@ import { listOne, paginate, selectFields, + touchesUniqueFields, } from "./adapter-utils.js"; import { getAuthTables } from "better-auth/db"; +import type { BetterAuthDBSchema } from "better-auth/db"; import type { TableNames } from "../component/_generated/dataModel.js"; import type { BetterAuthOptions } from "better-auth/minimal"; @@ -63,6 +65,66 @@ const whereValidator = ( mode: v.optional(v.union(v.literal("sensitive"), v.literal("insensitive"))), }); +const requiredFieldNames = ( + betterAuthSchema: BetterAuthDBSchema, + model: string +) => { + const table = Object.values(betterAuthSchema).find( + (value) => value.modelName === model + ); + if (!table) { + return []; + } + return Object.entries(table.fields) + .filter(([, field]) => field.required) + .map(([fieldName, field]) => field.fieldName ?? fieldName); +}; + +export const assertRequiredFields = ( + betterAuthSchema: BetterAuthDBSchema, + model: string, + data: Record +) => { + for (const fieldName of requiredFieldNames(betterAuthSchema, model)) { + if (data[fieldName] == null) { + throw new Error(`Missing required field ${model}.${fieldName}`); + } + } +}; + +const assertRequiredUpdateFields = ( + betterAuthSchema: BetterAuthDBSchema, + model: string, + update: Record +) => { + for (const fieldName of requiredFieldNames(betterAuthSchema, model)) { + if ( + Object.prototype.hasOwnProperty.call(update, fieldName) && + update[fieldName] == null + ) { + throw new Error(`Cannot clear required field ${model}.${fieldName}`); + } + } +}; + +const assertRequiredFieldTransitions = ( + betterAuthSchema: BetterAuthDBSchema, + model: string, + before: Record, + after: Record +) => { + for (const fieldName of requiredFieldNames(betterAuthSchema, model)) { + if ( + after[fieldName] == null && + (before[fieldName] != null || + (!Object.prototype.hasOwnProperty.call(before, fieldName) && + Object.prototype.hasOwnProperty.call(after, fieldName))) + ) { + throw new Error(`Trigger cleared required field ${model}.${fieldName}`); + } + } +}; + export const createApi = >( schema: Schema, createAuthOptions: (ctx: any) => BetterAuthOptions @@ -75,6 +137,7 @@ export const createApi = >( update: Record, onUpdateHandle?: string ): Promise => { + assertRequiredUpdateFields(betterAuthSchema, model, update); await checkUniqueFields(ctx, schema, betterAuthSchema, model, update, doc); await ctx.db.patch(model, doc._id as GenericId, update as any); const updatedDoc = await ctx.db.get( @@ -101,6 +164,12 @@ export const createApi = >( `Failed to update ${model} (deleted by onUpdate trigger?)` ); } + assertRequiredFieldTransitions( + betterAuthSchema, + model, + updatedDoc, + triggeredDoc + ); return triggeredDoc as Doc; }; @@ -119,6 +188,11 @@ export const createApi = >( onCreateHandle: v.optional(v.string()), }, handler: async (ctx, args) => { + assertRequiredFields( + betterAuthSchema, + args.input.model, + args.input.data + ); await checkUniqueFields( ctx, schema, @@ -149,6 +223,7 @@ export const createApi = >( `Failed to create ${args.input.model} (deleted by onCreate trigger?)` ); } + assertRequiredFields(betterAuthSchema, args.input.model, updatedDoc); return selectFields(updatedDoc, args.select); } return result; @@ -310,7 +385,12 @@ export const createApi = >( `Attempted to set unique fields in multiple documents in ${args.input.model} with the same value. Fields: ${Object.keys(args.input.update ?? {}).join(", ")}` ); } - await asyncMap(page, async (doc) => { + const updateDoc = async (doc: (typeof page)[number]) => { + assertRequiredUpdateFields( + betterAuthSchema, + args.input.model, + args.input.update ?? {} + ); await checkUniqueFields( ctx, schema, @@ -326,19 +406,51 @@ export const createApi = >( ); if (args.onUpdateHandle) { + const updatedDoc = await ctx.db.get( + args.input.model, + doc._id as GenericId + ); + if (!updatedDoc) { + throw new Error(`Failed to update ${args.input.model}`); + } await ctx.runMutation( args.onUpdateHandle as FunctionHandle<"mutation">, { model: args.input.model, - newDoc: await ctx.db.get( - args.input.model, - doc._id as GenericId - ), + newDoc: updatedDoc, oldDoc: doc, } ); + const triggeredDoc = await ctx.db.get( + args.input.model, + doc._id as GenericId + ); + if (!triggeredDoc) { + throw new Error( + `Failed to update ${args.input.model} (deleted by onUpdate trigger?)` + ); + } + assertRequiredFieldTransitions( + betterAuthSchema, + args.input.model, + updatedDoc, + triggeredDoc + ); } - }); + }; + if ( + touchesUniqueFields( + betterAuthSchema, + args.input.model, + args.input.update ?? {} + ) + ) { + for (const doc of page) { + await updateDoc(doc); + } + } else { + await asyncMap(page, updateDoc); + } } return { ...result, diff --git a/src/client/required-fields.test.ts b/src/client/required-fields.test.ts new file mode 100644 index 00000000..0de2e75e --- /dev/null +++ b/src/client/required-fields.test.ts @@ -0,0 +1,268 @@ +/// + +import { describe, expect, it } from "vitest"; +import { convexTest } from "convex-test"; +import { createFunctionHandle } from "convex/server"; +import type { BetterAuthOptions } from "better-auth"; +import schema from "../component/schema.js"; +import { api, internal } from "../component/_generated/api.js"; +import type { DataModel } from "../component/_generated/dataModel.js"; +import { createClient } from "./index.js"; + +const accountData = () => ({ + issuer: "local:credential", + accountId: "user-1", + providerId: "credential", + userId: "user-1", + password: "password-hash", + createdAt: Date.now(), + updatedAt: Date.now(), +}); + +const createAccount = async ( + t: ReturnType, + onCreateHandle?: string +) => + await t.run(async (ctx) => + ctx.runMutation(api.adapter.create, { + input: { model: "account", data: accountData() }, + onCreateHandle, + }) + ); + +const insertLegacyAccount = async ( + t: ReturnType, + suffix: string +) => + await t.run((ctx) => + ctx.db.insert("account", { + accountId: `legacy-account-${suffix}`, + providerId: "credential", + userId: `legacy-user-${suffix}`, + password: "password-hash", + createdAt: Date.now(), + updatedAt: 100, + }) + ); + +describe("required fields", () => { + it.each(["updateOne", "incrementOne", "updateMany"] as const)( + "rejects null issuer through %s", + async (operation) => { + const t = convexTest(schema, import.meta.glob("../component/**/*.*s")); + await createAccount(t); + + const mutation = t.run(async (ctx) => { + if (operation === "updateOne") { + return await ctx.runMutation(api.adapter.updateOne, { + input: { + model: "account", + update: { issuer: null }, + where: [{ field: "accountId", operator: "eq", value: "user-1" }], + }, + }); + } + if (operation === "incrementOne") { + return await ctx.runMutation(api.adapter.incrementOne, { + input: { + model: "account", + increment: { updatedAt: 1 }, + set: { issuer: null }, + where: [{ field: "accountId", operator: "eq", value: "user-1" }], + }, + }); + } + return await ctx.runMutation(api.adapter.updateMany, { + input: { + model: "account", + update: { issuer: null }, + where: [{ field: "accountId", operator: "eq", value: "user-1" }], + }, + paginationOpts: { cursor: null, numItems: 10 }, + }); + }); + + await expect(mutation).rejects.toThrow( + "Cannot clear required field account.issuer" + ); + const accounts = await t.run((ctx) => ctx.db.query("account").collect()); + expect(accounts).toHaveLength(1); + expect(accounts[0]?.issuer).toBe("local:credential"); + } + ); + + it.each(["updateOne", "incrementOne", "updateMany"] as const)( + "allows an unrelated %s on a legacy account", + async (operation) => { + const t = convexTest(schema, import.meta.glob("../component/**/*.*s")); + await insertLegacyAccount(t, operation); + + await t.run(async (ctx) => { + const where = [ + { + field: "accountId", + operator: "eq" as const, + value: `legacy-account-${operation}`, + }, + ]; + if (operation === "updateOne") { + await ctx.runMutation(api.adapter.updateOne, { + input: { + model: "account", + update: { accessToken: "updated-token" }, + where, + }, + }); + return; + } + if (operation === "incrementOne") { + await ctx.runMutation(api.adapter.incrementOne, { + input: { + model: "account", + increment: { updatedAt: 1 }, + where, + }, + }); + return; + } + await ctx.runMutation(api.adapter.updateMany, { + input: { + model: "account", + update: { scope: "updated-scope" }, + where, + }, + paginationOpts: { cursor: null, numItems: 10 }, + }); + }); + + const accounts = await t.run((ctx) => ctx.db.query("account").collect()); + expect(accounts).toHaveLength(1); + expect(accounts[0]?.issuer).toBeUndefined(); + if (operation === "updateOne") { + expect(accounts[0]?.accessToken).toBe("updated-token"); + } else if (operation === "incrementOne") { + expect(accounts[0]?.updatedAt).toBe(101); + } else { + expect(accounts[0]?.scope).toBe("updated-scope"); + } + } + ); + + it("updates multiple pages containing a legacy account", async () => { + const t = convexTest(schema, import.meta.glob("../component/**/*.*s")); + await t.run(async (ctx) => { + for (let i = 0; i < 401; i += 1) { + await ctx.db.insert("account", { + ...(i === 250 ? {} : { issuer: `local:legacy-${i}` }), + accountId: `legacy-bulk-account-${i}`, + providerId: "legacy-bulk-provider", + userId: `legacy-bulk-user-${i.toString().padStart(3, "0")}`, + createdAt: Date.now(), + updatedAt: Date.now(), + }); + } + }); + const adapter = createClient({ adapter: api.adapter } as any, { + verbose: false, + }).adapter({ + runMutation: t.mutation.bind(t), + runQuery: t.query.bind(t), + } as any)({} as BetterAuthOptions); + + await expect( + adapter.updateMany({ + model: "account", + where: [{ field: "providerId", value: "legacy-bulk-provider" }], + update: { scope: "updated-scope" }, + }) + ).resolves.toBe(401); + + const accounts = await t.run((ctx) => ctx.db.query("account").collect()); + expect(accounts).toHaveLength(401); + expect(accounts.every(({ scope }) => scope === "updated-scope")).toBe(true); + expect(accounts.filter(({ issuer }) => issuer === undefined)).toHaveLength( + 1 + ); + }); + + it("rolls back an onCreate trigger that clears a required field", async () => { + const t = convexTest(schema, import.meta.glob("../component/**/*.*s")); + + await expect( + t.run(async (ctx) => { + const handle = await createFunctionHandle( + internal.testTriggerHandlers.accountOnCreateIssuerClearer + ); + return await ctx.runMutation(api.adapter.create, { + input: { model: "account", data: accountData() }, + onCreateHandle: handle, + }); + }) + ).rejects.toThrow("Missing required field account.issuer"); + expect(await t.run((ctx) => ctx.db.query("account").collect())).toEqual([]); + }); + + it("rolls back an onUpdate trigger that clears a required field", async () => { + const t = convexTest(schema, import.meta.glob("../component/**/*.*s")); + await createAccount(t); + + await expect( + t.run(async (ctx) => { + const handle = await createFunctionHandle( + internal.testTriggerHandlers.accountOnUpdateIssuerClearer + ); + return await ctx.runMutation(api.adapter.updateOne, { + input: { + model: "account", + update: { updatedAt: Date.now() + 1 }, + where: [{ field: "accountId", operator: "eq", value: "user-1" }], + }, + onUpdateHandle: handle, + }); + }) + ).rejects.toThrow("Trigger cleared required field account.issuer"); + + const accounts = await t.run((ctx) => ctx.db.query("account").collect()); + expect(accounts).toHaveLength(1); + expect(accounts[0]?.issuer).toBe("local:credential"); + }); + + it("allows a no-op trigger on a legacy account with a null issuer", async () => { + const t = convexTest(schema, import.meta.glob("../component/**/*.*s")); + await t.run((ctx) => + ctx.db.insert("account", { + issuer: null, + accountId: "legacy-null-account", + providerId: "credential", + userId: "legacy-null-user", + createdAt: Date.now(), + updatedAt: Date.now(), + }) + ); + + await t.run(async (ctx) => { + const handle = await createFunctionHandle( + internal.testTriggerHandlers.accountOnUpdateNoop + ); + await ctx.runMutation(api.adapter.updateOne, { + input: { + model: "account", + update: { scope: "updated-scope" }, + where: [ + { + field: "accountId", + operator: "eq", + value: "legacy-null-account", + }, + ], + }, + onUpdateHandle: handle, + }); + }); + + const accounts = await t.run((ctx) => ctx.db.query("account").collect()); + expect(accounts).toHaveLength(1); + expect(accounts[0]?.issuer).toBeNull(); + expect(accounts[0]?.scope).toBe("updated-scope"); + }); +}); diff --git a/src/client/triggers.test.ts b/src/client/triggers.test.ts index 8ed3ca11..b2489c0d 100644 --- a/src/client/triggers.test.ts +++ b/src/client/triggers.test.ts @@ -92,4 +92,37 @@ describe("trigger result propagation", () => { userAgent: "trigger-ran-on-update", }); }); + + it("api.adapter.updateMany runs onUpdateHandle for every updated doc", async () => { + const t = convexTest(schema, import.meta.glob("../component/**/*.*s")); + const result = await t.run(async (ctx) => { + for (const token of ["bulk-update-token-1", "bulk-update-token-2"]) { + await ctx.runMutation(api.adapter.create, { + input: { + model: "session", + data: { ...baseSessionData(), token }, + }, + }); + } + const handle = await createFunctionHandle( + internal.testTriggerHandlers.sessionOnUpdateUpdater + ); + return await ctx.runMutation(api.adapter.updateMany, { + input: { + model: "session", + update: { userAgent: "set-by-update" }, + where: [{ field: "userId", operator: "eq", value: "user-1" }], + }, + paginationOpts: { cursor: null, numItems: 10 }, + onUpdateHandle: handle, + }); + }); + expect(result.count).toBe(2); + + const sessions = await t.run((ctx) => ctx.db.query("session").collect()); + expect(sessions).toHaveLength(2); + expect( + sessions.every(({ userAgent }) => userAgent === "trigger-ran-on-update") + ).toBe(true); + }); }); diff --git a/src/component/migrations.test.ts b/src/component/migrations.test.ts index 6202ccc8..1997ad16 100644 --- a/src/component/migrations.test.ts +++ b/src/component/migrations.test.ts @@ -75,6 +75,33 @@ describe("backfillAccountIssuers", () => { expect(account?.issuer).toBeUndefined(); }); + it("does not treat inherited properties as provider issuer mappings", async () => { + const t = convexTest(schema, modules); + await t.run((ctx) => + ctx.db.insert("account", { + accountId: "subject", + providerId: "constructor", + userId: "user-1", + createdAt: 1, + updatedAt: 1, + }) + ); + const args = { + providerIssuers: {}, + paginationOpts: { cursor: null, numItems: 10 }, + }; + + await expect(t.query(validateAccountIssuerBackfill, args)).rejects.toThrow( + "Missing trusted issuer mapping" + ); + await expect(t.mutation(backfillAccountIssuers, args)).rejects.toThrow( + "Missing trusted issuer mapping" + ); + + const [account] = await t.run((ctx) => ctx.db.query("account").collect()); + expect(account?.issuer).toBeUndefined(); + }); + it("rejects issuer mappings with surrounding whitespace", async () => { const t = convexTest(schema, modules); await t.run((ctx) => diff --git a/src/component/migrations.ts b/src/component/migrations.ts index 80418a8b..1c6db1c9 100644 --- a/src/component/migrations.ts +++ b/src/component/migrations.ts @@ -61,7 +61,12 @@ const targetAccountIdentity = ( : account.providerId === "siwe" ? "local:siwe" : undefined; - const mappedIssuer = providerIssuers[account.providerId]; + const mappedIssuer = Object.prototype.hasOwnProperty.call( + providerIssuers, + account.providerId + ) + ? providerIssuers[account.providerId] + : undefined; const existingIssuer = account.issuer ?? undefined; if (existingIssuer !== undefined && !existingIssuer.trim()) { diff --git a/src/component/testTriggerHandlers.ts b/src/component/testTriggerHandlers.ts index 4fba0136..5a8b4690 100644 --- a/src/component/testTriggerHandlers.ts +++ b/src/component/testTriggerHandlers.ts @@ -25,3 +25,26 @@ export const sessionOnUpdateUpdater = internalMutationGeneric({ } }, }); + +export const accountOnCreateIssuerClearer = internalMutationGeneric({ + args: { doc: v.any(), model: v.string() }, + handler: async (ctx, args) => { + if (args.model === "account") { + await ctx.db.patch("account", args.doc._id, { issuer: null }); + } + }, +}); + +export const accountOnUpdateIssuerClearer = internalMutationGeneric({ + args: { newDoc: v.any(), oldDoc: v.any(), model: v.string() }, + handler: async (ctx, args) => { + if (args.model === "account") { + await ctx.db.patch("account", args.newDoc._id, { issuer: null }); + } + }, +}); + +export const accountOnUpdateNoop = internalMutationGeneric({ + args: { newDoc: v.any(), oldDoc: v.any(), model: v.string() }, + handler: async () => {}, +}); diff --git a/src/test/adapter-factory/convex-custom.ts b/src/test/adapter-factory/convex-custom.ts index f8584e65..17da16b3 100644 --- a/src/test/adapter-factory/convex-custom.ts +++ b/src/test/adapter-factory/convex-custom.ts @@ -514,6 +514,77 @@ export const convexCustomTestSuite = createTestSuite( ).toMatchObject({ emailVerified: true }); }, + "should roll back partial bulk updates that collide on a compound unique constraint": + async () => { + await adapter.create({ + model: "account", + data: { + issuer: "issuer-a", + accountId: "shared-account", + providerId: "provider-a", + userId: "user-a", + createdAt: Date.now(), + updatedAt: Date.now(), + }, + }); + await adapter.create({ + model: "account", + data: { + issuer: "issuer-b", + accountId: "shared-account", + providerId: "provider-b", + userId: "user-b", + createdAt: Date.now(), + updatedAt: Date.now(), + }, + }); + + await expect( + adapter.updateMany({ + model: "account", + where: [{ field: "accountId", value: "shared-account" }], + update: { issuer: "issuer-c" }, + }), + ).rejects.toThrow( + "account unique constraint issuer+accountId already exists", + ); + + const accounts = await adapter.findMany({ + model: "account", + where: [{ field: "accountId", value: "shared-account" }], + }); + expect(accounts).toHaveLength(2); + expect(accounts).toEqual( + expect.arrayContaining([ + expect.objectContaining({ issuer: "issuer-a" }), + expect.objectContaining({ issuer: "issuer-b" }), + ]), + ); + }, + + "should reject new accounts without the required issuer": async () => { + const account = { + accountId: "subject", + providerId: "custom-provider", + userId: "user-without-issuer", + createdAt: Date.now(), + updatedAt: Date.now(), + }; + + await expect( + adapter.create({ + model: "account", + data: account as any, + }), + ).rejects.toThrow("Missing required field account.issuer"); + await expect( + adapter.create({ + model: "account", + data: { ...account, issuer: null } as any, + }), + ).rejects.toThrow("Missing required field account.issuer"); + }, + "should delete and count each match only once for overlapping OR clauses": async () => { await adapter.create({ From ef66f09a9eeed4c24eb1d46d76eef109b8b3a859 Mon Sep 17 00:00:00 2001 From: Stephen Shkeda Date: Fri, 21 Aug 2026 01:39:27 -0400 Subject: [PATCH 08/29] fix: align credential account indexes --- .../docs/migrations/migrate-to-0-13.mdx | 13 ++--- .../next/convex/betterAuth/generatedSchema.ts | 6 +++ examples/tanstack/convex/betterAuth/schema.ts | 6 +++ src/client/adapter-utils.test.ts | 51 ++++++++++++++++++- src/client/create-schema.test.ts | 42 +++++++++++++++ src/client/create-schema.ts | 8 ++- src/component/schema.test.ts | 13 +++++ src/component/schema.ts | 11 ++-- 8 files changed, 136 insertions(+), 14 deletions(-) create mode 100644 src/component/schema.test.ts diff --git a/docs/content/docs/migrations/migrate-to-0-13.mdx b/docs/content/docs/migrations/migrate-to-0-13.mdx index b243d063..877ec9bc 100644 --- a/docs/content/docs/migrations/migrate-to-0-13.mdx +++ b/docs/content/docs/migrations/migrate-to-0-13.mdx @@ -418,9 +418,7 @@ Before deploying, copy the new definitions into your active schema with these temporary changes: 1. Make `account.issuer` optional. Add the generated `issuer_accountId` index - and retain the existing `accountId_issuer` index until the new index finishes - backfilling. Keep the generated `accountId` index for migration collision - checks. + and keep the generated `accountId` index for migration collision checks. 2. If you used `oidcProvider`, retain `oauthApplication` and merge the legacy and new `oauthAccessToken` and `oauthConsent` fields as optional unions. 3. Add the new OAuth tables without removing legacy records. @@ -435,9 +433,6 @@ If the local deployment used `oidcProvider`, also implement local equivalents of the export and clear functions above. Redact `clientSecret` from the export, save the client metadata, and clear `oauthAccessToken`, `oauthConsent`, and `oauthApplication` before replacing the temporary table definitions with the -final generated definitions. Retain the old `accountId_issuer` index through -this deployment. After `issuer_accountId` is active and the smoke tests pass, -remove the old index in a separate deployment; Convex may require interactive -confirmation when removing an index from a large table. Run Convex code -generation and deploy, then re-register clients and complete the provider smoke -tests before resuming authentication writes. +final generated definitions. Run Convex code generation and deploy, then +re-register clients and complete the provider smoke tests before resuming +authentication writes. diff --git a/examples/next/convex/betterAuth/generatedSchema.ts b/examples/next/convex/betterAuth/generatedSchema.ts index b672cdc4..fdb6901f 100644 --- a/examples/next/convex/betterAuth/generatedSchema.ts +++ b/examples/next/convex/betterAuth/generatedSchema.ts @@ -64,6 +64,12 @@ export const tables = { .index("accountId", ["accountId"]) .index("accountId_providerId", ["accountId", "providerId"]) .index("providerId_userId", ["providerId", "userId"]) + .index("userId_providerId_issuer_accountId", [ + "userId", + "providerId", + "issuer", + "accountId", + ]) .index("userId", ["userId"]), verification: defineTable({ identifier: v.string(), diff --git a/examples/tanstack/convex/betterAuth/schema.ts b/examples/tanstack/convex/betterAuth/schema.ts index 27ce3ac4..ae72bfa7 100644 --- a/examples/tanstack/convex/betterAuth/schema.ts +++ b/examples/tanstack/convex/betterAuth/schema.ts @@ -61,6 +61,12 @@ export const tables = { .index('accountId', ['accountId']) .index('accountId_providerId', ['accountId', 'providerId']) .index('providerId_userId', ['providerId', 'userId']) + .index('userId_providerId_issuer_accountId', [ + 'userId', + 'providerId', + 'issuer', + 'accountId', + ]) .index('userId', ['userId']), verification: defineTable({ identifier: v.string(), diff --git a/src/client/adapter-utils.test.ts b/src/client/adapter-utils.test.ts index 0974e9cb..1e0680f1 100644 --- a/src/client/adapter-utils.test.ts +++ b/src/client/adapter-utils.test.ts @@ -1,6 +1,6 @@ /// -import { describe, expect, it } from "vitest"; +import { describe, expect, it, vi } from "vitest"; import { convexTest } from "convex-test"; import { defineSchema, defineTable } from "convex/server"; import { v } from "convex/values"; @@ -25,9 +25,58 @@ const betterAuthSchema = { }, } satisfies BetterAuthDBSchema; +const credentialSchema = defineSchema({ + account: defineTable({ + userId: v.string(), + providerId: v.string(), + issuer: v.string(), + accountId: v.string(), + }).index("userId_providerId_issuer_accountId", [ + "userId", + "providerId", + "issuer", + "accountId", + ]), +}); + +const credentialBetterAuthSchema = { + account: { + modelName: "account", + fields: { + userId: { type: "string", required: true }, + providerId: { type: "string", required: true }, + issuer: { type: "string", required: true }, + accountId: { type: "string", required: true }, + }, + }, +} satisfies BetterAuthDBSchema; + const modules = import.meta.glob("../component/**/*.*s"); describe("ordered compound indexes", () => { + it("uses an index for Better Auth's credential account lookup", async () => { + const t = convexTest(credentialSchema, modules); + const warn = vi.spyOn(console, "warn").mockImplementation(() => undefined); + + try { + await t.run((ctx) => + paginate(ctx, credentialSchema, credentialBetterAuthSchema, { + model: "account", + where: [ + { field: "userId", value: "user-1" }, + { field: "providerId", value: "credential" }, + { field: "issuer", value: "local:credential" }, + { field: "accountId", value: "user-1" }, + ], + paginationOpts: { cursor: null, numItems: 1 }, + }) + ); + expect(warn).not.toHaveBeenCalled(); + } finally { + warn.mockRestore(); + } + }); + it("pairs equality values with fields in the selected index order", async () => { const t = convexTest(schema, modules); await t.run(async (ctx) => { diff --git a/src/client/create-schema.test.ts b/src/client/create-schema.test.ts index d36be1e1..619acb49 100644 --- a/src/client/create-schema.test.ts +++ b/src/client/create-schema.test.ts @@ -3,6 +3,31 @@ import type { BetterAuthDBSchema } from "better-auth/db"; import { createSchema } from "./create-schema.js"; describe("createSchema", () => { + it("generates the Better Auth credential account lookup index", async () => { + const tables = { + account: { + modelName: "account", + fields: { + userId: { + type: "string", + required: true, + references: { field: "id", model: "user" }, + }, + providerId: { type: "string", required: true }, + issuer: { type: "string", required: true }, + accountId: { type: "string", required: true }, + }, + }, + } satisfies BetterAuthDBSchema; + + const { code } = await createSchema({ tables }); + + expect(code).toContain( + '.index("userId_providerId_issuer_accountId", ["userId","providerId","issuer","accountId"])' + ); + expect(code).toContain('.index("userId", ["userId"])'); + }); + it("generates table-level compound indexes without mutating their order", async () => { const indexFields = ["issuer", "accountId"] as [string, ...string[]]; const tables = { @@ -24,6 +49,23 @@ describe("createSchema", () => { expect(indexFields).toEqual(["issuer", "accountId"]); }); + it("does not add a standalone index covered by a compound prefix", async () => { + const tables = { + user: { + modelName: "user", + fields: { + email: { type: "string", required: true, unique: true }, + name: { type: "string", required: true }, + }, + }, + } satisfies BetterAuthDBSchema; + + const { code } = await createSchema({ tables }); + + expect(code).toContain('.index("email_name", ["email","name"])'); + expect(code).not.toContain('.index("email", ["email"])'); + }); + it("keeps reversed compound indexes distinct", async () => { const tables = { account: { diff --git a/src/client/create-schema.ts b/src/client/create-schema.ts index 2b3d925a..836fea60 100644 --- a/src/client/create-schema.ts +++ b/src/client/create-schema.ts @@ -16,7 +16,13 @@ const resolveCdTarget = ( // Manually add fields to index on for schema generation, // all fields in the schema specialFields are automatically indexed export const indexFields = { - account: ["accountId", ["accountId", "providerId"], ["providerId", "userId"]], + account: [ + "accountId", + ["accountId", "providerId"], + ["providerId", "userId"], + ["userId", "providerId", "issuer", "accountId"], + "userId", + ], rateLimit: ["key"], session: ["expiresAt", ["expiresAt", "userId"]], verification: ["expiresAt", "identifier"], diff --git a/src/component/schema.test.ts b/src/component/schema.test.ts new file mode 100644 index 00000000..df57aa41 --- /dev/null +++ b/src/component/schema.test.ts @@ -0,0 +1,13 @@ +import { describe, expect, it } from "vitest"; +import schema from "./schema.js"; + +describe("staged component schema", () => { + it("keeps only the generated account identity index order", () => { + const indexNames = schema.tables.account[" indexes"]().map( + (index) => index.indexDescriptor + ); + + expect(indexNames).toContain("issuer_accountId"); + expect(indexNames).not.toContain("accountId_issuer"); + }); +}); diff --git a/src/component/schema.ts b/src/component/schema.ts index 4eca1b0d..2a423924 100644 --- a/src/component/schema.ts +++ b/src/component/schema.ts @@ -1,8 +1,8 @@ /** * Generated from Better Auth 1.7, then intentionally staged for the 0.13 * migration. Do not regenerate it without preserving the documented migration - * exceptions: optional account.issuer, the previous accountId_issuer index, - * and the three legacy OAuth provider tables retained below. + * exceptions: optional account.issuer and the three legacy OAuth provider + * tables retained below. * To regenerate the schema, from your project root: * * npx auth generate --output src/component/schema.ts @@ -67,10 +67,15 @@ export const tables = { updatedAt: v.number(), }) .index("issuer_accountId", ["issuer", "accountId"]) - .index("accountId_issuer", ["accountId", "issuer"]) .index("accountId", ["accountId"]) .index("accountId_providerId", ["accountId", "providerId"]) .index("providerId_userId", ["providerId", "userId"]) + .index("userId_providerId_issuer_accountId", [ + "userId", + "providerId", + "issuer", + "accountId", + ]) .index("userId", ["userId"]), verification: defineTable({ identifier: v.string(), From ca807a13de922791f83dd11c64965dc8d36aaa01 Mon Sep 17 00:00:00 2001 From: Stephen Shkeda Date: Fri, 21 Aug 2026 01:42:03 -0400 Subject: [PATCH 09/29] fix: detect cross-domain client capability --- src/plugins/cross-domain/client.test.ts | 11 +++++++- src/plugins/cross-domain/client.ts | 4 +++ src/react/cross-domain.test.ts | 35 +++++++++++++++++++++++-- src/react/cross-domain.ts | 4 +++ 4 files changed, 51 insertions(+), 3 deletions(-) diff --git a/src/plugins/cross-domain/client.test.ts b/src/plugins/cross-domain/client.test.ts index 56dc9261..a0135a5c 100644 --- a/src/plugins/cross-domain/client.test.ts +++ b/src/plugins/cross-domain/client.test.ts @@ -1,5 +1,10 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; -import { crossDomainClient, getCookie, getSetCookie } from "./client.js"; +import { + crossDomainCapability, + crossDomainClient, + getCookie, + getSetCookie, +} from "./client.js"; describe("getSetCookie", () => { it("stores cookies from RFC-1123 Expires headers without splitting the date", () => { @@ -158,6 +163,10 @@ describe("crossDomainClient", () => { return { onSuccess: plugin.fetchPlugins![0].hooks!.onSuccess!, notify }; } + it("exposes an explicit cross-domain capability marker", () => { + expect(getActions().crossDomainCapability).toBe(crossDomainCapability); + }); + describe("getSessionData", () => { it("returns null when storage is empty", () => { const actions = getActions(); diff --git a/src/plugins/cross-domain/client.ts b/src/plugins/cross-domain/client.ts index 381c0783..afe594c4 100644 --- a/src/plugins/cross-domain/client.ts +++ b/src/plugins/cross-domain/client.ts @@ -10,11 +10,14 @@ interface StoredCookie { } type CrossDomainActions = { + crossDomainCapability: typeof crossDomainCapability; getCookie: () => string; updateSession: () => void; getSessionData: () => Record | null; }; +export const crossDomainCapability = () => true; + type CrossDomainClientPlugin = Omit< BetterAuthClientPlugin, "$InferServerPlugin" | "getActions" @@ -93,6 +96,7 @@ export const crossDomainClient = ( getActions(_, $store) { store = $store; return { + crossDomainCapability, /** * Get the stored cookie. * diff --git a/src/react/cross-domain.test.ts b/src/react/cross-domain.test.ts index d39a68f3..2ae9ac09 100644 --- a/src/react/cross-domain.test.ts +++ b/src/react/cross-domain.test.ts @@ -1,4 +1,9 @@ import { describe, expect, it, vi } from "vitest"; +import { createAuthClient } from "better-auth/react"; +import { + crossDomainCapability, + crossDomainClient, +} from "../plugins/cross-domain/client.js"; import { handleCrossDomainCallback } from "./cross-domain.js"; import type { RequiredAuthClient } from "./cross-domain.js"; @@ -12,7 +17,9 @@ const coreClient = (): RequiredAuthClient => ({ describe("handleCrossDomainCallback", () => { it("preserves the token when the client lacks cross-domain support", async () => { - const authClient = coreClient(); + const authClient = createAuthClient({ + baseURL: "https://example.com", + }) as unknown as RequiredAuthClient; const replaceUrl = vi.fn(); await handleCrossDomainCallback( @@ -22,12 +29,36 @@ describe("handleCrossDomainCallback", () => { ); expect(replaceUrl).not.toHaveBeenCalled(); - expect(authClient.getSession).not.toHaveBeenCalled(); + }); + + it("marks real clients only when the cross-domain plugin is installed", () => { + const stored = new Map(); + const core = createAuthClient({ baseURL: "https://example.com" }); + const crossDomain = createAuthClient({ + baseURL: "https://example.com", + plugins: [ + crossDomainClient({ + storage: { + getItem: (key) => stored.get(key) ?? null, + setItem: (key, value) => stored.set(key, value), + }, + }), + ], + }); + + expect( + (core as unknown as { crossDomainCapability?: unknown }) + .crossDomainCapability === crossDomainCapability + ).toBe(false); + expect(crossDomain.crossDomainCapability === crossDomainCapability).toBe( + true + ); }); it("verifies supported callbacks and refreshes the session", async () => { const authClient = { ...coreClient(), + crossDomainCapability, crossDomain: { oneTimeToken: { verify: vi.fn().mockResolvedValue({ diff --git a/src/react/cross-domain.ts b/src/react/cross-domain.ts index da6f84d1..eae3d6da 100644 --- a/src/react/cross-domain.ts +++ b/src/react/cross-domain.ts @@ -1,3 +1,5 @@ +import { crossDomainCapability } from "../plugins/cross-domain/client.js"; + export type RequiredAuthClient = { useSession: () => { data: { session?: { id: string } } | null; @@ -14,6 +16,7 @@ export type RequiredAuthClient = { }; type AuthClientWithCrossDomain = RequiredAuthClient & { + crossDomainCapability: typeof crossDomainCapability; crossDomain: { oneTimeToken: { verify: (args: { token: string }) => Promise<{ @@ -29,6 +32,7 @@ const hasCrossDomain = ( ): authClient is AuthClientWithCrossDomain => { const candidate = authClient as Partial; return ( + candidate.crossDomainCapability === crossDomainCapability && typeof candidate.crossDomain?.oneTimeToken?.verify === "function" && typeof candidate.updateSession === "function" ); From 791953f150f0773958e3fe6cc3bdc8f223476188 Mon Sep 17 00:00:00 2001 From: Stephen Shkeda Date: Fri, 21 Aug 2026 01:43:26 -0400 Subject: [PATCH 10/29] fix: harden account migration validation --- src/component/migrations.test.ts | 117 +++++++++++++++++++++++++++++++ src/component/migrations.ts | 81 +++++++++++++++------ 2 files changed, 177 insertions(+), 21 deletions(-) diff --git a/src/component/migrations.test.ts b/src/component/migrations.test.ts index 1997ad16..187ae80c 100644 --- a/src/component/migrations.test.ts +++ b/src/component/migrations.test.ts @@ -124,6 +124,60 @@ describe("backfillAccountIssuers", () => { ).rejects.toThrow("contains leading or trailing whitespace"); }); + it.each([ + { + accountId: " ", + issuer: "https://issuer.example.com", + label: "blank accountId", + }, + { + accountId: "undefined", + issuer: "https://issuer.example.com", + label: "undefined accountId", + }, + { + accountId: "null", + issuer: "https://issuer.example.com", + label: "null accountId", + }, + { + accountId: "subject", + issuer: "undefined", + label: "undefined issuer", + }, + { + accountId: "subject", + issuer: "null", + label: "null issuer", + }, + ])("rejects a Better Auth-invalid $label", async ({ accountId, issuer }) => { + const t = convexTest(schema, modules); + await t.run((ctx) => + ctx.db.insert("account", { + accountId, + providerId: "custom-provider", + userId: "user-1", + createdAt: 1, + updatedAt: 1, + }) + ); + const args = { + providerIssuers: { "custom-provider": issuer }, + paginationOpts: { cursor: null, numItems: 10 }, + }; + + await expect(t.query(validateAccountIssuerBackfill, args)).rejects.toThrow( + "is not a valid Better Auth account identity" + ); + await expect(t.mutation(backfillAccountIssuers, args)).rejects.toThrow( + "is not a valid Better Auth account identity" + ); + + const [account] = await t.run((ctx) => ctx.db.query("account").collect()); + expect(account).toMatchObject({ accountId }); + expect(account?.issuer).toBeUndefined(); + }); + it("validates supplied external issuer mappings before reading a page", async () => { const t = convexTest(schema, modules); @@ -564,6 +618,69 @@ describe("backfillAccountIssuers", () => { ).resolves.toMatchObject({ isDone: true, deleted: 0 }); }); + it("rejects terminal cursors from other migrations and tables", async () => { + const t = convexTest(schema, modules); + await t.run(async (ctx) => { + await ctx.db.insert("account", { + accountId: "github-subject", + providerId: "github", + userId: "user-1", + createdAt: 1, + updatedAt: 1, + }); + await ctx.db.insert("oauthConsent", { + clientId: "legacy-client", + userId: "user-1", + scopes: "openid", + consentGiven: true, + }); + }); + const providerIssuers = { github: "local:oauth:github" }; + const paginationOpts = { cursor: null, numItems: 10 }; + + const validation = await t.query(validateAccountIssuerBackfill, { + providerIssuers, + paginationOpts, + }); + await expect( + t.mutation(backfillAccountIssuers, { + providerIssuers, + paginationOpts: { + cursor: validation.continueCursor, + numItems: 10, + }, + }) + ).rejects.toThrow( + "Migration cursor is not valid for backfillAccountIssuers" + ); + + const clearedApplications = await t.mutation( + clearLegacyOAuthProviderRecords, + { + table: "oauthApplication", + paginationOpts, + } + ); + await expect( + t.mutation(clearLegacyOAuthProviderRecords, { + table: "oauthConsent", + paginationOpts: { + cursor: clearedApplications.continueCursor, + numItems: 10, + }, + }) + ).rejects.toThrow( + "Migration cursor is not valid for clearLegacyOAuthProviderRecords:oauthConsent" + ); + + const [account] = await t.run((ctx) => ctx.db.query("account").collect()); + const consents = await t.run((ctx) => + ctx.db.query("oauthConsent").collect() + ); + expect(account?.issuer).toBeUndefined(); + expect(consents).toHaveLength(1); + }); + it("accepts, exports, and clears legacy OAuth provider records", async () => { const t = convexTest(schema, modules); await t.run(async (ctx) => { diff --git a/src/component/migrations.ts b/src/component/migrations.ts index 1c6db1c9..d9ea2a07 100644 --- a/src/component/migrations.ts +++ b/src/component/migrations.ts @@ -12,7 +12,38 @@ import schema from "./schema.js"; type ProviderIssuers = Record; -const migrationCompleteCursor = "convex-better-auth:migration-complete"; +const invalidAccountKeyValues = new Set(["undefined", "null"]); + +const assertValidAccountKeyValue = ( + field: "accountId" | "issuer", + value: string, + providerId: string +) => { + if (!value.trim() || invalidAccountKeyValues.has(value)) { + throw new Error( + `Account ${field} for providerId ${providerId} is not a valid Better Auth account identity` + ); + } +}; + +const migrationCompleteCursorPrefix = "convex-better-auth:migration-complete:"; + +const migrationCompleteCursorFor = (operation: string) => + `${migrationCompleteCursorPrefix}${operation}`; + +const isMigrationCompleteCursor = ( + cursor: string | null, + operation: string +) => { + const expected = migrationCompleteCursorFor(operation); + if (cursor === expected) { + return true; + } + if (cursor?.startsWith(migrationCompleteCursorPrefix)) { + throw new Error(`Migration cursor is not valid for ${operation}`); + } + return false; +}; const assertValidPaginationOpts = (paginationOpts: { cursor: string | null; @@ -36,6 +67,11 @@ const assertValidProviderIssuers = (providerIssuers: ProviderIssuers) => { `Trusted issuer mapping for providerId ${providerId} must not be empty` ); } + if (invalidAccountKeyValues.has(issuer)) { + throw new Error( + `Trusted issuer mapping for providerId ${providerId} is not a valid Better Auth account identity` + ); + } if (issuer !== issuer.trim()) { throw new Error( `Trusted issuer mapping for providerId ${providerId} contains leading or trailing whitespace` @@ -104,6 +140,9 @@ const targetAccountIdentity = ( ); } + assertValidAccountKeyValue("accountId", accountId, account.providerId); + assertValidAccountKeyValue("issuer", issuer, account.providerId); + return { accountId, issuer }; }; @@ -187,11 +226,13 @@ export const listLegacyOAuthApplications = query({ args: { paginationOpts: paginationOptsValidator }, returns: paginationResultValidator(legacyOAuthApplicationExportValidator), handler: async (ctx, args) => { + const operation = "listLegacyOAuthApplications"; + const completeCursor = migrationCompleteCursorFor(operation); assertValidPaginationOpts(args.paginationOpts); - if (args.paginationOpts.cursor === migrationCompleteCursor) { + if (isMigrationCompleteCursor(args.paginationOpts.cursor, operation)) { return { page: [], - continueCursor: migrationCompleteCursor, + continueCursor: completeCursor, isDone: true, }; } @@ -200,9 +241,7 @@ export const listLegacyOAuthApplications = query({ .paginate(args.paginationOpts); return { ...result, - continueCursor: result.isDone - ? migrationCompleteCursor - : result.continueCursor, + continueCursor: result.isDone ? completeCursor : result.continueCursor, page: result.page.map(({ clientSecret, ...application }) => ({ ...application, hadClientSecret: Boolean(clientSecret), @@ -222,10 +261,12 @@ export const clearLegacyOAuthProviderRecords = mutation({ }, returns: clearedLegacyRecordsValidator, handler: async (ctx, args) => { + const operation = `clearLegacyOAuthProviderRecords:${args.table}`; + const completeCursor = migrationCompleteCursorFor(operation); assertValidPaginationOpts(args.paginationOpts); - if (args.paginationOpts.cursor === migrationCompleteCursor) { + if (isMigrationCompleteCursor(args.paginationOpts.cursor, operation)) { return { - continueCursor: migrationCompleteCursor, + continueCursor: completeCursor, isDone: true, deleted: 0, }; @@ -246,9 +287,7 @@ export const clearLegacyOAuthProviderRecords = mutation({ await ctx.db.delete(args.table, record._id); } return { - continueCursor: result.isDone - ? migrationCompleteCursor - : result.continueCursor, + continueCursor: result.isDone ? completeCursor : result.continueCursor, isDone: result.isDone, deleted: result.page.length, }; @@ -259,11 +298,13 @@ export const validateAccountIssuerBackfill = query({ args: migrationArgs, returns: accountValidationValidator, handler: async (ctx, args) => { + const operation = "validateAccountIssuerBackfill"; + const completeCursor = migrationCompleteCursorFor(operation); assertValidPaginationOpts(args.paginationOpts); assertValidProviderIssuers(args.providerIssuers); - if (args.paginationOpts.cursor === migrationCompleteCursor) { + if (isMigrationCompleteCursor(args.paginationOpts.cursor, operation)) { return { - continueCursor: migrationCompleteCursor, + continueCursor: completeCursor, isDone: true, pending: 0, alreadyMigrated: 0, @@ -292,9 +333,7 @@ export const validateAccountIssuerBackfill = query({ } return { - continueCursor: result.isDone - ? migrationCompleteCursor - : result.continueCursor, + continueCursor: result.isDone ? completeCursor : result.continueCursor, isDone: result.isDone, pending, alreadyMigrated, @@ -306,11 +345,13 @@ export const backfillAccountIssuers = mutation({ args: migrationArgs, returns: accountBackfillValidator, handler: async (ctx, args) => { + const operation = "backfillAccountIssuers"; + const completeCursor = migrationCompleteCursorFor(operation); assertValidPaginationOpts(args.paginationOpts); assertValidProviderIssuers(args.providerIssuers); - if (args.paginationOpts.cursor === migrationCompleteCursor) { + if (isMigrationCompleteCursor(args.paginationOpts.cursor, operation)) { return { - continueCursor: migrationCompleteCursor, + continueCursor: completeCursor, isDone: true, migrated: 0, alreadyMigrated: 0, @@ -341,9 +382,7 @@ export const backfillAccountIssuers = mutation({ } return { - continueCursor: result.isDone - ? migrationCompleteCursor - : result.continueCursor, + continueCursor: result.isDone ? completeCursor : result.continueCursor, isDone: result.isDone, migrated, alreadyMigrated, From 8fe58f0b2feb54d8803b22ca199f2d76744c420b Mon Sep 17 00:00:00 2001 From: Stephen Shkeda Date: Fri, 21 Aug 2026 01:48:02 -0400 Subject: [PATCH 11/29] fix: guard unique updates across pagination --- src/client/create-api.ts | 34 ++++++-- src/client/update-many-pagination.test.ts | 55 +++++++++++++ src/test/adapter-factory/convex-custom.ts | 95 ++++++++++++++++++++++- 3 files changed, 175 insertions(+), 9 deletions(-) create mode 100644 src/client/update-many-pagination.test.ts diff --git a/src/client/create-api.ts b/src/client/create-api.ts index 42d50638..6600c9dc 100644 --- a/src/client/create-api.ts +++ b/src/client/create-api.ts @@ -363,16 +363,40 @@ export const createApi = >( onUpdateHandle: v.optional(v.string()), }, handler: async (ctx, args) => { + const uniqueFieldsTouched = touchesUniqueFields( + betterAuthSchema, + args.input.model, + args.input.update ?? {} + ); + const firstInWhere = args.input.where?.find( + (where) => where.operator === "in" + ); + const usesAtomicIdSet = firstInWhere?.field === "_id"; + const requestedPageSize = args.paginationOpts.numItems; const { page, ...result } = await paginate( ctx, schema, betterAuthSchema, { ...args.input, - paginationOpts: args.paginationOpts, + paginationOpts: uniqueFieldsTouched + ? { + ...args.paginationOpts, + numItems: requestedPageSize + 1, + } + : args.paginationOpts, } ); if (args.input.update) { + if ( + uniqueFieldsTouched && + !usesAtomicIdSet && + (page.length > requestedPageSize || !result.isDone) + ) { + throw new Error( + `Cannot update unique fields across multiple pages in ${args.input.model}` + ); + } if ( hasUniqueFields( betterAuthSchema, @@ -438,13 +462,7 @@ export const createApi = >( ); } }; - if ( - touchesUniqueFields( - betterAuthSchema, - args.input.model, - args.input.update ?? {} - ) - ) { + if (uniqueFieldsTouched) { for (const doc of page) { await updateDoc(doc); } diff --git a/src/client/update-many-pagination.test.ts b/src/client/update-many-pagination.test.ts new file mode 100644 index 00000000..6b89ad29 --- /dev/null +++ b/src/client/update-many-pagination.test.ts @@ -0,0 +1,55 @@ +/// + +import { describe, expect, it } from "vitest"; +import { convexTest } from "convex-test"; +import schema from "../component/schema.js"; +import { api } from "../component/_generated/api.js"; + +describe("updateMany pagination", () => { + it("does not treat a later ID filter as the atomic ID-set path", async () => { + const t = convexTest(schema, import.meta.glob("../component/**/*.*s")); + const ids = await t.run(async (ctx) => { + const insertedIds = []; + for (let i = 0; i < 300; i += 1) { + insertedIds.push( + await ctx.db.insert("account", { + issuer: `original-${i}`, + accountId: `mixed-filter-account-${i}`, + providerId: "mixed-filter-provider", + userId: `mixed-filter-user-${i}`, + scope: "mixed-filter-scope", + createdAt: Date.now(), + updatedAt: Date.now(), + }) + ); + } + return insertedIds; + }); + + await expect( + t.mutation(api.adapter.updateMany, { + input: { + model: "account", + update: { issuer: "updated-issuer" }, + where: [ + { + field: "scope", + operator: "in", + value: ["mixed-filter-scope"], + }, + { field: "_id", operator: "in", value: ids }, + ], + }, + paginationOpts: { cursor: null, numItems: 200 }, + }) + ).rejects.toThrow( + "Cannot update unique fields across multiple pages in account" + ); + + const accounts = await t.run((ctx) => ctx.db.query("account").collect()); + expect(accounts).toHaveLength(300); + expect( + accounts.every(({ issuer }) => issuer?.startsWith("original-") === true) + ).toBe(true); + }); +}); diff --git a/src/test/adapter-factory/convex-custom.ts b/src/test/adapter-factory/convex-custom.ts index 17da16b3..d9c2db97 100644 --- a/src/test/adapter-factory/convex-custom.ts +++ b/src/test/adapter-factory/convex-custom.ts @@ -514,7 +514,7 @@ export const convexCustomTestSuite = createTestSuite( ).toMatchObject({ emailVerified: true }); }, - "should roll back partial bulk updates that collide on a compound unique constraint": + "should roll back single-page bulk updates that collide on a compound unique constraint": async () => { await adapter.create({ model: "account", @@ -562,6 +562,99 @@ export const convexCustomTestSuite = createTestSuite( ); }, + "should allow a full-page unique update and reject larger updates": async () => { + for (let i = 0; i < 200; i += 1) { + await adapter.create({ + model: "account", + data: { + issuer: `original-${i}`, + accountId: `bulk-account-${i}`, + providerId: "bulk-provider", + userId: `bulk-user-${i}`, + createdAt: Date.now(), + updatedAt: Date.now(), + }, + }); + } + + await expect( + adapter.updateMany({ + model: "account", + where: [{ field: "providerId", value: "bulk-provider" }], + update: { issuer: "full-page-issuer" }, + }), + ).resolves.toBe(200); + await expect( + adapter.findOne({ + model: "account", + where: [{ field: "accountId", value: "bulk-account-0" }], + }), + ).resolves.toMatchObject({ issuer: "full-page-issuer" }); + + await adapter.create({ + model: "account", + data: { + issuer: "overflow-issuer", + accountId: "bulk-overflow", + providerId: "bulk-provider", + userId: "bulk-user-overflow", + createdAt: Date.now(), + updatedAt: Date.now(), + }, + }); + + await expect( + adapter.updateMany({ + model: "account", + where: [{ field: "providerId", value: "bulk-provider" }], + update: { issuer: "updated-issuer" }, + }), + ).rejects.toThrow( + "Cannot update unique fields across multiple pages in account", + ); + + await expect( + adapter.findOne({ + model: "account", + where: [{ field: "accountId", value: "bulk-account-0" }], + }), + ).resolves.toMatchObject({ issuer: "full-page-issuer" }); + }, + + "should allow atomic ID-set unique updates larger than one page": async () => { + for (let i = 0; i < 201; i += 1) { + await adapter.create({ + model: "account", + data: { + issuer: `original-${i}`, + accountId: `or-bulk-account-${i}`, + providerId: i % 2 === 0 ? "or-bulk-a" : "or-bulk-b", + userId: `or-bulk-user-${i}`, + createdAt: Date.now(), + updatedAt: Date.now(), + }, + }); + } + + await expect( + adapter.updateMany({ + model: "account", + where: [ + { field: "providerId", value: "or-bulk-a", connector: "OR" }, + { field: "providerId", value: "or-bulk-b", connector: "OR" }, + ], + update: { issuer: "atomic-id-set-issuer" }, + }), + ).resolves.toBe(201); + + await expect( + adapter.findOne({ + model: "account", + where: [{ field: "accountId", value: "or-bulk-account-0" }], + }), + ).resolves.toMatchObject({ issuer: "atomic-id-set-issuer" }); + }, + "should reject new accounts without the required issuer": async () => { const account = { accountId: "subject", From 6dff06e5ff9aa9788f5111de670c8bd965f5facd Mon Sep 17 00:00:00 2001 From: Stephen Shkeda Date: Fri, 21 Aug 2026 01:49:33 -0400 Subject: [PATCH 12/29] fix: validate Convex JWKS URL configuration --- src/auth-config.test.ts | 33 ++++++++++++++++++++++++++++++++ src/auth-config.ts | 9 +++++++-- src/jwks-url.ts | 7 +++++++ src/plugins/convex/index.test.ts | 25 ++++++++++++++++++++++++ src/plugins/convex/index.ts | 5 ++--- 5 files changed, 74 insertions(+), 5 deletions(-) create mode 100644 src/auth-config.test.ts create mode 100644 src/jwks-url.ts diff --git a/src/auth-config.test.ts b/src/auth-config.test.ts new file mode 100644 index 00000000..c92bfdaf --- /dev/null +++ b/src/auth-config.test.ts @@ -0,0 +1,33 @@ +import { afterEach, beforeEach, describe, expect, it } from "vitest"; +import { getAuthConfigProvider } from "./auth-config.js"; + +describe("getAuthConfigProvider", () => { + const originalConvexSiteUrl = process.env.CONVEX_SITE_URL; + + beforeEach(() => { + process.env.CONVEX_SITE_URL = "https://deployment.convex.site/"; + }); + + afterEach(() => { + if (originalConvexSiteUrl === undefined) { + delete process.env.CONVEX_SITE_URL; + } else { + process.env.CONVEX_SITE_URL = originalConvexSiteUrl; + } + }); + + it.each([ + [undefined, "https://deployment.convex.site/api/auth/convex/jwks"], + ["/", "https://deployment.convex.site/convex/jwks"], + ["/custom/auth/", "https://deployment.convex.site/custom/auth/convex/jwks"], + ["custom/auth", "https://deployment.convex.site/custom/auth/convex/jwks"], + ])("normalizes the %s base path", (basePath, expected) => { + expect(getAuthConfigProvider({ basePath }).jwks).toBe(expected); + }); + + it("reports a missing Convex site URL", () => { + delete process.env.CONVEX_SITE_URL; + + expect(() => getAuthConfigProvider()).toThrow("CONVEX_SITE_URL is not set"); + }); +}); diff --git a/src/auth-config.ts b/src/auth-config.ts index 246974fc..fad168e6 100644 --- a/src/auth-config.ts +++ b/src/auth-config.ts @@ -1,6 +1,7 @@ import type { JwtOptions } from "better-auth/plugins/jwt"; import type { AuthProvider } from "convex/server"; import type { JSONWebKeySet } from "jose"; +import { getJwksUrl } from "./jwks-url.js"; type JwksDoc = { id: string; @@ -69,14 +70,18 @@ export const getAuthConfigProvider = (opts?: { */ jwks?: string; }) => { + const siteUrl = process.env.CONVEX_SITE_URL; + if (!siteUrl) { + throw new Error("CONVEX_SITE_URL is not set"); + } const parsedJwks = opts?.jwks ? JSON.parse(opts.jwks) : undefined; return { type: "customJwt", - issuer: `${process.env.CONVEX_SITE_URL}`, + issuer: siteUrl, applicationID: "convex", algorithm: "RS256", jwks: parsedJwks ? `data:text/plain;charset=utf-8;base64,${btoa(JSON.stringify(createPublicJwks(parsedJwks)))}` - : `${process.env.CONVEX_SITE_URL}${opts?.basePath ?? "/api/auth"}/convex/jwks`, + : getJwksUrl(siteUrl, opts?.basePath), } satisfies AuthProvider; }; diff --git a/src/jwks-url.ts b/src/jwks-url.ts new file mode 100644 index 00000000..b97c522f --- /dev/null +++ b/src/jwks-url.ts @@ -0,0 +1,7 @@ +export const getJwksUrl = (siteUrl: string, basePath: string | undefined) => { + const normalizedSiteUrl = siteUrl.replace(/\/+$/, ""); + const configuredBasePath = basePath ?? "/api/auth"; + const trimmedBasePath = configuredBasePath.replace(/^\/+|\/+$/g, ""); + const normalizedBasePath = trimmedBasePath ? `/${trimmedBasePath}` : ""; + return `${normalizedSiteUrl}${normalizedBasePath}/convex/jwks`; +}; diff --git a/src/plugins/convex/index.test.ts b/src/plugins/convex/index.test.ts index a51c6b0d..802cb18a 100644 --- a/src/plugins/convex/index.test.ts +++ b/src/plugins/convex/index.test.ts @@ -96,4 +96,29 @@ describe("convex plugin OpenID configuration", () => { } } }); + + it.each([ + ["/", "https://deployment.convex.site/convex/jwks"], + ["/custom/auth/", "https://deployment.convex.site/custom/auth/convex/jwks"], + ])("normalizes the %s base path", async (basePath, expected) => { + const originalConvexSiteUrl = process.env.CONVEX_SITE_URL; + process.env.CONVEX_SITE_URL = "https://deployment.convex.site"; + try { + const plugin = convex({ authConfig, options: { basePath } }); + const response = await plugin.endpoints!.getOpenIdConfig!({ + context: {}, + asResponse: false, + returnHeaders: false, + returnStatus: false, + }); + + expect(response).toMatchObject({ jwks_uri: expected }); + } finally { + if (originalConvexSiteUrl === undefined) { + delete process.env.CONVEX_SITE_URL; + } else { + process.env.CONVEX_SITE_URL = originalConvexSiteUrl; + } + } + }); }); diff --git a/src/plugins/convex/index.ts b/src/plugins/convex/index.ts index af18b791..1769d650 100644 --- a/src/plugins/convex/index.ts +++ b/src/plugins/convex/index.ts @@ -11,6 +11,7 @@ import type { JwtOptions, Jwk } from "better-auth/plugins/jwt"; import { omit } from "convex-helpers"; import type { AuthConfig, AuthProvider } from "convex/server"; import { VERSION } from "../../version.js"; +import { getJwksUrl } from "../../jwks-url.js"; export const JWT_COOKIE_NAME = "convex_jwt"; @@ -371,11 +372,9 @@ export const convex = (opts: { if (!issuer) { throw new Error("CONVEX_SITE_URL is not set"); } - const basePath = opts.options?.basePath ?? "/api/auth"; - const baseURL = `${issuer}${basePath === "/" ? "" : basePath}`; return { issuer, - jwks_uri: `${baseURL}/convex/jwks`, + jwks_uri: getJwksUrl(issuer, opts.options?.basePath), id_token_signing_alg_values_supported: [getJwksAlg(providerConfig)], claims_supported: [ "sub", From 21410bf6a04784863c3ec6a3d7cf614548f7a640 Mon Sep 17 00:00:00 2001 From: Stephen Shkeda Date: Fri, 21 Aug 2026 02:50:06 -0400 Subject: [PATCH 13/29] docs: clarify Local Install migration indexes --- docs/content/docs/migrations/migrate-to-0-13.mdx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/content/docs/migrations/migrate-to-0-13.mdx b/docs/content/docs/migrations/migrate-to-0-13.mdx index 877ec9bc..47ba345b 100644 --- a/docs/content/docs/migrations/migrate-to-0-13.mdx +++ b/docs/content/docs/migrations/migrate-to-0-13.mdx @@ -418,7 +418,8 @@ Before deploying, copy the new definitions into your active schema with these temporary changes: 1. Make `account.issuer` optional. Add the generated `issuer_accountId` index - and keep the generated `accountId` index for migration collision checks. + and keep the generated `accountId` and `providerId_userId` indexes for + migration collision checks. 2. If you used `oidcProvider`, retain `oauthApplication` and merge the legacy and new `oauthAccessToken` and `oauthConsent` fields as optional unions. 3. Add the new OAuth tables without removing legacy records. From 6c31eb61d08e591e6e69dec58150266f2bed40ad Mon Sep 17 00:00:00 2001 From: Stephen Shkeda Date: Fri, 21 Aug 2026 04:34:25 -0400 Subject: [PATCH 14/29] fix: validate unique constraints after triggers --- src/client/create-api.ts | 14 ++ src/client/triggers.test.ts | 192 +++++++++++++++++++++++++++ src/component/testTriggerHandlers.ts | 22 +++ 3 files changed, 228 insertions(+) diff --git a/src/client/create-api.ts b/src/client/create-api.ts index 6600c9dc..a1083dd6 100644 --- a/src/client/create-api.ts +++ b/src/client/create-api.ts @@ -130,6 +130,13 @@ export const createApi = >( createAuthOptions: (ctx: any) => BetterAuthOptions ) => { const betterAuthSchema = getAuthTables(createAuthOptions({} as any)); + const checkPersistedUniqueFields = async ( + ctx: GenericMutationCtx, + model: string, + doc: Record + ) => { + await checkUniqueFields(ctx, schema, betterAuthSchema, model, doc, doc); + }; const applySingleUpdate = async >( ctx: GenericMutationCtx, model: TableNames, @@ -170,6 +177,7 @@ export const createApi = >( updatedDoc, triggeredDoc ); + await checkPersistedUniqueFields(ctx, model, triggeredDoc); return triggeredDoc as Doc; }; @@ -224,6 +232,7 @@ export const createApi = >( ); } assertRequiredFields(betterAuthSchema, args.input.model, updatedDoc); + await checkPersistedUniqueFields(ctx, args.input.model, updatedDoc); return selectFields(updatedDoc, args.select); } return result; @@ -460,6 +469,11 @@ export const createApi = >( updatedDoc, triggeredDoc ); + await checkPersistedUniqueFields( + ctx, + args.input.model, + triggeredDoc + ); } }; if (uniqueFieldsTouched) { diff --git a/src/client/triggers.test.ts b/src/client/triggers.test.ts index b2489c0d..6b7ad078 100644 --- a/src/client/triggers.test.ts +++ b/src/client/triggers.test.ts @@ -14,6 +14,25 @@ const baseSessionData = () => ({ userAgent: "original", }); +const accountData = ({ + issuer, + accountId, + providerId, + userId, +}: { + issuer: string; + accountId: string; + providerId: string; + userId: string; +}) => ({ + issuer, + accountId, + providerId, + userId, + createdAt: Date.now(), + updatedAt: Date.now(), +}); + describe("trigger result propagation", () => { it("api.adapter.create returns the doc reflecting onCreateHandle writes", async () => { const t = convexTest(schema, import.meta.glob("../component/**/*.*s")); @@ -126,3 +145,176 @@ describe("trigger result propagation", () => { ).toBe(true); }); }); + +describe("trigger unique constraint enforcement", () => { + it("rolls back an onCreate trigger that creates an account identity collision", async () => { + const t = convexTest(schema, import.meta.glob("../component/**/*.*s")); + await t.run((ctx) => + ctx.runMutation(api.adapter.create, { + input: { + model: "account", + data: accountData({ + issuer: "collision-issuer", + accountId: "collision-account", + providerId: "existing-provider", + userId: "existing-user", + }), + }, + }) + ); + + await expect( + t.run(async (ctx) => { + const handle = await createFunctionHandle( + internal.testTriggerHandlers.accountOnCreateIssuerCollider + ); + return await ctx.runMutation(api.adapter.create, { + input: { + model: "account", + data: accountData({ + issuer: "safe-issuer", + accountId: "collision-account", + providerId: "candidate-provider", + userId: "candidate-user", + }), + }, + onCreateHandle: handle, + }); + }) + ).rejects.toThrow( + "account unique constraint issuer+accountId already exists" + ); + + const accounts = await t.run((ctx) => ctx.db.query("account").collect()); + expect(accounts).toHaveLength(1); + expect(accounts[0]).toMatchObject({ + issuer: "collision-issuer", + accountId: "collision-account", + providerId: "existing-provider", + }); + }); + + it("rolls back an onUpdate trigger that creates an account identity collision", async () => { + const t = convexTest(schema, import.meta.glob("../component/**/*.*s")); + await t.run(async (ctx) => { + for (const data of [ + accountData({ + issuer: "collision-issuer", + accountId: "collision-account", + providerId: "existing-provider", + userId: "existing-user", + }), + accountData({ + issuer: "collision-issuer", + accountId: "safe-account", + providerId: "candidate-provider", + userId: "candidate-user", + }), + ]) { + await ctx.runMutation(api.adapter.create, { + input: { model: "account", data }, + }); + } + }); + + await expect( + t.run(async (ctx) => { + const handle = await createFunctionHandle( + internal.testTriggerHandlers.accountOnUpdateAccountIdCollider + ); + return await ctx.runMutation(api.adapter.updateOne, { + input: { + model: "account", + update: { scope: "updated-scope" }, + where: [ + { + field: "providerId", + operator: "eq", + value: "candidate-provider", + }, + ], + }, + onUpdateHandle: handle, + }); + }) + ).rejects.toThrow( + "account unique constraint issuer+accountId already exists" + ); + + const candidate = await t.run((ctx) => + ctx.db + .query("account") + .withIndex("providerId_userId", (q) => + q + .eq("providerId", "candidate-provider") + .eq("userId", "candidate-user") + ) + .unique() + ); + expect(candidate).toMatchObject({ + accountId: "safe-account", + issuer: "collision-issuer", + }); + expect(candidate?.scope).toBeUndefined(); + }); + + it("rolls back updateMany when a trigger creates an account identity collision", async () => { + const t = convexTest(schema, import.meta.glob("../component/**/*.*s")); + await t.run(async (ctx) => { + for (const data of [ + accountData({ + issuer: "collision-issuer", + accountId: "collision-account", + providerId: "existing-provider", + userId: "existing-user", + }), + accountData({ + issuer: "collision-issuer", + accountId: "safe-account", + providerId: "candidate-provider", + userId: "candidate-user", + }), + ]) { + await ctx.runMutation(api.adapter.create, { + input: { model: "account", data }, + }); + } + }); + + await expect( + t.run(async (ctx) => { + const handle = await createFunctionHandle( + internal.testTriggerHandlers.accountOnUpdateAccountIdCollider + ); + return await ctx.runMutation(api.adapter.updateMany, { + input: { + model: "account", + update: { scope: "updated-scope" }, + where: [ + { + field: "providerId", + operator: "eq", + value: "candidate-provider", + }, + ], + }, + paginationOpts: { cursor: null, numItems: 10 }, + onUpdateHandle: handle, + }); + }) + ).rejects.toThrow( + "account unique constraint issuer+accountId already exists" + ); + + const accounts = await t.run((ctx) => ctx.db.query("account").collect()); + expect(accounts).toHaveLength(2); + const candidate = accounts.find( + ({ providerId }) => providerId === "candidate-provider" + ); + expect(candidate).toMatchObject({ + accountId: "safe-account", + issuer: "collision-issuer", + }); + expect(candidate?.scope).toBeUndefined(); + }); +}); diff --git a/src/component/testTriggerHandlers.ts b/src/component/testTriggerHandlers.ts index 5a8b4690..b6ab9147 100644 --- a/src/component/testTriggerHandlers.ts +++ b/src/component/testTriggerHandlers.ts @@ -48,3 +48,25 @@ export const accountOnUpdateNoop = internalMutationGeneric({ args: { newDoc: v.any(), oldDoc: v.any(), model: v.string() }, handler: async () => {}, }); + +export const accountOnCreateIssuerCollider = internalMutationGeneric({ + args: { doc: v.any(), model: v.string() }, + handler: async (ctx, args) => { + if (args.model === "account") { + await ctx.db.patch("account", args.doc._id, { + issuer: "collision-issuer", + }); + } + }, +}); + +export const accountOnUpdateAccountIdCollider = internalMutationGeneric({ + args: { newDoc: v.any(), oldDoc: v.any(), model: v.string() }, + handler: async (ctx, args) => { + if (args.model === "account") { + await ctx.db.patch("account", args.newDoc._id, { + accountId: "collision-account", + }); + } + }, +}); From 1c60ebac3631a55dbbc1da001a71d280c2e277e6 Mon Sep 17 00:00:00 2001 From: Stephen Shkeda Date: Fri, 21 Aug 2026 04:35:16 -0400 Subject: [PATCH 15/29] fix: validate Convex site URL format --- src/auth-config.test.ts | 22 ++++++++++++++++++++++ src/auth-config.ts | 3 ++- src/jwks-url.ts | 19 +++++++++++++++++++ 3 files changed, 43 insertions(+), 1 deletion(-) diff --git a/src/auth-config.test.ts b/src/auth-config.test.ts index c92bfdaf..74f70794 100644 --- a/src/auth-config.test.ts +++ b/src/auth-config.test.ts @@ -30,4 +30,26 @@ describe("getAuthConfigProvider", () => { expect(() => getAuthConfigProvider()).toThrow("CONVEX_SITE_URL is not set"); }); + + it.each(["deployment.convex.site", "ftp://deployment.convex.site"])( + "rejects the invalid Convex site URL %s", + (siteUrl) => { + process.env.CONVEX_SITE_URL = siteUrl; + + expect(() => getAuthConfigProvider()).toThrow( + "CONVEX_SITE_URL must be a valid HTTP or HTTPS URL" + ); + expect(() => getAuthConfigProvider({ jwks: "[]" })).toThrow( + "CONVEX_SITE_URL must be a valid HTTP or HTTPS URL" + ); + } + ); + + it("accepts an HTTP URL for a local Convex deployment", () => { + process.env.CONVEX_SITE_URL = "http://127.0.0.1:3211"; + + expect(getAuthConfigProvider().jwks).toBe( + "http://127.0.0.1:3211/api/auth/convex/jwks" + ); + }); }); diff --git a/src/auth-config.ts b/src/auth-config.ts index fad168e6..b7ecae36 100644 --- a/src/auth-config.ts +++ b/src/auth-config.ts @@ -1,7 +1,7 @@ import type { JwtOptions } from "better-auth/plugins/jwt"; import type { AuthProvider } from "convex/server"; import type { JSONWebKeySet } from "jose"; -import { getJwksUrl } from "./jwks-url.js"; +import { getJwksUrl, validateConvexSiteUrl } from "./jwks-url.js"; type JwksDoc = { id: string; @@ -74,6 +74,7 @@ export const getAuthConfigProvider = (opts?: { if (!siteUrl) { throw new Error("CONVEX_SITE_URL is not set"); } + validateConvexSiteUrl(siteUrl); const parsedJwks = opts?.jwks ? JSON.parse(opts.jwks) : undefined; return { type: "customJwt", diff --git a/src/jwks-url.ts b/src/jwks-url.ts index b97c522f..e6802370 100644 --- a/src/jwks-url.ts +++ b/src/jwks-url.ts @@ -1,4 +1,23 @@ +export const validateConvexSiteUrl = (siteUrl: string) => { + let parsedSiteUrl: URL; + try { + parsedSiteUrl = new URL(siteUrl); + } catch { + throw new Error("CONVEX_SITE_URL must be a valid HTTP or HTTPS URL"); + } + + if ( + parsedSiteUrl.protocol !== "http:" && + parsedSiteUrl.protocol !== "https:" + ) { + throw new Error("CONVEX_SITE_URL must be a valid HTTP or HTTPS URL"); + } + + return siteUrl; +}; + export const getJwksUrl = (siteUrl: string, basePath: string | undefined) => { + validateConvexSiteUrl(siteUrl); const normalizedSiteUrl = siteUrl.replace(/\/+$/, ""); const configuredBasePath = basePath ?? "/api/auth"; const trimmedBasePath = configuredBasePath.replace(/^\/+|\/+$/g, ""); From 64196d7b9fd93ce43cdd363bb3fce4cfe83e2a57 Mon Sep 17 00:00:00 2001 From: Stephen Shkeda Date: Fri, 21 Aug 2026 04:35:45 -0400 Subject: [PATCH 16/29] docs: list required Local Install account indexes --- docs/content/docs/migrations/migrate-to-0-13.mdx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/content/docs/migrations/migrate-to-0-13.mdx b/docs/content/docs/migrations/migrate-to-0-13.mdx index 47ba345b..adaa191b 100644 --- a/docs/content/docs/migrations/migrate-to-0-13.mdx +++ b/docs/content/docs/migrations/migrate-to-0-13.mdx @@ -417,9 +417,9 @@ npx auth generate --output generatedSchema.next.ts Before deploying, copy the new definitions into your active schema with these temporary changes: -1. Make `account.issuer` optional. Add the generated `issuer_accountId` index - and keep the generated `accountId` and `providerId_userId` indexes for - migration collision checks. +1. Make `account.issuer` optional. Add the generated `issuer_accountId` and + `userId_providerId_issuer_accountId` indexes. Keep the generated `accountId` + and `providerId_userId` indexes for migration collision checks. 2. If you used `oidcProvider`, retain `oauthApplication` and merge the legacy and new `oauthAccessToken` and `oauthConsent` fields as optional unions. 3. Add the new OAuth tables without removing legacy records. From 4e8f35a2a944f7a7090037097245d23e8475dad3 Mon Sep 17 00:00:00 2001 From: Stephen Shkeda Date: Fri, 21 Aug 2026 09:20:12 -0400 Subject: [PATCH 17/29] fix: reject URL components in Convex site URL --- src/auth-config.test.ts | 11 +++++++++++ src/jwks-url.ts | 5 +++++ 2 files changed, 16 insertions(+) diff --git a/src/auth-config.test.ts b/src/auth-config.test.ts index 74f70794..a7725bc5 100644 --- a/src/auth-config.test.ts +++ b/src/auth-config.test.ts @@ -45,6 +45,17 @@ describe("getAuthConfigProvider", () => { } ); + it.each([ + "https://deployment.convex.site?region=us", + "https://deployment.convex.site#fragment", + ])("rejects URL components in the Convex site URL %s", (siteUrl) => { + process.env.CONVEX_SITE_URL = siteUrl; + + expect(() => getAuthConfigProvider()).toThrow( + "CONVEX_SITE_URL must not include a query string or fragment" + ); + }); + it("accepts an HTTP URL for a local Convex deployment", () => { process.env.CONVEX_SITE_URL = "http://127.0.0.1:3211"; diff --git a/src/jwks-url.ts b/src/jwks-url.ts index e6802370..fd080f99 100644 --- a/src/jwks-url.ts +++ b/src/jwks-url.ts @@ -12,6 +12,11 @@ export const validateConvexSiteUrl = (siteUrl: string) => { ) { throw new Error("CONVEX_SITE_URL must be a valid HTTP or HTTPS URL"); } + if (parsedSiteUrl.search || parsedSiteUrl.hash) { + throw new Error( + "CONVEX_SITE_URL must not include a query string or fragment" + ); + } return siteUrl; }; From 7919b6cac2d32e099994ff2a6feea6171787ce2c Mon Sep 17 00:00:00 2001 From: Ian Macartney <366683+ianmacartney@users.noreply.github.com> Date: Fri, 9 Oct 2026 18:00:58 -0700 Subject: [PATCH 18/29] Return ComponentApi from the test register function (#447) This lets convex-test's defineTestApp infer the component's API when the test module is passed directly in its components map. --- src/test.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/test.ts b/src/test.ts index 14542e2e..8e8e3e36 100644 --- a/src/test.ts +++ b/src/test.ts @@ -1,6 +1,8 @@ /// import type { TestConvex } from "convex-test"; import type { GenericSchema, SchemaDefinition } from "convex/server"; +import { componentsGeneric } from "convex/server"; +import type { ComponentApi } from "./component/_generated/component.js"; import schema from "./component/schema.js"; const modules = import.meta.glob("./component/**/*.ts"); @@ -8,11 +10,14 @@ const modules = import.meta.glob("./component/**/*.ts"); * Register the component with the test convex instance. * @param t - The test convex instance, e.g. from calling `convexTest`. * @param name - The name of the component, as registered in convex.config.ts. + * @returns a component api to test via ctx.runMutation or for thick client + * usage. Also provides types for convex-test's defineTestApp. */ export function register( t: TestConvex>, name: string = "betterAuth" ) { t.registerComponent(name, schema, modules); + return componentsGeneric()[name] as unknown as ComponentApi; } export default { register, schema, modules }; From e3d4815bf91cb215286e5a2af87dc845b86c6194 Mon Sep 17 00:00:00 2001 From: Stephen Shkeda Date: Mon, 12 Oct 2026 21:34:21 -0400 Subject: [PATCH 19/29] Update Better Auth compatibility and migration to 1.7.7 Raise the supported baseline to >=1.7.7 <1.8 and strip modelKey metadata before calling Convex component functions. Remove the obsolete required-issuer migration while retaining optional legacy issuer data and provider export tools. Update generated schemas, examples and the migration guide. Exercise required fields and compound constraints through an explicit custom test profile. --- docs/content/docs/framework-guides/expo.mdx | 2 +- docs/content/docs/framework-guides/next.mdx | 2 +- docs/content/docs/framework-guides/react.mdx | 2 +- .../docs/framework-guides/sveltekit.mdx | 2 +- .../docs/framework-guides/tanstack-start.mdx | 2 +- .../docs/migrations/migrate-to-0-13.mdx | 448 +-- .../convex/betterAuth/_generated/component.ts | 9 - .../next/convex/betterAuth/generatedSchema.ts | 5 +- examples/next/package-lock.json | 10 +- examples/react/package-lock.json | 10 +- .../convex/betterAuth/_generated/component.ts | 9 - examples/tanstack/convex/betterAuth/schema.ts | 5 +- examples/tanstack/package-lock.json | 62 +- package-lock.json | 134 +- package.json | 10 +- src/client/adapter-compatibility.test.ts | 96 + src/client/adapter.test.ts | 24 +- src/client/adapter.ts | 30 +- src/client/create-schema.test.ts | 3 +- src/client/create-schema.ts | 2 +- src/client/required-fields.test.ts | 123 +- src/client/triggers.test.ts | 188 +- src/client/update-many-pagination.test.ts | 4 +- src/component/_generated/api.ts | 2 + src/component/_generated/component.ts | 2427 ++++++++++++++++- src/component/migrations.test.ts | 586 +--- src/component/migrations.ts | 266 -- src/component/schema.test.ts | 16 +- src/component/schema.ts | 12 +- .../testProfiles/adapterConstraints.ts | 14 + .../testProfiles/auth-options.constraints.ts | 20 + .../testProfiles/schema.constraints.ts | 14 + .../schema.profile-plugin-table.ts | 3 +- src/plugins/cross-domain/index.test.ts | 4 +- src/test/adapter-factory/convex-custom.ts | 3 +- 35 files changed, 2974 insertions(+), 1575 deletions(-) create mode 100644 src/client/adapter-compatibility.test.ts create mode 100644 src/component/testProfiles/adapterConstraints.ts create mode 100644 src/component/testProfiles/auth-options.constraints.ts create mode 100644 src/component/testProfiles/schema.constraints.ts diff --git a/docs/content/docs/framework-guides/expo.mdx b/docs/content/docs/framework-guides/expo.mdx index 41c692fb..446f2146 100644 --- a/docs/content/docs/framework-guides/expo.mdx +++ b/docs/content/docs/framework-guides/expo.mdx @@ -17,7 +17,7 @@ description: Install and configure Convex + Better Auth for Expo. ```npm npm install convex@latest @convex-dev/better-auth - npm install better-auth@~1.7.1 @better-auth/expo@~1.7.1 + npm install better-auth@~1.7.7 @better-auth/expo@~1.7.7 ``` diff --git a/docs/content/docs/framework-guides/next.mdx b/docs/content/docs/framework-guides/next.mdx index 92e071bf..c434d0fa 100644 --- a/docs/content/docs/framework-guides/next.mdx +++ b/docs/content/docs/framework-guides/next.mdx @@ -22,7 +22,7 @@ description: Install and configure Convex + Better Auth for Next.js. ```npm npm install convex@latest @convex-dev/better-auth - npm install better-auth@~1.7.1 + npm install better-auth@~1.7.7 ``` diff --git a/docs/content/docs/framework-guides/react.mdx b/docs/content/docs/framework-guides/react.mdx index 6bde2c0e..76908b30 100644 --- a/docs/content/docs/framework-guides/react.mdx +++ b/docs/content/docs/framework-guides/react.mdx @@ -22,7 +22,7 @@ description: Install and configure Convex + Better Auth for React. ```npm npm install convex@latest @convex-dev/better-auth - npm install better-auth@~1.7.1 + npm install better-auth@~1.7.7 ``` diff --git a/docs/content/docs/framework-guides/sveltekit.mdx b/docs/content/docs/framework-guides/sveltekit.mdx index ab684188..f1742d0e 100644 --- a/docs/content/docs/framework-guides/sveltekit.mdx +++ b/docs/content/docs/framework-guides/sveltekit.mdx @@ -99,7 +99,7 @@ npm install convex @mmailaender/convex-svelte ```npm npm install @convex-dev/better-auth @mmailaender/convex-better-auth-svelte - npm install better-auth@~1.7.1 + npm install better-auth@~1.7.7 ``` diff --git a/docs/content/docs/framework-guides/tanstack-start.mdx b/docs/content/docs/framework-guides/tanstack-start.mdx index f34e93fd..782ec0e5 100644 --- a/docs/content/docs/framework-guides/tanstack-start.mdx +++ b/docs/content/docs/framework-guides/tanstack-start.mdx @@ -23,7 +23,7 @@ description: Install and configure Convex + Better Auth for TanStack Start. ```npm npm install convex@latest @convex-dev/better-auth - npm install better-auth@~1.7.1 + npm install better-auth@~1.7.7 npm install @types/node --save-dev ``` diff --git a/docs/content/docs/migrations/migrate-to-0-13.mdx b/docs/content/docs/migrations/migrate-to-0-13.mdx index adaa191b..d56dbb4b 100644 --- a/docs/content/docs/migrations/migrate-to-0-13.mdx +++ b/docs/content/docs/migrations/migrate-to-0-13.mdx @@ -3,294 +3,130 @@ title: Migrate to 0.13 description: Migrate to @convex-dev/better-auth@0.13 --- -Version 0.13 supports Better Auth 1.7. Better Auth 1.7 adds an issuer-scoped -account identity, requires atomic adapter operations, moves the OIDC provider to -its own package, and changes the Generic OAuth client API. The bundled component -does not include the schema-changing replacement provider. If your application -acts as an OAuth or OIDC provider, switch to [Local Install](/local-install). - -Review Better Auth's -[complete 1.7 upgrade guide](https://www.better-auth.com/docs/guides/1-7-upgrade-guide) -alongside this component-specific migration. - - - Existing applications must backfill account issuers. Use a maintenance window, - stop authentication writes, and back up your Better Auth data before - upgrading. - - -## Before upgrading - -Inventory every distinct `providerId` in the `account` table and match it to the -`accountIssuer` Better Auth 1.7 will use for that exact provider configuration. -Do not infer an issuer from an email address, profile field, or unverified -request value. - -| Account type | Issuer | -| ------------------------------------- | ------------------------------------------ | -| Email and password | `local:credential` | -| SIWE | `local:siwe` | -| OAuth provider with a verified issuer | The exact issuer published by the provider | -| OAuth provider without an issuer | `local:oauth:` | - -The component migration handles `credential` and `siwe` automatically. Pass an -explicit mapping for every other legacy provider whose rows all use the same -issuer. If one `providerId` can contain rows from multiple issuers, populate -each row's trusted issuer before running the component migration and omit that -provider from the mapping. For an OAuth provider without an `accountIssuer`, use -Better Auth's helper rather than constructing the value by hand: - -```ts -import { createOAuthAccountIssuer } from "better-auth/db"; - -createOAuthAccountIssuer("github"); // local:oauth:github -createOAuthAccountIssuer("team/github"); // local:oauth:team%2Fgithub -``` - -Providers with an explicit or discovered `accountIssuer` must use that exact -verified value. For example, Better Auth's built-in Google provider uses -`https://accounts.google.com`, while its built-in GitHub provider uses the -synthetic `local:oauth:github` value. Existing rows already written by Better -Auth 1.7 retain their stored issuer and do not need a mapping on a rerun. - - - Better Auth 1.7 changes Microsoft identities from the app-specific `sub` claim - to the stable directory `oid` claim. Before running this component migration, - populate both the trusted `issuer` and the `oid`-based `accountId` on every - `microsoft` and `microsoft-entra-id` row using verified ID tokens or a trusted - Microsoft Entra export. The old account row cannot safely supply either value - by itself. - -The built-in `microsoft` provider derives the issuer from each verified token, -so accounts from different tenants can have different issuers under the same -`providerId`. Do not add one `microsoft` entry to `providerIssuers` unless every -row uses that exact issuer. After populating per-row issuers, omit `microsoft` -from the mapping. See Better Auth's upgrade guide for the full provider-specific -procedure. - - - -Also audit custom OAuth providers whose immutable identifier is not their -default `sub` or `id`; configure Better Auth's `accountSubject` and migrate the -stored `accountId` from trusted provider data before the backfill. - -### If your app is an OAuth or OIDC provider - -Better Auth replaced `oidcProvider` with `@better-auth/oauth-provider` and -changed its storage model. The replacement is not included in the bundled -component. Complete the account backfill on the staged bundled component first, -then: - -1. Export every legacy `oauthApplication` client registration. The migration - query described below redacts client secrets; confidential clients receive - new secrets when re-registered. -2. Clear the legacy `oauthAccessToken`, `oauthConsent`, and `oauthApplication` - tables while the bundled migration functions are still available. -3. Convert the existing `betterAuth` component mount to Local Install and deploy - the generated Better Auth 1.7 schema with `@better-auth/oauth-provider`. -4. Re-register the saved clients in the Local Install `oauthClient` table. Map - `redirectUrls` to `redirectUris`, convert the legacy `metadata` string to - JSON, and review grant types and the token endpoint authentication method. - -Legacy access and refresh tokens cannot be reused with the new provider. - -## Upgrade and backfill accounts +## Upgrade
-### Start the maintenance window - -Back up the Better Auth component data, disable the Better Auth HTTP routes, and -pause every background job or admin function that can create or update an -`account`. Leave writes stopped through the validation and backfill steps. - -If a 1.7 sign-in reaches an un-migrated account, the new `(issuer, accountId)` -lookup cannot find it and may create a duplicate account. A read-only UI is not -enough if server jobs can still write authentication data. - -
- -
- ### Install dependencies +Update Better Auth and the component. Better Auth must be on `1.7.7` or later in +the `1.7.x` line. Update any installed `@better-auth/*` packages together. + ```npm npm install @convex-dev/better-auth@^0.13.0 -npm install better-auth@~1.7.1 -``` - -
- -
- -### Deploy the staged component schema - -Run your normal Convex development or deploy command. The bundled component -temporarily accepts accounts without `issuer` so the backfill can run, while -Better Auth 1.7 supplies `issuer` for every new account. It retains the legacy -OAuth application, token, and consent tables so identity-provider deployments -can export and clear their old component data before moving to Local Install. It -does not add the replacement provider's schema to the bundled component. - -Do not restore authentication writes after this deploy. - -
- -
- -### Add validation and migration wrappers - -Create temporary internal functions in your application. Replace the example -mapping with the issuers verified from your Better Auth provider configuration. - -```ts title="convex/migrations.ts" -import { createOAuthAccountIssuer } from "better-auth/db"; -import { internalMutation, internalQuery } from "./_generated/server"; -import { components } from "./_generated/api"; -import { v } from "convex/values"; - -const providerIssuers = { - github: createOAuthAccountIssuer("github"), - google: "https://accounts.google.com", - // "my-oidc-provider": "https://issuer.example.com", - // Do not add one Microsoft issuer when accounts span multiple tenants. -}; - -export const validateAccountIssuerBackfill = internalQuery({ - args: { cursor: v.union(v.string(), v.null()) }, - handler: async (ctx, { cursor }) => { - return await ctx.runQuery( - components.betterAuth.migrations.validateAccountIssuerBackfill, - { - providerIssuers, - paginationOpts: { cursor, numItems: 100 }, - } - ); - }, -}); - -export const backfillAccountIssuers = internalMutation({ - args: { cursor: v.union(v.string(), v.null()) }, - handler: async (ctx, { cursor }) => { - return await ctx.runMutation( - components.betterAuth.migrations.backfillAccountIssuers, - { - providerIssuers, - paginationOpts: { cursor, numItems: 100 }, - } - ); - }, -}); +npm install better-auth@~1.7.7 ``` -The migration normalizes credential accounts so `accountId` is the linked user -ID, as required by Better Auth 1.7. Both functions calculate the target identity -of legacy rows outside the current page, so a collision split across pages is -detected before either row is changed. + + Review the [Better Auth 1.7 upgrade + guide](https://www.better-auth.com/docs/guides/1-7-upgrade-guide) and [1.7.7 + release notes](https://github.com/better-auth/better-auth/releases/tag/v1.7.7) + for the features your app uses. +
-### Validate every page without writing - -Start the validation query with a null cursor: - -```bash -npx convex run migrations:validateAccountIssuerBackfill '{"cursor":null}' -``` - -If the result has `isDone: false`, run it again with the returned -`continueCursor`. Repeat until `isDone` is true. This pass must finish before -the first backfill mutation. The query stops at the first invalid account in a -page; fix that record or mapping and rerun the same page until validation can -advance. Resolve missing mappings, issuer mismatches, and collisions from -trusted provider data; never merge users based only on matching email. +### Check existing accounts -Treat every cursor as opaque and only advance with `continueCursor` while -`isDone` is false. The cursor returned with `isDone: true` is terminal and -replaying it is a no-op. Start again with a null cursor when intentionally -beginning a new validation or migration pass. +Accounts still use `(providerId, accountId)`, as in 1.6. **No issuer backfill is +needed.** Check for duplicate pairs before upgrading; Better Auth now rejects +ambiguous account lookups. Resolve duplicates using trusted provider data and +keep different users separate. Credential accounts should have `accountId` equal +to `userId`. -Run the same process with `--prod` for production. +If you tested 1.7.0–1.7.2, see [Early 1.7 deployments](#early-17-deployments) +below. Microsoft accounts have a separate migration described below.
-### Backfill every page +### Deploy and test -After validation finishes, start the mutation with a new null cursor: +Back up your data and test the upgrade on a development deployment first. +Regenerate Convex bindings, then deploy the component and application changes +together. Verify sign-in, existing sessions, account linking, token refresh, and +`/api/auth/convex/jwks` for the features you use. -```bash -npx convex run migrations:backfillAccountIssuers '{"cursor":null}' -``` - -Repeat with each returned `continueCursor` until `isDone` is true. Do not reuse -the validation cursor. The mutation repeats the collision checks and is safe to -rerun for accounts already migrated with the same identity. Add `--prod` to each -command when running against production. - -
- -
- -### Verify before resuming writes - -Confirm that a second validation pass reports `pending: 0` on every page. -Applications acting as an OAuth or OIDC provider must now complete the provider -conversion below while remaining in maintenance mode. Other applications can -temporarily enable the HTTP routes for a controlled smoke test. Verify -email-and-password sign-in, returning OAuth or SSO sign-in, explicit account -linking and unlinking, provider profile retrieval, and token refresh for every -configured provider. Only then restore normal public traffic and background -writes. +Upgrade servers sharing verification storage and OAuth Proxy participants +together. Request fresh Magic Links and restart pending OAuth/SAML sign-ins +after deploying, as required by the 1.7.7 security fixes.
-## Update Better Auth configuration +## Breaking changes ### Account selectors -Account-management APIs now select the local Better Auth account row, not the -provider-side subject. Read `id` from `listAccounts` and pass that value as -`accountId` to `unlinkAccount`. For `getAccessToken`, `refreshToken`, and -`accountInfo`, pass exactly one of `{ accountId: account.id }` or -`{ useAccountCookie: true }`. Remove `providerId` from these selectors; the -provider-side identity remains available as `account.accountId`. +Read the local account `id` from `listAccounts` and pass it as `accountId` to +`unlinkAccount`. For `getAccessToken`, `refreshToken`, and `accountInfo`, pass +exactly one of `{ accountId: account.id }` or `{ useAccountCookie: true }`. +Remove `providerId` from these selectors. Cookie selection requires +`account.storeAccountCookie` in your Better Auth configuration. ### Generic OAuth -Generic OAuth now uses the standard social client API: +Use the standard social client APIs: ```ts await authClient.signIn.social({ provider: "my-provider" }); +await authClient.linkSocial({ provider: "my-provider" }); ``` -Remove `genericOAuthClient()` from the client plugins. Update provider callback -URLs from `/api/auth/oauth2/callback/:id` to `/api/auth/callback/:id`. +These replace `signIn.oauth2()` and `oauth2.link()`. Remove +`genericOAuthClient()` and change provider callback URLs from +`/api/auth/oauth2/callback/:id` to `/api/auth/callback/:id`. + +### Microsoft accounts + +Microsoft accounts now use `oid` instead of `sub`, including the Generic OAuth +Microsoft Entra helper. Pause Microsoft sign-in and linking while updating +stored account IDs from verified ID tokens or a trusted Entra export. The old +account row alone cannot supply this mapping. + +The Generic OAuth `microsoftEntraId` helper requires a concrete tenant GUID and +an `id_token` from the token exchange. Use the built-in Microsoft provider for +`common`, `organizations`, or `consumers`. Follow the upstream guide's +[Microsoft migration steps](https://www.better-auth.com/docs/guides/1-7-upgrade-guide#migrate-microsoft-account-identifiers). ### Database joins -If you enabled `experimental.joins`, move the option to -`advanced.database.joins`. The Convex adapter still disables native joins and -uses its existing relation queries. +Move `experimental.joins` to `advanced.database.joins`. The Convex adapter +continues to disable native joins and use its existing relation queries. + +## Early 1.7 deployments + +Better Auth 1.7.3 removed the required `issuer` field introduced in 1.7.0–1.7.2. +The bundled component keeps it optional to accept existing data, but no longer +includes issuer indexes or backfill functions. -### OAuth or OIDC provider +For Local Install, remove issuer-based constraints and indexes. Keep `issuer` +optional until stored values are removed. If different issuers shared one +provider ID and overlapping account IDs, assign distinct provider IDs and update +the matching account rows before resuming sign-in. -This section applies only if the application previously used the bundled -`oidcProvider` plugin to act as an identity provider. Complete it after the -account-issuer backfill, while the staged bundled component is still mounted and -authentication writes remain stopped. +## OAuth or OIDC provider -Add temporary internal wrappers for the staged component's legacy OAuth -functions: +Only follow this section if your app used `oidcProvider` to act as an identity +provider. Apps that only sign in through an external provider can skip it. + +### Export and clear legacy records + +Back up your data and pause authentication writes and public HTTP routes during +the conversion. Keep the bundled component mounted until export and cleanup are +complete. Add these internal wrappers: ```ts title="convex/migrations.ts" +import { v } from "convex/values"; +import { internalMutation, internalQuery } from "./_generated/server"; +import { components } from "./_generated/api"; + export const listLegacyOAuthApplications = internalQuery({ args: { cursor: v.union(v.string(), v.null()) }, handler: async (ctx, { cursor }) => { @@ -319,26 +155,16 @@ export const clearLegacyOAuthProviderRecords = internalMutation({ }); ``` -Run `listLegacyOAuthApplications` through every cursor page and save the -redacted registration metadata somewhere access-controlled. The result says -whether each client had a secret but never returns `clientSecret` itself. Other -fields, especially custom `metadata`, may still contain sensitive application -data and must be reviewed before reuse. Existing confidential clients receive a -new secret when re-registered. +Export every page of client registrations and save the results securely. The +export omits `clientSecret`; review custom `metadata` before reusing it. ```bash npx convex run migrations:listLegacyOAuthApplications '{"cursor":null}' ``` -Repeat with each returned `continueCursor` until `isDone` is true. Add `--prod` -to each command when running against production. Follow the same terminal cursor -rule as the account migration above. - -After verifying the exported client metadata, run -`clearLegacyOAuthProviderRecords` through every page for `oauthAccessToken`, -`oauthConsent`, and finally `oauthApplication`. This intentionally invalidates -all legacy provider tokens and consents and removes the old client secrets. Run -it before any 1.7 provider tokens are issued. +After verifying the export, clear the old tokens, consents, and applications in +that order. This invalidates existing provider tokens and removes client +secrets. ```bash npx convex run migrations:clearLegacyOAuthProviderRecords \ @@ -349,91 +175,47 @@ npx convex run migrations:clearLegacyOAuthProviderRecords \ '{"table":"oauthApplication","cursor":null}' ``` -For each table, repeat the command with its returned `continueCursor` until -`isDone` is true. Add `--prod` to each production command. Do not switch to -Local Install until all three tables report `isDone: true`; the bundled -migration functions are not available after the switch. A cursor returned with -`isDone: true` is terminal; start from null to intentionally run a new pass. - -#### Convert the staged component to Local Install - -1. Follow the [Local Install guide](/local-install) to create the local - `convex/betterAuth` component. Keep the component mounted as `betterAuth` in - `app.use(betterAuth)` so the generated `components.betterAuth` path does not - change. Export every adapter function shown there, including `incrementOne`. -2. Add the replacement package and review Better Auth's full OAuth provider - migration requirements: - - ```npm - npm install @better-auth/oauth-provider@~1.7.1 - ``` - - ```ts - import { oauthProvider } from "@better-auth/oauth-provider"; - import { jwt } from "better-auth/plugins/jwt"; - - betterAuth({ - plugins: [ - jwt(), - oauthProvider({ - loginPage: "/sign-in", - consentPage: "/consent", - }), - ], - }); - ``` - -3. Generate the Local Install schema only after the static Better Auth options - include `oauthProvider()`. The account backfill is complete and all three - legacy provider tables are empty, so this converting deployment can use the - final generated Better Auth 1.7 schema rather than the temporary merged - schema described below for existing Local Install deployments. -4. Deploy the local component while writes and public HTTP routes remain - stopped. Before registering clients, confirm that existing users, accounts, - and sessions are still present under the unchanged `components.betterAuth` - mount. If they are not, roll back the component registration and restore the - backup; do not resume traffic. -5. Re-register the exported clients using the new provider, map `redirectUrls` - to `redirectUris`, convert the legacy `metadata` string to JSON, review grant - types and the token endpoint authentication method, and distribute new client - secrets through the normal secret channel. Existing provider tokens must be - issued again. -6. Run the controlled smoke tests described above, including authorization, - consent, token issuance, refresh, user info, and logout, before restoring - normal traffic and background writes. +For each command, pass the returned `continueCursor` to the next call until +`isDone` is true. A completed cursor is terminal; use `null` only to start a new +pass. Add `--prod` when running against production. + +### Switch to Local Install + +1. Follow the [Local Install guide](/local-install). Keep the component mounted + as `betterAuth` so existing data stays under `components.betterAuth`. +2. Install `@better-auth/oauth-provider@~1.7.7` and replace `oidcProvider()` + with `jwt()` and `oauthProvider()` in your static Better Auth options. Follow + the upstream + [provider migration guide](https://www.better-auth.com/docs/guides/1-7-upgrade-guide#as-an-identity-provider). +3. Generate the new schema after adding the plugin and clearing all three legacy + tables. Retain optional `account.issuer` if existing records still contain + it. +4. Deploy while writes remain paused. Confirm that existing users, accounts, and + sessions remain under the same component mount before proceeding. +5. Re-register exported clients. Map `redirectUrls` to `redirectUris`, parse + legacy `metadata` as JSON, and review grant types and token authentication. + Distribute new client secrets and issue new provider tokens. +6. Test authorization, consent, token issuance, refresh, user info, and logout + before restoring traffic. Restore the backup with the old application version + if the conversion fails. ## Local Install additional steps -This section applies to applications that already used Local Install before the -upgrade. They control their own schema and cannot call the bundled component -migration functions above. Generate the 1.7 schema to a side file instead of -overwriting the deployed schema: +Generate the 1.7.7 schema to a side file and compare it with your deployed +schema: ```npm cd convex/betterAuth npx auth generate --output generatedSchema.next.ts ``` -Before deploying, copy the new definitions into your active schema with these -temporary changes: - -1. Make `account.issuer` optional. Add the generated `issuer_accountId` and - `userId_providerId_issuer_accountId` indexes. Keep the generated `accountId` - and `providerId_userId` indexes for migration collision checks. -2. If you used `oidcProvider`, retain `oauthApplication` and merge the legacy - and new `oauthAccessToken` and `oauthConsent` fields as optional unions. -3. Add the new OAuth tables without removing legacy records. - -Implement the same two-pass process against your local `account` table: a -read-only paginated validation that calculates every target identity and checks -legacy candidates outside the page, followed by the paginated write migration. -Do not deploy the required `issuer` validator from `generatedSchema.next.ts` -until validation reports no pending rows and the backfill is complete. - -If the local deployment used `oidcProvider`, also implement local equivalents of -the export and clear functions above. Redact `clientSecret` from the export, -save the client metadata, and clear `oauthAccessToken`, `oauthConsent`, and -`oauthApplication` before replacing the temporary table definitions with the -final generated definitions. Run Convex code generation and deploy, then -re-register clients and complete the provider smoke tests before resuming -authentication writes. +Preserve custom fields and indexes. Include the generated credential lookup +index `userId_providerId_accountId` on `["userId", "providerId", "accountId"]` +and export `incrementOne` with your other adapter functions. The adapter +implements `consumeOne` using the component's atomic `deleteOne`. + +If you already use Local Install with `oidcProvider`, implement local +equivalents of the export and cleanup helpers above. During export, retain +`oauthApplication` and merge legacy/new `oauthAccessToken` and `oauthConsent` +fields as optional unions. Clear legacy records before applying the final +provider schema. diff --git a/examples/next/convex/betterAuth/_generated/component.ts b/examples/next/convex/betterAuth/_generated/component.ts index e7ed48e3..6c60a754 100644 --- a/examples/next/convex/betterAuth/_generated/component.ts +++ b/examples/next/convex/betterAuth/_generated/component.ts @@ -65,7 +65,6 @@ export type ComponentApi = accountId: string; createdAt: number; idToken?: null | string; - issuer: string; password?: null | string; providerId: string; refreshToken?: null | string; @@ -199,7 +198,6 @@ export type ComponentApi = where?: Array<{ connector?: "AND" | "OR"; field: - | "issuer" | "accountId" | "providerId" | "userId" @@ -434,7 +432,6 @@ export type ComponentApi = where?: Array<{ connector?: "AND" | "OR"; field: - | "issuer" | "accountId" | "providerId" | "userId" @@ -783,7 +780,6 @@ export type ComponentApi = accountId?: string; createdAt?: number; idToken?: null | string; - issuer?: string; password?: null | string; providerId?: string; refreshToken?: null | string; @@ -795,7 +791,6 @@ export type ComponentApi = where: Array<{ connector?: "AND" | "OR"; field: - | "issuer" | "accountId" | "providerId" | "userId" @@ -1074,7 +1069,6 @@ export type ComponentApi = accountId?: string; createdAt?: number; idToken?: null | string; - issuer?: string; password?: null | string; providerId?: string; refreshToken?: null | string; @@ -1086,7 +1080,6 @@ export type ComponentApi = where?: Array<{ connector?: "AND" | "OR"; field: - | "issuer" | "accountId" | "providerId" | "userId" @@ -1370,7 +1363,6 @@ export type ComponentApi = accountId?: string; createdAt?: number; idToken?: null | string; - issuer?: string; password?: null | string; providerId?: string; refreshToken?: null | string; @@ -1382,7 +1374,6 @@ export type ComponentApi = where?: Array<{ connector?: "AND" | "OR"; field: - | "issuer" | "accountId" | "providerId" | "userId" diff --git a/examples/next/convex/betterAuth/generatedSchema.ts b/examples/next/convex/betterAuth/generatedSchema.ts index fdb6901f..2e6af099 100644 --- a/examples/next/convex/betterAuth/generatedSchema.ts +++ b/examples/next/convex/betterAuth/generatedSchema.ts @@ -46,7 +46,6 @@ export const tables = { .index("token", ["token"]) .index("userId", ["userId"]), account: defineTable({ - issuer: v.string(), accountId: v.string(), providerId: v.string(), userId: v.string(), @@ -60,14 +59,12 @@ export const tables = { createdAt: v.number(), updatedAt: v.number(), }) - .index("issuer_accountId", ["issuer", "accountId"]) .index("accountId", ["accountId"]) .index("accountId_providerId", ["accountId", "providerId"]) .index("providerId_userId", ["providerId", "userId"]) - .index("userId_providerId_issuer_accountId", [ + .index("userId_providerId_accountId", [ "userId", "providerId", - "issuer", "accountId", ]) .index("userId", ["userId"]), diff --git a/examples/next/package-lock.json b/examples/next/package-lock.json index d87138e0..4d06060f 100644 --- a/examples/next/package-lock.json +++ b/examples/next/package-lock.json @@ -52,7 +52,7 @@ "version": "0.12.5", "license": "Apache-2.0", "dependencies": { - "@better-fetch/fetch": "^1.1.18", + "@better-fetch/fetch": "^1.3.2", "common-tags": "^1.8.2", "convex-helpers": "^0.1.95", "jose": "^6.1.0", @@ -62,8 +62,8 @@ "zod": "^4.0.0" }, "devDependencies": { - "@better-auth/core": "1.7.1", - "@better-auth/test-utils": "1.7.1", + "@better-auth/core": "1.7.7", + "@better-auth/test-utils": "1.7.7", "@convex-dev/eslint-plugin": "2.0.0", "@edge-runtime/vm": "5.0.0", "@eslint/eslintrc": "3.3.5", @@ -74,7 +74,7 @@ "@types/react": "19.2.14", "@types/react-dom": "19.2.3", "@types/semver": "7.7.1", - "better-auth": "1.7.1", + "better-auth": "1.7.7", "chokidar-cli": "3.0.0", "concurrently": "10.0.3", "convex": "1.41.0", @@ -95,7 +95,7 @@ "vitest": "4.1.10" }, "peerDependencies": { - "better-auth": ">=1.7.1 <1.8.0", + "better-auth": ">=1.7.7 <1.8.0", "convex": "^1.25.0", "react": "^18.3.1 || ^19.0.0" } diff --git a/examples/react/package-lock.json b/examples/react/package-lock.json index c4ae80ab..865c208c 100644 --- a/examples/react/package-lock.json +++ b/examples/react/package-lock.json @@ -51,7 +51,7 @@ "version": "0.12.5", "license": "Apache-2.0", "dependencies": { - "@better-fetch/fetch": "^1.1.18", + "@better-fetch/fetch": "^1.3.2", "common-tags": "^1.8.2", "convex-helpers": "^0.1.95", "jose": "^6.1.0", @@ -61,8 +61,8 @@ "zod": "^4.0.0" }, "devDependencies": { - "@better-auth/core": "1.7.1", - "@better-auth/test-utils": "1.7.1", + "@better-auth/core": "1.7.7", + "@better-auth/test-utils": "1.7.7", "@convex-dev/eslint-plugin": "2.0.0", "@edge-runtime/vm": "5.0.0", "@eslint/eslintrc": "3.3.5", @@ -73,7 +73,7 @@ "@types/react": "19.2.14", "@types/react-dom": "19.2.3", "@types/semver": "7.7.1", - "better-auth": "1.7.1", + "better-auth": "1.7.7", "chokidar-cli": "3.0.0", "concurrently": "10.0.3", "convex": "1.41.0", @@ -94,7 +94,7 @@ "vitest": "4.1.10" }, "peerDependencies": { - "better-auth": ">=1.7.1 <1.8.0", + "better-auth": ">=1.7.7 <1.8.0", "convex": "^1.25.0", "react": "^18.3.1 || ^19.0.0" } diff --git a/examples/tanstack/convex/betterAuth/_generated/component.ts b/examples/tanstack/convex/betterAuth/_generated/component.ts index 835c4644..832040aa 100644 --- a/examples/tanstack/convex/betterAuth/_generated/component.ts +++ b/examples/tanstack/convex/betterAuth/_generated/component.ts @@ -63,7 +63,6 @@ export type ComponentApi = accountId: string createdAt: number idToken?: null | string - issuer: string password?: null | string providerId: string refreshToken?: null | string @@ -195,7 +194,6 @@ export type ComponentApi = where?: Array<{ connector?: 'AND' | 'OR' field: - | 'issuer' | 'accountId' | 'providerId' | 'userId' @@ -428,7 +426,6 @@ export type ComponentApi = where?: Array<{ connector?: 'AND' | 'OR' field: - | 'issuer' | 'accountId' | 'providerId' | 'userId' @@ -773,7 +770,6 @@ export type ComponentApi = accountId?: string createdAt?: number idToken?: null | string - issuer?: string password?: null | string providerId?: string refreshToken?: null | string @@ -785,7 +781,6 @@ export type ComponentApi = where: Array<{ connector?: 'AND' | 'OR' field: - | 'issuer' | 'accountId' | 'providerId' | 'userId' @@ -1060,7 +1055,6 @@ export type ComponentApi = accountId?: string createdAt?: number idToken?: null | string - issuer?: string password?: null | string providerId?: string refreshToken?: null | string @@ -1072,7 +1066,6 @@ export type ComponentApi = where?: Array<{ connector?: 'AND' | 'OR' field: - | 'issuer' | 'accountId' | 'providerId' | 'userId' @@ -1352,7 +1345,6 @@ export type ComponentApi = accountId?: string createdAt?: number idToken?: null | string - issuer?: string password?: null | string providerId?: string refreshToken?: null | string @@ -1364,7 +1356,6 @@ export type ComponentApi = where?: Array<{ connector?: 'AND' | 'OR' field: - | 'issuer' | 'accountId' | 'providerId' | 'userId' diff --git a/examples/tanstack/convex/betterAuth/schema.ts b/examples/tanstack/convex/betterAuth/schema.ts index ae72bfa7..67730355 100644 --- a/examples/tanstack/convex/betterAuth/schema.ts +++ b/examples/tanstack/convex/betterAuth/schema.ts @@ -43,7 +43,6 @@ export const tables = { .index('token', ['token']) .index('userId', ['userId']), account: defineTable({ - issuer: v.string(), accountId: v.string(), providerId: v.string(), userId: v.string(), @@ -57,14 +56,12 @@ export const tables = { createdAt: v.number(), updatedAt: v.number(), }) - .index('issuer_accountId', ['issuer', 'accountId']) .index('accountId', ['accountId']) .index('accountId_providerId', ['accountId', 'providerId']) .index('providerId_userId', ['providerId', 'userId']) - .index('userId_providerId_issuer_accountId', [ + .index('userId_providerId_accountId', [ 'userId', 'providerId', - 'issuer', 'accountId', ]) .index('userId', ['userId']), diff --git a/examples/tanstack/package-lock.json b/examples/tanstack/package-lock.json index b0098662..111ee6fa 100644 --- a/examples/tanstack/package-lock.json +++ b/examples/tanstack/package-lock.json @@ -60,7 +60,7 @@ "version": "0.12.5", "license": "Apache-2.0", "dependencies": { - "@better-fetch/fetch": "^1.1.18", + "@better-fetch/fetch": "^1.3.2", "common-tags": "^1.8.2", "convex-helpers": "^0.1.95", "jose": "^6.1.0", @@ -70,8 +70,8 @@ "zod": "^4.0.0" }, "devDependencies": { - "@better-auth/core": "1.7.1", - "@better-auth/test-utils": "1.7.1", + "@better-auth/core": "1.7.7", + "@better-auth/test-utils": "1.7.7", "@convex-dev/eslint-plugin": "2.0.0", "@edge-runtime/vm": "5.0.0", "@eslint/eslintrc": "3.3.5", @@ -82,7 +82,7 @@ "@types/react": "19.2.14", "@types/react-dom": "19.2.3", "@types/semver": "7.7.1", - "better-auth": "1.7.1", + "better-auth": "1.7.7", "chokidar-cli": "3.0.0", "concurrently": "10.0.3", "convex": "1.41.0", @@ -103,7 +103,7 @@ "vitest": "4.1.10" }, "peerDependencies": { - "better-auth": ">=1.7.1 <1.8.0", + "better-auth": ">=1.7.7 <1.8.0", "convex": "^1.25.0", "react": "^18.3.1 || ^19.0.0" } @@ -678,7 +678,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -695,7 +694,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -712,7 +710,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -729,7 +726,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -746,7 +742,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -763,7 +758,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -780,7 +774,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -797,7 +790,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -814,7 +806,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -831,7 +822,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -848,7 +838,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -865,7 +854,6 @@ "cpu": [ "loong64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -882,7 +870,6 @@ "cpu": [ "mips64el" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -899,7 +886,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -916,7 +902,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -933,7 +918,6 @@ "cpu": [ "s390x" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -950,7 +934,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -967,7 +950,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -984,7 +966,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1001,7 +982,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1018,7 +998,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1035,7 +1014,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1052,7 +1030,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1069,7 +1046,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1086,7 +1062,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1103,7 +1078,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2224,7 +2198,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2238,7 +2211,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2252,7 +2224,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2266,7 +2237,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2280,7 +2250,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2294,7 +2263,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2308,7 +2276,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2322,7 +2289,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2336,7 +2302,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2350,7 +2315,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2364,7 +2328,6 @@ "cpu": [ "loong64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2378,7 +2341,6 @@ "cpu": [ "loong64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2392,7 +2354,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2406,7 +2367,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2420,7 +2380,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2434,7 +2393,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2448,7 +2406,6 @@ "cpu": [ "s390x" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2462,7 +2419,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2476,7 +2432,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2490,7 +2445,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2504,7 +2458,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2518,7 +2471,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2532,7 +2484,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2546,7 +2497,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2560,7 +2510,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4745,7 +4694,6 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, diff --git a/package-lock.json b/package-lock.json index 34bbc015..ea73b0ff 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "0.12.5", "license": "Apache-2.0", "dependencies": { - "@better-fetch/fetch": "^1.1.18", + "@better-fetch/fetch": "^1.3.2", "common-tags": "^1.8.2", "convex-helpers": "^0.1.95", "jose": "^6.1.0", @@ -19,8 +19,8 @@ "zod": "^4.0.0" }, "devDependencies": { - "@better-auth/core": "1.7.1", - "@better-auth/test-utils": "1.7.1", + "@better-auth/core": "1.7.7", + "@better-auth/test-utils": "1.7.7", "@convex-dev/eslint-plugin": "2.0.0", "@edge-runtime/vm": "5.0.0", "@eslint/eslintrc": "3.3.5", @@ -31,7 +31,7 @@ "@types/react": "19.2.14", "@types/react-dom": "19.2.3", "@types/semver": "7.7.1", - "better-auth": "1.7.1", + "better-auth": "1.7.7", "chokidar-cli": "3.0.0", "concurrently": "10.0.3", "convex": "1.41.0", @@ -52,7 +52,7 @@ "vitest": "4.1.10" }, "peerDependencies": { - "better-auth": ">=1.7.1 <1.8.0", + "better-auth": ">=1.7.7 <1.8.0", "convex": "^1.25.0", "react": "^18.3.1 || ^19.0.0" } @@ -360,20 +360,19 @@ } }, "node_modules/@better-auth/core": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@better-auth/core/-/core-1.7.1.tgz", - "integrity": "sha512-eZ9lqcnVLMZ3QtUByRo4VZqkB1ESyRddd9NfWjBdDPgh+jcwLScoIUAqhtHLR8zaSUJZah8OLGlkzObyPdUH7A==", + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/@better-auth/core/-/core-1.7.7.tgz", + "integrity": "sha512-srgFzHEjB2WWlokFz1i4BfG41sNhsA105Pukgqu6RrEh5A+LSkzf1mCHydVipkjt9l5qswtBMi7IL6ouHbZL4Q==", "dev": true, "license": "MIT", "dependencies": { "@opentelemetry/semantic-conventions": "^1.41.1", "@standard-schema/spec": "^1.1.0", - "zod": "^4.3.6" + "zod": "^4.5.4" }, "peerDependencies": { "@better-auth/utils": "0.4.2", - "@better-fetch/fetch": "1.3.1", - "@cloudflare/workers-types": ">=4", + "@better-fetch/fetch": "1.3.2", "@opentelemetry/api": "^1.9.0", "better-call": "1.4.0", "jose": "^6.1.0", @@ -381,22 +380,19 @@ "nanostores": "^1.0.1" }, "peerDependenciesMeta": { - "@cloudflare/workers-types": { - "optional": true - }, "@opentelemetry/api": { "optional": true } } }, "node_modules/@better-auth/drizzle-adapter": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@better-auth/drizzle-adapter/-/drizzle-adapter-1.7.1.tgz", - "integrity": "sha512-qlqNyg5V9bXHSP68/vtlsiZayhR4hgvEGiS/E3SIj8bCpWWFGmyQkxJbQCqpBmC7vT30wE/kNtJMHIgnV3rkiw==", + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/@better-auth/drizzle-adapter/-/drizzle-adapter-1.7.7.tgz", + "integrity": "sha512-qon0U94PR5FQysuyGoAlGVQpIIiNg5dtDpwjE63sN/HjoRWZadZzHPcS9mn7JsJyQZK6iTNhd7LD0AaXt2f78w==", "dev": true, "license": "MIT", "peerDependencies": { - "@better-auth/core": "^1.7.1", + "@better-auth/core": "1.7.7", "@better-auth/utils": "0.4.2", "drizzle-orm": "^0.45.2 || >=1.0.0-rc.1 <2.0.0" }, @@ -407,13 +403,13 @@ } }, "node_modules/@better-auth/kysely-adapter": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@better-auth/kysely-adapter/-/kysely-adapter-1.7.1.tgz", - "integrity": "sha512-yWCpE1cZpMUj37nD6JFDK+GDR8zS37L5WI73il3qbU9TXtWsxUQKc/5c3IHsHizWQsmcQI8uv2pAFKxsRDa+AQ==", + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/@better-auth/kysely-adapter/-/kysely-adapter-1.7.7.tgz", + "integrity": "sha512-9FONOCgOcrQI9wJ5v1oDGIg9sT7pa9RjZZQ4fsCTudo8R87SrIGNUvUxDFTJhZvUblvFfGaIBR9Vb8LA+8bqLQ==", "dev": true, "license": "MIT", "peerDependencies": { - "@better-auth/core": "^1.7.1", + "@better-auth/core": "1.7.7", "@better-auth/utils": "0.4.2", "kysely": "^0.28.17 || ^0.29.0" }, @@ -424,24 +420,24 @@ } }, "node_modules/@better-auth/memory-adapter": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@better-auth/memory-adapter/-/memory-adapter-1.7.1.tgz", - "integrity": "sha512-6NX1yv88DeqdoG7owYFqKwlrDGaIPhsC52JUGrUgeGVKyOq8a/6hHlHsqG1C2FwT23SHQiKVYCEAG9N6aH5OvQ==", + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/@better-auth/memory-adapter/-/memory-adapter-1.7.7.tgz", + "integrity": "sha512-FqKFEe+b5tXXV0gRbyirca+qXgMpLOeJ8Wk19yykb/scQKy0WJ4k22WzEV3TTPctQV9DZhwCXPlDtQhnlOv5Gg==", "dev": true, "license": "MIT", "peerDependencies": { - "@better-auth/core": "^1.7.1", + "@better-auth/core": "1.7.7", "@better-auth/utils": "0.4.2" } }, "node_modules/@better-auth/mongo-adapter": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@better-auth/mongo-adapter/-/mongo-adapter-1.7.1.tgz", - "integrity": "sha512-9ILTcNqhG37QK//qR4UhYLyKzNqq6w6zVTf5KX6xkiTjNcV7Oh1yS31lkIJEVTqRNcy9AoV6FZMW6Bbsm8IMDA==", + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/@better-auth/mongo-adapter/-/mongo-adapter-1.7.7.tgz", + "integrity": "sha512-ZnVDuRrXqbf0oHphrEN27oTImNH5NTO26dtWatq5cXyYAsH7goof9QIoTOmxTbBxUQahKBw9T6+9h6n+RtOE+g==", "dev": true, "license": "MIT", "peerDependencies": { - "@better-auth/core": "^1.7.1", + "@better-auth/core": "1.7.7", "@better-auth/utils": "0.4.2", "mongodb": "^6.0.0 || ^7.0.0" }, @@ -452,13 +448,13 @@ } }, "node_modules/@better-auth/prisma-adapter": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@better-auth/prisma-adapter/-/prisma-adapter-1.7.1.tgz", - "integrity": "sha512-ZiUcafQ85InAofcUjyGgCPjKLfQjXr9SvDmMjuFUW8oEbreA6C6GaFAEA77VuV2doZUQlzvQQ4gCoTmS28W92A==", + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/@better-auth/prisma-adapter/-/prisma-adapter-1.7.7.tgz", + "integrity": "sha512-4YcnrcVdvWiAZw0sZl63tWUClPXg5r3QsLn7C2sG7kKEBY06zOng0ibecyxRBixKq1zE1ceHuEUqez5Rhi+nQA==", "dev": true, "license": "MIT", "peerDependencies": { - "@better-auth/core": "^1.7.1", + "@better-auth/core": "1.7.7", "@better-auth/utils": "0.4.2", "@prisma/client": "^5.0.0 || ^6.0.0 || ^7.0.0", "prisma": "^5.0.0 || ^6.0.0 || ^7.0.0" @@ -473,27 +469,27 @@ } }, "node_modules/@better-auth/telemetry": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@better-auth/telemetry/-/telemetry-1.7.1.tgz", - "integrity": "sha512-kLKjMfFlTbyt49DGeI9okHAsn0MtBZcMoQYKaEdgR0H3BHzqqyzePcQz/hxAmRgjB4p/6inise3zJwhX0sgXrQ==", + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/@better-auth/telemetry/-/telemetry-1.7.7.tgz", + "integrity": "sha512-PaRA6i+kAioVYmza2gHPcdrxP0hAotph4KV9hft2GeNJq+AZXDiBu3FuLM9HxvcmiZeUT74Vufqd9ypmB8veTA==", "dev": true, "license": "MIT", "peerDependencies": { - "@better-auth/core": "^1.7.1", + "@better-auth/core": "1.7.7", "@better-auth/utils": "0.4.2", - "@better-fetch/fetch": "1.3.1" + "@better-fetch/fetch": "1.3.2" } }, "node_modules/@better-auth/test-utils": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@better-auth/test-utils/-/test-utils-1.7.1.tgz", - "integrity": "sha512-Whb2s5EX9xwninIW70p+YVmcgzWUoYktKFJm0eNWXw/Noqv9+Op27ZUuKk8hMmCImnm6YuToSzkz3OBAP5OYPg==", + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/@better-auth/test-utils/-/test-utils-1.7.7.tgz", + "integrity": "sha512-110vhQ9r/va7h/4ABIcejoy+p/lwBgoyEI8d4N1RRZ8Zh4T8IiDnGsOCj/VUVP3WBAy2vIZcd3rajZtjkg9yAw==", "dev": true, "license": "MIT", "peerDependencies": { - "@better-auth/core": "^1.7.1", - "better-auth": "^1.7.1", - "vitest": "^4.1.10" + "@better-auth/core": "1.7.7", + "better-auth": "1.7.7", + "vitest": "^4.0.0 || ^5.0.0" } }, "node_modules/@better-auth/utils": { @@ -507,9 +503,9 @@ } }, "node_modules/@better-fetch/fetch": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@better-fetch/fetch/-/fetch-1.3.1.tgz", - "integrity": "sha512-ABkD1WhyfPZprKRQI3bhATjeiFuNWC9PXhfGWqL+sg/gKrM977oFrYkdb4msM3hgUGonr7KlOsOFT5TU2rht9g==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@better-fetch/fetch/-/fetch-1.3.2.tgz", + "integrity": "sha512-Gs7n99b5tqUC6cQAPbV0uED3IraHB6xQbHLQ/C3l7ZFafHScOx9pQ+DYmP5blbLFShVWLqxNUlI9wi4xU/X+ow==", "license": "MIT" }, "node_modules/@convex-dev/eslint-plugin": { @@ -2105,9 +2101,9 @@ } }, "node_modules/@noble/ciphers": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-2.2.0.tgz", - "integrity": "sha512-Z6pjIZ/8IJcCGzb2S/0Px5J81yij85xASuk1teLNeg75bfT07MV3a/O2Mtn1I2se43k3lkVEcFaR10N4cgQcZA==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-2.4.0.tgz", + "integrity": "sha512-AnjFn0Jv92laAkvMrghlFZq4qQCIN/4DxFV/eooqtC2YTjB7kBeLMS2T9KJX4Dn+ZVXLOwK0lSgqDtx9gvxtiw==", "dev": true, "license": "MIT", "engines": { @@ -3830,21 +3826,21 @@ } }, "node_modules/better-auth": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/better-auth/-/better-auth-1.7.1.tgz", - "integrity": "sha512-g8WlTQijxXWJjPVZfFu1+EJg9cwwHrKDmIkcYMzx8CzYA+tDxl6NI7qQbKkbgw5UtHILsT5VH+RMzFzwnVJqAg==", + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/better-auth/-/better-auth-1.7.7.tgz", + "integrity": "sha512-Zhgxi/c5ylmfB/sX+nwnBbsFf/j6k0L8DvL6CJXZSrXgxc5pPMC/e7j812zNd4L4B4ELmdURCPw3X8nAOUdcjw==", "dev": true, "license": "MIT", "dependencies": { - "@better-auth/core": "1.7.1", - "@better-auth/drizzle-adapter": "1.7.1", - "@better-auth/kysely-adapter": "1.7.1", - "@better-auth/memory-adapter": "1.7.1", - "@better-auth/mongo-adapter": "1.7.1", - "@better-auth/prisma-adapter": "1.7.1", - "@better-auth/telemetry": "1.7.1", + "@better-auth/core": "1.7.7", + "@better-auth/drizzle-adapter": "1.7.7", + "@better-auth/kysely-adapter": "1.7.7", + "@better-auth/memory-adapter": "1.7.7", + "@better-auth/mongo-adapter": "1.7.7", + "@better-auth/prisma-adapter": "1.7.7", + "@better-auth/telemetry": "1.7.7", "@better-auth/utils": "0.4.2", - "@better-fetch/fetch": "1.3.1", + "@better-fetch/fetch": "1.3.2", "@noble/ciphers": "^2.2.0", "@noble/hashes": "^2.2.0", "better-call": "1.4.0", @@ -3852,7 +3848,7 @@ "jose": "^6.2.3", "kysely": "^0.28.17 || ^0.29.0", "nanostores": "^1.3.0", - "zod": "^4.3.6" + "zod": "^4.5.4" }, "peerDependencies": { "@lynx-js/react": "*", @@ -3860,7 +3856,6 @@ "@sveltejs/kit": "^2.0.0", "@tanstack/react-start": "^1.0.0", "@tanstack/solid-start": "^1.0.0", - "better-sqlite3": "^12.0.0", "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", @@ -3872,7 +3867,7 @@ "react-dom": "^18.0.0 || ^19.0.0", "solid-js": "^1.0.0", "svelte": "^4.0.0 || ^5.0.0", - "vitest": "^2.0.0 || ^3.0.0 || ^4.0.0", + "vitest": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0", "vue": "^3.0.0" }, "peerDependenciesMeta": { @@ -3891,9 +3886,6 @@ "@tanstack/solid-start": { "optional": true }, - "better-sqlite3": { - "optional": true - }, "drizzle-kit": { "optional": true }, @@ -9449,9 +9441,9 @@ } }, "node_modules/zod": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", - "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", + "version": "4.6.5", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.6.5.tgz", + "integrity": "sha512-v5l/aFXZQeai4awLbOpSoHecE9UiMrnfx75tEXLjNonXVARxQ5mOeipTjROUchszUNCqnE+hqAMujRsRHsut2Q==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" diff --git a/package.json b/package.json index 2b1d3fe3..2ec44f78 100644 --- a/package.json +++ b/package.json @@ -101,13 +101,13 @@ } }, "peerDependencies": { - "better-auth": ">=1.7.1 <1.8.0", + "better-auth": ">=1.7.7 <1.8.0", "convex": "^1.25.0", "react": "^18.3.1 || ^19.0.0" }, "devDependencies": { - "@better-auth/core": "1.7.1", - "@better-auth/test-utils": "1.7.1", + "@better-auth/core": "1.7.7", + "@better-auth/test-utils": "1.7.7", "@convex-dev/eslint-plugin": "2.0.0", "@edge-runtime/vm": "5.0.0", "@eslint/eslintrc": "3.3.5", @@ -118,7 +118,7 @@ "@types/react": "19.2.14", "@types/react-dom": "19.2.3", "@types/semver": "7.7.1", - "better-auth": "1.7.1", + "better-auth": "1.7.7", "chokidar-cli": "3.0.0", "concurrently": "10.0.3", "convex": "1.41.0", @@ -141,7 +141,7 @@ "types": "./dist/client/index.d.ts", "module": "./dist/client/index.js", "dependencies": { - "@better-fetch/fetch": "^1.1.18", + "@better-fetch/fetch": "^1.3.2", "common-tags": "^1.8.2", "convex-helpers": "^0.1.95", "jose": "^6.1.0", diff --git a/src/client/adapter-compatibility.test.ts b/src/client/adapter-compatibility.test.ts new file mode 100644 index 00000000..0b198f2a --- /dev/null +++ b/src/client/adapter-compatibility.test.ts @@ -0,0 +1,96 @@ +/// + +import { describe, expect, it } from "vitest"; +import { convexTest } from "convex-test"; +import { api } from "../component/_generated/api.js"; +import schema from "../component/schema.js"; +import { convexAdapter } from "./adapter.js"; + +const setup = () => { + const t = convexTest(schema, import.meta.glob("../component/**/*.*s")); + const adapter = convexAdapter( + { + runQuery: t.query.bind(t), + runMutation: t.mutation.bind(t), + } as any, + { adapter: api.adapter } as any + )({}); + return { t, adapter }; +}; + +describe("Better Auth 1.7.7 compatibility", () => { + // Use the real factory so modelKey is injected upstream. Convex validators + // reject leaked metadata on both normal and OR/paginated component calls. + it.each([false, true])( + "keeps modelKey out of component calls (OR: %s)", + async (or) => { + const { adapter } = setup(); + await adapter.create({ + model: "user", + data: { name: "Alice", email: "alice@example.com" }, + }); + const where = or + ? [ + { field: "name", value: "Alice", connector: "OR" as const }, + { + field: "email", + value: "alice@example.com", + connector: "OR" as const, + }, + ] + : [{ field: "name", value: "Alice" }]; + expect(await adapter.findOne({ model: "user", where })).toMatchObject({ + name: "Alice", + }); + expect(await adapter.findMany({ model: "user", where })).toHaveLength(1); + expect(await adapter.count({ model: "user", where })).toBe(1); + expect( + await adapter.updateMany({ + model: "user", + where, + update: { emailVerified: true }, + }) + ).toBe(1); + expect(await adapter.findOne({ model: "user", where })).toMatchObject({ + emailVerified: true, + }); + expect(await adapter.deleteMany({ model: "user", where })).toBe(1); + expect(await adapter.count({ model: "user", where })).toBe(0); + } + ); + + it("creates and updates core accounts without an issuer backfill", async () => { + const { t, adapter } = setup(); + const account = await adapter.create({ + model: "account", + data: { + accountId: "subject", + providerId: "provider-a", + userId: "user-a", + createdAt: new Date(), + updatedAt: new Date(), + }, + }); + await adapter.create({ + model: "account", + data: { + accountId: "subject", + providerId: "provider-b", + userId: "user-b", + createdAt: new Date(), + updatedAt: new Date(), + }, + }); + await adapter.update({ + model: "account", + where: [{ field: "id", value: account.id }], + update: { scope: "openid" }, + }); + const accounts = await t.run((ctx) => ctx.db.query("account").collect()); + expect(accounts).toHaveLength(2); + expect(accounts.every((row) => row.issuer === undefined)).toBe(true); + expect(accounts.find((row) => row.providerId === "provider-a")?.scope).toBe( + "openid" + ); + }); +}); diff --git a/src/client/adapter.test.ts b/src/client/adapter.test.ts index c5fe58ee..f6139834 100644 --- a/src/client/adapter.test.ts +++ b/src/client/adapter.test.ts @@ -25,6 +25,8 @@ import { renameModelUserTableTestSuite, } from "../test/adapter-factory/index.js"; +import { constraintsProfileOptions } from "../component/testProfiles/auth-options.constraints.js"; + const MIN_NODE_MAJOR = 24; const currentNodeMajor = Number.parseInt( process.versions.node.split(".")[0] ?? "0", @@ -91,6 +93,7 @@ const getOverrideBetterAuthOptions = ( type AdapterModule = ComponentApi["adapter"]; type TestProfileName = + | "adapterConstraints" | "adapterAdditionalFields" | "adapterPluginTable" | "adapterRenameField" @@ -169,10 +172,28 @@ if (currentNodeMajor < MIN_NODE_MAJOR) { }), transactionsTestSuite({ disableTests: { ALL: true } }), coreAuthFlowTestSuite(), - convexCustomTestSuite(), ], }); + const constraintsProfileClient = createClient( + profileApi("adapterConstraints"), + { verbose: false } + ); + const { execute: executeConstraintsProfile } = await testAdapter({ + adapter: () => constraintsProfileClient.adapter(wrappedCtx), + runMigrations: noMigrations, + overrideBetterAuthOptions: (opts) => + getOverrideBetterAuthOptions({ + ...opts, + plugins: [ + ...(opts.plugins ?? []), + ...constraintsProfileOptions.plugins, + ], + }), + prefixTests: "profile:custom-constraints", + tests: [convexCustomTestSuite()], + }); + const { execute: executeAdditionalFieldsProfile } = await testAdapter({ adapter: () => additionalFieldsProfileClient.adapter(wrappedCtx), runMigrations: noMigrations, @@ -225,6 +246,7 @@ if (currentNodeMajor < MIN_NODE_MAJOR) { }); executeBaseProfile(); + executeConstraintsProfile(); executeAdditionalFieldsProfile(); executePluginTableProfile(); executeRenameFieldProfile(); diff --git a/src/client/adapter.ts b/src/client/adapter.ts index 43944443..7e44a405 100644 --- a/src/client/adapter.ts +++ b/src/client/adapter.ts @@ -84,8 +84,7 @@ const handlePagination = async ( // A page that returns nothing and doesn't advance the cursor while !isDone // can never terminate; fail loudly instead of looping forever. const advanced = state.cursor !== cursorBeforePage; - const produced = - (result.page?.length ?? 0) > 0 || (result.count ?? 0) > 0; + const produced = (result.page?.length ?? 0) > 0 || (result.count ?? 0) > 0; if (!state.isDone && !advanced && !produced) { throw new Error( "handlePagination made no forward progress; aborting to avoid an infinite pagination loop" @@ -259,15 +258,13 @@ export const convexAdapter = < where: (Where & { join?: undefined })[]; }) => { const results = await asyncMap(data.where, async (w) => - handlePagination( - async ({ paginationOpts }) => { - return await ctx.runQuery(api.adapter.findMany, { - model: data.model as TableNames, - where: parseWhere(w), - paginationOpts, - }); - } - ) + handlePagination(async ({ paginationOpts }) => { + return await ctx.runQuery(api.adapter.findMany, { + model: data.model as TableNames, + where: parseWhere(w), + paginationOpts, + }); + }) ); const ids = dedupeDocsById(results.flatMap((r) => r.docs)) .map((doc) => getDocId(doc)) @@ -300,7 +297,8 @@ export const convexAdapter = < onCreateHandle: onCreateHandle, }); }, - findOne: async (data): Promise => { + // modelKey is Better Auth metadata, not a Convex component argument. + findOne: async ({ modelKey: _modelKey, ...data }): Promise => { if (data.where?.every((w) => w.connector === "OR")) { for (const w of data.where) { const result = await ctx.runQuery(api.adapter.findOne, { @@ -319,7 +317,7 @@ export const convexAdapter = < where: parseWhere(data.where), }); }, - findMany: async (data): Promise => { + findMany: async ({ modelKey: _modelKey, ...data }): Promise => { if (data.offset) { throw new Error("offset not supported"); } @@ -367,7 +365,7 @@ export const convexAdapter = < ); return result.docs; }, - count: async (data) => { + count: async ({ modelKey: _modelKey, ...data }) => { // Yes, count is just findMany returning a number. if (data.where?.some((w) => w.connector === "OR")) { const results = await asyncMap(data.where, async (w) => @@ -487,7 +485,7 @@ export const convexAdapter = < onDeleteHandle: onDeleteHandle, }); }, - deleteMany: async (data) => { + deleteMany: async ({ modelKey: _modelKey, ...data }) => { if (!("runMutation" in ctx)) { throw new Error("ctx is not a mutation ctx"); } @@ -527,7 +525,7 @@ export const convexAdapter = < }); return result.count; }, - updateMany: async (data) => { + updateMany: async ({ modelKey: _modelKey, ...data }) => { if (!("runMutation" in ctx)) { throw new Error("ctx is not a mutation ctx"); } diff --git a/src/client/create-schema.test.ts b/src/client/create-schema.test.ts index 619acb49..8c7e9027 100644 --- a/src/client/create-schema.test.ts +++ b/src/client/create-schema.test.ts @@ -14,7 +14,6 @@ describe("createSchema", () => { references: { field: "id", model: "user" }, }, providerId: { type: "string", required: true }, - issuer: { type: "string", required: true }, accountId: { type: "string", required: true }, }, }, @@ -23,7 +22,7 @@ describe("createSchema", () => { const { code } = await createSchema({ tables }); expect(code).toContain( - '.index("userId_providerId_issuer_accountId", ["userId","providerId","issuer","accountId"])' + '.index("userId_providerId_accountId", ["userId","providerId","accountId"])' ); expect(code).toContain('.index("userId", ["userId"])'); }); diff --git a/src/client/create-schema.ts b/src/client/create-schema.ts index 836fea60..2fcf2fee 100644 --- a/src/client/create-schema.ts +++ b/src/client/create-schema.ts @@ -20,7 +20,7 @@ export const indexFields = { "accountId", ["accountId", "providerId"], ["providerId", "userId"], - ["userId", "providerId", "issuer", "accountId"], + ["userId", "providerId", "accountId"], "userId", ], rateLimit: ["key"], diff --git a/src/client/required-fields.test.ts b/src/client/required-fields.test.ts index 0de2e75e..be2f8ebf 100644 --- a/src/client/required-fields.test.ts +++ b/src/client/required-fields.test.ts @@ -3,8 +3,8 @@ import { describe, expect, it } from "vitest"; import { convexTest } from "convex-test"; import { createFunctionHandle } from "convex/server"; -import type { BetterAuthOptions } from "better-auth"; -import schema from "../component/schema.js"; +import { constraintsProfileOptions } from "../component/testProfiles/auth-options.constraints.js"; +import schema from "../component/testProfiles/schema.constraints.js"; import { api, internal } from "../component/_generated/api.js"; import type { DataModel } from "../component/_generated/dataModel.js"; import { createClient } from "./index.js"; @@ -24,7 +24,7 @@ const createAccount = async ( onCreateHandle?: string ) => await t.run(async (ctx) => - ctx.runMutation(api.adapter.create, { + ctx.runMutation(api.testProfiles.adapterConstraints.create, { input: { model: "account", data: accountData() }, onCreateHandle, }) @@ -54,32 +54,45 @@ describe("required fields", () => { const mutation = t.run(async (ctx) => { if (operation === "updateOne") { - return await ctx.runMutation(api.adapter.updateOne, { - input: { - model: "account", - update: { issuer: null }, - where: [{ field: "accountId", operator: "eq", value: "user-1" }], - }, - }); + return await ctx.runMutation( + api.testProfiles.adapterConstraints.updateOne, + { + input: { + model: "account", + update: { issuer: null }, + where: [ + { field: "accountId", operator: "eq", value: "user-1" }, + ], + }, + } + ); } if (operation === "incrementOne") { - return await ctx.runMutation(api.adapter.incrementOne, { + return await ctx.runMutation( + api.testProfiles.adapterConstraints.incrementOne, + { + input: { + model: "account", + increment: { updatedAt: 1 }, + set: { issuer: null }, + where: [ + { field: "accountId", operator: "eq", value: "user-1" }, + ], + }, + } + ); + } + return await ctx.runMutation( + api.testProfiles.adapterConstraints.updateMany, + { input: { model: "account", - increment: { updatedAt: 1 }, - set: { issuer: null }, + update: { issuer: null }, where: [{ field: "accountId", operator: "eq", value: "user-1" }], }, - }); - } - return await ctx.runMutation(api.adapter.updateMany, { - input: { - model: "account", - update: { issuer: null }, - where: [{ field: "accountId", operator: "eq", value: "user-1" }], - }, - paginationOpts: { cursor: null, numItems: 10 }, - }); + paginationOpts: { cursor: null, numItems: 10 }, + } + ); }); await expect(mutation).rejects.toThrow( @@ -106,7 +119,7 @@ describe("required fields", () => { }, ]; if (operation === "updateOne") { - await ctx.runMutation(api.adapter.updateOne, { + await ctx.runMutation(api.testProfiles.adapterConstraints.updateOne, { input: { model: "account", update: { accessToken: "updated-token" }, @@ -116,16 +129,19 @@ describe("required fields", () => { return; } if (operation === "incrementOne") { - await ctx.runMutation(api.adapter.incrementOne, { - input: { - model: "account", - increment: { updatedAt: 1 }, - where, - }, - }); + await ctx.runMutation( + api.testProfiles.adapterConstraints.incrementOne, + { + input: { + model: "account", + increment: { updatedAt: 1 }, + where, + }, + } + ); return; } - await ctx.runMutation(api.adapter.updateMany, { + await ctx.runMutation(api.testProfiles.adapterConstraints.updateMany, { input: { model: "account", update: { scope: "updated-scope" }, @@ -162,12 +178,15 @@ describe("required fields", () => { }); } }); - const adapter = createClient({ adapter: api.adapter } as any, { - verbose: false, - }).adapter({ + const adapter = createClient( + { adapter: api.testProfiles.adapterConstraints } as any, + { + verbose: false, + } + ).adapter({ runMutation: t.mutation.bind(t), runQuery: t.query.bind(t), - } as any)({} as BetterAuthOptions); + } as any)(constraintsProfileOptions); await expect( adapter.updateMany({ @@ -193,10 +212,13 @@ describe("required fields", () => { const handle = await createFunctionHandle( internal.testTriggerHandlers.accountOnCreateIssuerClearer ); - return await ctx.runMutation(api.adapter.create, { - input: { model: "account", data: accountData() }, - onCreateHandle: handle, - }); + return await ctx.runMutation( + api.testProfiles.adapterConstraints.create, + { + input: { model: "account", data: accountData() }, + onCreateHandle: handle, + } + ); }) ).rejects.toThrow("Missing required field account.issuer"); expect(await t.run((ctx) => ctx.db.query("account").collect())).toEqual([]); @@ -211,14 +233,17 @@ describe("required fields", () => { const handle = await createFunctionHandle( internal.testTriggerHandlers.accountOnUpdateIssuerClearer ); - return await ctx.runMutation(api.adapter.updateOne, { - input: { - model: "account", - update: { updatedAt: Date.now() + 1 }, - where: [{ field: "accountId", operator: "eq", value: "user-1" }], - }, - onUpdateHandle: handle, - }); + return await ctx.runMutation( + api.testProfiles.adapterConstraints.updateOne, + { + input: { + model: "account", + update: { updatedAt: Date.now() + 1 }, + where: [{ field: "accountId", operator: "eq", value: "user-1" }], + }, + onUpdateHandle: handle, + } + ); }) ).rejects.toThrow("Trigger cleared required field account.issuer"); @@ -244,7 +269,7 @@ describe("required fields", () => { const handle = await createFunctionHandle( internal.testTriggerHandlers.accountOnUpdateNoop ); - await ctx.runMutation(api.adapter.updateOne, { + await ctx.runMutation(api.testProfiles.adapterConstraints.updateOne, { input: { model: "account", update: { scope: "updated-scope" }, diff --git a/src/client/triggers.test.ts b/src/client/triggers.test.ts index 6b7ad078..6b668e0f 100644 --- a/src/client/triggers.test.ts +++ b/src/client/triggers.test.ts @@ -3,7 +3,7 @@ import { describe, expect, it } from "vitest"; import { convexTest } from "convex-test"; import { createFunctionHandle } from "convex/server"; -import schema from "../component/schema.js"; +import schema from "../component/testProfiles/schema.constraints.js"; import { api, internal } from "../component/_generated/api.js"; const baseSessionData = () => ({ @@ -34,13 +34,13 @@ const accountData = ({ }); describe("trigger result propagation", () => { - it("api.adapter.create returns the doc reflecting onCreateHandle writes", async () => { + it("api.testProfiles.adapterConstraints.create returns the doc reflecting onCreateHandle writes", async () => { const t = convexTest(schema, import.meta.glob("../component/**/*.*s")); const result = await t.run(async (ctx) => { const handle = await createFunctionHandle( internal.testTriggerHandlers.sessionOnCreateUpdater ); - return await ctx.runMutation(api.adapter.create, { + return await ctx.runMutation(api.testProfiles.adapterConstraints.create, { input: { model: "session", data: { ...baseSessionData(), token: "create-token-1" }, @@ -51,10 +51,10 @@ describe("trigger result propagation", () => { expect(result.userAgent).toBe("trigger-ran-on-create"); }); - it("api.adapter.updateOne returns the doc reflecting onUpdateHandle writes", async () => { + it("api.testProfiles.adapterConstraints.updateOne returns the doc reflecting onUpdateHandle writes", async () => { const t = convexTest(schema, import.meta.glob("../component/**/*.*s")); const result = await t.run(async (ctx) => { - await ctx.runMutation(api.adapter.create, { + await ctx.runMutation(api.testProfiles.adapterConstraints.create, { input: { model: "session", data: { ...baseSessionData(), token: "update-token-1" }, @@ -63,22 +63,27 @@ describe("trigger result propagation", () => { const handle = await createFunctionHandle( internal.testTriggerHandlers.sessionOnUpdateUpdater ); - return await ctx.runMutation(api.adapter.updateOne, { - input: { - model: "session", - update: { userAgent: "set-by-update" }, - where: [{ field: "token", operator: "eq", value: "update-token-1" }], - }, - onUpdateHandle: handle, - }); + return await ctx.runMutation( + api.testProfiles.adapterConstraints.updateOne, + { + input: { + model: "session", + update: { userAgent: "set-by-update" }, + where: [ + { field: "token", operator: "eq", value: "update-token-1" }, + ], + }, + onUpdateHandle: handle, + } + ); }); expect(result).toMatchObject({ userAgent: "trigger-ran-on-update" }); }); - it("api.adapter.incrementOne returns the doc reflecting onUpdateHandle writes", async () => { + it("api.testProfiles.adapterConstraints.incrementOne returns the doc reflecting onUpdateHandle writes", async () => { const t = convexTest(schema, import.meta.glob("../component/**/*.*s")); const result = await t.run(async (ctx) => { - await ctx.runMutation(api.adapter.create, { + await ctx.runMutation(api.testProfiles.adapterConstraints.create, { input: { model: "session", data: { @@ -91,20 +96,23 @@ describe("trigger result propagation", () => { const handle = await createFunctionHandle( internal.testTriggerHandlers.sessionOnUpdateUpdater ); - return await ctx.runMutation(api.adapter.incrementOne, { - input: { - model: "session", - increment: { expiresAt: 1 }, - where: [ - { - field: "token", - operator: "eq", - value: "increment-token-1", - }, - ], - }, - onUpdateHandle: handle, - }); + return await ctx.runMutation( + api.testProfiles.adapterConstraints.incrementOne, + { + input: { + model: "session", + increment: { expiresAt: 1 }, + where: [ + { + field: "token", + operator: "eq", + value: "increment-token-1", + }, + ], + }, + onUpdateHandle: handle, + } + ); }); expect(result).toMatchObject({ expiresAt: 101, @@ -112,11 +120,11 @@ describe("trigger result propagation", () => { }); }); - it("api.adapter.updateMany runs onUpdateHandle for every updated doc", async () => { + it("api.testProfiles.adapterConstraints.updateMany runs onUpdateHandle for every updated doc", async () => { const t = convexTest(schema, import.meta.glob("../component/**/*.*s")); const result = await t.run(async (ctx) => { for (const token of ["bulk-update-token-1", "bulk-update-token-2"]) { - await ctx.runMutation(api.adapter.create, { + await ctx.runMutation(api.testProfiles.adapterConstraints.create, { input: { model: "session", data: { ...baseSessionData(), token }, @@ -126,15 +134,18 @@ describe("trigger result propagation", () => { const handle = await createFunctionHandle( internal.testTriggerHandlers.sessionOnUpdateUpdater ); - return await ctx.runMutation(api.adapter.updateMany, { - input: { - model: "session", - update: { userAgent: "set-by-update" }, - where: [{ field: "userId", operator: "eq", value: "user-1" }], - }, - paginationOpts: { cursor: null, numItems: 10 }, - onUpdateHandle: handle, - }); + return await ctx.runMutation( + api.testProfiles.adapterConstraints.updateMany, + { + input: { + model: "session", + update: { userAgent: "set-by-update" }, + where: [{ field: "userId", operator: "eq", value: "user-1" }], + }, + paginationOpts: { cursor: null, numItems: 10 }, + onUpdateHandle: handle, + } + ); }); expect(result.count).toBe(2); @@ -150,7 +161,7 @@ describe("trigger unique constraint enforcement", () => { it("rolls back an onCreate trigger that creates an account identity collision", async () => { const t = convexTest(schema, import.meta.glob("../component/**/*.*s")); await t.run((ctx) => - ctx.runMutation(api.adapter.create, { + ctx.runMutation(api.testProfiles.adapterConstraints.create, { input: { model: "account", data: accountData({ @@ -168,18 +179,21 @@ describe("trigger unique constraint enforcement", () => { const handle = await createFunctionHandle( internal.testTriggerHandlers.accountOnCreateIssuerCollider ); - return await ctx.runMutation(api.adapter.create, { - input: { - model: "account", - data: accountData({ - issuer: "safe-issuer", - accountId: "collision-account", - providerId: "candidate-provider", - userId: "candidate-user", - }), - }, - onCreateHandle: handle, - }); + return await ctx.runMutation( + api.testProfiles.adapterConstraints.create, + { + input: { + model: "account", + data: accountData({ + issuer: "safe-issuer", + accountId: "collision-account", + providerId: "candidate-provider", + userId: "candidate-user", + }), + }, + onCreateHandle: handle, + } + ); }) ).rejects.toThrow( "account unique constraint issuer+accountId already exists" @@ -211,7 +225,7 @@ describe("trigger unique constraint enforcement", () => { userId: "candidate-user", }), ]) { - await ctx.runMutation(api.adapter.create, { + await ctx.runMutation(api.testProfiles.adapterConstraints.create, { input: { model: "account", data }, }); } @@ -222,20 +236,23 @@ describe("trigger unique constraint enforcement", () => { const handle = await createFunctionHandle( internal.testTriggerHandlers.accountOnUpdateAccountIdCollider ); - return await ctx.runMutation(api.adapter.updateOne, { - input: { - model: "account", - update: { scope: "updated-scope" }, - where: [ - { - field: "providerId", - operator: "eq", - value: "candidate-provider", - }, - ], - }, - onUpdateHandle: handle, - }); + return await ctx.runMutation( + api.testProfiles.adapterConstraints.updateOne, + { + input: { + model: "account", + update: { scope: "updated-scope" }, + where: [ + { + field: "providerId", + operator: "eq", + value: "candidate-provider", + }, + ], + }, + onUpdateHandle: handle, + } + ); }) ).rejects.toThrow( "account unique constraint issuer+accountId already exists" @@ -275,7 +292,7 @@ describe("trigger unique constraint enforcement", () => { userId: "candidate-user", }), ]) { - await ctx.runMutation(api.adapter.create, { + await ctx.runMutation(api.testProfiles.adapterConstraints.create, { input: { model: "account", data }, }); } @@ -286,21 +303,24 @@ describe("trigger unique constraint enforcement", () => { const handle = await createFunctionHandle( internal.testTriggerHandlers.accountOnUpdateAccountIdCollider ); - return await ctx.runMutation(api.adapter.updateMany, { - input: { - model: "account", - update: { scope: "updated-scope" }, - where: [ - { - field: "providerId", - operator: "eq", - value: "candidate-provider", - }, - ], - }, - paginationOpts: { cursor: null, numItems: 10 }, - onUpdateHandle: handle, - }); + return await ctx.runMutation( + api.testProfiles.adapterConstraints.updateMany, + { + input: { + model: "account", + update: { scope: "updated-scope" }, + where: [ + { + field: "providerId", + operator: "eq", + value: "candidate-provider", + }, + ], + }, + paginationOpts: { cursor: null, numItems: 10 }, + onUpdateHandle: handle, + } + ); }) ).rejects.toThrow( "account unique constraint issuer+accountId already exists" diff --git a/src/client/update-many-pagination.test.ts b/src/client/update-many-pagination.test.ts index 6b89ad29..0dc58231 100644 --- a/src/client/update-many-pagination.test.ts +++ b/src/client/update-many-pagination.test.ts @@ -2,7 +2,7 @@ import { describe, expect, it } from "vitest"; import { convexTest } from "convex-test"; -import schema from "../component/schema.js"; +import schema from "../component/testProfiles/schema.constraints.js"; import { api } from "../component/_generated/api.js"; describe("updateMany pagination", () => { @@ -27,7 +27,7 @@ describe("updateMany pagination", () => { }); await expect( - t.mutation(api.adapter.updateMany, { + t.mutation(api.testProfiles.adapterConstraints.updateMany, { input: { model: "account", update: { issuer: "updated-issuer" }, diff --git a/src/component/_generated/api.ts b/src/component/_generated/api.ts index 4fd79042..c0e1b2fc 100644 --- a/src/component/_generated/api.ts +++ b/src/component/_generated/api.ts @@ -11,6 +11,7 @@ import type * as adapter from "../adapter.js"; import type * as migrations from "../migrations.js"; import type * as testProfiles_adapterAdditionalFields from "../testProfiles/adapterAdditionalFields.js"; +import type * as testProfiles_adapterConstraints from "../testProfiles/adapterConstraints.js"; import type * as testProfiles_adapterOrganizationJoins from "../testProfiles/adapterOrganizationJoins.js"; import type * as testProfiles_adapterPluginTable from "../testProfiles/adapterPluginTable.js"; import type * as testProfiles_adapterRenameField from "../testProfiles/adapterRenameField.js"; @@ -29,6 +30,7 @@ const fullApi: ApiFromModules<{ adapter: typeof adapter; migrations: typeof migrations; "testProfiles/adapterAdditionalFields": typeof testProfiles_adapterAdditionalFields; + "testProfiles/adapterConstraints": typeof testProfiles_adapterConstraints; "testProfiles/adapterOrganizationJoins": typeof testProfiles_adapterOrganizationJoins; "testProfiles/adapterPluginTable": typeof testProfiles_adapterPluginTable; "testProfiles/adapterRenameField": typeof testProfiles_adapterRenameField; diff --git a/src/component/_generated/component.ts b/src/component/_generated/component.ts index 9a550292..e4dd6382 100644 --- a/src/component/_generated/component.ts +++ b/src/component/_generated/component.ts @@ -2399,28 +2399,6 @@ export type ComponentApi = >; }; migrations: { - backfillAccountIssuers: FunctionReference< - "mutation", - "internal", - { - paginationOpts: { - cursor: string | null; - endCursor?: string | null; - id?: number; - maximumBytesRead?: number; - maximumRowsRead?: number; - numItems: number; - }; - providerIssuers: Record; - }, - { - alreadyMigrated: number; - continueCursor: string; - isDone: boolean; - migrated: number; - }, - Name - >; clearLegacyOAuthProviderRecords: FunctionReference< "mutation", "internal", @@ -2474,28 +2452,6 @@ export type ComponentApi = }, Name >; - validateAccountIssuerBackfill: FunctionReference< - "query", - "internal", - { - paginationOpts: { - cursor: string | null; - endCursor?: string | null; - id?: number; - maximumBytesRead?: number; - maximumRowsRead?: number; - numItems: number; - }; - providerIssuers: Record; - }, - { - alreadyMigrated: number; - continueCursor: string; - isDone: boolean; - pending: number; - }, - Name - >; }; testProfiles: { adapterAdditionalFields: { @@ -4926,6 +4882,2389 @@ export type ComponentApi = Name >; }; + adapterConstraints: { + create: FunctionReference< + "mutation", + "internal", + { + input: + | { + data: { + createdAt: number; + displayUsername?: null | string; + email: string; + emailVerified: boolean; + image?: null | string; + isAnonymous?: null | boolean; + name: string; + phoneNumber?: null | string; + phoneNumberVerified?: null | boolean; + twoFactorEnabled?: null | boolean; + updatedAt: number; + userId?: null | string; + username?: null | string; + }; + model: "user"; + } + | { + data: { + createdAt: number; + expiresAt: number; + ipAddress?: null | string; + token: string; + updatedAt: number; + userAgent?: null | string; + userId: string; + }; + model: "session"; + } + | { + data: { + accessToken?: null | string; + accessTokenExpiresAt?: null | number; + accountId: string; + createdAt: number; + idToken?: null | string; + issuer?: null | string; + password?: null | string; + providerId: string; + refreshToken?: null | string; + refreshTokenExpiresAt?: null | number; + scope?: null | string; + updatedAt: number; + userId: string; + }; + model: "account"; + } + | { + data: { + createdAt: number; + expiresAt: number; + identifier: string; + updatedAt: number; + value: string; + }; + model: "verification"; + } + | { + data: { + backupCodes: string; + failedVerificationCount?: null | number; + lockedUntil?: null | number; + secret: string; + userId: string; + verified?: null | boolean; + }; + model: "twoFactor"; + } + | { + data: { + clientId?: null | string; + clientSecret?: null | string; + createdAt?: null | number; + disabled?: null | boolean; + icon?: null | string; + metadata?: null | string; + name?: null | string; + redirectUrls?: null | string; + type?: null | string; + updatedAt?: null | number; + userId?: null | string; + }; + model: "oauthApplication"; + } + | { + data: { + accessToken?: null | string; + accessTokenExpiresAt?: null | number; + clientId?: null | string; + createdAt?: null | number; + refreshToken?: null | string; + refreshTokenExpiresAt?: null | number; + scopes?: null | string; + updatedAt?: null | number; + userId?: null | string; + }; + model: "oauthAccessToken"; + } + | { + data: { + clientId?: null | string; + consentGiven?: null | boolean; + createdAt?: null | number; + scopes?: null | string; + updatedAt?: null | number; + userId?: null | string; + }; + model: "oauthConsent"; + } + | { + data: { + alg?: null | string; + createdAt: number; + crv?: null | string; + expiresAt?: null | number; + privateKey: string; + publicKey: string; + }; + model: "jwks"; + } + | { + data: { count: number; key: string; lastRequest: number }; + model: "rateLimit"; + }; + onCreateHandle?: string; + select?: Array; + }, + any, + Name + >; + deleteMany: FunctionReference< + "mutation", + "internal", + { + input: + | { + model: "user"; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "name" + | "email" + | "emailVerified" + | "image" + | "createdAt" + | "updatedAt" + | "twoFactorEnabled" + | "isAnonymous" + | "username" + | "displayUsername" + | "phoneNumber" + | "phoneNumberVerified" + | "userId" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "session"; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "expiresAt" + | "token" + | "createdAt" + | "updatedAt" + | "ipAddress" + | "userAgent" + | "userId" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "account"; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "issuer" + | "accountId" + | "providerId" + | "userId" + | "accessToken" + | "refreshToken" + | "idToken" + | "accessTokenExpiresAt" + | "refreshTokenExpiresAt" + | "scope" + | "password" + | "createdAt" + | "updatedAt" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "verification"; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "identifier" + | "value" + | "expiresAt" + | "createdAt" + | "updatedAt" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "twoFactor"; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "secret" + | "backupCodes" + | "userId" + | "verified" + | "failedVerificationCount" + | "lockedUntil" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "oauthApplication"; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "name" + | "icon" + | "metadata" + | "clientId" + | "clientSecret" + | "redirectUrls" + | "type" + | "disabled" + | "userId" + | "createdAt" + | "updatedAt" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "oauthAccessToken"; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "accessToken" + | "refreshToken" + | "accessTokenExpiresAt" + | "refreshTokenExpiresAt" + | "clientId" + | "userId" + | "scopes" + | "createdAt" + | "updatedAt" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "oauthConsent"; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "clientId" + | "userId" + | "scopes" + | "createdAt" + | "updatedAt" + | "consentGiven" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "jwks"; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "publicKey" + | "privateKey" + | "createdAt" + | "expiresAt" + | "alg" + | "crv" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "rateLimit"; + where?: Array<{ + connector?: "AND" | "OR"; + field: "key" | "count" | "lastRequest" | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + }; + onDeleteHandle?: string; + paginationOpts: { + cursor: string | null; + endCursor?: string | null; + id?: number; + maximumBytesRead?: number; + maximumRowsRead?: number; + numItems: number; + }; + }, + any, + Name + >; + deleteOne: FunctionReference< + "mutation", + "internal", + { + input: + | { + model: "user"; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "name" + | "email" + | "emailVerified" + | "image" + | "createdAt" + | "updatedAt" + | "twoFactorEnabled" + | "isAnonymous" + | "username" + | "displayUsername" + | "phoneNumber" + | "phoneNumberVerified" + | "userId" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "session"; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "expiresAt" + | "token" + | "createdAt" + | "updatedAt" + | "ipAddress" + | "userAgent" + | "userId" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "account"; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "issuer" + | "accountId" + | "providerId" + | "userId" + | "accessToken" + | "refreshToken" + | "idToken" + | "accessTokenExpiresAt" + | "refreshTokenExpiresAt" + | "scope" + | "password" + | "createdAt" + | "updatedAt" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "verification"; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "identifier" + | "value" + | "expiresAt" + | "createdAt" + | "updatedAt" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "twoFactor"; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "secret" + | "backupCodes" + | "userId" + | "verified" + | "failedVerificationCount" + | "lockedUntil" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "oauthApplication"; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "name" + | "icon" + | "metadata" + | "clientId" + | "clientSecret" + | "redirectUrls" + | "type" + | "disabled" + | "userId" + | "createdAt" + | "updatedAt" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "oauthAccessToken"; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "accessToken" + | "refreshToken" + | "accessTokenExpiresAt" + | "refreshTokenExpiresAt" + | "clientId" + | "userId" + | "scopes" + | "createdAt" + | "updatedAt" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "oauthConsent"; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "clientId" + | "userId" + | "scopes" + | "createdAt" + | "updatedAt" + | "consentGiven" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "jwks"; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "publicKey" + | "privateKey" + | "createdAt" + | "expiresAt" + | "alg" + | "crv" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "rateLimit"; + where?: Array<{ + connector?: "AND" | "OR"; + field: "key" | "count" | "lastRequest" | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + }; + onDeleteHandle?: string; + }, + any, + Name + >; + findMany: FunctionReference< + "query", + "internal", + { + join?: any; + limit?: number; + model: + | "user" + | "session" + | "account" + | "verification" + | "twoFactor" + | "oauthApplication" + | "oauthAccessToken" + | "oauthConsent" + | "jwks" + | "rateLimit"; + offset?: number; + paginationOpts: { + cursor: string | null; + endCursor?: string | null; + id?: number; + maximumBytesRead?: number; + maximumRowsRead?: number; + numItems: number; + }; + select?: Array; + sortBy?: { direction: "asc" | "desc"; field: string }; + where?: Array<{ + connector?: "AND" | "OR"; + field: string; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + }, + any, + Name + >; + findOne: FunctionReference< + "query", + "internal", + { + join?: any; + model: + | "user" + | "session" + | "account" + | "verification" + | "twoFactor" + | "oauthApplication" + | "oauthAccessToken" + | "oauthConsent" + | "jwks" + | "rateLimit"; + select?: Array; + where?: Array<{ + connector?: "AND" | "OR"; + field: string; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + }, + any, + Name + >; + incrementOne: FunctionReference< + "mutation", + "internal", + { + input: + | { + increment: Record; + model: "user"; + set?: { + createdAt?: number; + displayUsername?: null | string; + email?: string; + emailVerified?: boolean; + image?: null | string; + isAnonymous?: null | boolean; + name?: string; + phoneNumber?: null | string; + phoneNumberVerified?: null | boolean; + twoFactorEnabled?: null | boolean; + updatedAt?: number; + userId?: null | string; + username?: null | string; + }; + where: Array<{ + connector?: "AND" | "OR"; + field: + | "name" + | "email" + | "emailVerified" + | "image" + | "createdAt" + | "updatedAt" + | "twoFactorEnabled" + | "isAnonymous" + | "username" + | "displayUsername" + | "phoneNumber" + | "phoneNumberVerified" + | "userId" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + increment: Record; + model: "session"; + set?: { + createdAt?: number; + expiresAt?: number; + ipAddress?: null | string; + token?: string; + updatedAt?: number; + userAgent?: null | string; + userId?: string; + }; + where: Array<{ + connector?: "AND" | "OR"; + field: + | "expiresAt" + | "token" + | "createdAt" + | "updatedAt" + | "ipAddress" + | "userAgent" + | "userId" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + increment: Record; + model: "account"; + set?: { + accessToken?: null | string; + accessTokenExpiresAt?: null | number; + accountId?: string; + createdAt?: number; + idToken?: null | string; + issuer?: null | string; + password?: null | string; + providerId?: string; + refreshToken?: null | string; + refreshTokenExpiresAt?: null | number; + scope?: null | string; + updatedAt?: number; + userId?: string; + }; + where: Array<{ + connector?: "AND" | "OR"; + field: + | "issuer" + | "accountId" + | "providerId" + | "userId" + | "accessToken" + | "refreshToken" + | "idToken" + | "accessTokenExpiresAt" + | "refreshTokenExpiresAt" + | "scope" + | "password" + | "createdAt" + | "updatedAt" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + increment: Record; + model: "verification"; + set?: { + createdAt?: number; + expiresAt?: number; + identifier?: string; + updatedAt?: number; + value?: string; + }; + where: Array<{ + connector?: "AND" | "OR"; + field: + | "identifier" + | "value" + | "expiresAt" + | "createdAt" + | "updatedAt" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + increment: Record; + model: "twoFactor"; + set?: { + backupCodes?: string; + failedVerificationCount?: null | number; + lockedUntil?: null | number; + secret?: string; + userId?: string; + verified?: null | boolean; + }; + where: Array<{ + connector?: "AND" | "OR"; + field: + | "secret" + | "backupCodes" + | "userId" + | "verified" + | "failedVerificationCount" + | "lockedUntil" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + increment: Record; + model: "oauthApplication"; + set?: { + clientId?: null | string; + clientSecret?: null | string; + createdAt?: null | number; + disabled?: null | boolean; + icon?: null | string; + metadata?: null | string; + name?: null | string; + redirectUrls?: null | string; + type?: null | string; + updatedAt?: null | number; + userId?: null | string; + }; + where: Array<{ + connector?: "AND" | "OR"; + field: + | "name" + | "icon" + | "metadata" + | "clientId" + | "clientSecret" + | "redirectUrls" + | "type" + | "disabled" + | "userId" + | "createdAt" + | "updatedAt" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + increment: Record; + model: "oauthAccessToken"; + set?: { + accessToken?: null | string; + accessTokenExpiresAt?: null | number; + clientId?: null | string; + createdAt?: null | number; + refreshToken?: null | string; + refreshTokenExpiresAt?: null | number; + scopes?: null | string; + updatedAt?: null | number; + userId?: null | string; + }; + where: Array<{ + connector?: "AND" | "OR"; + field: + | "accessToken" + | "refreshToken" + | "accessTokenExpiresAt" + | "refreshTokenExpiresAt" + | "clientId" + | "userId" + | "scopes" + | "createdAt" + | "updatedAt" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + increment: Record; + model: "oauthConsent"; + set?: { + clientId?: null | string; + consentGiven?: null | boolean; + createdAt?: null | number; + scopes?: null | string; + updatedAt?: null | number; + userId?: null | string; + }; + where: Array<{ + connector?: "AND" | "OR"; + field: + | "clientId" + | "userId" + | "scopes" + | "createdAt" + | "updatedAt" + | "consentGiven" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + increment: Record; + model: "jwks"; + set?: { + alg?: null | string; + createdAt?: number; + crv?: null | string; + expiresAt?: null | number; + privateKey?: string; + publicKey?: string; + }; + where: Array<{ + connector?: "AND" | "OR"; + field: + | "publicKey" + | "privateKey" + | "createdAt" + | "expiresAt" + | "alg" + | "crv" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + increment: Record; + model: "rateLimit"; + set?: { count?: number; key?: string; lastRequest?: number }; + where: Array<{ + connector?: "AND" | "OR"; + field: "key" | "count" | "lastRequest" | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + }; + onUpdateHandle?: string; + }, + any, + Name + >; + updateMany: FunctionReference< + "mutation", + "internal", + { + input: + | { + model: "user"; + update: { + createdAt?: number; + displayUsername?: null | string; + email?: string; + emailVerified?: boolean; + image?: null | string; + isAnonymous?: null | boolean; + name?: string; + phoneNumber?: null | string; + phoneNumberVerified?: null | boolean; + twoFactorEnabled?: null | boolean; + updatedAt?: number; + userId?: null | string; + username?: null | string; + }; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "name" + | "email" + | "emailVerified" + | "image" + | "createdAt" + | "updatedAt" + | "twoFactorEnabled" + | "isAnonymous" + | "username" + | "displayUsername" + | "phoneNumber" + | "phoneNumberVerified" + | "userId" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "session"; + update: { + createdAt?: number; + expiresAt?: number; + ipAddress?: null | string; + token?: string; + updatedAt?: number; + userAgent?: null | string; + userId?: string; + }; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "expiresAt" + | "token" + | "createdAt" + | "updatedAt" + | "ipAddress" + | "userAgent" + | "userId" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "account"; + update: { + accessToken?: null | string; + accessTokenExpiresAt?: null | number; + accountId?: string; + createdAt?: number; + idToken?: null | string; + issuer?: null | string; + password?: null | string; + providerId?: string; + refreshToken?: null | string; + refreshTokenExpiresAt?: null | number; + scope?: null | string; + updatedAt?: number; + userId?: string; + }; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "issuer" + | "accountId" + | "providerId" + | "userId" + | "accessToken" + | "refreshToken" + | "idToken" + | "accessTokenExpiresAt" + | "refreshTokenExpiresAt" + | "scope" + | "password" + | "createdAt" + | "updatedAt" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "verification"; + update: { + createdAt?: number; + expiresAt?: number; + identifier?: string; + updatedAt?: number; + value?: string; + }; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "identifier" + | "value" + | "expiresAt" + | "createdAt" + | "updatedAt" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "twoFactor"; + update: { + backupCodes?: string; + failedVerificationCount?: null | number; + lockedUntil?: null | number; + secret?: string; + userId?: string; + verified?: null | boolean; + }; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "secret" + | "backupCodes" + | "userId" + | "verified" + | "failedVerificationCount" + | "lockedUntil" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "oauthApplication"; + update: { + clientId?: null | string; + clientSecret?: null | string; + createdAt?: null | number; + disabled?: null | boolean; + icon?: null | string; + metadata?: null | string; + name?: null | string; + redirectUrls?: null | string; + type?: null | string; + updatedAt?: null | number; + userId?: null | string; + }; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "name" + | "icon" + | "metadata" + | "clientId" + | "clientSecret" + | "redirectUrls" + | "type" + | "disabled" + | "userId" + | "createdAt" + | "updatedAt" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "oauthAccessToken"; + update: { + accessToken?: null | string; + accessTokenExpiresAt?: null | number; + clientId?: null | string; + createdAt?: null | number; + refreshToken?: null | string; + refreshTokenExpiresAt?: null | number; + scopes?: null | string; + updatedAt?: null | number; + userId?: null | string; + }; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "accessToken" + | "refreshToken" + | "accessTokenExpiresAt" + | "refreshTokenExpiresAt" + | "clientId" + | "userId" + | "scopes" + | "createdAt" + | "updatedAt" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "oauthConsent"; + update: { + clientId?: null | string; + consentGiven?: null | boolean; + createdAt?: null | number; + scopes?: null | string; + updatedAt?: null | number; + userId?: null | string; + }; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "clientId" + | "userId" + | "scopes" + | "createdAt" + | "updatedAt" + | "consentGiven" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "jwks"; + update: { + alg?: null | string; + createdAt?: number; + crv?: null | string; + expiresAt?: null | number; + privateKey?: string; + publicKey?: string; + }; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "publicKey" + | "privateKey" + | "createdAt" + | "expiresAt" + | "alg" + | "crv" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "rateLimit"; + update: { + count?: number; + key?: string; + lastRequest?: number; + }; + where?: Array<{ + connector?: "AND" | "OR"; + field: "key" | "count" | "lastRequest" | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + }; + onUpdateHandle?: string; + paginationOpts: { + cursor: string | null; + endCursor?: string | null; + id?: number; + maximumBytesRead?: number; + maximumRowsRead?: number; + numItems: number; + }; + }, + any, + Name + >; + updateOne: FunctionReference< + "mutation", + "internal", + { + input: + | { + model: "user"; + update: { + createdAt?: number; + displayUsername?: null | string; + email?: string; + emailVerified?: boolean; + image?: null | string; + isAnonymous?: null | boolean; + name?: string; + phoneNumber?: null | string; + phoneNumberVerified?: null | boolean; + twoFactorEnabled?: null | boolean; + updatedAt?: number; + userId?: null | string; + username?: null | string; + }; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "name" + | "email" + | "emailVerified" + | "image" + | "createdAt" + | "updatedAt" + | "twoFactorEnabled" + | "isAnonymous" + | "username" + | "displayUsername" + | "phoneNumber" + | "phoneNumberVerified" + | "userId" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "session"; + update: { + createdAt?: number; + expiresAt?: number; + ipAddress?: null | string; + token?: string; + updatedAt?: number; + userAgent?: null | string; + userId?: string; + }; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "expiresAt" + | "token" + | "createdAt" + | "updatedAt" + | "ipAddress" + | "userAgent" + | "userId" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "account"; + update: { + accessToken?: null | string; + accessTokenExpiresAt?: null | number; + accountId?: string; + createdAt?: number; + idToken?: null | string; + issuer?: null | string; + password?: null | string; + providerId?: string; + refreshToken?: null | string; + refreshTokenExpiresAt?: null | number; + scope?: null | string; + updatedAt?: number; + userId?: string; + }; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "issuer" + | "accountId" + | "providerId" + | "userId" + | "accessToken" + | "refreshToken" + | "idToken" + | "accessTokenExpiresAt" + | "refreshTokenExpiresAt" + | "scope" + | "password" + | "createdAt" + | "updatedAt" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "verification"; + update: { + createdAt?: number; + expiresAt?: number; + identifier?: string; + updatedAt?: number; + value?: string; + }; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "identifier" + | "value" + | "expiresAt" + | "createdAt" + | "updatedAt" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "twoFactor"; + update: { + backupCodes?: string; + failedVerificationCount?: null | number; + lockedUntil?: null | number; + secret?: string; + userId?: string; + verified?: null | boolean; + }; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "secret" + | "backupCodes" + | "userId" + | "verified" + | "failedVerificationCount" + | "lockedUntil" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "oauthApplication"; + update: { + clientId?: null | string; + clientSecret?: null | string; + createdAt?: null | number; + disabled?: null | boolean; + icon?: null | string; + metadata?: null | string; + name?: null | string; + redirectUrls?: null | string; + type?: null | string; + updatedAt?: null | number; + userId?: null | string; + }; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "name" + | "icon" + | "metadata" + | "clientId" + | "clientSecret" + | "redirectUrls" + | "type" + | "disabled" + | "userId" + | "createdAt" + | "updatedAt" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "oauthAccessToken"; + update: { + accessToken?: null | string; + accessTokenExpiresAt?: null | number; + clientId?: null | string; + createdAt?: null | number; + refreshToken?: null | string; + refreshTokenExpiresAt?: null | number; + scopes?: null | string; + updatedAt?: null | number; + userId?: null | string; + }; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "accessToken" + | "refreshToken" + | "accessTokenExpiresAt" + | "refreshTokenExpiresAt" + | "clientId" + | "userId" + | "scopes" + | "createdAt" + | "updatedAt" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "oauthConsent"; + update: { + clientId?: null | string; + consentGiven?: null | boolean; + createdAt?: null | number; + scopes?: null | string; + updatedAt?: null | number; + userId?: null | string; + }; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "clientId" + | "userId" + | "scopes" + | "createdAt" + | "updatedAt" + | "consentGiven" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "jwks"; + update: { + alg?: null | string; + createdAt?: number; + crv?: null | string; + expiresAt?: null | number; + privateKey?: string; + publicKey?: string; + }; + where?: Array<{ + connector?: "AND" | "OR"; + field: + | "publicKey" + | "privateKey" + | "createdAt" + | "expiresAt" + | "alg" + | "crv" + | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + } + | { + model: "rateLimit"; + update: { + count?: number; + key?: string; + lastRequest?: number; + }; + where?: Array<{ + connector?: "AND" | "OR"; + field: "key" | "count" | "lastRequest" | "_id"; + mode?: "sensitive" | "insensitive"; + operator?: + | "lt" + | "lte" + | "gt" + | "gte" + | "eq" + | "in" + | "not_in" + | "ne" + | "contains" + | "starts_with" + | "ends_with"; + value: + | string + | number + | boolean + | Array + | Array + | null; + }>; + }; + onUpdateHandle?: string; + }, + any, + Name + >; + }; adapterOrganizationJoins: { create: FunctionReference< "mutation", diff --git a/src/component/migrations.test.ts b/src/component/migrations.test.ts index 187ae80c..ac65696e 100644 --- a/src/component/migrations.test.ts +++ b/src/component/migrations.test.ts @@ -6,542 +6,11 @@ import { api } from "./_generated/api.js"; import schema from "./schema.js"; const modules = import.meta.glob("./**/*.*s"); -const backfillAccountIssuers = api.migrations.backfillAccountIssuers; -const validateAccountIssuerBackfill = - api.migrations.validateAccountIssuerBackfill; const listLegacyOAuthApplications = api.migrations.listLegacyOAuthApplications; const clearLegacyOAuthProviderRecords = api.migrations.clearLegacyOAuthProviderRecords; -describe("backfillAccountIssuers", () => { - it("backfills trusted issuers and normalizes credential account IDs", async () => { - const t = convexTest(schema, modules); - await t.run(async (ctx) => { - await ctx.db.insert("account", { - accountId: "legacy-credential-id", - providerId: "credential", - userId: "user-1", - createdAt: 1, - updatedAt: 1, - }); - await ctx.db.insert("account", { - accountId: "github-subject", - providerId: "github", - userId: "user-2", - createdAt: 1, - updatedAt: 1, - }); - }); - - await t.mutation(backfillAccountIssuers, { - providerIssuers: { github: "local:oauth:github" }, - paginationOpts: { cursor: null, numItems: 10 }, - }); - - const accounts = await t.run((ctx) => ctx.db.query("account").collect()); - expect(accounts).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - issuer: "local:credential", - accountId: "user-1", - }), - expect.objectContaining({ - issuer: "local:oauth:github", - accountId: "github-subject", - }), - ]) - ); - }); - - it("rolls back a page when a provider mapping is missing", async () => { - const t = convexTest(schema, modules); - await t.run((ctx) => - ctx.db.insert("account", { - accountId: "subject", - providerId: "custom-provider", - userId: "user-1", - createdAt: 1, - updatedAt: 1, - }) - ); - - await expect( - t.mutation(backfillAccountIssuers, { - providerIssuers: {}, - paginationOpts: { cursor: null, numItems: 10 }, - }) - ).rejects.toThrow("Missing trusted issuer mapping"); - const [account] = await t.run((ctx) => ctx.db.query("account").collect()); - expect(account?.issuer).toBeUndefined(); - }); - - it("does not treat inherited properties as provider issuer mappings", async () => { - const t = convexTest(schema, modules); - await t.run((ctx) => - ctx.db.insert("account", { - accountId: "subject", - providerId: "constructor", - userId: "user-1", - createdAt: 1, - updatedAt: 1, - }) - ); - const args = { - providerIssuers: {}, - paginationOpts: { cursor: null, numItems: 10 }, - }; - - await expect(t.query(validateAccountIssuerBackfill, args)).rejects.toThrow( - "Missing trusted issuer mapping" - ); - await expect(t.mutation(backfillAccountIssuers, args)).rejects.toThrow( - "Missing trusted issuer mapping" - ); - - const [account] = await t.run((ctx) => ctx.db.query("account").collect()); - expect(account?.issuer).toBeUndefined(); - }); - - it("rejects issuer mappings with surrounding whitespace", async () => { - const t = convexTest(schema, modules); - await t.run((ctx) => - ctx.db.insert("account", { - accountId: "subject", - providerId: "custom-provider", - userId: "user-1", - createdAt: 1, - updatedAt: 1, - }) - ); - - await expect( - t.query(validateAccountIssuerBackfill, { - providerIssuers: { - "custom-provider": " https://issuer.example.com ", - }, - paginationOpts: { cursor: null, numItems: 10 }, - }) - ).rejects.toThrow("contains leading or trailing whitespace"); - }); - - it.each([ - { - accountId: " ", - issuer: "https://issuer.example.com", - label: "blank accountId", - }, - { - accountId: "undefined", - issuer: "https://issuer.example.com", - label: "undefined accountId", - }, - { - accountId: "null", - issuer: "https://issuer.example.com", - label: "null accountId", - }, - { - accountId: "subject", - issuer: "undefined", - label: "undefined issuer", - }, - { - accountId: "subject", - issuer: "null", - label: "null issuer", - }, - ])("rejects a Better Auth-invalid $label", async ({ accountId, issuer }) => { - const t = convexTest(schema, modules); - await t.run((ctx) => - ctx.db.insert("account", { - accountId, - providerId: "custom-provider", - userId: "user-1", - createdAt: 1, - updatedAt: 1, - }) - ); - const args = { - providerIssuers: { "custom-provider": issuer }, - paginationOpts: { cursor: null, numItems: 10 }, - }; - - await expect(t.query(validateAccountIssuerBackfill, args)).rejects.toThrow( - "is not a valid Better Auth account identity" - ); - await expect(t.mutation(backfillAccountIssuers, args)).rejects.toThrow( - "is not a valid Better Auth account identity" - ); - - const [account] = await t.run((ctx) => ctx.db.query("account").collect()); - expect(account).toMatchObject({ accountId }); - expect(account?.issuer).toBeUndefined(); - }); - - it("validates supplied external issuer mappings before reading a page", async () => { - const t = convexTest(schema, modules); - - await expect( - t.query(validateAccountIssuerBackfill, { - providerIssuers: { - "unused-provider": " https://issuer.example.com ", - }, - paginationOpts: { cursor: null, numItems: 10 }, - }) - ).rejects.toThrow( - "Trusted issuer mapping for providerId unused-provider contains leading or trailing whitespace" - ); - }); - - it("ignores issuer mappings for fixed local authentication methods", async () => { - const t = convexTest(schema, modules); - await t.run((ctx) => - ctx.db.insert("account", { - accountId: "legacy-credential-id", - providerId: "credential", - userId: "user-1", - createdAt: 1, - updatedAt: 1, - }) - ); - - await expect( - t.query(validateAccountIssuerBackfill, { - providerIssuers: { credential: "ignored" }, - paginationOpts: { cursor: null, numItems: 10 }, - }) - ).resolves.toMatchObject({ pending: 1, isDone: true }); - }); - - it("distinguishes an invalid stored issuer from an invalid mapping", async () => { - const t = convexTest(schema, modules); - await t.run((ctx) => - ctx.db.insert("account", { - accountId: "subject", - issuer: " https://issuer.example.com ", - providerId: "custom-provider", - userId: "user-1", - createdAt: 1, - updatedAt: 1, - }) - ); - - await expect( - t.query(validateAccountIssuerBackfill, { - providerIssuers: {}, - paginationOpts: { cursor: null, numItems: 10 }, - }) - ).rejects.toThrow( - "Existing issuer for providerId custom-provider contains leading or trailing whitespace" - ); - }); - - it("rejects non-positive page sizes on every migration entry point", async () => { - const t = convexTest(schema, modules); - const paginationOpts = { cursor: null, numItems: 0 }; - - await expect( - t.query(validateAccountIssuerBackfill, { - providerIssuers: {}, - paginationOpts, - }) - ).rejects.toThrow("paginationOpts.numItems must be a positive integer"); - await expect( - t.mutation(backfillAccountIssuers, { - providerIssuers: {}, - paginationOpts, - }) - ).rejects.toThrow("paginationOpts.numItems must be a positive integer"); - await expect( - t.query(listLegacyOAuthApplications, { paginationOpts }) - ).rejects.toThrow("paginationOpts.numItems must be a positive integer"); - await expect( - t.mutation(clearLegacyOAuthProviderRecords, { - table: "oauthApplication", - paginationOpts, - }) - ).rejects.toThrow("paginationOpts.numItems must be a positive integer"); - }); - - it("rolls back a page when trusted issuer mappings reveal a collision", async () => { - const t = convexTest(schema, modules); - await t.run(async (ctx) => { - await ctx.db.insert("account", { - accountId: "same-subject", - providerId: "oidc-primary", - userId: "user-1", - createdAt: 1, - updatedAt: 1, - }); - await ctx.db.insert("account", { - accountId: "same-subject", - providerId: "oidc-secondary", - userId: "user-2", - createdAt: 1, - updatedAt: 1, - }); - }); - - await expect( - t.mutation(backfillAccountIssuers, { - providerIssuers: { - "oidc-primary": "https://issuer.example.com", - "oidc-secondary": "https://issuer.example.com", - }, - paginationOpts: { cursor: null, numItems: 10 }, - }) - ).rejects.toThrow("Account identity collision"); - const accounts = await t.run((ctx) => ctx.db.query("account").collect()); - expect(accounts.every((account) => account.issuer === undefined)).toBe( - true - ); - }); - - it("detects collisions beyond the current page before writing", async () => { - const t = convexTest(schema, modules); - await t.run(async (ctx) => { - await ctx.db.insert("account", { - accountId: "same-subject", - providerId: "oidc-primary", - userId: "user-1", - createdAt: 1, - updatedAt: 1, - }); - await ctx.db.insert("account", { - accountId: "same-subject", - providerId: "oidc-secondary", - userId: "user-2", - createdAt: 1, - updatedAt: 1, - }); - }); - - const args = { - providerIssuers: { - "oidc-primary": "https://issuer.example.com", - "oidc-secondary": "https://issuer.example.com", - }, - paginationOpts: { cursor: null, numItems: 1 }, - }; - await expect(t.query(validateAccountIssuerBackfill, args)).rejects.toThrow( - "Account identity collision" - ); - await expect(t.mutation(backfillAccountIssuers, args)).rejects.toThrow( - "Account identity collision" - ); - - const accounts = await t.run((ctx) => ctx.db.query("account").collect()); - expect(accounts.every((account) => account.issuer === undefined)).toBe( - true - ); - }); - - it("detects credential collisions after account ID normalization", async () => { - const t = convexTest(schema, modules); - await t.run(async (ctx) => { - await ctx.db.insert("account", { - accountId: "legacy-credential-1", - providerId: "credential", - userId: "same-user", - createdAt: 1, - updatedAt: 1, - }); - await ctx.db.insert("account", { - accountId: "legacy-credential-2", - providerId: "credential", - userId: "same-user", - createdAt: 1, - updatedAt: 1, - }); - }); - - await expect( - t.mutation(backfillAccountIssuers, { - providerIssuers: {}, - paginationOpts: { cursor: null, numItems: 1 }, - }) - ).rejects.toThrow("Account identity collision"); - const accounts = await t.run((ctx) => ctx.db.query("account").collect()); - expect(accounts.every((account) => account.issuer === undefined)).toBe( - true - ); - }); - - it("is idempotent for accounts already written by Better Auth 1.7", async () => { - const t = convexTest(schema, modules); - await t.run((ctx) => - ctx.db.insert("account", { - issuer: "local:oauth:github", - accountId: "github-subject", - providerId: "github", - userId: "user-1", - createdAt: 1, - updatedAt: 1, - }) - ); - - const result = await t.mutation(backfillAccountIssuers, { - providerIssuers: {}, - paginationOpts: { cursor: null, numItems: 10 }, - }); - expect(result).toMatchObject({ - isDone: true, - migrated: 0, - alreadyMigrated: 1, - }); - }); - - it("preserves per-row issuers for Microsoft accounts from different tenants", async () => { - const t = convexTest(schema, modules); - await t.run(async (ctx) => { - await ctx.db.insert("account", { - issuer: "https://login.microsoftonline.com/tenant-a/v2.0", - accountId: "directory-object-id", - providerId: "microsoft", - userId: "user-1", - createdAt: 1, - updatedAt: 1, - }); - await ctx.db.insert("account", { - issuer: "https://login.microsoftonline.com/tenant-b/v2.0", - accountId: "directory-object-id", - providerId: "microsoft", - userId: "user-2", - createdAt: 1, - updatedAt: 1, - }); - }); - - const args = { - providerIssuers: {}, - paginationOpts: { cursor: null, numItems: 10 }, - }; - await expect( - t.query(validateAccountIssuerBackfill, args) - ).resolves.toMatchObject({ - isDone: true, - pending: 0, - alreadyMigrated: 2, - }); - await expect( - t.mutation(backfillAccountIssuers, args) - ).resolves.toMatchObject({ - isDone: true, - migrated: 0, - alreadyMigrated: 2, - }); - - const accounts = await t.run((ctx) => ctx.db.query("account").collect()); - expect(accounts.map(({ issuer }) => issuer).sort()).toEqual([ - "https://login.microsoftonline.com/tenant-a/v2.0", - "https://login.microsoftonline.com/tenant-b/v2.0", - ]); - }); - - it("rejects one Microsoft issuer for accounts from different tenants", async () => { - const t = convexTest(schema, modules); - await t.run(async (ctx) => { - await ctx.db.insert("account", { - issuer: "https://login.microsoftonline.com/tenant-a/v2.0", - accountId: "directory-object-id", - providerId: "microsoft", - userId: "user-1", - createdAt: 1, - updatedAt: 1, - }); - await ctx.db.insert("account", { - issuer: "https://login.microsoftonline.com/tenant-b/v2.0", - accountId: "directory-object-id", - providerId: "microsoft", - userId: "user-2", - createdAt: 1, - updatedAt: 1, - }); - }); - - const args = { - providerIssuers: { - microsoft: "https://login.microsoftonline.com/tenant-a/v2.0", - }, - paginationOpts: { cursor: null, numItems: 10 }, - }; - await expect(t.query(validateAccountIssuerBackfill, args)).rejects.toThrow( - "does not match trusted issuer" - ); - await expect(t.mutation(backfillAccountIssuers, args)).rejects.toThrow( - "does not match trusted issuer" - ); - - const accounts = await t.run((ctx) => ctx.db.query("account").collect()); - expect( - accounts - .map(({ issuer, accountId }) => ({ issuer, accountId })) - .sort((left, right) => left.issuer!.localeCompare(right.issuer!)) - ).toEqual([ - { - issuer: "https://login.microsoftonline.com/tenant-a/v2.0", - accountId: "directory-object-id", - }, - { - issuer: "https://login.microsoftonline.com/tenant-b/v2.0", - accountId: "directory-object-id", - }, - ]); - }); - - it("validates and migrates every cursor page", async () => { - const t = convexTest(schema, modules); - await t.run(async (ctx) => { - for (let index = 0; index < 3; index += 1) { - await ctx.db.insert("account", { - accountId: `subject-${index}`, - providerId: "github", - userId: `user-${index}`, - createdAt: 1, - updatedAt: 1, - }); - } - }); - - let cursor: string | null = null; - let validated = 0; - do { - const result: { - pending: number; - isDone: boolean; - continueCursor: string; - } = await t.query(validateAccountIssuerBackfill, { - providerIssuers: { github: "local:oauth:github" }, - paginationOpts: { cursor, numItems: 1 }, - }); - validated += result.pending; - cursor = result.isDone ? null : result.continueCursor; - if (result.isDone) break; - } while (cursor); - expect(validated).toBe(3); - - cursor = null; - let migrated = 0; - do { - const result: { - migrated: number; - isDone: boolean; - continueCursor: string; - } = await t.mutation(backfillAccountIssuers, { - providerIssuers: { github: "local:oauth:github" }, - paginationOpts: { cursor, numItems: 1 }, - }); - migrated += result.migrated; - cursor = result.isDone ? null : result.continueCursor; - if (result.isDone) break; - } while (cursor); - expect(migrated).toBe(3); - const accounts = await t.run((ctx) => ctx.db.query("account").collect()); - expect( - accounts.every((account) => account.issuer === "local:oauth:github") - ).toBe(true); - }); - +describe("legacy OAuth provider migrations", () => { it("treats completed cursors as terminal instead of restarting", async () => { const t = convexTest(schema, modules); await t.run(async (ctx) => { @@ -557,44 +26,8 @@ describe("backfillAccountIssuers", () => { clientSecret: "legacy-secret", }); }); - const providerIssuers = { github: "local:oauth:github" }; const initialPage = { cursor: null, numItems: 10 }; - const validation = await t.query(validateAccountIssuerBackfill, { - providerIssuers, - paginationOpts: initialPage, - }); - expect(validation.isDone).toBe(true); - await expect( - t.query(validateAccountIssuerBackfill, { - providerIssuers, - paginationOpts: { - cursor: validation.continueCursor, - numItems: 10, - }, - }) - ).resolves.toMatchObject({ - isDone: true, - pending: 0, - alreadyMigrated: 0, - }); - - const backfill = await t.mutation(backfillAccountIssuers, { - providerIssuers, - paginationOpts: initialPage, - }); - expect(backfill.isDone).toBe(true); - await expect( - t.mutation(backfillAccountIssuers, { - providerIssuers, - paginationOpts: { cursor: backfill.continueCursor, numItems: 10 }, - }) - ).resolves.toMatchObject({ - isDone: true, - migrated: 0, - alreadyMigrated: 0, - }); - const exported = await t.query(listLegacyOAuthApplications, { paginationOpts: initialPage, }); @@ -635,25 +68,8 @@ describe("backfillAccountIssuers", () => { consentGiven: true, }); }); - const providerIssuers = { github: "local:oauth:github" }; const paginationOpts = { cursor: null, numItems: 10 }; - const validation = await t.query(validateAccountIssuerBackfill, { - providerIssuers, - paginationOpts, - }); - await expect( - t.mutation(backfillAccountIssuers, { - providerIssuers, - paginationOpts: { - cursor: validation.continueCursor, - numItems: 10, - }, - }) - ).rejects.toThrow( - "Migration cursor is not valid for backfillAccountIssuers" - ); - const clearedApplications = await t.mutation( clearLegacyOAuthProviderRecords, { diff --git a/src/component/migrations.ts b/src/component/migrations.ts index d9ea2a07..618abc9f 100644 --- a/src/component/migrations.ts +++ b/src/component/migrations.ts @@ -5,27 +5,9 @@ import { import { v } from "convex/values"; import { omit } from "convex-helpers"; import { paginator } from "convex-helpers/server/pagination"; -import type { Doc } from "./_generated/dataModel.js"; import { mutation, query } from "./_generated/server.js"; -import type { QueryCtx } from "./_generated/server.js"; import schema from "./schema.js"; -type ProviderIssuers = Record; - -const invalidAccountKeyValues = new Set(["undefined", "null"]); - -const assertValidAccountKeyValue = ( - field: "accountId" | "issuer", - value: string, - providerId: string -) => { - if (!value.trim() || invalidAccountKeyValues.has(value)) { - throw new Error( - `Account ${field} for providerId ${providerId} is not a valid Better Auth account identity` - ); - } -}; - const migrationCompleteCursorPrefix = "convex-better-auth:migration-complete:"; const migrationCompleteCursorFor = (operation: string) => @@ -57,144 +39,6 @@ const assertValidPaginationOpts = (paginationOpts: { } }; -const assertValidProviderIssuers = (providerIssuers: ProviderIssuers) => { - for (const [providerId, issuer] of Object.entries(providerIssuers)) { - if (providerId === "credential" || providerId === "siwe") { - continue; - } - if (!issuer.trim()) { - throw new Error( - `Trusted issuer mapping for providerId ${providerId} must not be empty` - ); - } - if (invalidAccountKeyValues.has(issuer)) { - throw new Error( - `Trusted issuer mapping for providerId ${providerId} is not a valid Better Auth account identity` - ); - } - if (issuer !== issuer.trim()) { - throw new Error( - `Trusted issuer mapping for providerId ${providerId} contains leading or trailing whitespace` - ); - } - if (issuer === "local:credential" || issuer === "local:siwe") { - throw new Error( - `Issuer ${issuer} is reserved for a local authentication method` - ); - } - } -}; - -const targetAccountIdentity = ( - account: Doc<"account">, - providerIssuers: ProviderIssuers -) => { - const accountId = - account.providerId === "credential" ? account.userId : account.accountId; - const fixedIssuer = - account.providerId === "credential" - ? "local:credential" - : account.providerId === "siwe" - ? "local:siwe" - : undefined; - const mappedIssuer = Object.prototype.hasOwnProperty.call( - providerIssuers, - account.providerId - ) - ? providerIssuers[account.providerId] - : undefined; - const existingIssuer = account.issuer ?? undefined; - - if (existingIssuer !== undefined && !existingIssuer.trim()) { - throw new Error( - `Existing issuer for providerId ${account.providerId} must not be empty` - ); - } - if ( - existingIssuer !== undefined && - existingIssuer !== existingIssuer.trim() - ) { - throw new Error( - `Existing issuer for providerId ${account.providerId} contains leading or trailing whitespace` - ); - } - - const issuer = fixedIssuer ?? mappedIssuer ?? existingIssuer; - - if (!issuer) { - throw new Error( - `Missing trusted issuer mapping for providerId ${account.providerId}` - ); - } - if ( - !fixedIssuer && - (issuer === "local:credential" || issuer === "local:siwe") - ) { - throw new Error( - `Issuer ${issuer} is reserved for a local authentication method` - ); - } - if (existingIssuer && existingIssuer !== issuer) { - throw new Error( - `Existing issuer ${existingIssuer} does not match trusted issuer ${issuer} for providerId ${account.providerId}` - ); - } - - assertValidAccountKeyValue("accountId", accountId, account.providerId); - assertValidAccountKeyValue("issuer", issuer, account.providerId); - - return { accountId, issuer }; -}; - -const assertNoAccountIdentityCollision = async ( - ctx: Pick, - account: Doc<"account">, - providerIssuers: ProviderIssuers -) => { - const target = targetAccountIdentity(account, providerIssuers); - const candidates = await ctx.db - .query("account") - .withIndex("accountId", (index) => index.eq("accountId", target.accountId)) - .collect(); - - if (account.providerId === "credential") { - const credentialCandidates = await ctx.db - .query("account") - .withIndex("providerId_userId", (index) => - index.eq("providerId", "credential").eq("userId", account.userId) - ) - .collect(); - const candidateIds = new Set(candidates.map((candidate) => candidate._id)); - candidates.push( - ...credentialCandidates.filter( - (candidate) => !candidateIds.has(candidate._id) - ) - ); - } - - for (const candidate of candidates) { - if (candidate._id === account._id) { - continue; - } - const candidateTarget = targetAccountIdentity(candidate, providerIssuers); - if ( - candidateTarget.issuer === target.issuer && - candidateTarget.accountId === target.accountId - ) { - throw new Error( - `Account identity collision for issuer ${target.issuer} and accountId ${target.accountId}` - ); - } - } - - return target; -}; - -const migrationArgs = { - providerIssuers: v.record(v.string(), v.string()), - paginationOpts: paginationOptsValidator, -}; - const legacyOAuthApplicationExportValidator = v.object({ ...omit(schema.tables.oauthApplication.validator.fields, ["clientSecret"]), _id: v.id("oauthApplication"), @@ -208,20 +52,6 @@ const clearedLegacyRecordsValidator = v.object({ deleted: v.number(), }); -const accountValidationValidator = v.object({ - continueCursor: v.string(), - isDone: v.boolean(), - pending: v.number(), - alreadyMigrated: v.number(), -}); - -const accountBackfillValidator = v.object({ - continueCursor: v.string(), - isDone: v.boolean(), - migrated: v.number(), - alreadyMigrated: v.number(), -}); - export const listLegacyOAuthApplications = query({ args: { paginationOpts: paginationOptsValidator }, returns: paginationResultValidator(legacyOAuthApplicationExportValidator), @@ -293,99 +123,3 @@ export const clearLegacyOAuthProviderRecords = mutation({ }; }, }); - -export const validateAccountIssuerBackfill = query({ - args: migrationArgs, - returns: accountValidationValidator, - handler: async (ctx, args) => { - const operation = "validateAccountIssuerBackfill"; - const completeCursor = migrationCompleteCursorFor(operation); - assertValidPaginationOpts(args.paginationOpts); - assertValidProviderIssuers(args.providerIssuers); - if (isMigrationCompleteCursor(args.paginationOpts.cursor, operation)) { - return { - continueCursor: completeCursor, - isDone: true, - pending: 0, - alreadyMigrated: 0, - }; - } - const result = await paginator(ctx.db, schema) - .query("account") - .paginate(args.paginationOpts); - let pending = 0; - let alreadyMigrated = 0; - - for (const account of result.page) { - const target = await assertNoAccountIdentityCollision( - ctx, - account, - args.providerIssuers - ); - if ( - account.issuer === target.issuer && - account.accountId === target.accountId - ) { - alreadyMigrated += 1; - } else { - pending += 1; - } - } - - return { - continueCursor: result.isDone ? completeCursor : result.continueCursor, - isDone: result.isDone, - pending, - alreadyMigrated, - }; - }, -}); - -export const backfillAccountIssuers = mutation({ - args: migrationArgs, - returns: accountBackfillValidator, - handler: async (ctx, args) => { - const operation = "backfillAccountIssuers"; - const completeCursor = migrationCompleteCursorFor(operation); - assertValidPaginationOpts(args.paginationOpts); - assertValidProviderIssuers(args.providerIssuers); - if (isMigrationCompleteCursor(args.paginationOpts.cursor, operation)) { - return { - continueCursor: completeCursor, - isDone: true, - migrated: 0, - alreadyMigrated: 0, - }; - } - const result = await paginator(ctx.db, schema) - .query("account") - .paginate(args.paginationOpts); - let migrated = 0; - let alreadyMigrated = 0; - - for (const account of result.page) { - const target = await assertNoAccountIdentityCollision( - ctx, - account, - args.providerIssuers - ); - if ( - account.issuer === target.issuer && - account.accountId === target.accountId - ) { - alreadyMigrated += 1; - continue; - } - - await ctx.db.patch("account", account._id, target); - migrated += 1; - } - - return { - continueCursor: result.isDone ? completeCursor : result.continueCursor, - isDone: result.isDone, - migrated, - alreadyMigrated, - }; - }, -}); diff --git a/src/component/schema.test.ts b/src/component/schema.test.ts index df57aa41..34a6010d 100644 --- a/src/component/schema.test.ts +++ b/src/component/schema.test.ts @@ -1,13 +1,15 @@ import { describe, expect, it } from "vitest"; import schema from "./schema.js"; -describe("staged component schema", () => { - it("keeps only the generated account identity index order", () => { - const indexNames = schema.tables.account[" indexes"]().map( - (index) => index.indexDescriptor +describe("account schema", () => { + it("uses the current credential lookup fields without issuer indexes", () => { + const indexes = schema.tables.account[" indexes"](); + expect(indexes).toContainEqual({ + indexDescriptor: "userId_providerId_accountId", + fields: ["userId", "providerId", "accountId"], + }); + expect(indexes.every(({ fields }) => !fields.includes("issuer"))).toBe( + true ); - - expect(indexNames).toContain("issuer_accountId"); - expect(indexNames).not.toContain("accountId_issuer"); }); }); diff --git a/src/component/schema.ts b/src/component/schema.ts index 2a423924..373e23ba 100644 --- a/src/component/schema.ts +++ b/src/component/schema.ts @@ -1,7 +1,7 @@ /** - * Generated from Better Auth 1.7, then intentionally staged for the 0.13 + * Generated from Better Auth 1.7.7, then intentionally staged for the 0.13 * migration. Do not regenerate it without preserving the documented migration - * exceptions: optional account.issuer and the three legacy OAuth provider + * exceptions: optional legacy account.issuer and the three legacy OAuth provider * tables retained below. * To regenerate the schema, from your project root: * @@ -50,8 +50,8 @@ export const tables = { .index("token", ["token"]) .index("userId", ["userId"]), account: defineTable({ - // Temporarily optional so existing deployments can run the 1.7 backfill. - // Better Auth supplies this field for all new accounts. + // Retained only for data written by Better Auth 1.7.0–1.7.2. + // Better Auth 1.7.7 neither requires nor writes issuer. issuer: v.optional(v.union(v.null(), v.string())), accountId: v.string(), providerId: v.string(), @@ -66,14 +66,12 @@ export const tables = { createdAt: v.number(), updatedAt: v.number(), }) - .index("issuer_accountId", ["issuer", "accountId"]) .index("accountId", ["accountId"]) .index("accountId_providerId", ["accountId", "providerId"]) .index("providerId_userId", ["providerId", "userId"]) - .index("userId_providerId_issuer_accountId", [ + .index("userId_providerId_accountId", [ "userId", "providerId", - "issuer", "accountId", ]) .index("userId", ["userId"]), diff --git a/src/component/testProfiles/adapterConstraints.ts b/src/component/testProfiles/adapterConstraints.ts new file mode 100644 index 00000000..7782ae34 --- /dev/null +++ b/src/component/testProfiles/adapterConstraints.ts @@ -0,0 +1,14 @@ +import { createApi } from "../../client/index.js"; +import { constraintsProfileOptions } from "./auth-options.constraints.js"; +import schema from "./schema.constraints.js"; + +export const { + create, + findOne, + findMany, + updateOne, + incrementOne, + updateMany, + deleteOne, + deleteMany, +} = createApi(schema, () => constraintsProfileOptions); diff --git a/src/component/testProfiles/auth-options.constraints.ts b/src/component/testProfiles/auth-options.constraints.ts new file mode 100644 index 00000000..02197d91 --- /dev/null +++ b/src/component/testProfiles/auth-options.constraints.ts @@ -0,0 +1,20 @@ +import type { BetterAuthOptions } from "better-auth/minimal"; +import { options } from "../../auth-options.js"; + +// Exercise custom required fields and compound uniqueness independently of +// Better Auth's core account schema, which no longer requires issuer in 1.7.3+. +export const constraintsProfileOptions = { + ...options, + plugins: [ + ...(options.plugins ?? []), + { + id: "test-account-constraints", + schema: { + account: { + fields: { issuer: { type: "string", required: true } }, + indexes: [{ fields: ["issuer", "accountId"], unique: true }], + }, + }, + }, + ], +} satisfies BetterAuthOptions; diff --git a/src/component/testProfiles/schema.constraints.ts b/src/component/testProfiles/schema.constraints.ts new file mode 100644 index 00000000..984eca4b --- /dev/null +++ b/src/component/testProfiles/schema.constraints.ts @@ -0,0 +1,14 @@ +import { defineSchema, defineTable } from "convex/server"; +import { tables } from "../schema.js"; + +// Only this test profile declares issuer+accountId uniqueness. Production +// accounts use Better Auth 1.7.7's providerId/accountId lookup semantics. +const account = defineTable(tables.account.validator) + .index("issuer_accountId", ["issuer", "accountId"]) + .index("accountId", ["accountId"]) + .index("accountId_providerId", ["accountId", "providerId"]) + .index("providerId_userId", ["providerId", "userId"]) + .index("userId_providerId_accountId", ["userId", "providerId", "accountId"]) + .index("userId", ["userId"]); + +export default defineSchema({ ...tables, account }); diff --git a/src/component/testProfiles/schema.profile-plugin-table.ts b/src/component/testProfiles/schema.profile-plugin-table.ts index ac83f2d5..657211af 100644 --- a/src/component/testProfiles/schema.profile-plugin-table.ts +++ b/src/component/testProfiles/schema.profile-plugin-table.ts @@ -1,6 +1,7 @@ import { defineSchema, defineTable } from "convex/server"; import { v } from "convex/values"; -import { tables as baseTables } from "../schema.js"; +import constraintsSchema from "./schema.constraints.js"; +const baseTables = constraintsSchema.tables; const makeUserTable = () => defineTable({ diff --git a/src/plugins/cross-domain/index.test.ts b/src/plugins/cross-domain/index.test.ts index f602cca4..8e3f0874 100644 --- a/src/plugins/cross-domain/index.test.ts +++ b/src/plugins/cross-domain/index.test.ts @@ -109,7 +109,7 @@ describe("crossDomain plugin", async () => { const { url } = (await response.json()) as { url: string }; const state = new URL(url).searchParams.get("state"); const verification = db.verification.find( - (entry) => entry.identifier === state + (entry) => entry.identifier === `auth-state:${state}` ); expect(verification).toBeDefined(); expect(JSON.parse(verification!.value).callbackURL).toBe(SITE_URL); @@ -124,7 +124,7 @@ describe("crossDomain plugin", async () => { const { url } = (await response.json()) as { url: string }; const state = new URL(url).searchParams.get("state"); const verification = db.verification.find( - (entry) => entry.identifier === state + (entry) => entry.identifier === `auth-state:${state}` ); expect(verification).toBeDefined(); expect(JSON.parse(verification!.value).callbackURL).toBe( diff --git a/src/test/adapter-factory/convex-custom.ts b/src/test/adapter-factory/convex-custom.ts index d9c2db97..b8a7a60d 100644 --- a/src/test/adapter-factory/convex-custom.ts +++ b/src/test/adapter-factory/convex-custom.ts @@ -2,6 +2,7 @@ import { createTestSuite } from "@better-auth/test-utils/adapter"; import type { BetterAuthOptions } from "better-auth"; import { twoFactor } from "better-auth/plugins/two-factor"; import { expect } from "vitest"; +import { constraintsProfileOptions } from "../../component/testProfiles/auth-options.constraints.js"; const withBetterAuthOptions = ( modifyBetterAuthOptions: ( @@ -1011,7 +1012,7 @@ export const convexCustomTestSuite = createTestSuite( ), "should restore Better Auth options after a custom test": async () => { - expect(getBetterAuthOptions().plugins).toBeUndefined(); + expect(getBetterAuthOptions().plugins).toEqual(constraintsProfileOptions.plugins); expect(getBetterAuthOptions().rateLimit).toBeUndefined(); }, From df61d21d3c8bc205f3fb9e0b44baf21db11aa3e4 Mon Sep 17 00:00:00 2001 From: Stephen Shkeda Date: Mon, 12 Oct 2026 21:34:27 -0400 Subject: [PATCH 20/29] Fix filtered pagination cursor advancement Resume from continueCursor rather than the splitCursor inside a page that has already been returned. Cover full and limited reads, count, updateMany and deleteMany across a filtered page split. --- src/client/adapter-compatibility.test.ts | 52 ++++++++++++++++++++++++ src/client/adapter.ts | 8 ++-- 2 files changed, 55 insertions(+), 5 deletions(-) diff --git a/src/client/adapter-compatibility.test.ts b/src/client/adapter-compatibility.test.ts index 0b198f2a..6f098685 100644 --- a/src/client/adapter-compatibility.test.ts +++ b/src/client/adapter-compatibility.test.ts @@ -59,6 +59,58 @@ describe("Better Auth 1.7.7 compatibility", () => { } ); + it("does not revisit rows when filtered pagination splits a page", async () => { + const { t, adapter } = setup(); + await t.run(async (ctx) => { + // Rejected rows force the scan budget to split before 200 matches. + for (let i = 0; i < 4; i++) { + await ctx.db.insert("user", { + name: `Other ${i}`, + email: `other-${i}@example.com`, + emailVerified: false, + createdAt: 1, + updatedAt: 1, + }); + } + for (let i = 0; i < 205; i++) { + await ctx.db.insert("user", { + name: `Fixture ${i}`, + email: `fixture-${i}@example.com`, + emailVerified: false, + createdAt: 1, + updatedAt: 1, + }); + } + }); + const where = [ + { field: "email", operator: "starts_with" as const, value: "fixture-" }, + ]; + expect(await adapter.count({ model: "user", where })).toBe(205); + const users = await adapter.findMany<{ id: string }>({ + model: "user", + where, + limit: 1000, + }); + expect(users).toHaveLength(205); + expect(new Set(users.map((user) => user.id)).size).toBe(205); + const limited = await adapter.findMany<{ id: string }>({ + model: "user", + where, + limit: 200, + }); + expect(limited).toHaveLength(200); + expect(new Set(limited.map((user) => user.id)).size).toBe(200); + expect( + await adapter.updateMany({ + model: "user", + where, + update: { name: "Updated" }, + }) + ).toBe(205); + expect(await adapter.deleteMany({ model: "user", where })).toBe(205); + expect(await adapter.count({ model: "user", where })).toBe(0); + }); + it("creates and updates core accounts without an issuer backfill", async () => { const { t, adapter } = setup(); const account = await adapter.create({ diff --git a/src/client/adapter.ts b/src/client/adapter.ts index 7e44a405..ad73e3fd 100644 --- a/src/client/adapter.ts +++ b/src/client/adapter.ts @@ -46,11 +46,9 @@ const handlePagination = async ( const onResult = ( result: SetOptional, "page"> & { count?: number } ) => { - state.cursor = - result.pageStatus === "SplitRecommended" || - result.pageStatus === "SplitRequired" - ? (result.splitCursor ?? result.continueCursor) - : result.continueCursor; + // splitCursor divides the page we already received. Continuing there would + // revisit its second half and double-count reads and mutations. + state.cursor = result.continueCursor; if (result.page) { state.docs.push(...result.page); state.isDone = (limit && state.docs.length >= limit) || result.isDone; From 4c2a5aa9df828b88071d526a59ebd2bf7cd7f2ef Mon Sep 17 00:00:00 2001 From: Stephen Shkeda Date: Mon, 12 Oct 2026 21:34:40 -0400 Subject: [PATCH 21/29] Fix refresh-token guards for unset optional fields Treat absent fields as null in static eq-null and ne-null comparisons. Better Auth's refresh rotation guards an unset revoked field with eq null, so valid tokens were rejected even with native incrementOne. Add regression cases for absent, explicit-null and set fields, including repeated guarded claims. Indexed null and in/not_in behavior remain separate. Credit @suhailopensource for the same fix in get-convex/better-auth#425, addressing the report by @a-fatah in #399. Co-authored-by: suhailopensource <114918598+suhailopensource@users.noreply.github.com> --- src/client/adapter-compatibility.test.ts | 54 ++++++++++++++++++++++++ src/client/adapter-utils.ts | 6 ++- 2 files changed, 58 insertions(+), 2 deletions(-) diff --git a/src/client/adapter-compatibility.test.ts b/src/client/adapter-compatibility.test.ts index 6f098685..2950df62 100644 --- a/src/client/adapter-compatibility.test.ts +++ b/src/client/adapter-compatibility.test.ts @@ -111,6 +111,60 @@ describe("Better Auth 1.7.7 compatibility", () => { expect(await adapter.count({ model: "user", where })).toBe(0); }); + it.each([undefined, null, "already-set"])( + "guards atomic updates against a null optional field (stored: %s)", + async (image) => { + const { adapter } = setup(); + const user = await adapter.create<{ id: string }>({ + model: "user", + data: { + name: "Alice", + email: "alice@example.com", + ...(image !== undefined ? { image } : {}), + }, + }); + const where = [ + { field: "id", value: user.id }, + { field: "image", operator: "eq" as const, value: null }, + ]; + const expectedMatch = image == null; + expect(Boolean(await adapter.findOne({ model: "user", where }))).toBe( + expectedMatch + ); + expect(await adapter.findMany({ model: "user", where })).toHaveLength( + expectedMatch ? 1 : 0 + ); + expect( + Boolean( + await adapter.findOne({ + model: "user", + where: [ + { field: "id", value: user.id }, + { field: "image", operator: "ne", value: null }, + ], + }) + ) + ).toBe(!expectedMatch); + // OAuth refresh rotation uses this shape: an empty increment with a + // guarded set, where the revocation field may never have been written. + const claimed = await adapter.incrementOne<{ image: string }>({ + model: "user", + where, + increment: {}, + set: { image: "claimed" }, + }); + expect(claimed?.image ?? null).toBe(expectedMatch ? "claimed" : null); + expect( + await adapter.incrementOne({ + model: "user", + where, + increment: {}, + set: { image: "claimed-again" }, + }) + ).toBeNull(); + } + ); + it("creates and updates core accounts without an issuer backfill", async () => { const { t, adapter } = setup(); const account = await adapter.create({ diff --git a/src/client/adapter-utils.ts b/src/client/adapter-utils.ts index dcdf4b45..66eafe76 100644 --- a/src/client/adapter-utils.ts +++ b/src/client/adapter-utils.ts @@ -408,7 +408,9 @@ const filterByWhere = < switch (w.operator) { case undefined: case "eq": { - return value === w.value; + // Convex omits unset optional fields; Better Auth's null guards must + // also match those fields (for example, unrevoked refresh tokens). + return w.value === null ? value == null : value === w.value; } case "in": { return Array.isArray(w.value) && (w.value as any[]).includes(value); @@ -431,7 +433,7 @@ const filterByWhere = < return value === w.value || isGreaterThan(value, w.value); } case "ne": { - return value !== w.value; + return w.value === null ? value != null : value !== w.value; } case "contains": { return typeof value === "string" && value.includes(w.value as string); From a2eed8af52a2f6dd4a50a65b59f2607907b7f0cd Mon Sep 17 00:00:00 2001 From: Stephen Shkeda Date: Mon, 12 Oct 2026 21:34:50 -0400 Subject: [PATCH 22/29] Fix OAuth provider discovery and document signing keys Route root OpenID discovery to the OAuth provider endpoint when configured, for eager and lazy registration. Preserve app-defined routes and the existing non-provider target; keep lazy initialization at request time. Cover default/custom base paths and provider/non-provider registration. Document plugin order, database-backed key requirements, matching algorithms and the distinction between provider discovery and Convex JWT verification. Keep generic endpoint-key cleanup separate. --- .../docs/migrations/migrate-to-0-13.mdx | 31 ++++++ src/client/create-client.test.ts | 94 ++++++++++++++++++- src/client/create-client.ts | 13 ++- 3 files changed, 132 insertions(+), 6 deletions(-) diff --git a/docs/content/docs/migrations/migrate-to-0-13.mdx b/docs/content/docs/migrations/migrate-to-0-13.mdx index d56dbb4b..ec4f844e 100644 --- a/docs/content/docs/migrations/migrate-to-0-13.mdx +++ b/docs/content/docs/migrations/migrate-to-0-13.mdx @@ -199,6 +199,37 @@ pass. Add `--prod` when running against production. before restoring traffic. Restore the backup with the old application version if the conversion fails. +### Configure provider signing keys + +Use database-backed signing keys for this provider setup. Static keys passed to +`convex({ jwks: ... })` are not supported: the JWT plugin signs provider tokens +with database keys, while Convex publishes the static keys. Clients would then +be unable to verify those provider tokens. + +In the runtime plugin array, place `jwt()` before `convex()` and +`oauthProvider()` after `convex()`. These plugins share endpoint keys, so order +matters. `registerRoutes` and `registerRoutesLazy` direct root OpenID discovery +to the OAuth provider when it is configured, so root discovery advertises the +provider issuer. Convex JWT verifiers should continue to use the Convex issuer +and `/api/auth/convex/jwks` directly. + +Configure the JWT plugin to use Convex's public signing-key URL: + +```ts +jwt({ + disableSettingJwtHeader: true, + jwks: { + remoteUrl: `${process.env.CONVEX_SITE_URL}/api/auth/convex/jwks`, + keyPairConfig: { alg: "RS256" }, + }, +}); +``` + +Replace `/api/auth` if you use a custom `basePath`. Use the same signing +algorithm as your Convex auth configuration (`RS256` by default). Disabling the +JWT header avoids generating an extra token when reading a session; Convex +clients use `/convex/token`. + ## Local Install additional steps Generate the 1.7.7 schema to a side file and compare it with your deployed diff --git a/src/client/create-client.test.ts b/src/client/create-client.test.ts index 548bdf43..6d2bbc9c 100644 --- a/src/client/create-client.test.ts +++ b/src/client/create-client.test.ts @@ -1,5 +1,5 @@ -import { describe, expect, it, vi } from "vitest"; -import { httpRouter } from "convex/server"; +import { afterEach, describe, expect, it, vi } from "vitest"; +import { httpActionGeneric, httpRouter } from "convex/server"; import { createClient } from "./create-client.js"; const component = { @@ -29,6 +29,92 @@ const getRouteHandler = ( }; describe("createClient route registration", () => { + afterEach(() => vi.unstubAllEnvs()); + + describe.each(["registerRoutes", "registerRoutesLazy"] as const)( + "%s discovery", + (register) => { + it.each([ + [undefined, false, "/api/auth/convex/.well-known/openid-configuration"], + [undefined, true, "/api/auth/.well-known/openid-configuration"], + ["/custom/auth", true, "/custom/auth/.well-known/openid-configuration"], + [ + "/custom/auth", + false, + "/custom/auth/convex/.well-known/openid-configuration", + ], + ])( + "redirects basePath=%s provider=%s", + async (basePath, provider, target) => { + vi.stubEnv("CONVEX_SITE_URL", "https://deployment.convex.site"); + const client = createClient(component); + const http = httpRouter(); + const createAuth = vi.fn(() => ({ + handler: async (request: Request) => + new URL(request.url).pathname === target + ? Response.json({ issuer: "https://deployment.convex.site" }) + : new Response(null, { status: 404 }), + options: { + basePath, + plugins: provider ? [{ id: "oauth-provider" }] : [], + }, + $context: Promise.resolve({ options: {} }), + })); + if (register === "registerRoutesLazy") { + client.registerRoutesLazy(http, createAuth, { basePath }); + expect(createAuth).not.toHaveBeenCalled(); + } else { + client.registerRoutes(http, createAuth); + } + const root = + "https://deployment.convex.site/.well-known/openid-configuration"; + const redirect = await getRouteHandler( + http, + new URL(root).pathname, + "GET" + )!._handler({}, new Request(root)); + expect(redirect.status).toBe(302); + const location = redirect.headers.get("location")!; + expect(location).toBe(`https://deployment.convex.site${target}`); + const response = await getRouteHandler( + http, + new URL(location).pathname, + "GET" + )!._handler({}, new Request(location)); + expect(response.status).toBe(200); + } + ); + + it("preserves an application-defined discovery route", async () => { + const client = createClient(component); + const http = httpRouter(); + http.route({ + path: "/.well-known/openid-configuration", + method: "GET", + handler: httpActionGeneric(async () => + Response.json({ custom: true }) + ), + }); + const createAuth = vi.fn(() => ({ + handler: async () => new Response(null, { status: 404 }), + options: { plugins: [{ id: "oauth-provider" }] }, + $context: Promise.resolve({ options: {} }), + })); + client[register](http, createAuth); + const response = await getRouteHandler( + http, + "/.well-known/openid-configuration", + "GET" + )!._handler( + {}, + new Request( + "https://deployment.convex.site/.well-known/openid-configuration" + ) + ); + expect(await response.json()).toEqual({ custom: true }); + }); + } + ); it("registerRoutes eagerly initializes auth and infers basePath", () => { const client = createClient(component); const http = httpRouter(); @@ -145,7 +231,9 @@ describe("createClient route registration", () => { const forwardedRequest = handler.mock.calls[0]?.[0]; expect(forwardedRequest).toBeInstanceOf(Request); - expect(forwardedRequest.headers.get("x-forwarded-host")).toBe("app.example.com"); + expect(forwardedRequest.headers.get("x-forwarded-host")).toBe( + "app.example.com" + ); expect(forwardedRequest.headers.get("x-forwarded-proto")).toBe("https"); }); diff --git a/src/client/create-client.ts b/src/client/create-client.ts index 61c68212..561b3a86 100644 --- a/src/client/create-client.ts +++ b/src/client/create-client.ts @@ -20,6 +20,12 @@ import type defaultSchema from "../component/schema.js"; import type { CreateAuth, GenericCtx } from "./index.js"; import type { TrustedOriginsOption } from "../utils/index.js"; +const getOpenIdConfigPath = (plugins: unknown) => + Array.isArray(plugins) && + plugins.some((plugin) => plugin?.id === "oauth-provider") + ? "/.well-known/openid-configuration" + : "/convex/.well-known/openid-configuration"; + export type AuthFunctions = { onCreate?: FunctionReference<"mutation", "internal", { [key: string]: any }>; onUpdate?: FunctionReference<"mutation", "internal", { [key: string]: any }>; @@ -412,7 +418,7 @@ export const createClient = < path: "/.well-known/openid-configuration", method: "GET", handler: httpActionGeneric(async () => { - const url = `${process.env.CONVEX_SITE_URL}${path}/convex/.well-known/openid-configuration`; + const url = `${process.env.CONVEX_SITE_URL}${path}${getOpenIdConfigPath(staticAuth.options.plugins)}`; return Response.redirect(url); }), }); @@ -531,8 +537,9 @@ export const createClient = < http.route({ path: "/.well-known/openid-configuration", method: "GET", - handler: httpActionGeneric(async () => { - const url = `${process.env.CONVEX_SITE_URL}${path}/convex/.well-known/openid-configuration`; + handler: httpActionGeneric(async (ctx) => { + const auth = createAuth(ctx as any); + const url = `${process.env.CONVEX_SITE_URL}${path}${getOpenIdConfigPath(auth.options.plugins)}`; return Response.redirect(url); }), }); From d3fc65cbf3e8676628c1e1d00f0675e6302fa14c Mon Sep 17 00:00:00 2001 From: Stephen Shkeda Date: Mon, 12 Oct 2026 21:52:12 -0400 Subject: [PATCH 23/29] Clarify Local Install provider migration steps --- .../docs/migrations/migrate-to-0-13.mdx | 30 ++++++++++++------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/docs/content/docs/migrations/migrate-to-0-13.mdx b/docs/content/docs/migrations/migrate-to-0-13.mdx index ec4f844e..9f25eda2 100644 --- a/docs/content/docs/migrations/migrate-to-0-13.mdx +++ b/docs/content/docs/migrations/migrate-to-0-13.mdx @@ -240,13 +240,23 @@ cd convex/betterAuth npx auth generate --output generatedSchema.next.ts ``` -Preserve custom fields and indexes. Include the generated credential lookup -index `userId_providerId_accountId` on `["userId", "providerId", "accountId"]` -and export `incrementOne` with your other adapter functions. The adapter -implements `consumeOne` using the component's atomic `deleteOne`. - -If you already use Local Install with `oidcProvider`, implement local -equivalents of the export and cleanup helpers above. During export, retain -`oauthApplication` and merge legacy/new `oauthAccessToken` and `oauthConsent` -fields as optional unions. Clear legacy records before applying the final -provider schema. +Preserve existing plugin tables, custom fields, and indexes. Include the +generated credential lookup index `userId_providerId_accountId` on +`["userId", "providerId", "accountId"]` and export `incrementOne` with your +other adapter functions. The adapter implements `consumeOne` using the +component's atomic `deleteOne`. + +If you already use Local Install with `oidcProvider`, copy +`src/component/migrations.ts` from the installed `@convex-dev/better-auth` +package to `convex/betterAuth/migrations.ts`. Install `convex-helpers` as a +direct dependency, choosing a version compatible with your installed Convex +version. The helpers use its `paginator`, since `ctx.db.query(...).paginate()` +is not supported inside components. Use the internal wrappers above to call +them. + +Before exporting, deploy a temporary schema that retains `oauthApplication` and +accepts both legacy and new `oauthAccessToken` and `oauthConsent` records. Keep +both sets of fields and indexes, make required new fields such as `clientId` and +`scopes` optional, and allow `scopes` to be either a string or an array of +strings. Clear legacy records before applying the final provider schema, and +remove the temporary migration helpers and wrappers in that same deployment. From 7c256030420ae9c566ad65e9bfed2d5cd2c47794 Mon Sep 17 00:00:00 2001 From: Stephen Shkeda Date: Mon, 12 Oct 2026 22:05:48 -0400 Subject: [PATCH 24/29] Test OAuth refresh and discovery on a real Convex backend --- e2e/.gitignore | 3 + e2e/convex/auth.config.ts | 2 + e2e/convex/auth.ts | 40 + e2e/convex/betterAuth/adapter.ts | 14 + e2e/convex/betterAuth/auth.ts | 3 + e2e/convex/betterAuth/convex.config.ts | 2 + e2e/convex/betterAuth/schema.ts | 207 ++++++ e2e/convex/convex.config.ts | 7 + e2e/convex/http.ts | 19 + e2e/convex/schema.ts | 2 + e2e/convex/tsconfig.json | 25 + e2e/oauth-provider/provider.spec.ts | 161 ++++ e2e/package-lock.json | 970 ++++++++++++++++++++++++- e2e/package.json | 10 +- e2e/playwright.oauth.config.ts | 13 + e2e/run-e2e-tests.sh | 2 + e2e/run-oauth-tests.sh | 18 + 17 files changed, 1495 insertions(+), 3 deletions(-) create mode 100644 e2e/convex/auth.config.ts create mode 100644 e2e/convex/auth.ts create mode 100644 e2e/convex/betterAuth/adapter.ts create mode 100644 e2e/convex/betterAuth/auth.ts create mode 100644 e2e/convex/betterAuth/convex.config.ts create mode 100644 e2e/convex/betterAuth/schema.ts create mode 100644 e2e/convex/convex.config.ts create mode 100644 e2e/convex/http.ts create mode 100644 e2e/convex/schema.ts create mode 100644 e2e/convex/tsconfig.json create mode 100644 e2e/oauth-provider/provider.spec.ts create mode 100644 e2e/playwright.oauth.config.ts create mode 100755 e2e/run-oauth-tests.sh diff --git a/e2e/.gitignore b/e2e/.gitignore index 6aaf5b72..0288404e 100644 --- a/e2e/.gitignore +++ b/e2e/.gitignore @@ -7,3 +7,6 @@ convex-local-backend convex-local-backend.log convex_local_storage convex_local_backend.sqlite3 + +# Generated when the E2E fixture is deployed. +convex/**/_generated/ diff --git a/e2e/convex/auth.config.ts b/e2e/convex/auth.config.ts new file mode 100644 index 00000000..793ec105 --- /dev/null +++ b/e2e/convex/auth.config.ts @@ -0,0 +1,2 @@ +import { getAuthConfigProvider } from "@convex-dev/better-auth/auth-config"; +export default { providers: [getAuthConfigProvider()] }; diff --git a/e2e/convex/auth.ts b/e2e/convex/auth.ts new file mode 100644 index 00000000..b1a0c45a --- /dev/null +++ b/e2e/convex/auth.ts @@ -0,0 +1,40 @@ +import { createClient, type GenericCtx } from "@convex-dev/better-auth"; +import { convex } from "@convex-dev/better-auth/plugins"; +import { oauthProvider } from "@better-auth/oauth-provider"; +import { betterAuth, type BetterAuthOptions } from "better-auth/minimal"; +import { jwt } from "better-auth/plugins/jwt"; +import { components } from "./_generated/api"; +import type { DataModel } from "./_generated/dataModel"; +import schema from "./betterAuth/schema"; +import authConfig from "./auth.config"; + +export const authComponent = createClient( + components.oauthProvider, + { local: { schema } } +); +export const createAuthOptions = (ctx: GenericCtx) => + ({ + emailAndPassword: { enabled: true }, + baseURL: process.env.CONVEX_SITE_URL, + database: authComponent.adapter(ctx), + plugins: [ + jwt({ + disableSettingJwtHeader: true, + jwks: { + remoteUrl: `${process.env.CONVEX_SITE_URL}/api/auth/convex/jwks`, + keyPairConfig: { alg: "RS256" }, + }, + }), + convex({ authConfig }), + oauthProvider({ + loginPage: "/sign-in", + consentPage: "/consent", + scopes: ["openid", "profile", "email", "offline_access"], + grantTypes: ["authorization_code", "refresh_token"], + refreshTokenReuseInterval: 0, + }), + ], + }) satisfies BetterAuthOptions; + +export const createAuth = (ctx: GenericCtx) => + betterAuth(createAuthOptions(ctx)); diff --git a/e2e/convex/betterAuth/adapter.ts b/e2e/convex/betterAuth/adapter.ts new file mode 100644 index 00000000..69c8c7c1 --- /dev/null +++ b/e2e/convex/betterAuth/adapter.ts @@ -0,0 +1,14 @@ +import { createApi } from "@convex-dev/better-auth"; +import schema from "./schema"; +import { createAuthOptions } from "../auth"; + +export const { + create, + findOne, + findMany, + updateOne, + incrementOne, + updateMany, + deleteOne, + deleteMany, +} = createApi(schema, createAuthOptions); diff --git a/e2e/convex/betterAuth/auth.ts b/e2e/convex/betterAuth/auth.ts new file mode 100644 index 00000000..f572a538 --- /dev/null +++ b/e2e/convex/betterAuth/auth.ts @@ -0,0 +1,3 @@ +import { createAuth } from "../auth"; +// Only used by the Better Auth schema generator. +export const auth = createAuth({} as never); diff --git a/e2e/convex/betterAuth/convex.config.ts b/e2e/convex/betterAuth/convex.config.ts new file mode 100644 index 00000000..948c02a7 --- /dev/null +++ b/e2e/convex/betterAuth/convex.config.ts @@ -0,0 +1,2 @@ +import { defineComponent } from "convex/server"; +export default defineComponent("betterAuth"); diff --git a/e2e/convex/betterAuth/schema.ts b/e2e/convex/betterAuth/schema.ts new file mode 100644 index 00000000..61127971 --- /dev/null +++ b/e2e/convex/betterAuth/schema.ts @@ -0,0 +1,207 @@ +/** + * This file is auto-generated. Do not edit this file manually. + * To regenerate the schema, from your project root: + * + * cd convex/betterAuth + * npx auth generate --output schema.ts + * + * To customize the schema, generate to an alternate file and import + * the table definitions to your schema file. See + * https://labs.convex.dev/better-auth/features/local-install#adding-custom-indexes. + */ + +import { defineSchema, defineTable } from "convex/server"; +import { v } from "convex/values"; + +export const tables = { + user: defineTable({ + name: v.string(), + email: v.string(), + emailVerified: v.boolean(), + image: v.optional(v.union(v.null(), v.string())), + createdAt: v.number(), + updatedAt: v.number(), + userId: v.optional(v.union(v.null(), v.string())), + }) + .index("email_name", ["email", "name"]) + .index("name", ["name"]) + .index("userId", ["userId"]), + session: defineTable({ + expiresAt: v.number(), + token: v.string(), + createdAt: v.number(), + updatedAt: v.number(), + ipAddress: v.optional(v.union(v.null(), v.string())), + userAgent: v.optional(v.union(v.null(), v.string())), + userId: v.string(), + }) + .index("expiresAt", ["expiresAt"]) + .index("expiresAt_userId", ["expiresAt", "userId"]) + .index("token", ["token"]) + .index("userId", ["userId"]), + account: defineTable({ + accountId: v.string(), + providerId: v.string(), + userId: v.string(), + accessToken: v.optional(v.union(v.null(), v.string())), + refreshToken: v.optional(v.union(v.null(), v.string())), + idToken: v.optional(v.union(v.null(), v.string())), + accessTokenExpiresAt: v.optional(v.union(v.null(), v.number())), + refreshTokenExpiresAt: v.optional(v.union(v.null(), v.number())), + scope: v.optional(v.union(v.null(), v.string())), + password: v.optional(v.union(v.null(), v.string())), + createdAt: v.number(), + updatedAt: v.number(), + }) + .index("accountId", ["accountId"]) + .index("accountId_providerId", ["accountId", "providerId"]) + .index("providerId_userId", ["providerId", "userId"]) + .index("userId_providerId_accountId", ["userId", "providerId", "accountId"]) + .index("userId", ["userId"]), + verification: defineTable({ + identifier: v.string(), + value: v.string(), + expiresAt: v.number(), + createdAt: v.number(), + updatedAt: v.number(), + }) + .index("expiresAt", ["expiresAt"]) + .index("identifier", ["identifier"]), + jwks: defineTable({ + publicKey: v.string(), + privateKey: v.string(), + createdAt: v.number(), + expiresAt: v.optional(v.union(v.null(), v.number())), + alg: v.optional(v.union(v.null(), v.string())), + crv: v.optional(v.union(v.null(), v.string())), + }), + oauthClient: defineTable({ + clientId: v.string(), + clientSecret: v.optional(v.union(v.null(), v.string())), + clientDiscoveryId: v.optional(v.union(v.null(), v.string())), + disabled: v.optional(v.union(v.null(), v.boolean())), + skipConsent: v.optional(v.union(v.null(), v.boolean())), + enableEndSession: v.optional(v.union(v.null(), v.boolean())), + subjectType: v.optional(v.union(v.null(), v.string())), + scopes: v.optional(v.union(v.null(), v.array(v.string()))), + clientCredentialsScopes: v.optional(v.union(v.null(), v.array(v.string()))), + userId: v.optional(v.union(v.null(), v.string())), + createdAt: v.optional(v.union(v.null(), v.number())), + updatedAt: v.optional(v.union(v.null(), v.number())), + name: v.optional(v.union(v.null(), v.string())), + uri: v.optional(v.union(v.null(), v.string())), + icon: v.optional(v.union(v.null(), v.string())), + contacts: v.optional(v.union(v.null(), v.array(v.string()))), + tos: v.optional(v.union(v.null(), v.string())), + policy: v.optional(v.union(v.null(), v.string())), + softwareId: v.optional(v.union(v.null(), v.string())), + softwareVersion: v.optional(v.union(v.null(), v.string())), + softwareStatement: v.optional(v.union(v.null(), v.string())), + redirectUris: v.array(v.string()), + postLogoutRedirectUris: v.optional(v.union(v.null(), v.array(v.string()))), + backchannelLogoutUri: v.optional(v.union(v.null(), v.string())), + backchannelLogoutSessionRequired: v.optional( + v.union(v.null(), v.boolean()) + ), + tokenEndpointAuthMethod: v.optional(v.union(v.null(), v.string())), + applicationType: v.optional(v.union(v.null(), v.string())), + jwks: v.optional(v.union(v.null(), v.string())), + jwksUri: v.optional(v.union(v.null(), v.string())), + grantTypes: v.optional(v.union(v.null(), v.array(v.string()))), + responseTypes: v.optional(v.union(v.null(), v.array(v.string()))), + requirePKCE: v.optional(v.union(v.null(), v.boolean())), + dpopBoundAccessTokens: v.optional(v.union(v.null(), v.boolean())), + referenceId: v.optional(v.union(v.null(), v.string())), + metadata: v.optional(v.union(v.null(), v.string())), + }) + .index("clientId", ["clientId"]) + .index("userId", ["userId"]), + oauthResource: defineTable({ + identifier: v.string(), + name: v.string(), + accessTokenTtl: v.optional(v.union(v.null(), v.number())), + refreshTokenTtl: v.optional(v.union(v.null(), v.number())), + signingAlgorithm: v.optional(v.union(v.null(), v.string())), + signingKeyId: v.optional(v.union(v.null(), v.string())), + allowedScopes: v.optional(v.union(v.null(), v.array(v.string()))), + customClaims: v.optional(v.union(v.null(), v.string())), + dpopBoundAccessTokensRequired: v.optional(v.union(v.null(), v.boolean())), + disabled: v.optional(v.union(v.null(), v.boolean())), + createdAt: v.optional(v.union(v.null(), v.number())), + updatedAt: v.optional(v.union(v.null(), v.number())), + policyVersion: v.optional(v.union(v.null(), v.number())), + metadata: v.optional(v.union(v.null(), v.string())), + }).index("identifier", ["identifier"]), + oauthClientResource: defineTable({ + clientId: v.string(), + resourceId: v.string(), + metadata: v.optional(v.union(v.null(), v.string())), + createdAt: v.optional(v.union(v.null(), v.number())), + }) + .index("clientId_resourceId", ["clientId", "resourceId"]) + .index("clientId", ["clientId"]) + .index("resourceId", ["resourceId"]), + oauthRefreshToken: defineTable({ + token: v.string(), + clientId: v.string(), + sessionId: v.optional(v.union(v.null(), v.string())), + userId: v.string(), + referenceId: v.optional(v.union(v.null(), v.string())), + authorizationCodeId: v.optional(v.union(v.null(), v.string())), + resources: v.optional(v.union(v.null(), v.array(v.string()))), + requestedUserInfoClaims: v.optional(v.union(v.null(), v.array(v.string()))), + expiresAt: v.optional(v.union(v.null(), v.number())), + createdAt: v.optional(v.union(v.null(), v.number())), + revoked: v.optional(v.union(v.null(), v.number())), + rotatedAt: v.optional(v.union(v.null(), v.number())), + rotationReplayResponse: v.optional(v.union(v.null(), v.string())), + rotationReplayExpiresAt: v.optional(v.union(v.null(), v.number())), + authTime: v.optional(v.union(v.null(), v.number())), + confirmation: v.optional(v.union(v.null(), v.string())), + scopes: v.array(v.string()), + }) + .index("token", ["token"]) + .index("clientId", ["clientId"]) + .index("sessionId", ["sessionId"]) + .index("userId", ["userId"]), + oauthAccessToken: defineTable({ + token: v.optional(v.union(v.null(), v.string())), + clientId: v.string(), + sessionId: v.optional(v.union(v.null(), v.string())), + userId: v.optional(v.union(v.null(), v.string())), + referenceId: v.optional(v.union(v.null(), v.string())), + authorizationCodeId: v.optional(v.union(v.null(), v.string())), + resources: v.optional(v.union(v.null(), v.array(v.string()))), + requestedUserInfoClaims: v.optional(v.union(v.null(), v.array(v.string()))), + refreshId: v.optional(v.union(v.null(), v.string())), + expiresAt: v.optional(v.union(v.null(), v.number())), + createdAt: v.optional(v.union(v.null(), v.number())), + revoked: v.optional(v.union(v.null(), v.number())), + confirmation: v.optional(v.union(v.null(), v.string())), + scopes: v.array(v.string()), + }) + .index("token", ["token"]) + .index("clientId", ["clientId"]) + .index("sessionId", ["sessionId"]) + .index("userId", ["userId"]) + .index("refreshId", ["refreshId"]), + oauthConsent: defineTable({ + clientId: v.string(), + userId: v.optional(v.union(v.null(), v.string())), + referenceId: v.optional(v.union(v.null(), v.string())), + resources: v.optional(v.union(v.null(), v.array(v.string()))), + requestedUserInfoClaims: v.optional(v.union(v.null(), v.array(v.string()))), + scopes: v.array(v.string()), + createdAt: v.optional(v.union(v.null(), v.number())), + updatedAt: v.optional(v.union(v.null(), v.number())), + }) + .index("clientId_userId", ["clientId", "userId"]) + .index("userId", ["userId"]), + oauthClientAssertion: defineTable({ + expiresAt: v.number(), + }), +}; + +const schema = defineSchema(tables); + +export default schema; diff --git a/e2e/convex/convex.config.ts b/e2e/convex/convex.config.ts new file mode 100644 index 00000000..28c30c9d --- /dev/null +++ b/e2e/convex/convex.config.ts @@ -0,0 +1,7 @@ +import { defineApp } from "convex/server"; +import oauthProvider from "./betterAuth/convex.config"; + +const app = defineApp(); +// Keep provider fixtures separate from the bundled component used by the React tests. +app.use(oauthProvider, { name: "oauthProvider" }); +export default app; diff --git a/e2e/convex/http.ts b/e2e/convex/http.ts new file mode 100644 index 00000000..d6af2133 --- /dev/null +++ b/e2e/convex/http.ts @@ -0,0 +1,19 @@ +import { httpRouter, httpActionGeneric } from "convex/server"; +import { authComponent, createAuth } from "./auth"; + +const http = httpRouter(); +const registration = process.env.OAUTH_ROUTE_REGISTRATION; +if (registration !== "eager" && registration !== "lazy") { + throw new Error("OAUTH_ROUTE_REGISTRATION must be eager or lazy"); +} +http.route({ + path: "/registration-mode", + method: "GET", + handler: httpActionGeneric(async () => Response.json({ registration })), +}); +if (registration === "lazy") { + authComponent.registerRoutesLazy(http, createAuth); +} else { + authComponent.registerRoutes(http, createAuth); +} +export default http; diff --git a/e2e/convex/schema.ts b/e2e/convex/schema.ts new file mode 100644 index 00000000..8cc1e2ba --- /dev/null +++ b/e2e/convex/schema.ts @@ -0,0 +1,2 @@ +import { defineSchema } from "convex/server"; +export default defineSchema({}); diff --git a/e2e/convex/tsconfig.json b/e2e/convex/tsconfig.json new file mode 100644 index 00000000..41bfbb9b --- /dev/null +++ b/e2e/convex/tsconfig.json @@ -0,0 +1,25 @@ +{ + /* This TypeScript project config describes the environment that + * Convex functions run in and is used to typecheck them. + * You can modify it, but some settings are required to use Convex. + */ + "compilerOptions": { + /* These settings are not required by Convex and can be modified. */ + "allowJs": true, + "strict": true, + "moduleResolution": "Bundler", + "jsx": "react-jsx", + "skipLibCheck": true, + "allowSyntheticDefaultImports": true, + + /* These compiler options are required by Convex */ + "target": "ESNext", + "lib": ["ES2023", "dom"], + "forceConsistentCasingInFileNames": true, + "module": "ESNext", + "isolatedModules": true, + "noEmit": true + }, + "include": ["./**/*"], + "exclude": ["./_generated"] +} diff --git a/e2e/oauth-provider/provider.spec.ts b/e2e/oauth-provider/provider.spec.ts new file mode 100644 index 00000000..f1b1d16e --- /dev/null +++ b/e2e/oauth-provider/provider.spec.ts @@ -0,0 +1,161 @@ +import { createHash, randomBytes, randomUUID } from "node:crypto"; +import { test, expect, type APIRequestContext } from "@playwright/test"; +import { createLocalJWKSet, jwtVerify } from "jose"; + +const site = "http://127.0.0.1:3211"; +const base = `${site}/api/auth`; +// Authorization redirects are inspected, never followed to this synthetic client. +const redirectUri = "https://client.example.com/callback"; + +// Issue real provider tokens through signup, client registration, consent and PKCE. +// No adapter rows or endpoint responses are mocked. +async function authorize(request: APIRequestContext) { + const signup = await request.post(`${base}/sign-up/email`, { + data: { + name: "OAuth test user", + email: `${randomUUID()}@example.com`, + password: "TestPassword123!", + }, + }); + expect(signup.status()).toBe(200); + const { user } = await signup.json(); + const registration = await request.post(`${base}/oauth2/create-client`, { + data: { + client_name: "E2E client", + redirect_uris: [redirectUri], + token_endpoint_auth_method: "client_secret_post", + grant_types: ["authorization_code", "refresh_token"], + response_types: ["code"], + scope: "openid profile email offline_access", + }, + }); + expect(registration.status()).toBe(201); + const { client_id, client_secret } = await registration.json(); + expect(client_id).toBeTruthy(); + expect(client_secret).toBeTruthy(); + const verifier = randomBytes(32).toString("base64url"); + const state = randomUUID(); + const nonce = randomUUID(); + const authorization = await request.get(`${base}/oauth2/authorize`, { + params: { + client_id, + redirect_uri: redirectUri, + response_type: "code", + scope: "openid profile email offline_access", + state, + nonce, + code_challenge: createHash("sha256").update(verifier).digest("base64url"), + code_challenge_method: "S256", + prompt: "consent", + }, + maxRedirects: 0, + }); + expect(authorization.status()).toBe(302); + const consentUrl = new URL(authorization.headers().location, site); + expect(consentUrl.pathname).toBe("/consent"); + const consent = await request.post(`${base}/oauth2/consent`, { + data: { accept: true, oauth_query: consentUrl.search.slice(1) }, + }); + expect(consent.status()).toBe(200); + const callback = new URL((await consent.json()).url); + expect(`${callback.origin}${callback.pathname}`).toBe(redirectUri); + expect(callback.searchParams.get("state")).toBe(state); + const code = callback.searchParams.get("code"); + expect(code).toBeTruthy(); + const credentials = { client_id, client_secret }; + const exchange = await request.post(`${base}/oauth2/token`, { + headers: { cookie: "" }, + form: { + ...credentials, + grant_type: "authorization_code", + code: code!, + code_verifier: verifier, + redirect_uri: redirectUri, + }, + }); + expect(exchange.status()).toBe(200); + const tokens = await exchange.json(); + expect(tokens.access_token).toBeTruthy(); + expect(tokens.refresh_token).toBeTruthy(); + expect(tokens.id_token).toBeTruthy(); + return { credentials, tokens, user, nonce }; +} + +test.describe(`${process.env.OAUTH_ROUTE_REGISTRATION} OAuth provider`, () => { + test.beforeEach(async ({ request }) => { + const mode = await request.get("/registration-mode"); + expect(mode.status()).toBe(200); + expect(await mode.json()).toEqual({ + registration: process.env.OAUTH_ROUTE_REGISTRATION, + }); + }); + test("discovery leads to provider metadata and keys that verify its ID token", async ({ + request, + }) => { + const root = await request.get(`${site}/.well-known/openid-configuration`, { + maxRedirects: 0, + }); + expect(root.status()).toBe(302); + expect(root.headers().location).toBe( + `${base}/.well-known/openid-configuration` + ); + const discovery = await request.get(root.headers().location); + expect(discovery.status()).toBe(200); + const metadata = await discovery.json(); + expect(metadata).toMatchObject({ + issuer: base, + authorization_endpoint: `${base}/oauth2/authorize`, + token_endpoint: `${base}/oauth2/token`, + jwks_uri: `${base}/convex/jwks`, + }); + const { credentials, tokens, user, nonce } = await authorize(request); + const keys = await request.get(metadata.jwks_uri); + expect(keys.status()).toBe(200); + const jwks = await keys.json(); + expect(jwks.keys.length).toBeGreaterThan(0); + for (const key of jwks.keys) expect(key).not.toHaveProperty("d"); + const { payload } = await jwtVerify( + tokens.id_token, + createLocalJWKSet(jwks), + { + issuer: metadata.issuer, + audience: credentials.client_id, + algorithms: ["RS256"], + } + ); + expect(payload).toMatchObject({ sub: user.id, nonce }); + }); + + test("refresh rotates the token and rejects reuse of the old token and its family", async ({ + request, + }) => { + const { credentials, tokens, user } = await authorize(request); + const refresh = (token: string) => + request.post(`${base}/oauth2/token`, { + headers: { cookie: "" }, + form: { + ...credentials, + grant_type: "refresh_token", + refresh_token: token, + }, + }); + const rotation = await refresh(tokens.refresh_token); + expect(rotation.status()).toBe(200); + const rotated = await rotation.json(); + expect(rotated.refresh_token).toBeTruthy(); + expect(rotated.refresh_token).not.toBe(tokens.refresh_token); + expect(rotated.access_token).toBeTruthy(); + const profile = await request.get(`${base}/oauth2/userinfo`, { + headers: { cookie: "", authorization: `Bearer ${rotated.access_token}` }, + }); + expect(profile.status()).toBe(200); + expect(await profile.json()).toMatchObject({ sub: user.id }); + + const replay = await refresh(tokens.refresh_token); + expect(replay.status()).toBe(400); + expect(await replay.json()).toMatchObject({ error: "invalid_grant" }); + const family = await refresh(rotated.refresh_token); + expect(family.status()).toBe(400); + expect(await family.json()).toMatchObject({ error: "invalid_grant" }); + }); +}); diff --git a/e2e/package-lock.json b/e2e/package-lock.json index 576f4b09..2edf1c9d 100644 --- a/e2e/package-lock.json +++ b/e2e/package-lock.json @@ -6,9 +6,666 @@ "": { "name": "e2e-tests", "dependencies": { + "@better-auth/oauth-provider": "1.7.7", + "@convex-dev/better-auth": "file:..", "@playwright/test": "^1.60.0", "adm-zip": "^0.5.16", - "dotenv": "^17.0.0" + "better-auth": "1.7.7", + "convex": "1.41.0", + "dotenv": "^17.0.0", + "jose": "^6.1.0" + } + }, + "..": { + "name": "@convex-dev/better-auth", + "version": "0.12.5", + "license": "Apache-2.0", + "dependencies": { + "@better-fetch/fetch": "^1.3.2", + "common-tags": "^1.8.2", + "convex-helpers": "^0.1.95", + "jose": "^6.1.0", + "remeda": "^2.32.0", + "semver": "^7.7.3", + "type-fest": "^5.0.0", + "zod": "^4.0.0" + }, + "devDependencies": { + "@better-auth/core": "1.7.7", + "@better-auth/test-utils": "1.7.7", + "@convex-dev/eslint-plugin": "2.0.0", + "@edge-runtime/vm": "5.0.0", + "@eslint/eslintrc": "3.3.5", + "@eslint/js": "9.39.4", + "@tanstack/react-start": "1.167.65", + "@types/common-tags": "1.8.4", + "@types/node": "24.12.3", + "@types/react": "19.2.14", + "@types/react-dom": "19.2.3", + "@types/semver": "7.7.1", + "better-auth": "1.7.7", + "chokidar-cli": "3.0.0", + "concurrently": "10.0.3", + "convex": "1.41.0", + "convex-test": "0.0.51", + "eslint": "9.39.4", + "eslint-plugin-import": "2.32.0", + "eslint-plugin-react": "7.37.5", + "eslint-plugin-react-hooks": "7.1.1", + "eslint-plugin-react-refresh": "0.5.2", + "globals": "17.6.0", + "next": "16.2.6", + "pkg-pr-new": "0.0.71", + "prettier": "3.8.3", + "react": "19.2.6", + "react-dom": "19.2.6", + "typescript": "5.9.3", + "typescript-eslint": "8.59.2", + "vitest": "4.1.10" + }, + "peerDependencies": { + "better-auth": ">=1.7.7 <1.8.0", + "convex": "^1.25.0", + "react": "^18.3.1 || ^19.0.0" + } + }, + "node_modules/@better-auth/core": { + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/@better-auth/core/-/core-1.7.7.tgz", + "integrity": "sha512-srgFzHEjB2WWlokFz1i4BfG41sNhsA105Pukgqu6RrEh5A+LSkzf1mCHydVipkjt9l5qswtBMi7IL6ouHbZL4Q==", + "license": "MIT", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.41.1", + "@standard-schema/spec": "^1.1.0", + "zod": "^4.5.4" + }, + "peerDependencies": { + "@better-auth/utils": "0.4.2", + "@better-fetch/fetch": "1.3.2", + "@opentelemetry/api": "^1.9.0", + "better-call": "1.4.0", + "jose": "^6.1.0", + "kysely": "^0.28.5 || ^0.29.0", + "nanostores": "^1.0.1" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + } + } + }, + "node_modules/@better-auth/drizzle-adapter": { + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/@better-auth/drizzle-adapter/-/drizzle-adapter-1.7.7.tgz", + "integrity": "sha512-qon0U94PR5FQysuyGoAlGVQpIIiNg5dtDpwjE63sN/HjoRWZadZzHPcS9mn7JsJyQZK6iTNhd7LD0AaXt2f78w==", + "license": "MIT", + "peerDependencies": { + "@better-auth/core": "^1.7.7", + "@better-auth/utils": "0.4.2", + "drizzle-orm": "^0.45.2 || >=1.0.0-rc.1 <2.0.0" + }, + "peerDependenciesMeta": { + "drizzle-orm": { + "optional": true + } + } + }, + "node_modules/@better-auth/kysely-adapter": { + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/@better-auth/kysely-adapter/-/kysely-adapter-1.7.7.tgz", + "integrity": "sha512-9FONOCgOcrQI9wJ5v1oDGIg9sT7pa9RjZZQ4fsCTudo8R87SrIGNUvUxDFTJhZvUblvFfGaIBR9Vb8LA+8bqLQ==", + "license": "MIT", + "peerDependencies": { + "@better-auth/core": "^1.7.7", + "@better-auth/utils": "0.4.2", + "kysely": "^0.28.17 || ^0.29.0" + }, + "peerDependenciesMeta": { + "kysely": { + "optional": true + } + } + }, + "node_modules/@better-auth/memory-adapter": { + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/@better-auth/memory-adapter/-/memory-adapter-1.7.7.tgz", + "integrity": "sha512-FqKFEe+b5tXXV0gRbyirca+qXgMpLOeJ8Wk19yykb/scQKy0WJ4k22WzEV3TTPctQV9DZhwCXPlDtQhnlOv5Gg==", + "license": "MIT", + "peerDependencies": { + "@better-auth/core": "^1.7.7", + "@better-auth/utils": "0.4.2" + } + }, + "node_modules/@better-auth/mongo-adapter": { + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/@better-auth/mongo-adapter/-/mongo-adapter-1.7.7.tgz", + "integrity": "sha512-ZnVDuRrXqbf0oHphrEN27oTImNH5NTO26dtWatq5cXyYAsH7goof9QIoTOmxTbBxUQahKBw9T6+9h6n+RtOE+g==", + "license": "MIT", + "peerDependencies": { + "@better-auth/core": "^1.7.7", + "@better-auth/utils": "0.4.2", + "mongodb": "^6.0.0 || ^7.0.0" + }, + "peerDependenciesMeta": { + "mongodb": { + "optional": true + } + } + }, + "node_modules/@better-auth/oauth-provider": { + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/@better-auth/oauth-provider/-/oauth-provider-1.7.7.tgz", + "integrity": "sha512-dc4C/ZF+8qc88ebcU8kaFp8yladCtfTkuUt2zBSmYw25YCNC5BoUP9rpAkIx4nemlZ11gLtSnkEPc0sA7MwVmw==", + "license": "MIT", + "dependencies": { + "jose": "^6.2.3", + "zod": "^4.5.4" + }, + "peerDependencies": { + "@better-auth/core": "^1.7.7", + "@better-auth/utils": "0.4.2", + "@better-fetch/fetch": "1.3.2", + "better-auth": "^1.7.7", + "better-call": "1.4.0" + } + }, + "node_modules/@better-auth/prisma-adapter": { + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/@better-auth/prisma-adapter/-/prisma-adapter-1.7.7.tgz", + "integrity": "sha512-4YcnrcVdvWiAZw0sZl63tWUClPXg5r3QsLn7C2sG7kKEBY06zOng0ibecyxRBixKq1zE1ceHuEUqez5Rhi+nQA==", + "license": "MIT", + "peerDependencies": { + "@better-auth/core": "^1.7.7", + "@better-auth/utils": "0.4.2", + "@prisma/client": "^5.0.0 || ^6.0.0 || ^7.0.0", + "prisma": "^5.0.0 || ^6.0.0 || ^7.0.0" + }, + "peerDependenciesMeta": { + "@prisma/client": { + "optional": true + }, + "prisma": { + "optional": true + } + } + }, + "node_modules/@better-auth/telemetry": { + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/@better-auth/telemetry/-/telemetry-1.7.7.tgz", + "integrity": "sha512-PaRA6i+kAioVYmza2gHPcdrxP0hAotph4KV9hft2GeNJq+AZXDiBu3FuLM9HxvcmiZeUT74Vufqd9ypmB8veTA==", + "license": "MIT", + "peerDependencies": { + "@better-auth/core": "^1.7.7", + "@better-auth/utils": "0.4.2", + "@better-fetch/fetch": "1.3.2" + } + }, + "node_modules/@better-auth/utils": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@better-auth/utils/-/utils-0.4.2.tgz", + "integrity": "sha512-AUxrvu+HaaODsUyzDxFgwd/8RZ1yZaYo42LXKSrU2oGgR38pS1ij8nqQKNgtTWoYGpNevNXtCfgTy6loHveW9A==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "^2.0.1" + } + }, + "node_modules/@better-fetch/fetch": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@better-fetch/fetch/-/fetch-1.3.2.tgz", + "integrity": "sha512-Gs7n99b5tqUC6cQAPbV0uED3IraHB6xQbHLQ/C3l7ZFafHScOx9pQ+DYmP5blbLFShVWLqxNUlI9wi4xU/X+ow==", + "license": "MIT" + }, + "node_modules/@convex-dev/better-auth": { + "resolved": "..", + "link": true + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.0.tgz", + "integrity": "sha512-KuZrd2hRjz01y5JK9mEBSD3Vj3mbCvemhT466rSuJYeE/hjuBrHfjjcjMdTm/sz7au+++sdbJZJmuBwQLuw68A==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.0.tgz", + "integrity": "sha512-j67aezrPNYWJEOHUNLPj9maeJte7uSMM6gMoxfPC9hOg8N02JuQi/T7ewumf4tNvJadFkvLZMlAq73b9uwdMyQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.0.tgz", + "integrity": "sha512-CC3vt4+1xZrs97/PKDkl0yN7w8edvU2vZvAFGD16n9F0Cvniy5qvzRXjfO1l94efczkkQE6g1x0i73Qf5uthOQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.0.tgz", + "integrity": "sha512-wurMkF1nmQajBO1+0CJmcN17U4BP6GqNSROP8t0X/Jiw2ltYGLHpEksp9MpoBqkrFR3kv2/te6Sha26k3+yZ9Q==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.0.tgz", + "integrity": "sha512-uJOQKYCcHhg07DL7i8MzjvS2LaP7W7Pn/7uA0B5S1EnqAirJtbyw4yC5jQ5qcFjHK9l6o/MX9QisBg12kNkdHg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.0.tgz", + "integrity": "sha512-8mG6arH3yB/4ZXiEnXof5MK72dE6zM9cDvUcPtxhUZsDjESl9JipZYW60C3JGreKCEP+p8P/72r69m4AZGJd5g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.0.tgz", + "integrity": "sha512-9FHtyO988CwNMMOE3YIeci+UV+x5Zy8fI2qHNpsEtSF83YPBmE8UWmfYAQg6Ux7Gsmd4FejZqnEUZCMGaNQHQw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.0.tgz", + "integrity": "sha512-zCMeMXI4HS/tXvJz8vWGexpZj2YVtRAihHLk1imZj4efx1BQzN76YFeKqlDr3bUWI26wHwLWPd3rwh6pe4EV7g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.0.tgz", + "integrity": "sha512-t76XLQDpxgmq2cNXKTVEB7O7YMb42atj2Re2Haf45HkaUpjM2J0UuJZDuaGbPbamzZ7bawyGFUkodL+zcE+jvQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.0.tgz", + "integrity": "sha512-AS18v0V+vZiLJyi/4LphvBE+OIX682Pu7ZYNsdUHyUKSoRwdnOsMf6FDekwoAFKej14WAkOef3zAORJgAtXnlQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.0.tgz", + "integrity": "sha512-Mz1jxqm/kfgKkc/KLHC5qIujMvnnarD9ra1cEcrs7qshTUSksPihGrWHVG5+osAIQ68577Zpww7SGapmzSt4Nw==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.0.tgz", + "integrity": "sha512-QbEREjdJeIreIAbdG2hLU1yXm1uu+LTdzoq1KCo4G4pFOLlvIspBm36QrQOar9LFduavoWX2msNFAAAY9j4BDg==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.0.tgz", + "integrity": "sha512-sJz3zRNe4tO2wxvDpH/HYJilb6+2YJxo/ZNbVdtFiKDufzWq4JmKAiHy9iGoLjAV7r/W32VgaHGkk35cUXlNOg==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.0.tgz", + "integrity": "sha512-z9N10FBD0DCS2dmSABDBb5TLAyF1/ydVb+N4pi88T45efQ/w4ohr/F/QYCkxDPnkhkp6AIpIcQKQ8F0ANoA2JA==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.0.tgz", + "integrity": "sha512-pQdyAIZ0BWIC5GyvVFn5awDiO14TkT/19FTmFcPdDec94KJ1uZcmFs21Fo8auMXzD4Tt+diXu1LW1gHus9fhFQ==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.0.tgz", + "integrity": "sha512-hPlRWR4eIDDEci953RI1BLZitgi5uqcsjKMxwYfmi4LcwyWo2IcRP+lThVnKjNtk90pLS8nKdroXYOqW+QQH+w==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.0.tgz", + "integrity": "sha512-1hBWx4OUJE2cab++aVZ7pObD6s+DK4mPGpemtnAORBvb5l/g5xFGk0vc0PjSkrDs0XaXj9yyob3d14XqvnQ4gw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.0.tgz", + "integrity": "sha512-6m0sfQfxfQfy1qRuecMkJlf1cIzTOgyaeXaiVaaki8/v+WB+U4hc6ik15ZW6TAllRlg/WuQXxWj1jx6C+dfy3w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.0.tgz", + "integrity": "sha512-xbbOdfn06FtcJ9d0ShxxvSn2iUsGd/lgPIO2V3VZIPDbEaIj1/3nBBe1AwuEZKXVXkMmpr6LUAgMkLD/4D2PPA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.0.tgz", + "integrity": "sha512-fWgqR8uNbCQ/GGv0yhzttj6sU/9Z5/Sv/VGU3F5OuXK6J6SlriONKrQ7tNlwBrJZXRYk5jUhuWvF7GYzGguBZQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.0.tgz", + "integrity": "sha512-aCwlRdSNMNxkGGqQajMUza6uXzR/U0dIl1QmLjPtRbLOx3Gy3otfFu/VjATy4yQzo9yFDGTxYDo1FfAD9oRD2A==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.0.tgz", + "integrity": "sha512-nyvsBccxNAsNYz2jVFYwEGuRRomqZ149A39SHWk4hV0jWxKM0hjBPm3AmdxcbHiFLbBSwG6SbpIcUbXjgyECfA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.0.tgz", + "integrity": "sha512-Q1KY1iJafM+UX6CFEL+F4HRTgygmEW568YMqDA5UV97AuZSm21b7SXIrRJDwXWPzr8MGr75fUZPV67FdtMHlHA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.0.tgz", + "integrity": "sha512-W1eyGNi6d+8kOmZIwi/EDjrL9nxQIQ0MiGqe/AWc6+IaHloxHSGoeRgDRKHFISThLmsewZ5nHFvGFWdBYlgKPg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.0.tgz", + "integrity": "sha512-30z1aKL9h22kQhilnYkORFYt+3wp7yZsHWus+wSKAJR8JtdfI76LJ4SBdMsCopTR3z/ORqVu5L1vtnHZWVj4cQ==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.0.tgz", + "integrity": "sha512-aIitBcjQeyOhMTImhLZmtxfdOcuNRpwlPNmlFKPcHQYPhEssw75Cl1TSXJXpMkzaua9FUetx/4OQKq7eJul5Cg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@noble/ciphers": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-2.4.0.tgz", + "integrity": "sha512-AnjFn0Jv92laAkvMrghlFZq4qQCIN/4DxFV/eooqtC2YTjB7kBeLMS2T9KJX4Dn+ZVXLOwK0lSgqDtx9gvxtiw==", + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.4.0.tgz", + "integrity": "sha512-X5XaVWZIBCT7HHZGm5I7ZQXDwLG+bGXuSrMQAW+7Zvl87h1kmc1ZB1VSRJcpUfoUrGQp4Fkoxm5kZ+Ms+aW+eA==", + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@opentelemetry/semantic-conventions": { + "version": "1.43.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.43.0.tgz", + "integrity": "sha512-eSYWTm620tTk45EKSedaUL8MFYI8hW164hIXsgIHyxu3VobUB3fFCu5t0hQby6OoWRPsG1KkKUG2M5UadiLiVg==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" } }, "node_modules/@playwright/test": { @@ -26,6 +683,12 @@ "node": ">=18" } }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "license": "MIT" + }, "node_modules/adm-zip": { "version": "0.5.17", "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.17.tgz", @@ -35,6 +698,180 @@ "node": ">=12.0" } }, + "node_modules/better-auth": { + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/better-auth/-/better-auth-1.7.7.tgz", + "integrity": "sha512-Zhgxi/c5ylmfB/sX+nwnBbsFf/j6k0L8DvL6CJXZSrXgxc5pPMC/e7j812zNd4L4B4ELmdURCPw3X8nAOUdcjw==", + "license": "MIT", + "dependencies": { + "@better-auth/core": "1.7.7", + "@better-auth/drizzle-adapter": "1.7.7", + "@better-auth/kysely-adapter": "1.7.7", + "@better-auth/memory-adapter": "1.7.7", + "@better-auth/mongo-adapter": "1.7.7", + "@better-auth/prisma-adapter": "1.7.7", + "@better-auth/telemetry": "1.7.7", + "@better-auth/utils": "0.4.2", + "@better-fetch/fetch": "1.3.2", + "@noble/ciphers": "^2.2.0", + "@noble/hashes": "^2.2.0", + "better-call": "1.4.0", + "defu": "^6.1.4", + "jose": "^6.2.3", + "kysely": "^0.28.17 || ^0.29.0", + "nanostores": "^1.3.0", + "zod": "^4.5.4" + }, + "peerDependencies": { + "@lynx-js/react": "*", + "@prisma/client": "^5.0.0 || ^6.0.0 || ^7.0.0", + "@sveltejs/kit": "^2.0.0", + "@tanstack/react-start": "^1.0.0", + "@tanstack/solid-start": "^1.0.0", + "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", + "pg": "^8.0.0", + "prisma": "^5.0.0 || ^6.0.0 || ^7.0.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0", + "solid-js": "^1.0.0", + "svelte": "^4.0.0 || ^5.0.0", + "vitest": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0", + "vue": "^3.0.0" + }, + "peerDependenciesMeta": { + "@lynx-js/react": { + "optional": true + }, + "@prisma/client": { + "optional": true + }, + "@sveltejs/kit": { + "optional": true + }, + "@tanstack/react-start": { + "optional": true + }, + "@tanstack/solid-start": { + "optional": true + }, + "drizzle-kit": { + "optional": true + }, + "drizzle-orm": { + "optional": true + }, + "mongodb": { + "optional": true + }, + "mysql2": { + "optional": true + }, + "next": { + "optional": true + }, + "pg": { + "optional": true + }, + "prisma": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "solid-js": { + "optional": true + }, + "svelte": { + "optional": true + }, + "vitest": { + "optional": true + }, + "vue": { + "optional": true + } + } + }, + "node_modules/better-call": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/better-call/-/better-call-1.4.0.tgz", + "integrity": "sha512-bBKOT4vv1kZLDgxVePdilk/Jwkn+dtRRsmi3DzHcDP+WnswyVl6dR59l2HEeP/0cB+bDoopASAesWDPIdd/zZA==", + "license": "MIT", + "dependencies": { + "@better-auth/utils": "^0.5.0", + "@better-fetch/fetch": "^1.3.1", + "rou3": "^0.9.1", + "set-cookie-parser": "^3.1.2" + }, + "peerDependencies": { + "zod": "^4.0.0" + }, + "peerDependenciesMeta": { + "zod": { + "optional": true + } + } + }, + "node_modules/better-call/node_modules/@better-auth/utils": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@better-auth/utils/-/utils-0.5.0.tgz", + "integrity": "sha512-BL8W4EfIZFwlu0r54m3v1ztjDhu6dDe/amLTm0xybmbZaNgYUqhD3SjpAsnq0q8YD6/ki4iwIgxJNLP/N3TxiA==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "^2.0.1" + } + }, + "node_modules/convex": { + "version": "1.41.0", + "resolved": "https://registry.npmjs.org/convex/-/convex-1.41.0.tgz", + "integrity": "sha512-euxVf6yfpB7/VGKOobkLgjpbJidsUgW+b0ezonEyCUPqlpHFwR4/yIiI1hjjErzraiw91GxrtxpXQClMLNqU+w==", + "license": "Apache-2.0", + "dependencies": { + "esbuild": "0.27.0", + "prettier": "^3.0.0", + "ws": "8.20.1" + }, + "bin": { + "convex": "bin/main.js" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=7.0.0" + }, + "peerDependencies": { + "@auth0/auth0-react": "^2.0.1", + "@clerk/clerk-react": "^4.12.8 || ^5.0.0", + "@clerk/react": "^6.4.3", + "react": "^18.0.0 || ^19.0.0-0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@auth0/auth0-react": { + "optional": true + }, + "@clerk/clerk-react": { + "optional": true + }, + "@clerk/react": { + "optional": true + }, + "react": { + "optional": true + } + } + }, + "node_modules/defu": { + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.7.tgz", + "integrity": "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==", + "license": "MIT" + }, "node_modules/dotenv": { "version": "17.4.2", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz", @@ -47,6 +884,47 @@ "url": "https://dotenvx.com" } }, + "node_modules/esbuild": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.0.tgz", + "integrity": "sha512-jd0f4NHbD6cALCyGElNpGAOtWxSq46l9X/sWB0Nzd5er4Kz2YTm+Vl0qKFT9KUJvD8+fiO8AvoHhFvEatfVixA==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.0", + "@esbuild/android-arm": "0.27.0", + "@esbuild/android-arm64": "0.27.0", + "@esbuild/android-x64": "0.27.0", + "@esbuild/darwin-arm64": "0.27.0", + "@esbuild/darwin-x64": "0.27.0", + "@esbuild/freebsd-arm64": "0.27.0", + "@esbuild/freebsd-x64": "0.27.0", + "@esbuild/linux-arm": "0.27.0", + "@esbuild/linux-arm64": "0.27.0", + "@esbuild/linux-ia32": "0.27.0", + "@esbuild/linux-loong64": "0.27.0", + "@esbuild/linux-mips64el": "0.27.0", + "@esbuild/linux-ppc64": "0.27.0", + "@esbuild/linux-riscv64": "0.27.0", + "@esbuild/linux-s390x": "0.27.0", + "@esbuild/linux-x64": "0.27.0", + "@esbuild/netbsd-arm64": "0.27.0", + "@esbuild/netbsd-x64": "0.27.0", + "@esbuild/openbsd-arm64": "0.27.0", + "@esbuild/openbsd-x64": "0.27.0", + "@esbuild/openharmony-arm64": "0.27.0", + "@esbuild/sunos-x64": "0.27.0", + "@esbuild/win32-arm64": "0.27.0", + "@esbuild/win32-ia32": "0.27.0", + "@esbuild/win32-x64": "0.27.0" + } + }, "node_modules/fsevents": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", @@ -61,6 +939,39 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/jose": { + "version": "6.2.12", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.12.tgz", + "integrity": "sha512-9NiFmJEex0sy2Dk58j2UGBSHgUs2ypF9eZSu4L6vjOX3Dp96Sw1F3uL+H+D1sx02jZZdzUT0HgvCy59CuvXcWw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/kysely": { + "version": "0.29.6", + "resolved": "https://registry.npmjs.org/kysely/-/kysely-0.29.6.tgz", + "integrity": "sha512-hHaB8C/rfzDDtr/t8YZwxAuPJTT0zHyaPoVzcXwDYhYNAgH/4sIfVhi/XLLIY+bL/FqaIJnjATDbi8ObSELmxg==", + "license": "MIT", + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/nanostores": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/nanostores/-/nanostores-1.5.4.tgz", + "integrity": "sha512-4UXY7OAJEK0Y6V3SudBU7cXjAzTDc189R78dDOh0Xbm19gpKYto6WizSg58LJf12lTsRxKSLGyE117OiLpM4DQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "engines": { + "node": "^20.0.0 || >=22.0.0" + } + }, "node_modules/playwright": { "version": "1.60.0", "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.60.0.tgz", @@ -90,6 +1001,63 @@ "engines": { "node": ">=18" } + }, + "node_modules/prettier": { + "version": "3.9.9", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.9.tgz", + "integrity": "sha512-Z/CJHIkdujO/OtN7nXUii0Rf3VT5SRuhjBA82Xvu2XhBUgX3nhP67T0LHceBdQLex7OOFGTox+Q5Yg8Jk2Qivg==", + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/rou3": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/rou3/-/rou3-0.9.2.tgz", + "integrity": "sha512-3SOzvaAg8rkHrXtRjpCvCvbyO5to9oOO27Z/XqHEYXfMRVSw/qMIVdmaOk9W2lcRLtR6dlqTjo9hDeJk70QBYQ==", + "license": "MIT" + }, + "node_modules/set-cookie-parser": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-3.1.2.tgz", + "integrity": "sha512-5/r/lTwbJ3zQ+qwdUFZYeRNqda7P5HD8zQKqlSjdGt1/S0cjLAphHusj4Y58ahDtWn/g32xrIS58/ikOvwl0Lw==", + "license": "MIT" + }, + "node_modules/ws": { + "version": "8.20.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.1.tgz", + "integrity": "sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/zod": { + "version": "4.6.5", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.6.5.tgz", + "integrity": "sha512-v5l/aFXZQeai4awLbOpSoHecE9UiMrnfx75tEXLjNonXVARxQ5mOeipTjROUchszUNCqnE+hqAMujRsRHsut2Q==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } } } diff --git a/e2e/package.json b/e2e/package.json index d6d9a9b9..9f703403 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -4,11 +4,17 @@ "type": "module", "scripts": { "test": "node backendHarness.js 'npm run testExistingBackend'", - "testExistingBackend": "./run-e2e-tests.sh" + "testExistingBackend": "./run-e2e-tests.sh", + "test:oauth": "node backendHarness.js './run-oauth-tests.sh'" }, "dependencies": { + "@better-auth/oauth-provider": "1.7.7", + "@convex-dev/better-auth": "file:..", "@playwright/test": "^1.60.0", "adm-zip": "^0.5.16", - "dotenv": "^17.0.0" + "better-auth": "1.7.7", + "convex": "1.41.0", + "dotenv": "^17.0.0", + "jose": "^6.1.0" } } diff --git a/e2e/playwright.oauth.config.ts b/e2e/playwright.oauth.config.ts new file mode 100644 index 00000000..7eced2d4 --- /dev/null +++ b/e2e/playwright.oauth.config.ts @@ -0,0 +1,13 @@ +import { defineConfig } from "@playwright/test"; + +export default defineConfig({ + testDir: "./oauth-provider", + forbidOnly: !!process.env.CI, + workers: 1, + timeout: 60_000, + reporter: "list", + use: { + baseURL: "http://127.0.0.1:3211", + extraHTTPHeaders: { origin: "http://127.0.0.1:3211" }, + }, +}); diff --git a/e2e/run-e2e-tests.sh b/e2e/run-e2e-tests.sh index 0fc33dea..a9bcdc3a 100755 --- a/e2e/run-e2e-tests.sh +++ b/e2e/run-e2e-tests.sh @@ -27,3 +27,5 @@ popd > /dev/null echo "Running Playwright tests..." npx playwright test --config "$SCRIPT_DIR/playwright.config.ts" + +"$SCRIPT_DIR/run-oauth-tests.sh" diff --git a/e2e/run-oauth-tests.sh b/e2e/run-oauth-tests.sh new file mode 100755 index 00000000..740d8689 --- /dev/null +++ b/e2e/run-oauth-tests.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +set -euo pipefail +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +CONVEX_URL="http://127.0.0.1:3210" +ADMIN_KEY="${CONVEX_ADMIN_KEY:?CONVEX_ADMIN_KEY must be set (the backend harness injects it)}" + +# The fixture consumes the local build; keep its Better Auth and Convex versions +# in e2e/package.json aligned with the root development dependencies. +# Replace the React app on the disposable backend. The separate oauthProvider +# mount keeps this fixture independent of data written by the browser tests. +pushd "$SCRIPT_DIR" > /dev/null +npx convex env set BETTER_AUTH_SECRET "e2e-provider-secret-key-do-not-use-in-production" --url "$CONVEX_URL" --admin-key "$ADMIN_KEY" +for registration in eager lazy; do + npx convex env set OAUTH_ROUTE_REGISTRATION "$registration" --url "$CONVEX_URL" --admin-key "$ADMIN_KEY" + npx convex deploy --url "$CONVEX_URL" --admin-key "$ADMIN_KEY" -y + OAUTH_ROUTE_REGISTRATION="$registration" npx playwright test --config playwright.oauth.config.ts +done +popd > /dev/null From 1bd223e23d0ec61c29436412f015c3de29d1ca79 Mon Sep 17 00:00:00 2001 From: Stephen Shkeda Date: Mon, 12 Oct 2026 22:16:56 -0400 Subject: [PATCH 25/29] Clarify Better Auth 1.7 migration and plugin requirements --- .../docs/migrations/migrate-to-0-13.mdx | 46 ++++++++++++++++++- docs/content/docs/supported-plugins.mdx | 19 +++++--- 2 files changed, 58 insertions(+), 7 deletions(-) diff --git a/docs/content/docs/migrations/migrate-to-0-13.mdx b/docs/content/docs/migrations/migrate-to-0-13.mdx index 9f25eda2..6d91c73d 100644 --- a/docs/content/docs/migrations/migrate-to-0-13.mdx +++ b/docs/content/docs/migrations/migrate-to-0-13.mdx @@ -83,6 +83,16 @@ These replace `signIn.oauth2()` and `oauth2.link()`. Remove `genericOAuthClient()` and change provider callback URLs from `/api/auth/oauth2/callback/:id` to `/api/auth/callback/:id`. +PKCE is enabled by default. Set `pkce: false` only for providers that reject it. +Remove `issuer` and `requireIssuerValidation`; issuer validation is automatic. + +Review custom provider identity mapping. Discovery providers use verified `sub`, +and plain OAuth providers use `id`. Set `accountSubject` if that default is not +the provider's immutable identifier. Keep the provider identifier in +`getUserInfo().data`; `getUserInfo().user` contains mutable user fields, and +`mapProfileToUser` can no longer return `id`. See the upstream +[account identity guidance](https://www.better-auth.com/docs/guides/1-7-upgrade-guide#account-identity-keeps-the-provider-key). + ### Microsoft accounts Microsoft accounts now use `oid` instead of `sub`, including the Generic OAuth @@ -100,6 +110,20 @@ an `id_token` from the token exchange. Use the built-in Microsoft provider for Move `experimental.joins` to `advanced.database.joins`. The Convex adapter continues to disable native joins and use its existing relation queries. +### Expo + +The `@better-auth/expo` client's `getCookie()` now returns a promise. Await it +and make callbacks that read cookies asynchronous. Passing `expo-secure-store` +directly still works; custom storage must implement `getItem`, `getItemAsync`, +`setItem`, and `setItemAsync`. See the upstream +[Expo migration guidance](https://www.better-auth.com/docs/guides/1-7-upgrade-guide#expo-and-react-native). + +### Two-factor authentication + +Check the returned `method` before reading `totpURI` or backup codes from +`enableTwoFactor`. Those fields are present only when `method === "totp"`. The +Next.js and TanStack examples include this check. + ## Early 1.7 deployments Better Auth 1.7.3 removed the required `issuer` field introduced in 1.7.0–1.7.2. @@ -113,9 +137,21 @@ the matching account rows before resuming sign-in. ## OAuth or OIDC provider -Only follow this section if your app used `oidcProvider` to act as an identity +The conversion below is for apps that used `oidcProvider` to act as an identity provider. Apps that only sign in through an external provider can skip it. +If you already use `@better-auth/oauth-provider` with Local Install, follow the +upstream +[OAuth client migration](https://www.better-auth.com/docs/guides/1-7-upgrade-guide#migrate-oauth-client-records) +before applying the generated schema. Existing clients need field backfills and +configuration changes; schema generation alone does not migrate their data. Skip +the legacy export and cleanup below. + +Use a temporary Convex schema that accepts both the existing and migrated client +records. Backfill the new fields and remove the legacy `type` and `public` +fields from existing `oauthClient` documents before deploying the final +generated schema. + ### Export and clear legacy records Back up your data and pause authentication writes and public HTTP routes during @@ -230,6 +266,14 @@ algorithm as your Convex auth configuration (`RS256` by default). Disabling the JWT header avoids generating an extra token when reading a session; Convex clients use `/convex/token`. +### Custom consent pages + +Before displaying consent details, verify the signed query on the server with +`verifyOAuthQueryParams` from `@better-auth/oauth-provider`. Use the provider's +resolved secret, `(await auth.$context).secret`, from your server-side auth +instance and keep it on the server. See the upstream +[consent screen example](https://www.better-auth.com/docs/plugins/oauth-provider#consent-screen). + ## Local Install additional steps Generate the 1.7.7 schema to a side file and compare it with your deployed diff --git a/docs/content/docs/supported-plugins.mdx b/docs/content/docs/supported-plugins.mdx index c166b9e6..0c2bf46a 100644 --- a/docs/content/docs/supported-plugins.mdx +++ b/docs/content/docs/supported-plugins.mdx @@ -5,12 +5,12 @@ description: Supported plugins for Convex + Better Auth ## Supported plugins -Any Better Auth plugin may be used with Convex + Better Auth, but only a subset -are considered supported for the integration. Convex + Better Auth works out of -the box for these without any required schema changes. +Better Auth plugins may be used with Convex + Better Auth when their runtime and +adapter requirements are met, but only a subset are considered supported for the +integration. These work without any required schema changes. -For plugins that require schema changes and aren't in the list, check out [Local -Install](/local-install). +For plugins that require schema changes and aren't in the list, check out +[Local Install](/local-install). - [Anonymous](https://www.better-auth.com/docs/plugins/anonymous) - [Email OTP](https://www.better-auth.com/docs/plugins/email-otp) @@ -29,9 +29,16 @@ Schema-changing plugins that are not bundled with the component require [`@better-auth/oauth-provider`](https://www.better-auth.com/docs/plugins/oauth-provider) package used when your application acts as an OAuth or OIDC provider. +Local Install allows schema changes but does not add adapter capabilities +required by a plugin. Check its requirements before using it. + ### Incompatible plugins Official Better Auth plugins that are incompatible with Convex + Better Auth (even when Local Install is used): -- [SSO](https://www.better-auth.com/docs/plugins/sso) - has direct dependencies on Node.js +- [SSO](https://www.better-auth.com/docs/plugins/sso) - has direct dependencies + on Node.js +- [SCIM](https://www.better-auth.com/docs/plugins/scim) - requires native + interactive adapter transactions in Better Auth 1.7, which the Convex adapter + does not provide. From 1dc17b4f4ef981ddc6fe5767d2a2a612a06aacf9 Mon Sep 17 00:00:00 2001 From: Stephen Shkeda Date: Mon, 12 Oct 2026 23:00:01 -0400 Subject: [PATCH 26/29] Clarify removal of legacy OAuth client fields --- docs/content/docs/migrations/migrate-to-0-13.mdx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/content/docs/migrations/migrate-to-0-13.mdx b/docs/content/docs/migrations/migrate-to-0-13.mdx index 6d91c73d..2dbe16b4 100644 --- a/docs/content/docs/migrations/migrate-to-0-13.mdx +++ b/docs/content/docs/migrations/migrate-to-0-13.mdx @@ -150,7 +150,9 @@ the legacy export and cleanup below. Use a temporary Convex schema that accepts both the existing and migrated client records. Backfill the new fields and remove the legacy `type` and `public` fields from existing `oauthClient` documents before deploying the final -generated schema. +generated schema. Remove those fields in a component mutation with +`ctx.db.patch(id, { type: undefined, public: undefined })`; setting them to +`null` still leaves fields that the final schema rejects. ### Export and clear legacy records From c69354538fd69e21d9844a7118439d8ae57bf2f7 Mon Sep 17 00:00:00 2001 From: Stephen Shkeda Date: Tue, 13 Oct 2026 00:23:04 -0400 Subject: [PATCH 27/29] Run OAuth tests through the existing E2E script --- e2e/package.json | 3 +-- e2e/run-e2e-tests.sh | 9 ++++++++- e2e/run-oauth-tests.sh | 18 ------------------ 3 files changed, 9 insertions(+), 21 deletions(-) delete mode 100755 e2e/run-oauth-tests.sh diff --git a/e2e/package.json b/e2e/package.json index 9f703403..3a6c5ee0 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -4,8 +4,7 @@ "type": "module", "scripts": { "test": "node backendHarness.js 'npm run testExistingBackend'", - "testExistingBackend": "./run-e2e-tests.sh", - "test:oauth": "node backendHarness.js './run-oauth-tests.sh'" + "testExistingBackend": "./run-e2e-tests.sh" }, "dependencies": { "@better-auth/oauth-provider": "1.7.7", diff --git a/e2e/run-e2e-tests.sh b/e2e/run-e2e-tests.sh index a9bcdc3a..b425aa2d 100755 --- a/e2e/run-e2e-tests.sh +++ b/e2e/run-e2e-tests.sh @@ -28,4 +28,11 @@ popd > /dev/null echo "Running Playwright tests..." npx playwright test --config "$SCRIPT_DIR/playwright.config.ts" -"$SCRIPT_DIR/run-oauth-tests.sh" +pushd "$SCRIPT_DIR" > /dev/null +for registration in eager lazy; do + echo "Running OAuth provider tests ($registration)..." + npx convex env set OAUTH_ROUTE_REGISTRATION "$registration" --url "$CONVEX_URL" --admin-key "$ADMIN_KEY" + npx convex deploy --url "$CONVEX_URL" --admin-key "$ADMIN_KEY" -y + OAUTH_ROUTE_REGISTRATION="$registration" npx playwright test --config "$SCRIPT_DIR/playwright.oauth.config.ts" +done +popd > /dev/null diff --git a/e2e/run-oauth-tests.sh b/e2e/run-oauth-tests.sh deleted file mode 100755 index 740d8689..00000000 --- a/e2e/run-oauth-tests.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -CONVEX_URL="http://127.0.0.1:3210" -ADMIN_KEY="${CONVEX_ADMIN_KEY:?CONVEX_ADMIN_KEY must be set (the backend harness injects it)}" - -# The fixture consumes the local build; keep its Better Auth and Convex versions -# in e2e/package.json aligned with the root development dependencies. -# Replace the React app on the disposable backend. The separate oauthProvider -# mount keeps this fixture independent of data written by the browser tests. -pushd "$SCRIPT_DIR" > /dev/null -npx convex env set BETTER_AUTH_SECRET "e2e-provider-secret-key-do-not-use-in-production" --url "$CONVEX_URL" --admin-key "$ADMIN_KEY" -for registration in eager lazy; do - npx convex env set OAUTH_ROUTE_REGISTRATION "$registration" --url "$CONVEX_URL" --admin-key "$ADMIN_KEY" - npx convex deploy --url "$CONVEX_URL" --admin-key "$ADMIN_KEY" -y - OAUTH_ROUTE_REGISTRATION="$registration" npx playwright test --config playwright.oauth.config.ts -done -popd > /dev/null From 5a7c5bf3b59ff76a4f7efcdd40bbda02e0d4c52c Mon Sep 17 00:00:00 2001 From: Stephen Shkeda Date: Tue, 13 Oct 2026 00:23:04 -0400 Subject: [PATCH 28/29] Remove redundant comments --- e2e/convex/convex.config.ts | 1 - e2e/convex/tsconfig.json | 7 ------- e2e/oauth-provider/provider.spec.ts | 3 --- src/client/adapter-compatibility.test.ts | 2 -- src/component/testProfiles/auth-options.constraints.ts | 3 +-- src/component/testProfiles/schema.constraints.ts | 2 -- 6 files changed, 1 insertion(+), 17 deletions(-) diff --git a/e2e/convex/convex.config.ts b/e2e/convex/convex.config.ts index 28c30c9d..2f87f607 100644 --- a/e2e/convex/convex.config.ts +++ b/e2e/convex/convex.config.ts @@ -2,6 +2,5 @@ import { defineApp } from "convex/server"; import oauthProvider from "./betterAuth/convex.config"; const app = defineApp(); -// Keep provider fixtures separate from the bundled component used by the React tests. app.use(oauthProvider, { name: "oauthProvider" }); export default app; diff --git a/e2e/convex/tsconfig.json b/e2e/convex/tsconfig.json index 41bfbb9b..42b170fb 100644 --- a/e2e/convex/tsconfig.json +++ b/e2e/convex/tsconfig.json @@ -1,18 +1,11 @@ { - /* This TypeScript project config describes the environment that - * Convex functions run in and is used to typecheck them. - * You can modify it, but some settings are required to use Convex. - */ "compilerOptions": { - /* These settings are not required by Convex and can be modified. */ "allowJs": true, "strict": true, "moduleResolution": "Bundler", "jsx": "react-jsx", "skipLibCheck": true, "allowSyntheticDefaultImports": true, - - /* These compiler options are required by Convex */ "target": "ESNext", "lib": ["ES2023", "dom"], "forceConsistentCasingInFileNames": true, diff --git a/e2e/oauth-provider/provider.spec.ts b/e2e/oauth-provider/provider.spec.ts index f1b1d16e..49ae9fe4 100644 --- a/e2e/oauth-provider/provider.spec.ts +++ b/e2e/oauth-provider/provider.spec.ts @@ -4,11 +4,8 @@ import { createLocalJWKSet, jwtVerify } from "jose"; const site = "http://127.0.0.1:3211"; const base = `${site}/api/auth`; -// Authorization redirects are inspected, never followed to this synthetic client. const redirectUri = "https://client.example.com/callback"; -// Issue real provider tokens through signup, client registration, consent and PKCE. -// No adapter rows or endpoint responses are mocked. async function authorize(request: APIRequestContext) { const signup = await request.post(`${base}/sign-up/email`, { data: { diff --git a/src/client/adapter-compatibility.test.ts b/src/client/adapter-compatibility.test.ts index 2950df62..d552c9d7 100644 --- a/src/client/adapter-compatibility.test.ts +++ b/src/client/adapter-compatibility.test.ts @@ -19,8 +19,6 @@ const setup = () => { }; describe("Better Auth 1.7.7 compatibility", () => { - // Use the real factory so modelKey is injected upstream. Convex validators - // reject leaked metadata on both normal and OR/paginated component calls. it.each([false, true])( "keeps modelKey out of component calls (OR: %s)", async (or) => { diff --git a/src/component/testProfiles/auth-options.constraints.ts b/src/component/testProfiles/auth-options.constraints.ts index 02197d91..23ee99f1 100644 --- a/src/component/testProfiles/auth-options.constraints.ts +++ b/src/component/testProfiles/auth-options.constraints.ts @@ -1,8 +1,7 @@ import type { BetterAuthOptions } from "better-auth/minimal"; import { options } from "../../auth-options.js"; -// Exercise custom required fields and compound uniqueness independently of -// Better Auth's core account schema, which no longer requires issuer in 1.7.3+. +// Test-only constraints; production accounts do not require issuer. export const constraintsProfileOptions = { ...options, plugins: [ diff --git a/src/component/testProfiles/schema.constraints.ts b/src/component/testProfiles/schema.constraints.ts index 984eca4b..757c0c92 100644 --- a/src/component/testProfiles/schema.constraints.ts +++ b/src/component/testProfiles/schema.constraints.ts @@ -1,8 +1,6 @@ import { defineSchema, defineTable } from "convex/server"; import { tables } from "../schema.js"; -// Only this test profile declares issuer+accountId uniqueness. Production -// accounts use Better Auth 1.7.7's providerId/accountId lookup semantics. const account = defineTable(tables.account.validator) .index("issuer_accountId", ["issuer", "accountId"]) .index("accountId", ["accountId"]) From 6c9fe4948a54c18c879af866aa3c60e2272da689 Mon Sep 17 00:00:00 2001 From: Stephen Shkeda Date: Tue, 13 Oct 2026 00:34:39 -0400 Subject: [PATCH 29/29] Keep Vitest aligned with upstream --- package-lock.json | 90 +++++++++++++++++++++++------------------------ package.json | 2 +- 2 files changed, 46 insertions(+), 46 deletions(-) diff --git a/package-lock.json b/package-lock.json index ea73b0ff..39b7d10e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -49,7 +49,7 @@ "react-dom": "19.2.6", "typescript": "5.9.3", "typescript-eslint": "8.59.2", - "vitest": "4.1.10" + "vitest": "4.1.5" }, "peerDependencies": { "better-auth": ">=1.7.7 <1.8.0", @@ -3374,16 +3374,16 @@ } }, "node_modules/@vitest/expect": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.10.tgz", - "integrity": "sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.5.tgz", + "integrity": "sha512-PWBaRY5JoKuRnHlUHfpV/KohFylaDZTupcXN1H9vYryNLOnitSw60Mw9IAE2r67NbwwzBw/Cc/8q9BK3kIX8Kw==", "dev": true, "license": "MIT", "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", - "@vitest/spy": "4.1.10", - "@vitest/utils": "4.1.10", + "@vitest/spy": "4.1.5", + "@vitest/utils": "4.1.5", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" }, @@ -3392,13 +3392,13 @@ } }, "node_modules/@vitest/mocker": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.10.tgz", - "integrity": "sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.5.tgz", + "integrity": "sha512-/x2EmFC4mT4NNzqvC3fmesuV97w5FC903KPmey4gsnJiMQ3Be1IlDKVaDaG8iqaLFHqJ2FVEkxZk5VmeLjIItw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "4.1.10", + "@vitest/spy": "4.1.5", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, @@ -3419,9 +3419,9 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.10.tgz", - "integrity": "sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.5.tgz", + "integrity": "sha512-7I3q6l5qr03dVfMX2wCo9FxwSJbPdwKjy2uu/YPpU3wfHvIL4QHwVRp57OfGrDFeUJ8/8QdfBKIV12FTtLn00g==", "dev": true, "license": "MIT", "dependencies": { @@ -3432,13 +3432,13 @@ } }, "node_modules/@vitest/runner": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.10.tgz", - "integrity": "sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.5.tgz", + "integrity": "sha512-2D+o7Pr82IEO46YPpoA/YU0neeyr6FTerQb5Ro7BUnBuv6NQtT/kmVnczngiMEBhzgqz2UZYl5gArejsyERDSQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "4.1.10", + "@vitest/utils": "4.1.5", "pathe": "^2.0.3" }, "funding": { @@ -3446,14 +3446,14 @@ } }, "node_modules/@vitest/snapshot": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.10.tgz", - "integrity": "sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.5.tgz", + "integrity": "sha512-zypXEt4KH/XgKGPUz4eC2AvErYx0My5hfL8oDb1HzGFpEk1P62bxSohdyOmvz+d9UJwanI68MKwr2EquOaOgMQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.10", - "@vitest/utils": "4.1.10", + "@vitest/pretty-format": "4.1.5", + "@vitest/utils": "4.1.5", "magic-string": "^0.30.21", "pathe": "^2.0.3" }, @@ -3462,9 +3462,9 @@ } }, "node_modules/@vitest/spy": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.10.tgz", - "integrity": "sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.5.tgz", + "integrity": "sha512-2lNOsh6+R2Idnf1TCZqSwYlKN2E/iDlD8sgU59kYVl+OMDmvldO1VDk39smRfpUNwYpNRVn3w4YfuC7KfbBnkQ==", "dev": true, "license": "MIT", "funding": { @@ -3472,13 +3472,13 @@ } }, "node_modules/@vitest/utils": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.10.tgz", - "integrity": "sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.5.tgz", + "integrity": "sha512-76wdkrmfXfqGjueGgnb45ITPyUi1ycZ4IHgC2bhPDUfWHklY/q3MdLOAB+TF1e6xfl8NxNY0ZYaPCFNWSsw3Ug==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.10", + "@vitest/pretty-format": "4.1.5", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" }, @@ -8976,19 +8976,19 @@ } }, "node_modules/vitest": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.10.tgz", - "integrity": "sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.5.tgz", + "integrity": "sha512-9Xx1v3/ih3m9hN+SbfkUyy0JAs72ap3r7joc87XL6jwF0jGg6mFBvQ1SrwaX+h8BlkX6Hz9shdd1uo6AF+ZGpg==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/expect": "4.1.10", - "@vitest/mocker": "4.1.10", - "@vitest/pretty-format": "4.1.10", - "@vitest/runner": "4.1.10", - "@vitest/snapshot": "4.1.10", - "@vitest/spy": "4.1.10", - "@vitest/utils": "4.1.10", + "@vitest/expect": "4.1.5", + "@vitest/mocker": "4.1.5", + "@vitest/pretty-format": "4.1.5", + "@vitest/runner": "4.1.5", + "@vitest/snapshot": "4.1.5", + "@vitest/spy": "4.1.5", + "@vitest/utils": "4.1.5", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", @@ -9016,12 +9016,12 @@ "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", - "@vitest/browser-playwright": "4.1.10", - "@vitest/browser-preview": "4.1.10", - "@vitest/browser-webdriverio": "4.1.10", - "@vitest/coverage-istanbul": "4.1.10", - "@vitest/coverage-v8": "4.1.10", - "@vitest/ui": "4.1.10", + "@vitest/browser-playwright": "4.1.5", + "@vitest/browser-preview": "4.1.5", + "@vitest/browser-webdriverio": "4.1.5", + "@vitest/coverage-istanbul": "4.1.5", + "@vitest/coverage-v8": "4.1.5", + "@vitest/ui": "4.1.5", "happy-dom": "*", "jsdom": "*", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" diff --git a/package.json b/package.json index 2ec44f78..dce8df62 100644 --- a/package.json +++ b/package.json @@ -136,7 +136,7 @@ "react-dom": "19.2.6", "typescript": "5.9.3", "typescript-eslint": "8.59.2", - "vitest": "4.1.10" + "vitest": "4.1.5" }, "types": "./dist/client/index.d.ts", "module": "./dist/client/index.js",