@robelest/convex-auth 0.0.4-preview.13 → 0.0.4-preview.16

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 (328) hide show
  1. package/README.md +140 -9
  2. package/dist/bin.cjs +5957 -5478
  3. package/dist/client/index.d.ts +3 -7
  4. package/dist/client/index.d.ts.map +1 -1
  5. package/dist/client/index.js +27 -26
  6. package/dist/client/index.js.map +1 -1
  7. package/dist/component/_generated/api.d.ts +14 -0
  8. package/dist/component/_generated/api.d.ts.map +1 -1
  9. package/dist/component/_generated/api.js.map +1 -1
  10. package/dist/component/_generated/component.d.ts +1672 -24
  11. package/dist/component/_generated/component.d.ts.map +1 -1
  12. package/dist/component/convex.config.d.ts +2 -2
  13. package/dist/component/convex.config.d.ts.map +1 -1
  14. package/dist/component/index.d.ts +1 -1
  15. package/dist/component/index.js +2 -2
  16. package/dist/component/model.d.ts +153 -0
  17. package/dist/component/model.d.ts.map +1 -0
  18. package/dist/component/model.js +343 -0
  19. package/dist/component/model.js.map +1 -0
  20. package/dist/component/providers/sso.d.ts +1 -1
  21. package/dist/component/public/enterprise.d.ts +54 -0
  22. package/dist/component/public/enterprise.d.ts.map +1 -0
  23. package/dist/component/public/enterprise.js +515 -0
  24. package/dist/component/public/enterprise.js.map +1 -0
  25. package/dist/component/public/factors.d.ts +52 -0
  26. package/dist/component/public/factors.d.ts.map +1 -0
  27. package/dist/component/public/factors.js +285 -0
  28. package/dist/component/public/factors.js.map +1 -0
  29. package/dist/component/public/groups.d.ts +116 -0
  30. package/dist/component/public/groups.d.ts.map +1 -0
  31. package/dist/component/public/groups.js +596 -0
  32. package/dist/component/public/groups.js.map +1 -0
  33. package/dist/component/public/identity.d.ts +93 -0
  34. package/dist/component/public/identity.d.ts.map +1 -0
  35. package/dist/component/public/identity.js +426 -0
  36. package/dist/component/public/identity.js.map +1 -0
  37. package/dist/component/public/keys.d.ts +41 -0
  38. package/dist/component/public/keys.d.ts.map +1 -0
  39. package/dist/component/public/keys.js +157 -0
  40. package/dist/component/public/keys.js.map +1 -0
  41. package/dist/component/public/shared.d.ts +26 -0
  42. package/dist/component/public/shared.d.ts.map +1 -0
  43. package/dist/component/public/shared.js +32 -0
  44. package/dist/component/public/shared.js.map +1 -0
  45. package/dist/component/public.d.ts +9 -321
  46. package/dist/component/public.d.ts.map +1 -1
  47. package/dist/component/public.js +6 -2145
  48. package/dist/component/schema.d.ts +406 -260
  49. package/dist/component/schema.js +37 -32
  50. package/dist/component/schema.js.map +1 -1
  51. package/dist/component/server/auth.d.ts +161 -15
  52. package/dist/component/server/auth.d.ts.map +1 -1
  53. package/dist/component/server/auth.js +100 -7
  54. package/dist/component/server/auth.js.map +1 -1
  55. package/dist/component/server/cookies.js +3 -0
  56. package/dist/component/server/cookies.js.map +1 -1
  57. package/dist/component/server/db.js +1 -0
  58. package/dist/component/server/db.js.map +1 -1
  59. package/dist/component/server/device.js +3 -1
  60. package/dist/component/server/device.js.map +1 -1
  61. package/dist/component/server/domains/core.js +629 -0
  62. package/dist/component/server/domains/core.js.map +1 -0
  63. package/dist/component/server/domains/sso.js +884 -0
  64. package/dist/component/server/domains/sso.js.map +1 -0
  65. package/dist/component/server/factory.d.ts +136 -0
  66. package/dist/component/server/factory.d.ts.map +1 -0
  67. package/dist/component/server/factory.js +1134 -0
  68. package/dist/component/server/factory.js.map +1 -0
  69. package/dist/component/server/fx.js +2 -1
  70. package/dist/component/server/fx.js.map +1 -1
  71. package/dist/component/server/http.js +287 -0
  72. package/dist/component/server/http.js.map +1 -0
  73. package/dist/component/server/identity.js +13 -0
  74. package/dist/component/server/identity.js.map +1 -0
  75. package/dist/component/server/keys.js +4 -0
  76. package/dist/component/server/keys.js.map +1 -1
  77. package/dist/component/server/mutations/account.js +1 -1
  78. package/dist/component/server/mutations/index.js +2 -2
  79. package/dist/component/server/mutations/index.js.map +1 -1
  80. package/dist/component/server/mutations/invalidate.js +1 -1
  81. package/dist/component/server/mutations/oauth.js +10 -7
  82. package/dist/component/server/mutations/oauth.js.map +1 -1
  83. package/dist/component/server/mutations/refresh.js +1 -1
  84. package/dist/component/server/mutations/register.js +1 -1
  85. package/dist/component/server/mutations/retrieve.js +1 -1
  86. package/dist/component/server/mutations/signature.js +1 -1
  87. package/dist/component/server/mutations/store.js +6 -3
  88. package/dist/component/server/mutations/store.js.map +1 -1
  89. package/dist/component/server/mutations/verify.js +1 -1
  90. package/dist/component/server/oauth.js +3 -0
  91. package/dist/component/server/oauth.js.map +1 -1
  92. package/dist/component/server/passkey.js +3 -2
  93. package/dist/component/server/passkey.js.map +1 -1
  94. package/dist/component/server/provider.js +2 -0
  95. package/dist/component/server/provider.js.map +1 -1
  96. package/dist/component/server/providers.js +10 -0
  97. package/dist/component/server/providers.js.map +1 -1
  98. package/dist/component/server/ratelimit.js +3 -0
  99. package/dist/component/server/ratelimit.js.map +1 -1
  100. package/dist/component/server/redirects.js +2 -0
  101. package/dist/component/server/redirects.js.map +1 -1
  102. package/dist/component/server/refresh.js +5 -0
  103. package/dist/component/server/refresh.js.map +1 -1
  104. package/dist/component/server/sessions.js +5 -0
  105. package/dist/component/server/sessions.js.map +1 -1
  106. package/dist/component/server/signin.js +2 -1
  107. package/dist/component/server/signin.js.map +1 -1
  108. package/dist/component/server/sso.js +166 -19
  109. package/dist/component/server/sso.js.map +1 -1
  110. package/dist/component/server/tokens.js +1 -0
  111. package/dist/component/server/tokens.js.map +1 -1
  112. package/dist/component/server/totp.js +4 -2
  113. package/dist/component/server/totp.js.map +1 -1
  114. package/dist/component/server/types.d.ts +106 -38
  115. package/dist/component/server/types.d.ts.map +1 -1
  116. package/dist/component/server/types.js.map +1 -1
  117. package/dist/component/server/users.js +1 -0
  118. package/dist/component/server/users.js.map +1 -1
  119. package/dist/component/server/utils.js +44 -2
  120. package/dist/component/server/utils.js.map +1 -1
  121. package/dist/providers/anonymous.d.ts +1 -1
  122. package/dist/providers/credentials.d.ts +1 -1
  123. package/dist/providers/password.d.ts +1 -1
  124. package/dist/providers/sso.d.ts +1 -1
  125. package/dist/providers/sso.js.map +1 -1
  126. package/dist/server/auth.d.ts +163 -17
  127. package/dist/server/auth.d.ts.map +1 -1
  128. package/dist/server/auth.js +100 -7
  129. package/dist/server/auth.js.map +1 -1
  130. package/dist/server/cookies.d.ts +1 -38
  131. package/dist/server/cookies.js +3 -0
  132. package/dist/server/cookies.js.map +1 -1
  133. package/dist/server/db.d.ts +1 -125
  134. package/dist/server/db.js +1 -0
  135. package/dist/server/db.js.map +1 -1
  136. package/dist/server/device.d.ts +1 -24
  137. package/dist/server/device.js +3 -1
  138. package/dist/server/device.js.map +1 -1
  139. package/dist/server/domains/core.d.ts +434 -0
  140. package/dist/server/domains/core.d.ts.map +1 -0
  141. package/dist/server/domains/core.js +629 -0
  142. package/dist/server/domains/core.js.map +1 -0
  143. package/dist/server/domains/sso.d.ts +409 -0
  144. package/dist/server/domains/sso.d.ts.map +1 -0
  145. package/dist/server/domains/sso.js +884 -0
  146. package/dist/server/domains/sso.js.map +1 -0
  147. package/dist/server/enterpriseValidators.d.ts +1 -0
  148. package/dist/server/enterpriseValidators.js +60 -0
  149. package/dist/server/enterpriseValidators.js.map +1 -0
  150. package/dist/server/factory.d.ts +136 -0
  151. package/dist/server/factory.d.ts.map +1 -0
  152. package/dist/server/factory.js +1134 -0
  153. package/dist/server/factory.js.map +1 -0
  154. package/dist/server/fx.d.ts +1 -16
  155. package/dist/server/fx.d.ts.map +1 -1
  156. package/dist/server/fx.js +1 -0
  157. package/dist/server/fx.js.map +1 -1
  158. package/dist/server/http.d.ts +59 -0
  159. package/dist/server/http.d.ts.map +1 -0
  160. package/dist/server/http.js +287 -0
  161. package/dist/server/http.js.map +1 -0
  162. package/dist/server/identity.d.ts +1 -0
  163. package/dist/server/identity.js +13 -0
  164. package/dist/server/identity.js.map +1 -0
  165. package/dist/server/index.d.ts +468 -1
  166. package/dist/server/index.d.ts.map +1 -1
  167. package/dist/server/index.js +530 -36
  168. package/dist/server/index.js.map +1 -1
  169. package/dist/server/keys.d.ts +1 -57
  170. package/dist/server/keys.js +4 -0
  171. package/dist/server/keys.js.map +1 -1
  172. package/dist/server/mutations/account.d.ts +7 -7
  173. package/dist/server/mutations/account.d.ts.map +1 -1
  174. package/dist/server/mutations/code.d.ts +13 -13
  175. package/dist/server/mutations/code.d.ts.map +1 -1
  176. package/dist/server/mutations/index.d.ts +107 -107
  177. package/dist/server/mutations/index.d.ts.map +1 -1
  178. package/dist/server/mutations/index.js +1 -1
  179. package/dist/server/mutations/index.js.map +1 -1
  180. package/dist/server/mutations/invalidate.d.ts +5 -5
  181. package/dist/server/mutations/invalidate.d.ts.map +1 -1
  182. package/dist/server/mutations/oauth.d.ts +10 -10
  183. package/dist/server/mutations/oauth.d.ts.map +1 -1
  184. package/dist/server/mutations/oauth.js +9 -6
  185. package/dist/server/mutations/oauth.js.map +1 -1
  186. package/dist/server/mutations/refresh.d.ts +4 -4
  187. package/dist/server/mutations/register.d.ts +12 -12
  188. package/dist/server/mutations/register.d.ts.map +1 -1
  189. package/dist/server/mutations/retrieve.d.ts +7 -7
  190. package/dist/server/mutations/signature.d.ts +5 -5
  191. package/dist/server/mutations/signin.d.ts +6 -6
  192. package/dist/server/mutations/signin.d.ts.map +1 -1
  193. package/dist/server/mutations/signout.d.ts +1 -1
  194. package/dist/server/mutations/store.d.ts +3 -2
  195. package/dist/server/mutations/store.d.ts.map +1 -1
  196. package/dist/server/mutations/store.js +6 -3
  197. package/dist/server/mutations/store.js.map +1 -1
  198. package/dist/server/mutations/verifier.d.ts +1 -1
  199. package/dist/server/mutations/verify.d.ts +11 -11
  200. package/dist/server/mutations/verify.d.ts.map +1 -1
  201. package/dist/server/oauth.d.ts +1 -59
  202. package/dist/server/oauth.js +3 -0
  203. package/dist/server/oauth.js.map +1 -1
  204. package/dist/server/passkey.d.ts.map +1 -1
  205. package/dist/server/passkey.js +3 -2
  206. package/dist/server/passkey.js.map +1 -1
  207. package/dist/server/provider.d.ts +1 -14
  208. package/dist/server/provider.d.ts.map +1 -1
  209. package/dist/server/provider.js +2 -0
  210. package/dist/server/provider.js.map +1 -1
  211. package/dist/server/providers.js +10 -0
  212. package/dist/server/providers.js.map +1 -1
  213. package/dist/server/ratelimit.d.ts +1 -22
  214. package/dist/server/ratelimit.js +3 -0
  215. package/dist/server/ratelimit.js.map +1 -1
  216. package/dist/server/redirects.d.ts +1 -10
  217. package/dist/server/redirects.js +2 -0
  218. package/dist/server/redirects.js.map +1 -1
  219. package/dist/server/refresh.d.ts +1 -37
  220. package/dist/server/refresh.js +5 -0
  221. package/dist/server/refresh.js.map +1 -1
  222. package/dist/server/sessions.d.ts +1 -28
  223. package/dist/server/sessions.js +5 -0
  224. package/dist/server/sessions.js.map +1 -1
  225. package/dist/server/signin.d.ts +1 -55
  226. package/dist/server/signin.js +2 -1
  227. package/dist/server/signin.js.map +1 -1
  228. package/dist/server/sso.d.ts +1 -348
  229. package/dist/server/sso.js +165 -18
  230. package/dist/server/sso.js.map +1 -1
  231. package/dist/server/templates.d.ts +1 -21
  232. package/dist/server/templates.js +1 -0
  233. package/dist/server/templates.js.map +1 -1
  234. package/dist/server/tokens.d.ts +1 -11
  235. package/dist/server/tokens.js +1 -0
  236. package/dist/server/tokens.js.map +1 -1
  237. package/dist/server/totp.d.ts +1 -23
  238. package/dist/server/totp.js +4 -2
  239. package/dist/server/totp.js.map +1 -1
  240. package/dist/server/types.d.ts +114 -77
  241. package/dist/server/types.d.ts.map +1 -1
  242. package/dist/server/types.js.map +1 -1
  243. package/dist/server/users.d.ts +1 -31
  244. package/dist/server/users.js +1 -0
  245. package/dist/server/users.js.map +1 -1
  246. package/dist/server/utils.d.ts +1 -27
  247. package/dist/server/utils.js +44 -2
  248. package/dist/server/utils.js.map +1 -1
  249. package/dist/server/version.d.ts +1 -1
  250. package/dist/server/version.js +1 -1
  251. package/dist/server/version.js.map +1 -1
  252. package/package.json +4 -5
  253. package/src/cli/bin.ts +5 -0
  254. package/src/cli/index.ts +22 -9
  255. package/src/cli/keys.ts +3 -0
  256. package/src/client/index.ts +36 -37
  257. package/src/component/_generated/api.ts +14 -0
  258. package/src/component/_generated/component.ts +2106 -9
  259. package/src/component/index.ts +3 -1
  260. package/src/component/model.ts +441 -0
  261. package/src/component/public/enterprise.ts +753 -0
  262. package/src/component/public/factors.ts +332 -0
  263. package/src/component/public/groups.ts +932 -0
  264. package/src/component/public/identity.ts +566 -0
  265. package/src/component/public/keys.ts +209 -0
  266. package/src/component/public/shared.ts +119 -0
  267. package/src/component/public.ts +5 -2965
  268. package/src/component/schema.ts +68 -63
  269. package/src/providers/sso.ts +1 -1
  270. package/src/server/auth.ts +413 -18
  271. package/src/server/cookies.ts +3 -0
  272. package/src/server/db.ts +3 -0
  273. package/src/server/device.ts +3 -1
  274. package/src/server/domains/core.ts +1071 -0
  275. package/src/server/domains/sso.ts +1749 -0
  276. package/src/server/enterpriseValidators.ts +93 -0
  277. package/src/server/factory.ts +2181 -0
  278. package/src/server/fx.ts +1 -0
  279. package/src/server/http.ts +529 -0
  280. package/src/server/identity.ts +18 -0
  281. package/src/server/index.ts +806 -40
  282. package/src/server/keys.ts +4 -0
  283. package/src/server/mutations/index.ts +1 -1
  284. package/src/server/mutations/oauth.ts +36 -8
  285. package/src/server/mutations/store.ts +6 -3
  286. package/src/server/oauth.ts +6 -0
  287. package/src/server/passkey.ts +3 -2
  288. package/src/server/provider.ts +2 -0
  289. package/src/server/providers.ts +20 -0
  290. package/src/server/ratelimit.ts +3 -0
  291. package/src/server/redirects.ts +2 -0
  292. package/src/server/refresh.ts +5 -0
  293. package/src/server/sessions.ts +5 -0
  294. package/src/server/signin.ts +1 -0
  295. package/src/server/sso.ts +259 -17
  296. package/src/server/templates.ts +1 -0
  297. package/src/server/tokens.ts +1 -0
  298. package/src/server/totp.ts +4 -2
  299. package/src/server/types.ts +178 -83
  300. package/src/server/users.ts +1 -0
  301. package/src/server/utils.ts +71 -1
  302. package/src/server/version.ts +1 -1
  303. package/dist/component/public.js.map +0 -1
  304. package/dist/component/server/implementation.d.ts +0 -1264
  305. package/dist/component/server/implementation.d.ts.map +0 -1
  306. package/dist/component/server/implementation.js +0 -2365
  307. package/dist/component/server/implementation.js.map +0 -1
  308. package/dist/server/cookies.d.ts.map +0 -1
  309. package/dist/server/db.d.ts.map +0 -1
  310. package/dist/server/device.d.ts.map +0 -1
  311. package/dist/server/implementation.d.ts +0 -1264
  312. package/dist/server/implementation.d.ts.map +0 -1
  313. package/dist/server/implementation.js +0 -2365
  314. package/dist/server/implementation.js.map +0 -1
  315. package/dist/server/keys.d.ts.map +0 -1
  316. package/dist/server/oauth.d.ts.map +0 -1
  317. package/dist/server/ratelimit.d.ts.map +0 -1
  318. package/dist/server/redirects.d.ts.map +0 -1
  319. package/dist/server/refresh.d.ts.map +0 -1
  320. package/dist/server/sessions.d.ts.map +0 -1
  321. package/dist/server/signin.d.ts.map +0 -1
  322. package/dist/server/sso.d.ts.map +0 -1
  323. package/dist/server/templates.d.ts.map +0 -1
  324. package/dist/server/tokens.d.ts.map +0 -1
  325. package/dist/server/totp.d.ts.map +0 -1
  326. package/dist/server/users.d.ts.map +0 -1
  327. package/dist/server/utils.d.ts.map +0 -1
  328. package/src/server/implementation.ts +0 -5336
@@ -0,0 +1,566 @@
1
+ import {
2
+ mutation,
3
+ query,
4
+ v,
5
+ vAccountDoc,
6
+ vAuthVerifierDoc,
7
+ vPaginated,
8
+ vRefreshTokenDoc,
9
+ vSessionDoc,
10
+ vUserDoc,
11
+ vVerificationCodeDoc,
12
+ } from "./shared";
13
+
14
+ // ============================================================================
15
+ // Users
16
+ // ============================================================================
17
+
18
+ /**
19
+ * List users with optional filtering, sorting, and pagination.
20
+ *
21
+ * Returns `{ items, nextCursor }` — pass `nextCursor` back as `cursor`
22
+ * for the next page, or `null` when exhausted.
23
+ */
24
+ export const userList = query({
25
+ args: {
26
+ where: v.optional(
27
+ v.object({
28
+ email: v.optional(v.string()),
29
+ phone: v.optional(v.string()),
30
+ isAnonymous: v.optional(v.boolean()),
31
+ name: v.optional(v.string()),
32
+ }),
33
+ ),
34
+ limit: v.optional(v.number()),
35
+ cursor: v.optional(v.union(v.string(), v.null())),
36
+ orderBy: v.optional(
37
+ v.union(
38
+ v.literal("_creationTime"),
39
+ v.literal("name"),
40
+ v.literal("email"),
41
+ v.literal("phone"),
42
+ ),
43
+ ),
44
+ order: v.optional(v.union(v.literal("asc"), v.literal("desc"))),
45
+ },
46
+ returns: vPaginated(vUserDoc),
47
+ handler: async (ctx, args) => {
48
+ const where = args.where ?? {};
49
+ const limit = Math.min(Math.max(args.limit ?? 50, 1), 100);
50
+ const order = args.order ?? "desc";
51
+
52
+ // Pick index based on where fields
53
+ let q;
54
+ if (where.email !== undefined) {
55
+ q = ctx.db
56
+ .query("User")
57
+ .withIndex("email", (idx) => idx.eq("email", where.email!));
58
+ } else if (where.phone !== undefined) {
59
+ q = ctx.db
60
+ .query("User")
61
+ .withIndex("phone", (idx) => idx.eq("phone", where.phone!));
62
+ } else {
63
+ q = ctx.db.query("User");
64
+ }
65
+
66
+ // Apply remaining filters
67
+ if (where.isAnonymous !== undefined) {
68
+ q = q.filter((f) => f.eq(f.field("isAnonymous"), where.isAnonymous!));
69
+ }
70
+ if (where.name !== undefined) {
71
+ q = q.filter((f) => f.eq(f.field("name"), where.name!));
72
+ }
73
+ // email/phone filters when not used as index
74
+ if (where.email !== undefined && where.phone !== undefined) {
75
+ q = q.filter((f) => f.eq(f.field("phone"), where.phone!));
76
+ }
77
+
78
+ q = q.order(order);
79
+
80
+ // Cursor-based pagination: skip past the cursor ID
81
+ const all = await q.collect();
82
+ let startIdx = 0;
83
+ if (args.cursor) {
84
+ const cursorIdx = all.findIndex((doc) => doc._id === args.cursor);
85
+ if (cursorIdx !== -1) {
86
+ startIdx = cursorIdx + 1;
87
+ }
88
+ }
89
+ const page = all.slice(startIdx, startIdx + limit + 1);
90
+ const hasMore = page.length > limit;
91
+ const items = hasMore ? page.slice(0, limit) : page;
92
+ const nextCursor = hasMore ? items[items.length - 1]._id : null;
93
+ return { items, nextCursor };
94
+ },
95
+ });
96
+
97
+ /** Retrieve a user by their document ID. */
98
+ export const userGetById = query({
99
+ args: { userId: v.id("User") },
100
+ returns: v.union(vUserDoc, v.null()),
101
+ handler: async (ctx, { userId }) => {
102
+ return await ctx.db.get("User", userId);
103
+ },
104
+ });
105
+
106
+ /**
107
+ * Find a user by their verified email address. Returns `null` if no user
108
+ * has this email verified, or if multiple users share the same verified email
109
+ * (ambiguous — should not happen in normal operation).
110
+ */
111
+ export const userFindByVerifiedEmail = query({
112
+ args: { email: v.string() },
113
+ returns: v.union(vUserDoc, v.null()),
114
+ handler: async (ctx, { email }) => {
115
+ const users = await ctx.db
116
+ .query("User")
117
+ .withIndex("email", (q) => q.eq("email", email))
118
+ .filter((q) => q.neq(q.field("emailVerificationTime"), undefined))
119
+ .take(2);
120
+ return users.length === 1 ? users[0] : null;
121
+ },
122
+ });
123
+
124
+ /**
125
+ * Find a user by their verified phone number. Returns `null` if no user
126
+ * has this phone verified, or if multiple users share the same verified phone
127
+ * (ambiguous — should not happen in normal operation).
128
+ */
129
+ export const userFindByVerifiedPhone = query({
130
+ args: { phone: v.string() },
131
+ returns: v.union(vUserDoc, v.null()),
132
+ handler: async (ctx, { phone }) => {
133
+ const users = await ctx.db
134
+ .query("User")
135
+ .withIndex("phone", (q) => q.eq("phone", phone))
136
+ .filter((q) => q.neq(q.field("phoneVerificationTime"), undefined))
137
+ .take(2);
138
+ return users.length === 1 ? users[0] : null;
139
+ },
140
+ });
141
+
142
+ /** Insert a new user document. */
143
+ export const userInsert = mutation({
144
+ args: { data: v.any() },
145
+ returns: v.id("User"),
146
+ handler: async (ctx, { data }) => {
147
+ return await ctx.db.insert("User", data);
148
+ },
149
+ });
150
+
151
+ /** Insert a new user or update an existing one. */
152
+ export const userUpsert = mutation({
153
+ args: { userId: v.optional(v.id("User")), data: v.any() },
154
+ returns: v.id("User"),
155
+ handler: async (ctx, { userId, data }) => {
156
+ if (userId !== undefined) {
157
+ await ctx.db.patch("User", userId, data);
158
+ return userId;
159
+ }
160
+ return await ctx.db.insert("User", data);
161
+ },
162
+ });
163
+
164
+ /** Patch an existing user document with partial data. */
165
+ export const userPatch = mutation({
166
+ args: { userId: v.id("User"), data: v.any() },
167
+ returns: v.null(),
168
+ handler: async (ctx, { userId, data }) => {
169
+ await ctx.db.patch("User", userId, data);
170
+ return null;
171
+ },
172
+ });
173
+
174
+ /** Delete a user document by ID. No-op if the user does not exist. */
175
+ export const userDelete = mutation({
176
+ args: { userId: v.id("User") },
177
+ returns: v.null(),
178
+ handler: async (ctx, { userId }) => {
179
+ if ((await ctx.db.get("User", userId)) !== null) {
180
+ await ctx.db.delete("User", userId);
181
+ }
182
+ return null;
183
+ },
184
+ });
185
+
186
+ // ============================================================================
187
+ // Accounts
188
+ // ============================================================================
189
+
190
+ /** List all accounts for a user. */
191
+ export const accountListByUser = query({
192
+ args: { userId: v.id("User") },
193
+ returns: v.array(vAccountDoc),
194
+ handler: async (ctx, { userId }) => {
195
+ return await ctx.db
196
+ .query("Account")
197
+ .withIndex("user_id_provider", (q) => q.eq("userId", userId as any))
198
+ .collect();
199
+ },
200
+ });
201
+
202
+ /** Look up an account by provider and provider-specific account ID. */
203
+ export const accountGet = query({
204
+ args: { provider: v.string(), providerAccountId: v.string() },
205
+ returns: v.union(vAccountDoc, v.null()),
206
+ handler: async (ctx, { provider, providerAccountId }) => {
207
+ return await ctx.db
208
+ .query("Account")
209
+ .withIndex("provider_account_id", (q) =>
210
+ q.eq("provider", provider).eq("providerAccountId", providerAccountId),
211
+ )
212
+ .unique();
213
+ },
214
+ });
215
+
216
+ /** Retrieve an account by its document ID. */
217
+ export const accountGetById = query({
218
+ args: { accountId: v.id("Account") },
219
+ returns: v.union(vAccountDoc, v.null()),
220
+ handler: async (ctx, { accountId }) => {
221
+ return await ctx.db.get("Account", accountId);
222
+ },
223
+ });
224
+
225
+ /** Create a new account linking a user to an auth provider. */
226
+ export const accountInsert = mutation({
227
+ args: {
228
+ userId: v.id("User"),
229
+ provider: v.string(),
230
+ providerAccountId: v.string(),
231
+ secret: v.optional(v.string()),
232
+ extend: v.optional(v.any()),
233
+ },
234
+ returns: v.id("Account"),
235
+ handler: async (ctx, args) => {
236
+ return await ctx.db.insert("Account", args as any);
237
+ },
238
+ });
239
+
240
+ /** Patch an existing account document with partial data. */
241
+ export const accountPatch = mutation({
242
+ args: { accountId: v.id("Account"), data: v.any() },
243
+ returns: v.null(),
244
+ handler: async (ctx, { accountId, data }) => {
245
+ await ctx.db.patch("Account", accountId, data);
246
+ return null;
247
+ },
248
+ });
249
+
250
+ /** Delete an account document. */
251
+ export const accountDelete = mutation({
252
+ args: { accountId: v.id("Account") },
253
+ returns: v.null(),
254
+ handler: async (ctx, { accountId }) => {
255
+ await ctx.db.delete("Account", accountId);
256
+ return null;
257
+ },
258
+ });
259
+
260
+ // ============================================================================
261
+ // Sessions
262
+ // ============================================================================
263
+
264
+ /**
265
+ * List sessions with optional filtering and pagination.
266
+ *
267
+ * Returns `{ items, nextCursor }`.
268
+ */
269
+ export const sessionList = query({
270
+ args: {
271
+ where: v.optional(
272
+ v.object({
273
+ userId: v.optional(v.id("User")),
274
+ }),
275
+ ),
276
+ limit: v.optional(v.number()),
277
+ cursor: v.optional(v.union(v.string(), v.null())),
278
+ order: v.optional(v.union(v.literal("asc"), v.literal("desc"))),
279
+ },
280
+ returns: vPaginated(vSessionDoc),
281
+ handler: async (ctx, args) => {
282
+ const where = args.where ?? {};
283
+ const limit = Math.min(Math.max(args.limit ?? 50, 1), 100);
284
+ const order = args.order ?? "desc";
285
+
286
+ let q;
287
+ if (where.userId !== undefined) {
288
+ q = ctx.db
289
+ .query("Session")
290
+ .withIndex("user_id", (idx) => idx.eq("userId", where.userId!));
291
+ } else {
292
+ q = ctx.db.query("Session");
293
+ }
294
+
295
+ q = q.order(order);
296
+
297
+ const all = await q.collect();
298
+ let startIdx = 0;
299
+ if (args.cursor) {
300
+ const cursorIdx = all.findIndex((doc) => doc._id === args.cursor);
301
+ if (cursorIdx !== -1) {
302
+ startIdx = cursorIdx + 1;
303
+ }
304
+ }
305
+ const page = all.slice(startIdx, startIdx + limit + 1);
306
+ const hasMore = page.length > limit;
307
+ const items = hasMore ? page.slice(0, limit) : page;
308
+ const nextCursor = hasMore ? items[items.length - 1]._id : null;
309
+ return { items, nextCursor };
310
+ },
311
+ });
312
+
313
+ /** Create a new session for a user with an expiration time. */
314
+ export const sessionCreate = mutation({
315
+ args: { userId: v.id("User"), expirationTime: v.number() },
316
+ returns: v.id("Session"),
317
+ handler: async (ctx, { userId, expirationTime }) => {
318
+ return await ctx.db.insert("Session", {
319
+ userId: userId as any,
320
+ expirationTime,
321
+ });
322
+ },
323
+ });
324
+
325
+ /** Retrieve a session by its document ID. */
326
+ export const sessionGetById = query({
327
+ args: { sessionId: v.id("Session") },
328
+ returns: v.union(vSessionDoc, v.null()),
329
+ handler: async (ctx, { sessionId }) => {
330
+ return await ctx.db.get("Session", sessionId);
331
+ },
332
+ });
333
+
334
+ /** Delete a session. No-op if the session does not exist. */
335
+ export const sessionDelete = mutation({
336
+ args: { sessionId: v.id("Session") },
337
+ returns: v.null(),
338
+ handler: async (ctx, { sessionId }) => {
339
+ if ((await ctx.db.get("Session", sessionId)) !== null) {
340
+ await ctx.db.delete("Session", sessionId);
341
+ }
342
+ return null;
343
+ },
344
+ });
345
+
346
+ /** List all sessions for a user. */
347
+ export const sessionListByUser = query({
348
+ args: { userId: v.id("User") },
349
+ returns: v.array(vSessionDoc),
350
+ handler: async (ctx, { userId }) => {
351
+ return await ctx.db
352
+ .query("Session")
353
+ .withIndex("user_id", (q) => q.eq("userId", userId as any))
354
+ .collect();
355
+ },
356
+ });
357
+
358
+ // ============================================================================
359
+ // Verifiers
360
+ // ============================================================================
361
+
362
+ /** Create a new PKCE verifier, optionally linked to a session. */
363
+ export const verifierCreate = mutation({
364
+ args: { sessionId: v.optional(v.id("Session")) },
365
+ returns: v.id("AuthVerifier"),
366
+ handler: async (ctx, { sessionId }) => {
367
+ return await ctx.db.insert("AuthVerifier", { sessionId: sessionId as any });
368
+ },
369
+ });
370
+
371
+ /** Retrieve a verifier by its document ID. */
372
+ export const verifierGetById = query({
373
+ args: { verifierId: v.id("AuthVerifier") },
374
+ returns: v.union(vAuthVerifierDoc, v.null()),
375
+ handler: async (ctx, { verifierId }) => {
376
+ return await ctx.db.get("AuthVerifier", verifierId);
377
+ },
378
+ });
379
+
380
+ /** Look up a verifier by its cryptographic signature. */
381
+ export const verifierGetBySignature = query({
382
+ args: { signature: v.string() },
383
+ returns: v.union(vAuthVerifierDoc, v.null()),
384
+ handler: async (ctx, { signature }) => {
385
+ return await ctx.db
386
+ .query("AuthVerifier")
387
+ .withIndex("signature", (q) => q.eq("signature", signature))
388
+ .unique();
389
+ },
390
+ });
391
+
392
+ /** Patch a verifier document with partial data. */
393
+ export const verifierPatch = mutation({
394
+ args: { verifierId: v.id("AuthVerifier"), data: v.any() },
395
+ returns: v.null(),
396
+ handler: async (ctx, { verifierId, data }) => {
397
+ await ctx.db.patch("AuthVerifier", verifierId, data);
398
+ return null;
399
+ },
400
+ });
401
+
402
+ /** Delete a verifier document. */
403
+ export const verifierDelete = mutation({
404
+ args: { verifierId: v.id("AuthVerifier") },
405
+ returns: v.null(),
406
+ handler: async (ctx, { verifierId }) => {
407
+ await ctx.db.delete("AuthVerifier", verifierId);
408
+ return null;
409
+ },
410
+ });
411
+
412
+ // ============================================================================
413
+ // Verification Codes
414
+ // ============================================================================
415
+
416
+ /** Find a verification code by its associated account ID. */
417
+ export const verificationCodeGetByAccountId = query({
418
+ args: { accountId: v.id("Account") },
419
+ returns: v.union(vVerificationCodeDoc, v.null()),
420
+ handler: async (ctx, { accountId }) => {
421
+ return await ctx.db
422
+ .query("VerificationCode")
423
+ .withIndex("account_id", (q) => q.eq("accountId", accountId as any))
424
+ .unique();
425
+ },
426
+ });
427
+
428
+ /** Find a verification code by its code string. */
429
+ export const verificationCodeGetByCode = query({
430
+ args: { code: v.string() },
431
+ returns: v.union(vVerificationCodeDoc, v.null()),
432
+ handler: async (ctx, { code }) => {
433
+ return await ctx.db
434
+ .query("VerificationCode")
435
+ .withIndex("code", (q) => q.eq("code", code))
436
+ .unique();
437
+ },
438
+ });
439
+
440
+ /** Create a new verification code for OTP, magic link, or OAuth flows. */
441
+ export const verificationCodeCreate = mutation({
442
+ args: {
443
+ accountId: v.id("Account"),
444
+ provider: v.string(),
445
+ code: v.string(),
446
+ expirationTime: v.number(),
447
+ verifier: v.optional(v.string()),
448
+ emailVerified: v.optional(v.string()),
449
+ phoneVerified: v.optional(v.string()),
450
+ },
451
+ returns: v.id("VerificationCode"),
452
+ handler: async (ctx, args) => {
453
+ return await ctx.db.insert("VerificationCode", args as any);
454
+ },
455
+ });
456
+
457
+ /** Delete a verification code document. */
458
+ export const verificationCodeDelete = mutation({
459
+ args: { verificationCodeId: v.id("VerificationCode") },
460
+ returns: v.null(),
461
+ handler: async (ctx, { verificationCodeId }) => {
462
+ await ctx.db.delete("VerificationCode", verificationCodeId);
463
+ return null;
464
+ },
465
+ });
466
+
467
+ // ============================================================================
468
+ // Refresh Tokens
469
+ // ============================================================================
470
+
471
+ /** Create a new refresh token for a session. */
472
+ export const refreshTokenCreate = mutation({
473
+ args: {
474
+ sessionId: v.id("Session"),
475
+ expirationTime: v.number(),
476
+ parentRefreshTokenId: v.optional(v.id("RefreshToken")),
477
+ },
478
+ returns: v.id("RefreshToken"),
479
+ handler: async (ctx, args) => {
480
+ return await ctx.db.insert("RefreshToken", args as any);
481
+ },
482
+ });
483
+
484
+ /** Retrieve a refresh token by its document ID. */
485
+ export const refreshTokenGetById = query({
486
+ args: { refreshTokenId: v.id("RefreshToken") },
487
+ returns: v.union(vRefreshTokenDoc, v.null()),
488
+ handler: async (ctx, { refreshTokenId }) => {
489
+ return await ctx.db.get("RefreshToken", refreshTokenId);
490
+ },
491
+ });
492
+
493
+ /** Patch a refresh token document with partial data. */
494
+ export const refreshTokenPatch = mutation({
495
+ args: { refreshTokenId: v.id("RefreshToken"), data: v.any() },
496
+ returns: v.null(),
497
+ handler: async (ctx, { refreshTokenId, data }) => {
498
+ await ctx.db.patch("RefreshToken", refreshTokenId, data);
499
+ return null;
500
+ },
501
+ });
502
+
503
+ /** Get child tokens that were created by exchanging a specific parent token. */
504
+ export const refreshTokenGetChildren = query({
505
+ args: {
506
+ sessionId: v.id("Session"),
507
+ parentRefreshTokenId: v.id("RefreshToken"),
508
+ },
509
+ returns: v.array(vRefreshTokenDoc),
510
+ handler: async (ctx, { sessionId, parentRefreshTokenId }) => {
511
+ return await ctx.db
512
+ .query("RefreshToken")
513
+ .withIndex("session_id_parent_refresh_token_id", (q) =>
514
+ q
515
+ .eq("sessionId", sessionId as any)
516
+ .eq("parentRefreshTokenId", parentRefreshTokenId as any),
517
+ )
518
+ .collect();
519
+ },
520
+ });
521
+
522
+ /** List all refresh tokens for a session. */
523
+ export const refreshTokenListBySession = query({
524
+ args: { sessionId: v.id("Session") },
525
+ returns: v.array(vRefreshTokenDoc),
526
+ handler: async (ctx, { sessionId }) => {
527
+ return await ctx.db
528
+ .query("RefreshToken")
529
+ .withIndex("session_id_parent_refresh_token_id", (q) =>
530
+ q.eq("sessionId", sessionId as any),
531
+ )
532
+ .collect();
533
+ },
534
+ });
535
+
536
+ /** Delete all refresh tokens for a session. */
537
+ export const refreshTokenDeleteAll = mutation({
538
+ args: { sessionId: v.id("Session") },
539
+ returns: v.null(),
540
+ handler: async (ctx, { sessionId }) => {
541
+ const tokens = await ctx.db
542
+ .query("RefreshToken")
543
+ .withIndex("session_id_parent_refresh_token_id", (q) =>
544
+ q.eq("sessionId", sessionId as any),
545
+ )
546
+ .collect();
547
+ await Promise.all(
548
+ tokens.map((token) => ctx.db.delete("RefreshToken", token._id)),
549
+ );
550
+ return null;
551
+ },
552
+ });
553
+
554
+ /** Get the active (unused) refresh token for a session. */
555
+ export const refreshTokenGetActive = query({
556
+ args: { sessionId: v.id("Session") },
557
+ returns: v.union(vRefreshTokenDoc, v.null()),
558
+ handler: async (ctx, { sessionId }) => {
559
+ return await ctx.db
560
+ .query("RefreshToken")
561
+ .withIndex("session_id", (q) => q.eq("sessionId", sessionId as any))
562
+ .filter((q) => q.eq(q.field("firstUsedTime"), undefined))
563
+ .order("desc")
564
+ .first();
565
+ },
566
+ });