@convex-dev/better-auth 0.9.10 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/dist/auth-config.d.ts +43 -0
  2. package/dist/auth-config.d.ts.map +1 -0
  3. package/dist/auth-config.js +45 -0
  4. package/dist/auth-config.js.map +1 -0
  5. package/dist/auth-options.d.ts +3 -0
  6. package/dist/auth-options.d.ts.map +1 -0
  7. package/dist/auth-options.js +41 -0
  8. package/dist/auth-options.js.map +1 -0
  9. package/dist/auth.d.ts +1 -3
  10. package/dist/auth.d.ts.map +1 -1
  11. package/dist/auth.js +2 -42
  12. package/dist/auth.js.map +1 -1
  13. package/dist/client/{adapterUtils.d.ts → adapter-utils.d.ts} +15 -15
  14. package/dist/client/adapter-utils.d.ts.map +1 -0
  15. package/dist/client/{adapterUtils.js → adapter-utils.js} +1 -1
  16. package/dist/client/adapter-utils.js.map +1 -0
  17. package/dist/client/adapter.d.ts +1 -2
  18. package/dist/client/adapter.d.ts.map +1 -1
  19. package/dist/client/adapter.js +4 -4
  20. package/dist/client/adapter.js.map +1 -1
  21. package/dist/client/create-api.d.ts +139 -0
  22. package/dist/client/create-api.d.ts.map +1 -0
  23. package/dist/client/create-api.js +204 -0
  24. package/dist/client/create-api.js.map +1 -0
  25. package/dist/client/create-client.d.ts +183 -0
  26. package/dist/client/create-client.d.ts.map +1 -0
  27. package/dist/client/create-client.js +311 -0
  28. package/dist/client/create-client.js.map +1 -0
  29. package/dist/client/{createSchema.d.ts → create-schema.d.ts} +1 -1
  30. package/dist/client/create-schema.d.ts.map +1 -0
  31. package/dist/client/{createSchema.js → create-schema.js} +11 -5
  32. package/dist/client/create-schema.js.map +1 -0
  33. package/dist/client/index.d.ts +4 -279
  34. package/dist/client/index.d.ts.map +1 -1
  35. package/dist/client/index.js +6 -476
  36. package/dist/client/index.js.map +1 -1
  37. package/dist/component/_generated/component.d.ts +0 -3
  38. package/dist/component/_generated/component.d.ts.map +1 -1
  39. package/dist/component/adapter.d.ts +19 -21
  40. package/dist/component/adapter.d.ts.map +1 -1
  41. package/dist/component/adapter.js +2 -2
  42. package/dist/component/adapter.js.map +1 -1
  43. package/dist/component/schema.d.ts +50 -50
  44. package/dist/nextjs/client.d.ts +4 -0
  45. package/dist/nextjs/client.d.ts.map +1 -0
  46. package/dist/nextjs/client.js +37 -0
  47. package/dist/nextjs/client.js.map +1 -0
  48. package/dist/nextjs/index.d.ts +19 -7
  49. package/dist/nextjs/index.d.ts.map +1 -1
  50. package/dist/nextjs/index.js +90 -36
  51. package/dist/nextjs/index.js.map +1 -1
  52. package/dist/plugins/convex/client.d.ts +1 -1
  53. package/dist/plugins/convex/client.d.ts.map +1 -1
  54. package/dist/plugins/convex/client.js +0 -1
  55. package/dist/plugins/convex/client.js.map +1 -1
  56. package/dist/plugins/convex/index.d.ts +239 -227
  57. package/dist/plugins/convex/index.d.ts.map +1 -1
  58. package/dist/plugins/convex/index.js +191 -37
  59. package/dist/plugins/convex/index.js.map +1 -1
  60. package/dist/plugins/cross-domain/client.d.ts +3 -3
  61. package/dist/plugins/cross-domain/client.d.ts.map +1 -1
  62. package/dist/plugins/cross-domain/index.d.ts +15 -70
  63. package/dist/plugins/cross-domain/index.d.ts.map +1 -1
  64. package/dist/plugins/cross-domain/index.js +8 -0
  65. package/dist/plugins/cross-domain/index.js.map +1 -1
  66. package/dist/react/index.d.ts +52 -2
  67. package/dist/react/index.d.ts.map +1 -1
  68. package/dist/react/index.js +133 -9
  69. package/dist/react/index.js.map +1 -1
  70. package/dist/react-start/index.d.ts +11 -41
  71. package/dist/react-start/index.d.ts.map +1 -1
  72. package/dist/react-start/index.js +82 -106
  73. package/dist/react-start/index.js.map +1 -1
  74. package/dist/utils/index.d.ts +20 -2
  75. package/dist/utils/index.d.ts.map +1 -1
  76. package/dist/utils/index.js +54 -1
  77. package/dist/utils/index.js.map +1 -1
  78. package/package.json +19 -12
  79. package/src/auth-config.ts +82 -0
  80. package/src/auth-options.ts +54 -0
  81. package/src/auth.ts +3 -56
  82. package/src/client/adapter.ts +5 -5
  83. package/src/client/create-api.ts +337 -0
  84. package/src/client/create-client.ts +446 -0
  85. package/src/client/{createSchema.ts → create-schema.ts} +10 -4
  86. package/src/client/index.ts +22 -786
  87. package/src/component/_generated/component.ts +0 -7
  88. package/src/component/adapter.ts +2 -3
  89. package/src/nextjs/client.tsx +52 -0
  90. package/src/nextjs/index.ts +138 -45
  91. package/src/plugins/convex/client.ts +1 -1
  92. package/src/plugins/convex/index.ts +337 -51
  93. package/src/plugins/cross-domain/index.ts +10 -2
  94. package/src/react/index.tsx +195 -9
  95. package/src/react-start/index.ts +126 -171
  96. package/src/test.ts +1 -1
  97. package/src/utils/index.ts +96 -1
  98. package/dist/client/adapterUtils.d.ts.map +0 -1
  99. package/dist/client/adapterUtils.js.map +0 -1
  100. package/dist/client/createSchema.d.ts.map +0 -1
  101. package/dist/client/createSchema.js.map +0 -1
  102. /package/src/client/{adapterUtils.ts → adapter-utils.ts} +0 -0
package/package.json CHANGED
@@ -6,7 +6,7 @@
6
6
  "bugs": {
7
7
  "url": "https://github.com/get-convex/better-auth/issues"
8
8
  },
9
- "version": "0.9.10",
9
+ "version": "0.10.0",
10
10
  "license": "Apache-2.0",
11
11
  "keywords": [
12
12
  "convex",
@@ -20,7 +20,7 @@
20
20
  "scripts": {
21
21
  "dev": "cd examples/react && npm run dev",
22
22
  "clean": "rm -rf dist *.tsbuildinfo",
23
- "build": "tsc --project ./tsconfig.build.json",
23
+ "build": "tsc --project tsconfig.build.json",
24
24
  "typecheck": "tsc --noEmit && tsc -p examples/next && tsc -p examples/next/convex && tsc -p examples/react && tsc -p examples/react/convex && tsc -p examples/tanstack && tsc -p examples/tanstack/convex",
25
25
  "lint": "eslint .",
26
26
  "all": "run-p -r 'dev' 'test:watch'",
@@ -59,6 +59,10 @@
59
59
  "types": "./dist/component/adapter.d.ts",
60
60
  "default": "./dist/component/adapter.js"
61
61
  },
62
+ "./auth-config": {
63
+ "types": "./dist/auth-config.d.ts",
64
+ "default": "./dist/auth-config.js"
65
+ },
62
66
  "./client/plugins": {
63
67
  "types": "./dist/client/plugins/index.d.ts",
64
68
  "default": "./dist/client/plugins/index.js"
@@ -67,6 +71,10 @@
67
71
  "types": "./dist/nextjs/index.d.ts",
68
72
  "default": "./dist/nextjs/index.js"
69
73
  },
74
+ "./nextjs/client": {
75
+ "types": "./dist/nextjs/client.d.ts",
76
+ "default": "./dist/nextjs/client.js"
77
+ },
70
78
  "./plugins": {
71
79
  "types": "./dist/plugins/index.d.ts",
72
80
  "default": "./dist/plugins/index.js"
@@ -93,26 +101,26 @@
93
101
  }
94
102
  },
95
103
  "peerDependencies": {
96
- "better-auth": "1.3.34",
104
+ "better-auth": "1.4.7",
97
105
  "convex": "^1.25.0",
98
106
  "react": "^18.3.1 || ^19.0.0",
99
107
  "react-dom": "^18.3.1 || ^19.0.0"
100
108
  },
101
109
  "devDependencies": {
102
110
  "@better-fetch/fetch": "^1.1.18",
111
+ "@better-auth/passkey": "1.4.7",
103
112
  "@edge-runtime/vm": "5.0.0",
104
113
  "@eslint/eslintrc": "3.3.1",
105
114
  "@eslint/js": "9.39.1",
106
- "@tanstack/react-start": "^1.132.37",
115
+ "@tanstack/react-start": "^1.140.1",
107
116
  "@types/common-tags": "^1.8.4",
108
117
  "@types/node": "20.19.24",
109
118
  "@types/react": "18.3.26",
110
119
  "@types/react-dom": "18.3.7",
111
120
  "@types/semver": "^7.7.0",
112
- "@vitejs/plugin-react": "5.0.4",
113
- "concurrently": "^9.2.0",
114
121
  "chokidar-cli": "3.0.0",
115
- "convex": "^1.29.0",
122
+ "concurrently": "^9.2.0",
123
+ "convex": "^1.30.0",
116
124
  "convex-test": "0.0.41",
117
125
  "cpy-cli": "6.0.0",
118
126
  "eslint": "9.39.1",
@@ -120,16 +128,15 @@
120
128
  "eslint-plugin-react-hooks": "5.2.0",
121
129
  "eslint-plugin-react-refresh": "0.4.24",
122
130
  "globals": "15.14.0",
123
- "next": "^15.1.8",
124
- "npm-run-all2": "7.0.2",
131
+ "next": "^16.0.3",
132
+ "npm-run-all2": "8.0.4",
125
133
  "pkg-pr-new": "0.0.60",
126
134
  "prettier": "3.6.2",
127
135
  "react": "18.3.1",
128
136
  "react-dom": "18.3.1",
129
137
  "typescript": "5.9.3",
130
138
  "typescript-eslint": "8.46.4",
131
- "vite": "^7.1.5",
132
- "vitest": "3.2.4"
139
+ "vitest": "^4.0.15"
133
140
  },
134
141
  "types": "./dist/client/index.d.ts",
135
142
  "module": "./dist/client/index.js",
@@ -140,6 +147,6 @@
140
147
  "remeda": "^2.32.0",
141
148
  "semver": "^7.7.3",
142
149
  "type-fest": "^4.39.1",
143
- "zod": "^3.24.4"
150
+ "zod": "^4.0.0"
144
151
  }
145
152
  }
@@ -0,0 +1,82 @@
1
+ import type { JwtOptions } from "better-auth/plugins";
2
+ import type { AuthProvider } from "convex/server";
3
+ import type { JSONWebKeySet } from "jose";
4
+
5
+ type JwksDoc = {
6
+ id: string;
7
+ publicKey: string;
8
+ privateKey: string;
9
+ createdAt: number;
10
+ expiresAt?: number;
11
+ alg?: string;
12
+ crv?: string;
13
+ };
14
+
15
+ export const createPublicJwks = (jwks: JwksDoc[], options?: JwtOptions) => {
16
+ /*
17
+ const now = Date.now();
18
+ const DEFAULT_GRACE_PERIOD = 60 * 60 * 24 * 30;
19
+ const gracePeriod =
20
+ (options?.jwks?.gracePeriod ?? DEFAULT_GRACE_PERIOD) * 1000;
21
+
22
+ const keys = jwks.filter((key) => {
23
+ if (!key.expiresAt) {
24
+ return true;
25
+ }
26
+ return new Date(key.expiresAt).getTime() + gracePeriod > now;
27
+ });
28
+ */
29
+ const keys = jwks;
30
+
31
+ const keyPairConfig = options?.jwks?.keyPairConfig;
32
+ const defaultCrv = keyPairConfig
33
+ ? "crv" in keyPairConfig
34
+ ? (keyPairConfig as { crv: string }).crv
35
+ : undefined
36
+ : undefined;
37
+ return {
38
+ keys: keys.map((keySet) => {
39
+ return {
40
+ alg: keySet.alg ?? options?.jwks?.keyPairConfig?.alg ?? "EdDSA",
41
+ crv: keySet.crv ?? defaultCrv,
42
+ ...JSON.parse(keySet.publicKey),
43
+ kid: keySet.id,
44
+ };
45
+ }),
46
+ } satisfies JSONWebKeySet as JSONWebKeySet;
47
+ };
48
+
49
+ export const getAuthConfigProvider = (opts?: {
50
+ basePath?: string;
51
+ /**
52
+ * @param jwks - Optional static JWKS to avoid fetching from the database.
53
+ *
54
+ * This should be a stringified document from the Better Auth JWKS table. You
55
+ * can create one in the console.
56
+ *
57
+ * Example:
58
+ * ```bash
59
+ * npx convex run auth:generateJwk | npx convex env set JWKS
60
+ * ```
61
+ *
62
+ * Then use it in your auth config:
63
+ * ```ts
64
+ * export default {
65
+ * providers: [getAuthConfigProvider({ jwks: process.env.JWKS })],
66
+ * } satisfies AuthConfig;
67
+ * ```
68
+ *
69
+ */
70
+ jwks?: string;
71
+ }) => {
72
+ const parsedJwks = opts?.jwks ? JSON.parse(opts.jwks) : undefined;
73
+ return {
74
+ type: "customJwt",
75
+ issuer: `${process.env.CONVEX_SITE_URL}`,
76
+ applicationID: "convex",
77
+ algorithm: "RS256",
78
+ jwks: parsedJwks
79
+ ? `data:text/plain;charset=utf-8;base64,${btoa(JSON.stringify(createPublicJwks(parsedJwks)))}`
80
+ : `${process.env.CONVEX_SITE_URL}${opts?.basePath ?? "/api/auth"}/convex/jwks`,
81
+ } satisfies AuthProvider;
82
+ };
@@ -0,0 +1,54 @@
1
+ import { type BetterAuthOptions } from "better-auth";
2
+ import {
3
+ anonymous,
4
+ bearer,
5
+ emailOTP,
6
+ genericOAuth,
7
+ jwt,
8
+ magicLink,
9
+ oidcProvider,
10
+ oneTap,
11
+ oneTimeToken,
12
+ phoneNumber,
13
+ twoFactor,
14
+ username,
15
+ } from "better-auth/plugins";
16
+ import { passkey } from "@better-auth/passkey";
17
+ import { convex } from "./plugins/convex/index.js";
18
+ import { convexAdapter } from "./client/adapter.js";
19
+
20
+ // This is the config used to generate the schema
21
+ export const options = {
22
+ database: convexAdapter({} as any, {} as any),
23
+ rateLimit: {
24
+ storage: "database",
25
+ },
26
+ plugins: [
27
+ twoFactor(),
28
+ anonymous(),
29
+ username(),
30
+ phoneNumber(),
31
+ magicLink({ sendMagicLink: async () => {} }),
32
+ emailOTP({ sendVerificationOTP: async () => {} }),
33
+ passkey(),
34
+ genericOAuth({
35
+ config: [
36
+ {
37
+ clientId: "",
38
+ clientSecret: "",
39
+ providerId: "",
40
+ },
41
+ ],
42
+ }),
43
+ oneTap(),
44
+ oidcProvider({
45
+ loginPage: "/login",
46
+ }),
47
+ bearer(),
48
+ oneTimeToken(),
49
+ jwt(),
50
+ convex({
51
+ authConfig: { providers: [{ applicationID: "convex", domain: "" }] },
52
+ }),
53
+ ],
54
+ } as BetterAuthOptions; // assert type to avoid overloading ts compiler
package/src/auth.ts CHANGED
@@ -1,57 +1,4 @@
1
- import { betterAuth, type BetterAuthOptions } from "better-auth";
2
- import {
3
- anonymous,
4
- bearer,
5
- emailOTP,
6
- genericOAuth,
7
- jwt,
8
- magicLink,
9
- oidcProvider,
10
- oneTap,
11
- oneTimeToken,
12
- phoneNumber,
13
- twoFactor,
14
- username,
15
- } from "better-auth/plugins";
16
- import { convex } from "./plugins/convex/index.js";
17
- import { passkey } from "better-auth/plugins/passkey";
18
- import { convexAdapter } from "./client/adapter.js";
1
+ import { betterAuth } from "better-auth";
2
+ import { options } from "./auth-options.js";
19
3
 
20
- // This is the config used to generate the schema
21
- const options = {
22
- logger: {
23
- disabled: true,
24
- },
25
- database: convexAdapter({} as any, {} as any),
26
- rateLimit: {
27
- storage: "database",
28
- },
29
- plugins: [
30
- twoFactor(),
31
- anonymous(),
32
- username(),
33
- phoneNumber(),
34
- magicLink({ sendMagicLink: async () => {} }),
35
- emailOTP({ sendVerificationOTP: async () => {} }),
36
- passkey(),
37
- genericOAuth({
38
- config: [
39
- {
40
- clientId: "",
41
- clientSecret: "",
42
- providerId: "",
43
- },
44
- ],
45
- }),
46
- oneTap(),
47
- oidcProvider({
48
- loginPage: "/login",
49
- }),
50
- bearer(),
51
- oneTimeToken(),
52
- jwt(),
53
- convex(),
54
- ],
55
- } as BetterAuthOptions; // assert type to avoid overloading ts compiler
56
- const config = betterAuth(options) as ReturnType<typeof betterAuth>;
57
- export { config as auth };
4
+ export const auth = betterAuth(options);
@@ -105,7 +105,7 @@ export const convexAdapter = <
105
105
  >(
106
106
  ctx: Ctx,
107
107
  api: {
108
- adapter: SetOptional<ComponentApi["adapter"], "migrationRemoveUserId">;
108
+ adapter: ComponentApi["adapter"];
109
109
  adapterTest?: ComponentApi["adapterTest"];
110
110
  },
111
111
  config: {
@@ -130,10 +130,10 @@ export const convexAdapter = <
130
130
  mapKeysTransformOutput: {
131
131
  _id: "id",
132
132
  },
133
- // With supportsDates: false, dates are stored as strings,
134
- // we convert them to numbers here. This aligns with how
135
- // Convex stores _creationTime, and avoids a breaking change.
133
+ // Dates provided as strings
136
134
  supportsDates: false,
135
+ // Convert dates to numbers. This aligns with how
136
+ // Convex stores _creationTime and avoids a breaking change.
137
137
  customTransformInput: ({ data, fieldAttributes }) => {
138
138
  if (data && fieldAttributes.type === "date") {
139
139
  return new Date(data).getTime();
@@ -156,7 +156,7 @@ export const convexAdapter = <
156
156
  isRunMutationCtx: isRunMutationCtx(ctx),
157
157
  },
158
158
  createSchema: async ({ file, tables }) => {
159
- const { createSchema } = await import("./createSchema.js");
159
+ const { createSchema } = await import("./create-schema.js");
160
160
  return createSchema({ file, tables });
161
161
  },
162
162
  create: async ({ model, data, select }): Promise<any> => {
@@ -0,0 +1,337 @@
1
+ import {
2
+ type FunctionHandle,
3
+ type SchemaDefinition,
4
+ mutationGeneric,
5
+ paginationOptsValidator,
6
+ queryGeneric,
7
+ } from "convex/server";
8
+ import { type GenericId, v } from "convex/values";
9
+ import { asyncMap } from "convex-helpers";
10
+ import { partial } from "convex-helpers/validators";
11
+ import {
12
+ adapterWhereValidator,
13
+ checkUniqueFields,
14
+ hasUniqueFields,
15
+ listOne,
16
+ paginate,
17
+ selectFields,
18
+ } from "./adapter-utils.js";
19
+ import { getAuthTables } from "better-auth/db";
20
+ import { type TableNames } from "../component/_generated/dataModel.js";
21
+ import type { BetterAuthOptions } from "better-auth";
22
+
23
+ const whereValidator = (
24
+ schema: SchemaDefinition<any, any>,
25
+ tableName: TableNames
26
+ ) =>
27
+ v.object({
28
+ field: v.union(
29
+ ...Object.keys(schema.tables[tableName].validator.fields).map((field) =>
30
+ v.literal(field)
31
+ ),
32
+ v.literal("_id")
33
+ ),
34
+ operator: v.optional(
35
+ v.union(
36
+ v.literal("lt"),
37
+ v.literal("lte"),
38
+ v.literal("gt"),
39
+ v.literal("gte"),
40
+ v.literal("eq"),
41
+ v.literal("in"),
42
+ v.literal("not_in"),
43
+ v.literal("ne"),
44
+ v.literal("contains"),
45
+ v.literal("starts_with"),
46
+ v.literal("ends_with")
47
+ )
48
+ ),
49
+ value: v.union(
50
+ v.string(),
51
+ v.number(),
52
+ v.boolean(),
53
+ v.array(v.string()),
54
+ v.array(v.number()),
55
+ v.null()
56
+ ),
57
+ connector: v.optional(v.union(v.literal("AND"), v.literal("OR"))),
58
+ });
59
+
60
+ export const createApi = <Schema extends SchemaDefinition<any, any>>(
61
+ schema: Schema,
62
+ createAuthOptions: (ctx: any) => BetterAuthOptions
63
+ ) => {
64
+ const betterAuthSchema = getAuthTables(createAuthOptions({} as any));
65
+ return {
66
+ create: mutationGeneric({
67
+ args: {
68
+ input: v.union(
69
+ ...Object.entries(schema.tables).map(([model, table]) =>
70
+ v.object({
71
+ model: v.literal(model),
72
+ data: v.object((table as any).validator.fields),
73
+ })
74
+ )
75
+ ),
76
+ select: v.optional(v.array(v.string())),
77
+ onCreateHandle: v.optional(v.string()),
78
+ },
79
+ handler: async (ctx, args) => {
80
+ await checkUniqueFields(
81
+ ctx,
82
+ schema,
83
+ betterAuthSchema,
84
+ args.input.model,
85
+ args.input.data
86
+ );
87
+ const id = await ctx.db.insert(
88
+ args.input.model as any,
89
+ args.input.data
90
+ );
91
+ const doc = await ctx.db.get(id);
92
+ if (!doc) {
93
+ throw new Error(`Failed to create ${args.input.model}`);
94
+ }
95
+ const result = selectFields(doc, args.select);
96
+ if (args.onCreateHandle) {
97
+ await ctx.runMutation(
98
+ args.onCreateHandle as FunctionHandle<"mutation">,
99
+ {
100
+ model: args.input.model,
101
+ doc,
102
+ }
103
+ );
104
+ }
105
+ return result;
106
+ },
107
+ }),
108
+ findOne: queryGeneric({
109
+ args: {
110
+ model: v.union(
111
+ ...Object.keys(schema.tables).map((model) => v.literal(model))
112
+ ),
113
+ where: v.optional(v.array(adapterWhereValidator)),
114
+ select: v.optional(v.array(v.string())),
115
+ },
116
+ handler: async (ctx, args) => {
117
+ return await listOne(ctx, schema, betterAuthSchema, args);
118
+ },
119
+ }),
120
+ findMany: queryGeneric({
121
+ args: {
122
+ model: v.union(
123
+ ...Object.keys(schema.tables).map((model) => v.literal(model))
124
+ ),
125
+ where: v.optional(v.array(adapterWhereValidator)),
126
+ limit: v.optional(v.number()),
127
+ sortBy: v.optional(
128
+ v.object({
129
+ direction: v.union(v.literal("asc"), v.literal("desc")),
130
+ field: v.string(),
131
+ })
132
+ ),
133
+ offset: v.optional(v.number()),
134
+ paginationOpts: paginationOptsValidator,
135
+ },
136
+ handler: async (ctx, args) => {
137
+ return await paginate(ctx, schema, betterAuthSchema, args);
138
+ },
139
+ }),
140
+ updateOne: mutationGeneric({
141
+ args: {
142
+ input: v.union(
143
+ ...Object.entries(schema.tables).map(
144
+ ([name, table]: [string, Schema["tables"][string]]) => {
145
+ const tableName = name as TableNames;
146
+ const fields = partial(table.validator.fields);
147
+ return v.object({
148
+ model: v.literal(tableName),
149
+ update: v.object(fields),
150
+ where: v.optional(v.array(whereValidator(schema, tableName))),
151
+ });
152
+ }
153
+ )
154
+ ),
155
+ onUpdateHandle: v.optional(v.string()),
156
+ },
157
+ handler: async (ctx, args) => {
158
+ const doc = await listOne(ctx, schema, betterAuthSchema, args.input);
159
+ if (!doc) {
160
+ throw new Error(`Failed to update ${args.input.model}`);
161
+ }
162
+ await checkUniqueFields(
163
+ ctx,
164
+ schema,
165
+ betterAuthSchema,
166
+ args.input.model,
167
+ args.input.update,
168
+ doc
169
+ );
170
+ await ctx.db.patch(
171
+ doc._id as GenericId<string>,
172
+ args.input.update as any
173
+ );
174
+ const updatedDoc = await ctx.db.get(doc._id as GenericId<string>);
175
+ if (!updatedDoc) {
176
+ throw new Error(`Failed to update ${args.input.model}`);
177
+ }
178
+ if (args.onUpdateHandle) {
179
+ await ctx.runMutation(
180
+ args.onUpdateHandle as FunctionHandle<"mutation">,
181
+ {
182
+ model: args.input.model,
183
+ newDoc: updatedDoc,
184
+ oldDoc: doc,
185
+ }
186
+ );
187
+ }
188
+ return updatedDoc;
189
+ },
190
+ }),
191
+ updateMany: mutationGeneric({
192
+ args: {
193
+ input: v.union(
194
+ ...Object.entries(schema.tables).map(
195
+ ([name, table]: [string, Schema["tables"][string]]) => {
196
+ const tableName = name as TableNames;
197
+ const fields = partial(table.validator.fields);
198
+ return v.object({
199
+ model: v.literal(tableName),
200
+ update: v.object(fields),
201
+ where: v.optional(v.array(whereValidator(schema, tableName))),
202
+ });
203
+ }
204
+ )
205
+ ),
206
+ paginationOpts: paginationOptsValidator,
207
+ onUpdateHandle: v.optional(v.string()),
208
+ },
209
+ handler: async (ctx, args) => {
210
+ const { page, ...result } = await paginate(
211
+ ctx,
212
+ schema,
213
+ betterAuthSchema,
214
+ {
215
+ ...args.input,
216
+ paginationOpts: args.paginationOpts,
217
+ }
218
+ );
219
+ if (args.input.update) {
220
+ if (
221
+ hasUniqueFields(
222
+ betterAuthSchema,
223
+ args.input.model,
224
+ args.input.update ?? {}
225
+ ) &&
226
+ page.length > 1
227
+ ) {
228
+ throw new Error(
229
+ `Attempted to set unique fields in multiple documents in ${args.input.model} with the same value. Fields: ${Object.keys(args.input.update ?? {}).join(", ")}`
230
+ );
231
+ }
232
+ await asyncMap(page, async (doc) => {
233
+ await checkUniqueFields(
234
+ ctx,
235
+ schema,
236
+ betterAuthSchema,
237
+ args.input.model,
238
+ args.input.update ?? {},
239
+ doc
240
+ );
241
+ await ctx.db.patch(
242
+ doc._id as GenericId<string>,
243
+ args.input.update as any
244
+ );
245
+
246
+ if (args.onUpdateHandle) {
247
+ await ctx.runMutation(
248
+ args.onUpdateHandle as FunctionHandle<"mutation">,
249
+ {
250
+ model: args.input.model,
251
+ newDoc: await ctx.db.get(doc._id as GenericId<string>),
252
+ oldDoc: doc,
253
+ }
254
+ );
255
+ }
256
+ });
257
+ }
258
+ return {
259
+ ...result,
260
+ count: page.length,
261
+ ids: page.map((doc) => doc._id),
262
+ };
263
+ },
264
+ }),
265
+ deleteOne: mutationGeneric({
266
+ args: {
267
+ input: v.union(
268
+ ...Object.keys(schema.tables).map((name: string) => {
269
+ const tableName = name as TableNames;
270
+ return v.object({
271
+ model: v.literal(tableName),
272
+ where: v.optional(v.array(whereValidator(schema, tableName))),
273
+ });
274
+ })
275
+ ),
276
+ onDeleteHandle: v.optional(v.string()),
277
+ },
278
+ handler: async (ctx, args) => {
279
+ const doc = await listOne(ctx, schema, betterAuthSchema, args.input);
280
+ if (!doc) {
281
+ return;
282
+ }
283
+ await ctx.db.delete(doc._id as GenericId<string>);
284
+ if (args.onDeleteHandle) {
285
+ await ctx.runMutation(
286
+ args.onDeleteHandle as FunctionHandle<"mutation">,
287
+ { model: args.input.model, doc }
288
+ );
289
+ }
290
+ return doc;
291
+ },
292
+ }),
293
+ deleteMany: mutationGeneric({
294
+ args: {
295
+ input: v.union(
296
+ ...Object.keys(schema.tables).map((name: string) => {
297
+ const tableName = name as TableNames;
298
+ return v.object({
299
+ model: v.literal(tableName),
300
+ where: v.optional(v.array(whereValidator(schema, tableName))),
301
+ });
302
+ })
303
+ ),
304
+ paginationOpts: paginationOptsValidator,
305
+ onDeleteHandle: v.optional(v.string()),
306
+ },
307
+ handler: async (ctx, args) => {
308
+ const { page, ...result } = await paginate(
309
+ ctx,
310
+ schema,
311
+ betterAuthSchema,
312
+ {
313
+ ...args.input,
314
+ paginationOpts: args.paginationOpts,
315
+ }
316
+ );
317
+ await asyncMap(page, async (doc) => {
318
+ if (args.onDeleteHandle) {
319
+ await ctx.runMutation(
320
+ args.onDeleteHandle as FunctionHandle<"mutation">,
321
+ {
322
+ model: args.input.model,
323
+ doc,
324
+ }
325
+ );
326
+ }
327
+ await ctx.db.delete(doc._id as GenericId<string>);
328
+ });
329
+ return {
330
+ ...result,
331
+ count: page.length,
332
+ ids: page.map((doc) => doc._id),
333
+ };
334
+ },
335
+ }),
336
+ };
337
+ };