@convex-dev/better-auth 0.7.0-alpha.10 → 0.7.0-alpha.11

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 (91) hide show
  1. package/dist/commonjs/client/adapter.d.ts +10 -1
  2. package/dist/commonjs/client/adapter.d.ts.map +1 -1
  3. package/dist/commonjs/client/adapter.js +183 -192
  4. package/dist/commonjs/client/adapter.js.map +1 -1
  5. package/dist/commonjs/client/index.d.ts +238 -179
  6. package/dist/commonjs/client/index.d.ts.map +1 -1
  7. package/dist/commonjs/client/index.js +46 -58
  8. package/dist/commonjs/client/index.js.map +1 -1
  9. package/dist/commonjs/component/adapterTest.d.ts +19 -0
  10. package/dist/commonjs/component/adapterTest.d.ts.map +1 -0
  11. package/dist/commonjs/component/adapterTest.js +82 -0
  12. package/dist/commonjs/component/adapterTest.js.map +1 -0
  13. package/dist/commonjs/component/lib.d.ts +218 -548
  14. package/dist/commonjs/component/lib.d.ts.map +1 -1
  15. package/dist/commonjs/component/lib.js +315 -286
  16. package/dist/commonjs/component/lib.js.map +1 -1
  17. package/dist/commonjs/component/schema.d.ts +90 -28
  18. package/dist/commonjs/component/schema.d.ts.map +1 -1
  19. package/dist/commonjs/component/schema.js +76 -18
  20. package/dist/commonjs/component/schema.js.map +1 -1
  21. package/dist/commonjs/component/util.d.ts +148 -86
  22. package/dist/commonjs/component/util.d.ts.map +1 -1
  23. package/dist/commonjs/nextjs/index.d.ts.map +1 -1
  24. package/dist/commonjs/nextjs/index.js +3 -0
  25. package/dist/commonjs/nextjs/index.js.map +1 -1
  26. package/dist/commonjs/plugins/convex/index.d.ts +14 -11
  27. package/dist/commonjs/plugins/convex/index.d.ts.map +1 -1
  28. package/dist/commonjs/plugins/convex/index.js +3 -2
  29. package/dist/commonjs/plugins/convex/index.js.map +1 -1
  30. package/dist/commonjs/plugins/cross-domain/client.d.ts +1 -1
  31. package/dist/commonjs/plugins/cross-domain/index.d.ts +5 -3
  32. package/dist/commonjs/plugins/cross-domain/index.d.ts.map +1 -1
  33. package/dist/commonjs/plugins/cross-domain/index.js +19 -5
  34. package/dist/commonjs/plugins/cross-domain/index.js.map +1 -1
  35. package/dist/commonjs/react-start/index.d.ts +4 -4
  36. package/dist/commonjs/utils/index.d.ts +2 -0
  37. package/dist/commonjs/utils/index.d.ts.map +1 -0
  38. package/dist/commonjs/utils/index.js +8 -0
  39. package/dist/commonjs/utils/index.js.map +1 -0
  40. package/dist/esm/client/adapter.d.ts +10 -1
  41. package/dist/esm/client/adapter.d.ts.map +1 -1
  42. package/dist/esm/client/adapter.js +183 -192
  43. package/dist/esm/client/adapter.js.map +1 -1
  44. package/dist/esm/client/index.d.ts +238 -179
  45. package/dist/esm/client/index.d.ts.map +1 -1
  46. package/dist/esm/client/index.js +46 -58
  47. package/dist/esm/client/index.js.map +1 -1
  48. package/dist/esm/component/adapterTest.d.ts +19 -0
  49. package/dist/esm/component/adapterTest.d.ts.map +1 -0
  50. package/dist/esm/component/adapterTest.js +82 -0
  51. package/dist/esm/component/adapterTest.js.map +1 -0
  52. package/dist/esm/component/lib.d.ts +218 -548
  53. package/dist/esm/component/lib.d.ts.map +1 -1
  54. package/dist/esm/component/lib.js +315 -286
  55. package/dist/esm/component/lib.js.map +1 -1
  56. package/dist/esm/component/schema.d.ts +90 -28
  57. package/dist/esm/component/schema.d.ts.map +1 -1
  58. package/dist/esm/component/schema.js +76 -18
  59. package/dist/esm/component/schema.js.map +1 -1
  60. package/dist/esm/component/util.d.ts +148 -86
  61. package/dist/esm/component/util.d.ts.map +1 -1
  62. package/dist/esm/nextjs/index.d.ts.map +1 -1
  63. package/dist/esm/nextjs/index.js +3 -0
  64. package/dist/esm/nextjs/index.js.map +1 -1
  65. package/dist/esm/plugins/convex/index.d.ts +14 -11
  66. package/dist/esm/plugins/convex/index.d.ts.map +1 -1
  67. package/dist/esm/plugins/convex/index.js +3 -2
  68. package/dist/esm/plugins/convex/index.js.map +1 -1
  69. package/dist/esm/plugins/cross-domain/client.d.ts +1 -1
  70. package/dist/esm/plugins/cross-domain/index.d.ts +5 -3
  71. package/dist/esm/plugins/cross-domain/index.d.ts.map +1 -1
  72. package/dist/esm/plugins/cross-domain/index.js +19 -5
  73. package/dist/esm/plugins/cross-domain/index.js.map +1 -1
  74. package/dist/esm/react-start/index.d.ts +4 -4
  75. package/dist/esm/utils/index.d.ts +2 -0
  76. package/dist/esm/utils/index.d.ts.map +1 -0
  77. package/dist/esm/utils/index.js +8 -0
  78. package/dist/esm/utils/index.js.map +1 -0
  79. package/package.json +20 -5
  80. package/src/client/adapter.test.ts +144 -0
  81. package/src/client/adapter.ts +191 -195
  82. package/src/client/index.ts +46 -71
  83. package/src/component/_generated/api.d.ts +605 -149
  84. package/src/component/adapterTest.ts +141 -0
  85. package/src/component/lib.ts +444 -335
  86. package/src/component/schema.ts +81 -19
  87. package/src/nextjs/index.ts +3 -0
  88. package/src/plugins/convex/index.ts +5 -2
  89. package/src/plugins/cross-domain/index.ts +19 -5
  90. package/src/client/cors.ts +0 -425
  91. /package/src/{util.ts → utils/index.ts} +0 -0
@@ -1,351 +1,380 @@
1
- import { action, mutation, query, } from "../component/_generated/server.js";
1
+ import { mutation, query } from "../component/_generated/server.js";
2
2
  import { asyncMap } from "convex-helpers";
3
3
  import { v } from "convex/values";
4
- import { api } from "../component/_generated/api.js";
5
- import schema from "../component/schema.js";
6
- import { paginationOptsValidator } from "convex/server";
4
+ import schema, { specialFields } from "../component/schema.js";
5
+ import { paginationOptsValidator, } from "convex/server";
7
6
  import { paginator } from "convex-helpers/server/pagination";
8
7
  import { partial } from "convex-helpers/validators";
9
- export const transformInput = (model, data) => {
8
+ export const adapterWhereValidator = v.object({
9
+ field: v.string(),
10
+ operator: v.optional(v.union(v.literal("lt"), v.literal("lte"), v.literal("gt"), v.literal("gte"), v.literal("eq"), v.literal("in"), v.literal("ne"), v.literal("contains"), v.literal("starts_with"), v.literal("ends_with"))),
11
+ value: v.union(v.string(), v.number(), v.boolean(), v.array(v.string()), v.array(v.number()), v.null()),
12
+ connector: v.optional(v.union(v.literal("AND"), v.literal("OR"))),
13
+ });
14
+ export const adapterArgsValidator = v.object({
15
+ model: v.string(),
16
+ where: v.optional(v.array(adapterWhereValidator)),
17
+ sortBy: v.optional(v.object({
18
+ field: v.string(),
19
+ direction: v.union(v.literal("asc"), v.literal("desc")),
20
+ })),
21
+ select: v.optional(v.array(v.string())),
22
+ limit: v.optional(v.number()),
23
+ unique: v.optional(v.boolean()),
24
+ });
25
+ const getUniqueFields = (table, input) => {
26
+ const fields = specialFields[table];
27
+ if (!fields) {
28
+ return [];
29
+ }
30
+ return Object.entries(fields)
31
+ .filter(([key, value]) => value.unique && Object.keys(input).includes(key))
32
+ .map(([key]) => key);
33
+ };
34
+ const checkUniqueFields = async (ctx, table, input, doc) => {
35
+ const uniqueFields = getUniqueFields(table, input);
36
+ if (!uniqueFields.length) {
37
+ return;
38
+ }
39
+ for (const field of uniqueFields) {
40
+ const existingDoc = await ctx.db
41
+ .query(table)
42
+ .withIndex(field, (q) => q.eq(field, input[field]))
43
+ .unique();
44
+ if (existingDoc && existingDoc._id !== doc?._id) {
45
+ throw new Error(`${table} ${field} already exists`);
46
+ }
47
+ }
48
+ };
49
+ const findIndex = async (args) => {
50
+ if (!args.where && !args.sortBy) {
51
+ return;
52
+ }
53
+ if (args.where?.some((w) => w.field === "id")) {
54
+ throw new Error("id is not a valid index field");
55
+ }
56
+ if (args.where?.some((w) => w.connector && w.connector !== "AND")) {
57
+ throw new Error(`OR connector not supported: ${JSON.stringify(args.where)}`);
58
+ }
59
+ if (args.where?.some((w) => w.operator &&
60
+ !["lt", "lte", "gt", "gte", "eq", "in"].includes(w.operator))) {
61
+ throw new Error(`where clause not supported: ${JSON.stringify(args.where)}`);
62
+ }
63
+ const lowerBounds = args.where?.filter((w) => w.operator === "lt" || w.operator === "lte") ??
64
+ [];
65
+ if (lowerBounds.length > 1) {
66
+ throw new Error(`cannot have more than one lower bound where clause: ${JSON.stringify(args.where)}`);
67
+ }
68
+ const upperBounds = args.where?.filter((w) => w.operator === "gt" || w.operator === "gte") ??
69
+ [];
70
+ if (upperBounds.length > 1) {
71
+ throw new Error(`cannot have more than one upper bound where clause: ${JSON.stringify(args.where)}`);
72
+ }
73
+ const lowerBound = lowerBounds[0];
74
+ const upperBound = upperBounds[0];
75
+ if (lowerBound && upperBound && lowerBound.field !== upperBound.field) {
76
+ throw new Error(`lower bound and upper bound must have the same field: ${JSON.stringify(args.where)}`);
77
+ }
78
+ const boundField = lowerBound?.field || upperBound?.field;
79
+ if (boundField &&
80
+ args.where?.some((w) => w.field === boundField && w !== lowerBound && w !== upperBound)) {
81
+ throw new Error(`too many where clauses on the bound field: ${JSON.stringify(args.where)}`);
82
+ }
83
+ const indexFields = args.where
84
+ ?.filter((w) => !w.operator || w.operator === "eq")
85
+ .sort((a, b) => {
86
+ return a.field.localeCompare(b.field);
87
+ })
88
+ .map((w) => [w.field, w.value]) ?? [];
89
+ if (!indexFields?.length && !boundField && !args.sortBy) {
90
+ return;
91
+ }
92
+ const indexes = schema.tables[args.model][" indexes"]();
93
+ const sortField = args.sortBy?.field;
94
+ // We internally use _creationTime in place of Better Auth's createdAt
95
+ const indexName = indexFields
96
+ .map(([field]) => field)
97
+ .join("_")
98
+ .concat(boundField && boundField !== "createdAt"
99
+ ? `${indexFields.length ? "_" : ""}${boundField}`
100
+ : "")
101
+ .concat(sortField && sortField !== "createdAt" && boundField !== sortField
102
+ ? `${indexFields.length || boundField ? "_" : ""}${sortField}`
103
+ : "");
104
+ if (!indexName && !boundField && !sortField) {
105
+ return;
106
+ }
107
+ // Use the built in creationTime index if bounding or sorting by createdAt
108
+ // with no other fields
109
+ const index = !indexName
110
+ ? {
111
+ indexDescriptor: "by_creation_time",
112
+ fields: [],
113
+ }
114
+ : indexes.find(({ indexDescriptor }) => {
115
+ return boundField === "createdAt" || sortField === "createdAt"
116
+ ? indexDescriptor === indexName
117
+ : indexDescriptor.startsWith(indexName);
118
+ });
119
+ if (!index) {
120
+ throw new Error(`Index ${indexName} not found for table ${args.model}`);
121
+ }
10
122
  return {
11
- ...Object.fromEntries(Object.entries(data).map(([key, value]) => {
12
- if (value instanceof Date) {
13
- return [key, value.getTime()];
14
- }
15
- return [key, value];
16
- })),
123
+ index: {
124
+ indexDescriptor: index.indexDescriptor,
125
+ fields: [...index.fields, "_creationTime"],
126
+ },
127
+ boundField,
128
+ sortField,
129
+ values: {
130
+ eq: indexFields.map(([, value]) => value),
131
+ lt: lowerBound?.operator === "lt" ? lowerBound.value : undefined,
132
+ lte: lowerBound?.operator === "lte" ? lowerBound.value : undefined,
133
+ gt: upperBound?.operator === "gt" ? upperBound.value : undefined,
134
+ gte: upperBound?.operator === "gte" ? upperBound.value : undefined,
135
+ },
17
136
  };
18
137
  };
19
- export const transformOutput = ({ _id, _creationTime, ...data }, _model) => {
20
- // Provide the expected id field, but it can be overwritten if
21
- // the model has an id field
22
- return { id: _id, ...data };
138
+ const selectFields = (doc, select) => {
139
+ if (!doc) {
140
+ return null;
141
+ }
142
+ if (!select?.length) {
143
+ return doc;
144
+ }
145
+ return select.reduce((acc, field) => {
146
+ acc[field] = doc[field];
147
+ return acc;
148
+ }, {});
23
149
  };
24
- // Get the session via sessionId in jwt claims
25
- export const getCurrentSession = query({
26
- args: {},
27
- handler: async (ctx) => {
28
- const identity = await ctx.auth.getUserIdentity();
29
- if (!identity) {
30
- return null;
31
- }
32
- return ctx.db.get(identity.sessionId);
33
- },
34
- });
35
- export const getByHelper = async (ctx, args) => {
36
- if (args.field === "id") {
37
- return ctx.db.get(args.value);
150
+ // This is the core function for reading from the database, it parses and
151
+ // validates where conditions, selects indexes, and allows the caller to
152
+ // optionally paginate as needed.
153
+ const paginate = async (ctx, args) => {
154
+ // If any index is id, we can only return a single document
155
+ const idWhere = args.where?.find((w) => w.field === "id");
156
+ if (idWhere) {
157
+ const doc = await ctx.db.get(idWhere.value);
158
+ return {
159
+ page: [selectFields(doc, args.select)].filter(Boolean),
160
+ isDone: true,
161
+ continueCursor: "",
162
+ };
38
163
  }
39
- const query = ctx.db
40
- .query(args.table)
41
- .withIndex(args.field, (q) => q.eq(args.field, args.value));
42
- return args.unique ? await query.unique() : await query.first();
164
+ const { index, values, boundField } = (await findIndex(args)) ?? {};
165
+ const query = paginator(ctx.db, schema).query(args.model);
166
+ const hasValues = values?.eq?.length ||
167
+ values?.lt ||
168
+ values?.lte ||
169
+ values?.gt ||
170
+ values?.gte;
171
+ const indexedQuery = index && index.indexDescriptor !== "by_creation_time"
172
+ ? query.withIndex(index.indexDescriptor, hasValues
173
+ ? (q) => {
174
+ for (const [idx, value] of (values?.eq ?? []).entries()) {
175
+ q = q.eq(index.fields[idx], value);
176
+ }
177
+ if (values?.lt) {
178
+ q = q.lt(boundField, values.lt);
179
+ }
180
+ if (values?.lte) {
181
+ q = q.lte(boundField, values.lte);
182
+ }
183
+ if (values?.gt) {
184
+ q = q.gt(boundField, values.gt);
185
+ }
186
+ if (values?.gte) {
187
+ q = q.gte(boundField, values.gte);
188
+ }
189
+ return q;
190
+ }
191
+ : undefined)
192
+ : query;
193
+ const orderedQuery = args.sortBy
194
+ ? indexedQuery.order(args.sortBy.direction === "asc" ? "asc" : "desc")
195
+ : indexedQuery;
196
+ const result = await orderedQuery.paginate(args.paginationOpts);
197
+ return {
198
+ ...result,
199
+ page: result.page.map((doc) => selectFields(doc, args.select)),
200
+ };
43
201
  };
44
- export const getByArgsValidator = {
45
- table: v.string(),
46
- field: v.string(),
47
- unique: v.optional(v.boolean()),
48
- value: v.union(v.string(), v.number(), v.boolean(), v.array(v.string()), v.array(v.number()), v.null()),
202
+ const listOne = async (ctx, args) => {
203
+ return (await paginate(ctx, {
204
+ ...args,
205
+ paginationOpts: {
206
+ numItems: 1,
207
+ cursor: null,
208
+ },
209
+ })).page[0];
49
210
  };
50
- // Generic functions
51
- export const getByQuery = query({
52
- args: getByArgsValidator,
53
- handler: async (ctx, args) => {
54
- const doc = await getByHelper(ctx, args);
55
- if (!doc) {
56
- return;
57
- }
58
- return transformOutput(doc, args.table);
59
- },
60
- });
61
- export { getByQuery as getBy };
62
211
  export const create = mutation({
63
- args: v.object({
64
- input: v.union(...Object.values(schema.tables).map((table) => v.object({
65
- table: v.string(),
66
- ...table.validator.fields,
212
+ args: {
213
+ input: v.union(...Object.entries(schema.tables).map(([model, table]) => v.object({
214
+ model: v.literal(model),
215
+ where: v.optional(v.array(adapterWhereValidator)),
216
+ data: v.object(table.validator.fields),
67
217
  }))),
68
- }),
69
- handler: async (ctx, args) => {
70
- const { table, ...input } = args.input;
71
- const id = await ctx.db.insert(table, {
72
- ...input,
73
- });
74
- const doc = await ctx.db.get(id);
75
- if (!doc) {
76
- throw new Error(`Failed to create ${table}`);
77
- }
78
- return transformOutput(doc, table);
79
- },
80
- });
81
- export const updateArgsInputValidator = (table) => {
82
- return v.object({
83
- table: v.literal(table),
84
- where: v.object({ field: v.string(), value: getByArgsValidator.value }),
85
- value: v.record(v.string(), v.any()),
86
- });
87
- };
88
- const updateArgsValidator = {
89
- input: v.union(updateArgsInputValidator("account"), updateArgsInputValidator("session"), updateArgsInputValidator("verification"), updateArgsInputValidator("user")),
90
- };
91
- export const update = mutation({
92
- args: updateArgsValidator,
93
- handler: async (ctx, args) => {
94
- const { table, where, value } = args.input;
95
- const doc = where.field === "id"
96
- ? await ctx.db.get(where.value)
97
- : await getByHelper(ctx, { table, ...where });
98
- if (!doc) {
99
- throw new Error(`Failed to update ${table}`);
100
- }
101
- await ctx.db.patch(doc._id, value);
102
- const updatedDoc = await ctx.db.get(doc._id);
103
- if (!updatedDoc) {
104
- throw new Error(`Failed to update ${table}`);
105
- }
106
- return transformOutput(updatedDoc, table);
107
218
  },
108
- });
109
- export const deleteBy = mutation({
110
- args: getByArgsValidator,
111
219
  handler: async (ctx, args) => {
112
- const doc = await getByHelper(ctx, args);
220
+ await checkUniqueFields(ctx, args.input.model, args.input.data);
221
+ const id = await ctx.db.insert(args.input.model, args.input.data);
222
+ const doc = await ctx.db.get(id);
113
223
  if (!doc) {
114
- return;
224
+ throw new Error(`Failed to create ${args.input.model}`);
115
225
  }
116
- await ctx.db.delete(doc._id);
117
- // onDeleteUser requires userId from the doc,
118
- // so just return the whole thing
119
226
  return doc;
120
227
  },
121
228
  });
122
- // Single purpose functions
123
- export const getAccountsByUserId = query({
124
- args: { userId: v.string(), limit: v.optional(v.number()) },
229
+ export const findOne = query({
230
+ args: adapterArgsValidator,
125
231
  handler: async (ctx, args) => {
126
- const query = ctx.db
127
- .query("account")
128
- .withIndex("userId", (q) => q.eq("userId", args.userId));
129
- const docs = args.limit
130
- ? await query.take(args.limit)
131
- : await query.collect();
132
- return docs.map((doc) => transformOutput(doc, "account"));
232
+ return await listOne(ctx, args);
133
233
  },
134
234
  });
135
- export const getSessionsByUserId = query({
136
- args: { userId: v.string(), limit: v.optional(v.number()) },
137
- handler: async (ctx, args) => {
138
- const query = ctx.db
139
- .query("session")
140
- .withIndex("userId", (q) => q.eq("userId", args.userId));
141
- const docs = args.limit
142
- ? await query.take(args.limit)
143
- : await query.collect();
144
- return docs.map((doc) => transformOutput(doc, "session"));
145
- },
146
- });
147
- export const getJwks = query({
235
+ export const findMany = query({
148
236
  args: {
149
- limit: v.optional(v.number()),
237
+ ...adapterArgsValidator.fields,
238
+ paginationOpts: paginationOptsValidator,
150
239
  },
151
240
  handler: async (ctx, args) => {
152
- const query = ctx.db.query("jwks");
153
- const docs = args.limit
154
- ? await query.take(args.limit)
155
- : await query.collect();
156
- return docs.map((doc) => transformOutput(doc, "jwks"));
241
+ return await paginate(ctx, args);
157
242
  },
158
243
  });
159
- export const listVerificationsByIdentifier = query({
244
+ export const updateOne = mutation({
160
245
  args: {
161
- identifier: v.string(),
162
- sortBy: v.optional(v.object({
163
- field: v.string(),
164
- direction: v.union(v.literal("asc"), v.literal("desc")),
165
- })),
166
- limit: v.optional(v.number()),
246
+ input: v.union(...Object.entries(schema.tables).map(([model, table]) => v.object({
247
+ model: v.literal(model),
248
+ where: v.optional(v.array(adapterWhereValidator)),
249
+ update: v.object(Object.fromEntries(Object.entries(table.validator.fields).map(([key, value]) => [
250
+ key,
251
+ value.isOptional === "required" ? v.optional(value) : value,
252
+ ]))),
253
+ }))),
167
254
  },
168
255
  handler: async (ctx, args) => {
169
- if (args.sortBy && args.sortBy.field !== "createdAt") {
170
- throw new Error(`Unsupported sortBy field: ${args.sortBy.field}`);
256
+ const doc = await listOne(ctx, args.input);
257
+ if (!doc) {
258
+ throw new Error(`Failed to update ${args.input.model}`);
171
259
  }
172
- const query = ctx.db
173
- .query("verification")
174
- .withIndex("identifier", (q) => q.eq("identifier", args.identifier))
175
- .order(args.sortBy?.field === "createdAt" && args.sortBy?.direction
176
- ? args.sortBy.direction
177
- : "asc");
178
- const docs = args.limit
179
- ? await query.take(args.limit)
180
- : await query.collect();
181
- return docs.map((doc) => transformOutput(doc, "verification"));
182
- },
183
- });
184
- export const deleteOldVerificationsPage = mutation({
185
- args: {
186
- currentTimestamp: v.number(),
187
- paginationOpts: v.optional(paginationOptsValidator),
188
- },
189
- handler: async (ctx, args) => {
190
- const paginationOpts = args.paginationOpts ?? {
191
- numItems: 500,
192
- cursor: null,
193
- };
194
- const { page, ...result } = await paginator(ctx.db, schema)
195
- .query("verification")
196
- .withIndex("expiresAt", (q) => q.lt("expiresAt", args.currentTimestamp))
197
- .paginate(paginationOpts);
198
- await asyncMap(page, async (doc) => {
199
- await ctx.db.delete(doc._id);
200
- });
201
- return { ...result, count: page.length };
260
+ await checkUniqueFields(ctx, args.input.model, args.input.update, doc);
261
+ await ctx.db.patch(doc._id, args.input.update);
262
+ const updatedDoc = await ctx.db.get(doc._id);
263
+ if (!updatedDoc) {
264
+ throw new Error(`Failed to update ${args.input.model}`);
265
+ }
266
+ return updatedDoc;
202
267
  },
203
268
  });
204
- export const deleteOldVerifications = action({
269
+ export const updateMany = mutation({
205
270
  args: {
206
- currentTimestamp: v.number(),
271
+ ...adapterArgsValidator.fields,
272
+ update: v.optional(v.object(partial(schema.tables.user.validator.fields))),
273
+ paginationOpts: paginationOptsValidator,
207
274
  },
208
275
  handler: async (ctx, args) => {
209
- let count = 0;
210
- let cursor = null;
211
- let isDone = false;
212
- do {
213
- const result = await ctx.runMutation(api.lib.deleteOldVerificationsPage, {
214
- currentTimestamp: args.currentTimestamp,
215
- paginationOpts: {
216
- numItems: 500,
217
- cursor,
218
- },
276
+ const { page, ...result } = await paginate(ctx, args);
277
+ if (args.update) {
278
+ const uniqueFields = getUniqueFields(args.model, args.update ?? {});
279
+ if (uniqueFields.length && page.length > 1) {
280
+ throw new Error(`Attempted to set unique fields in multiple documents in ${args.model} with the same value. Fields: ${uniqueFields.join(", ")}`);
281
+ }
282
+ await asyncMap(page, async (doc) => {
283
+ await checkUniqueFields(ctx, args.model, args.update ?? {}, doc);
284
+ await ctx.db.patch(doc._id, args.update);
219
285
  });
220
- count += result.count;
221
- cursor =
222
- result.pageStatus &&
223
- result.splitCursor &&
224
- ["SplitRecommended", "SplitRequired"].includes(result.pageStatus)
225
- ? result.splitCursor
226
- : result.continueCursor;
227
- isDone = result.isDone;
228
- } while (!isDone);
229
- return count;
286
+ }
287
+ return {
288
+ ...result,
289
+ count: page.length,
290
+ };
230
291
  },
231
292
  });
232
- export const deleteExpiredSessions = mutation({
233
- args: {
234
- userId: v.string(),
235
- expiresAt: v.number(),
236
- },
293
+ export const deleteOne = mutation({
294
+ args: adapterArgsValidator,
237
295
  handler: async (ctx, args) => {
238
- const docs = await ctx.db
239
- .query("session")
240
- .withIndex("userId_expiresAt", (q) => q.eq("userId", args.userId).lt("expiresAt", args.expiresAt))
241
- .collect();
242
- await asyncMap(docs, async (doc) => {
243
- await ctx.db.delete(doc._id);
244
- });
245
- return docs.length;
296
+ const doc = await listOne(ctx, args);
297
+ if (!doc) {
298
+ return;
299
+ }
300
+ await ctx.db.delete(doc._id);
301
+ return doc;
246
302
  },
247
303
  });
248
- export const deleteAllForUserPage = mutation({
304
+ export const deleteMany = mutation({
249
305
  args: {
250
- table: v.string(),
251
- userId: v.string(),
252
- paginationOpts: v.optional(paginationOptsValidator),
306
+ ...adapterArgsValidator.fields,
307
+ paginationOpts: paginationOptsValidator,
253
308
  },
254
309
  handler: async (ctx, args) => {
255
- const paginationOpts = args.paginationOpts ?? {
256
- numItems: 500,
257
- cursor: null,
258
- };
259
- const { page, ...result } = await paginator(ctx.db, schema)
260
- .query(args.table)
261
- .withIndex("userId", (q) => q.eq("userId", args.userId))
262
- .paginate(paginationOpts);
310
+ const { page, ...result } = await paginate(ctx, args);
263
311
  await asyncMap(page, async (doc) => {
264
312
  await ctx.db.delete(doc._id);
265
313
  });
266
- return { ...result, count: page.length };
267
- },
268
- });
269
- export const deleteAllForUser = action({
270
- args: {
271
- table: v.string(),
272
- userId: v.string(),
273
- },
274
- handler: async (ctx, args) => {
275
- let count = 0;
276
- let cursor = null;
277
- let isDone = false;
278
- do {
279
- const result = await ctx.runMutation(api.lib.deleteAllForUserPage, {
280
- table: args.table,
281
- userId: args.userId,
282
- paginationOpts: {
283
- numItems: 500,
284
- cursor,
285
- },
286
- });
287
- count += result.count;
288
- cursor =
289
- result.pageStatus &&
290
- result.splitCursor &&
291
- ["SplitRecommended", "SplitRequired"].includes(result.pageStatus)
292
- ? result.splitCursor
293
- : result.continueCursor;
294
- isDone = result.isDone;
295
- } while (!isDone);
296
- return count;
314
+ return {
315
+ ...result,
316
+ count: page.length,
317
+ };
297
318
  },
298
319
  });
299
- export const getAccountByAccountIdAndProviderId = query({
300
- args: { accountId: v.string(), providerId: v.string() },
301
- handler: async (ctx, args) => {
302
- const doc = await ctx.db
303
- .query("account")
304
- .withIndex("providerId_accountId", (q) => q.eq("providerId", args.providerId).eq("accountId", args.accountId))
305
- .unique();
306
- if (!doc) {
307
- return;
320
+ // Get the session via sessionId in jwt claims
321
+ // TODO: this needs a refresh, subquery only necessary for actions
322
+ export const getCurrentSession = query({
323
+ args: {},
324
+ handler: async (ctx) => {
325
+ const identity = await ctx.auth.getUserIdentity();
326
+ if (!identity) {
327
+ return null;
308
328
  }
309
- return transformOutput(doc, "account");
329
+ return ctx.db.get(identity.sessionId);
310
330
  },
311
331
  });
312
- export const updateUserProviderAccounts = mutation({
313
- args: {
314
- userId: v.string(),
315
- providerId: v.string(),
316
- update: v.object(partial(schema.tables.account.validator.fields)),
317
- },
332
+ // TODO: rewrite functions below here to be dynamic
333
+ export const getIn = query({
334
+ args: adapterArgsValidator,
318
335
  handler: async (ctx, args) => {
319
- const docs = await ctx.db
320
- .query("account")
321
- .withIndex("userId_providerId", (q) => q.eq("userId", args.userId).eq("providerId", args.providerId))
322
- .collect();
323
- if (docs.length === 0) {
324
- return 0;
336
+ const where = args.where?.[0];
337
+ if (!where || where.operator !== "in" || args.where?.length !== 1) {
338
+ throw new Error("where must be a single in clause");
325
339
  }
326
- await asyncMap(docs, async (doc) => {
327
- await ctx.db.patch(doc._id, args.update);
328
- });
329
- return docs.length;
340
+ return (await asyncMap(where.value, async (value) => {
341
+ if (where.field === "id") {
342
+ return [await ctx.db.get(value)];
343
+ }
344
+ const query = ctx.db
345
+ .query(args.model)
346
+ .withIndex(where.field, (q) => q.eq(where.field, value));
347
+ if (args.limit) {
348
+ return await query.take(args.limit);
349
+ }
350
+ return await query.collect();
351
+ }))
352
+ .flat()
353
+ .filter(Boolean);
330
354
  },
331
355
  });
332
- export const updateTwoFactor = mutation({
356
+ export const deleteIn = mutation({
333
357
  args: {
334
- userId: v.string(),
335
- update: v.object(partial(schema.tables.twoFactor.validator.fields)),
358
+ input: v.union(v.object({
359
+ table: v.literal("session"),
360
+ field: v.literal("token"),
361
+ values: v.array(v.string()),
362
+ })),
336
363
  },
337
364
  handler: async (ctx, args) => {
338
- const docs = await ctx.db
339
- .query("twoFactor")
340
- .withIndex("userId", (q) => q.eq("userId", args.userId))
341
- .collect();
342
- if (docs.length === 0) {
343
- return 0;
344
- }
345
- await asyncMap(docs, async (doc) => {
346
- await ctx.db.patch(doc._id, args.update);
365
+ const { table, field, values } = args.input;
366
+ const docs = await asyncMap(values, async (value) => {
367
+ const doc = await ctx.db
368
+ .query(table)
369
+ .withIndex(field, (q) => q.eq(field, value))
370
+ .unique();
371
+ if (!doc) {
372
+ return;
373
+ }
374
+ await ctx.db.delete(doc._id);
375
+ return doc;
347
376
  });
348
- return docs.length;
377
+ return docs.filter(Boolean).length;
349
378
  },
350
379
  });
351
380
  //# sourceMappingURL=lib.js.map