@convex-dev/better-auth 0.7.0-alpha.12 → 0.7.0-alpha.2

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