@robelest/convex-auth 0.0.2-preview.0 → 0.0.2-preview.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 (166) hide show
  1. package/dist/bin.cjs +17 -15
  2. package/dist/client/index.d.ts +84 -30
  3. package/dist/client/index.d.ts.map +1 -1
  4. package/dist/client/index.js +259 -59
  5. package/dist/client/index.js.map +1 -1
  6. package/dist/component/_generated/component.d.ts +46 -120
  7. package/dist/component/_generated/component.d.ts.map +1 -1
  8. package/dist/component/index.d.ts +2 -4
  9. package/dist/component/index.d.ts.map +1 -1
  10. package/dist/component/index.js +2 -4
  11. package/dist/component/index.js.map +1 -1
  12. package/dist/component/public.d.ts +233 -167
  13. package/dist/component/public.d.ts.map +1 -1
  14. package/dist/component/public.js +328 -155
  15. package/dist/component/public.js.map +1 -1
  16. package/dist/component/schema.d.ts +127 -12
  17. package/dist/component/schema.d.ts.map +1 -1
  18. package/dist/component/schema.js +136 -10
  19. package/dist/component/schema.js.map +1 -1
  20. package/dist/providers/{Anonymous.d.ts → anonymous.d.ts} +8 -8
  21. package/dist/providers/{Anonymous.d.ts.map → anonymous.d.ts.map} +1 -1
  22. package/dist/providers/{Anonymous.js → anonymous.js} +9 -10
  23. package/dist/providers/anonymous.js.map +1 -0
  24. package/dist/providers/{ConvexCredentials.d.ts → credentials.d.ts} +11 -11
  25. package/dist/providers/credentials.d.ts.map +1 -0
  26. package/dist/providers/{ConvexCredentials.js → credentials.js} +8 -8
  27. package/dist/providers/credentials.js.map +1 -0
  28. package/dist/providers/{Email.d.ts → email.d.ts} +6 -6
  29. package/dist/providers/email.d.ts.map +1 -0
  30. package/dist/providers/{Email.js → email.js} +6 -6
  31. package/dist/providers/email.js.map +1 -0
  32. package/dist/providers/{Password.d.ts → password.d.ts} +10 -10
  33. package/dist/providers/{Password.d.ts.map → password.d.ts.map} +1 -1
  34. package/dist/providers/{Password.js → password.js} +19 -20
  35. package/dist/providers/password.js.map +1 -0
  36. package/dist/providers/{Phone.d.ts → phone.d.ts} +3 -3
  37. package/dist/providers/{Phone.d.ts.map → phone.d.ts.map} +1 -1
  38. package/dist/providers/{Phone.js → phone.js} +3 -3
  39. package/dist/providers/{Phone.js.map → phone.js.map} +1 -1
  40. package/dist/server/implementation/db.d.ts +5 -2
  41. package/dist/server/implementation/db.d.ts.map +1 -1
  42. package/dist/server/implementation/db.js +2 -1
  43. package/dist/server/implementation/db.js.map +1 -1
  44. package/dist/server/implementation/index.d.ts +285 -180
  45. package/dist/server/implementation/index.d.ts.map +1 -1
  46. package/dist/server/implementation/index.js +280 -173
  47. package/dist/server/implementation/index.js.map +1 -1
  48. package/dist/server/implementation/mutations/createAccountFromCredentials.d.ts.map +1 -1
  49. package/dist/server/implementation/mutations/createAccountFromCredentials.js +8 -18
  50. package/dist/server/implementation/mutations/createAccountFromCredentials.js.map +1 -1
  51. package/dist/server/implementation/mutations/createVerificationCode.d.ts.map +1 -1
  52. package/dist/server/implementation/mutations/createVerificationCode.js +16 -44
  53. package/dist/server/implementation/mutations/createVerificationCode.js.map +1 -1
  54. package/dist/server/implementation/mutations/invalidateSessions.d.ts.map +1 -1
  55. package/dist/server/implementation/mutations/invalidateSessions.js +4 -8
  56. package/dist/server/implementation/mutations/invalidateSessions.js.map +1 -1
  57. package/dist/server/implementation/mutations/modifyAccount.d.ts.map +1 -1
  58. package/dist/server/implementation/mutations/modifyAccount.js +8 -19
  59. package/dist/server/implementation/mutations/modifyAccount.js.map +1 -1
  60. package/dist/server/implementation/mutations/refreshSession.d.ts.map +1 -1
  61. package/dist/server/implementation/mutations/refreshSession.js +9 -23
  62. package/dist/server/implementation/mutations/refreshSession.js.map +1 -1
  63. package/dist/server/implementation/mutations/retrieveAccountWithCredentials.d.ts.map +1 -1
  64. package/dist/server/implementation/mutations/retrieveAccountWithCredentials.js +6 -12
  65. package/dist/server/implementation/mutations/retrieveAccountWithCredentials.js.map +1 -1
  66. package/dist/server/implementation/mutations/signIn.d.ts.map +1 -1
  67. package/dist/server/implementation/mutations/signIn.js +2 -1
  68. package/dist/server/implementation/mutations/signIn.js.map +1 -1
  69. package/dist/server/implementation/mutations/signOut.d.ts.map +1 -1
  70. package/dist/server/implementation/mutations/signOut.js +5 -6
  71. package/dist/server/implementation/mutations/signOut.js.map +1 -1
  72. package/dist/server/implementation/mutations/storeRef.d.ts +8 -0
  73. package/dist/server/implementation/mutations/storeRef.d.ts.map +1 -0
  74. package/dist/server/implementation/mutations/storeRef.js +8 -0
  75. package/dist/server/implementation/mutations/storeRef.js.map +1 -0
  76. package/dist/server/implementation/mutations/userOAuth.d.ts.map +1 -1
  77. package/dist/server/implementation/mutations/userOAuth.js +16 -53
  78. package/dist/server/implementation/mutations/userOAuth.js.map +1 -1
  79. package/dist/server/implementation/mutations/verifier.d.ts.map +1 -1
  80. package/dist/server/implementation/mutations/verifier.js +4 -8
  81. package/dist/server/implementation/mutations/verifier.js.map +1 -1
  82. package/dist/server/implementation/mutations/verifierSignature.d.ts.map +1 -1
  83. package/dist/server/implementation/mutations/verifierSignature.js +6 -10
  84. package/dist/server/implementation/mutations/verifierSignature.js.map +1 -1
  85. package/dist/server/implementation/mutations/verifyCodeAndSignIn.d.ts.map +1 -1
  86. package/dist/server/implementation/mutations/verifyCodeAndSignIn.js +7 -16
  87. package/dist/server/implementation/mutations/verifyCodeAndSignIn.js.map +1 -1
  88. package/dist/server/implementation/provider.d.ts +2 -1
  89. package/dist/server/implementation/provider.d.ts.map +1 -1
  90. package/dist/server/implementation/provider.js.map +1 -1
  91. package/dist/server/implementation/rateLimit.d.ts.map +1 -1
  92. package/dist/server/implementation/rateLimit.js +13 -39
  93. package/dist/server/implementation/rateLimit.js.map +1 -1
  94. package/dist/server/implementation/refreshTokens.d.ts +1 -8
  95. package/dist/server/implementation/refreshTokens.d.ts.map +1 -1
  96. package/dist/server/implementation/refreshTokens.js +14 -58
  97. package/dist/server/implementation/refreshTokens.js.map +1 -1
  98. package/dist/server/implementation/sessions.d.ts +2 -20
  99. package/dist/server/implementation/sessions.d.ts.map +1 -1
  100. package/dist/server/implementation/sessions.js +8 -35
  101. package/dist/server/implementation/sessions.js.map +1 -1
  102. package/dist/server/implementation/types.d.ts +11 -267
  103. package/dist/server/implementation/types.d.ts.map +1 -1
  104. package/dist/server/implementation/types.js +1 -181
  105. package/dist/server/implementation/types.js.map +1 -1
  106. package/dist/server/implementation/users.d.ts.map +1 -1
  107. package/dist/server/implementation/users.js +19 -67
  108. package/dist/server/implementation/users.js.map +1 -1
  109. package/dist/server/index.d.ts +18 -0
  110. package/dist/server/index.d.ts.map +1 -1
  111. package/dist/server/index.js +255 -0
  112. package/dist/server/index.js.map +1 -1
  113. package/dist/server/provider_utils.d.ts +1 -1
  114. package/dist/server/provider_utils.d.ts.map +1 -1
  115. package/dist/server/provider_utils.js +2 -2
  116. package/dist/server/provider_utils.js.map +1 -1
  117. package/dist/server/types.d.ts +91 -52
  118. package/dist/server/types.d.ts.map +1 -1
  119. package/package.json +3 -6
  120. package/src/cli/index.ts +20 -19
  121. package/src/client/index.ts +347 -110
  122. package/src/component/_generated/component.ts +55 -214
  123. package/src/component/index.ts +1 -11
  124. package/src/component/public.ts +366 -178
  125. package/src/component/schema.ts +150 -19
  126. package/src/providers/{Anonymous.ts → anonymous.ts} +10 -11
  127. package/src/providers/{ConvexCredentials.ts → credentials.ts} +11 -11
  128. package/src/providers/{Email.ts → email.ts} +5 -5
  129. package/src/providers/{Password.ts → password.ts} +22 -27
  130. package/src/providers/{Phone.ts → phone.ts} +2 -2
  131. package/src/server/implementation/db.ts +5 -2
  132. package/src/server/implementation/index.ts +368 -313
  133. package/src/server/implementation/mutations/createAccountFromCredentials.ts +11 -25
  134. package/src/server/implementation/mutations/createVerificationCode.ts +16 -47
  135. package/src/server/implementation/mutations/invalidateSessions.ts +4 -9
  136. package/src/server/implementation/mutations/modifyAccount.ts +8 -22
  137. package/src/server/implementation/mutations/refreshSession.ts +11 -24
  138. package/src/server/implementation/mutations/retrieveAccountWithCredentials.ts +9 -17
  139. package/src/server/implementation/mutations/signIn.ts +2 -1
  140. package/src/server/implementation/mutations/signOut.ts +5 -8
  141. package/src/server/implementation/mutations/storeRef.ts +7 -0
  142. package/src/server/implementation/mutations/userOAuth.ts +10 -50
  143. package/src/server/implementation/mutations/verifier.ts +4 -9
  144. package/src/server/implementation/mutations/verifierSignature.ts +6 -12
  145. package/src/server/implementation/mutations/verifyCodeAndSignIn.ts +7 -18
  146. package/src/server/implementation/provider.ts +2 -1
  147. package/src/server/implementation/rateLimit.ts +15 -41
  148. package/src/server/implementation/refreshTokens.ts +26 -76
  149. package/src/server/implementation/sessions.ts +8 -39
  150. package/src/server/implementation/types.ts +16 -191
  151. package/src/server/implementation/users.ts +19 -66
  152. package/src/server/index.ts +373 -0
  153. package/src/server/provider_utils.ts +2 -2
  154. package/src/server/types.ts +116 -51
  155. package/dist/providers/Anonymous.js.map +0 -1
  156. package/dist/providers/ConvexCredentials.d.ts.map +0 -1
  157. package/dist/providers/ConvexCredentials.js.map +0 -1
  158. package/dist/providers/Email.d.ts.map +0 -1
  159. package/dist/providers/Email.js.map +0 -1
  160. package/dist/providers/Password.js.map +0 -1
  161. package/providers/Anonymous/package.json +0 -6
  162. package/providers/ConvexCredentials/package.json +0 -6
  163. package/providers/Email/package.json +0 -6
  164. package/providers/Password/package.json +0 -6
  165. package/providers/Phone/package.json +0 -6
  166. package/server/package.json +0 -6
@@ -1,7 +1,11 @@
1
- import { v } from "convex/values";
1
+ import { ConvexError, v } from "convex/values";
2
2
  import { mutation, query } from "./_generated/server";
3
3
 
4
+ // ============================================================================
4
5
  // Users
6
+ // ============================================================================
7
+
8
+ /** Retrieve a user by their document ID. */
5
9
  export const userGetById = query({
6
10
  args: { userId: v.id("user") },
7
11
  handler: async (ctx, { userId }) => {
@@ -9,6 +13,11 @@ export const userGetById = query({
9
13
  },
10
14
  });
11
15
 
16
+ /**
17
+ * Find a user by their verified email address. Returns `null` if no user
18
+ * has this email verified, or if multiple users share the same verified email
19
+ * (ambiguous — should not happen in normal operation).
20
+ */
12
21
  export const userFindByVerifiedEmail = query({
13
22
  args: { email: v.string() },
14
23
  handler: async (ctx, { email }) => {
@@ -21,6 +30,11 @@ export const userFindByVerifiedEmail = query({
21
30
  },
22
31
  });
23
32
 
33
+ /**
34
+ * Find a user by their verified phone number. Returns `null` if no user
35
+ * has this phone verified, or if multiple users share the same verified phone
36
+ * (ambiguous — should not happen in normal operation).
37
+ */
24
38
  export const userFindByVerifiedPhone = query({
25
39
  args: { phone: v.string() },
26
40
  handler: async (ctx, { phone }) => {
@@ -33,6 +47,7 @@ export const userFindByVerifiedPhone = query({
33
47
  },
34
48
  });
35
49
 
50
+ /** Insert a new user document. */
36
51
  export const userInsert = mutation({
37
52
  args: { data: v.any() },
38
53
  handler: async (ctx, { data }) => {
@@ -40,6 +55,7 @@ export const userInsert = mutation({
40
55
  },
41
56
  });
42
57
 
58
+ /** Insert a new user or update an existing one. */
43
59
  export const userUpsert = mutation({
44
60
  args: { userId: v.optional(v.id("user")), data: v.any() },
45
61
  handler: async (ctx, { userId, data }) => {
@@ -51,6 +67,7 @@ export const userUpsert = mutation({
51
67
  },
52
68
  });
53
69
 
70
+ /** Patch an existing user document with partial data. */
54
71
  export const userPatch = mutation({
55
72
  args: { userId: v.id("user"), data: v.any() },
56
73
  handler: async (ctx, { userId, data }) => {
@@ -58,7 +75,11 @@ export const userPatch = mutation({
58
75
  },
59
76
  });
60
77
 
78
+ // ============================================================================
61
79
  // Accounts
80
+ // ============================================================================
81
+
82
+ /** Look up an account by provider and provider-specific account ID. */
62
83
  export const accountGet = query({
63
84
  args: { provider: v.string(), providerAccountId: v.string() },
64
85
  handler: async (ctx, { provider, providerAccountId }) => {
@@ -71,6 +92,7 @@ export const accountGet = query({
71
92
  },
72
93
  });
73
94
 
95
+ /** Retrieve an account by its document ID. */
74
96
  export const accountGetById = query({
75
97
  args: { accountId: v.id("account") },
76
98
  handler: async (ctx, { accountId }) => {
@@ -78,6 +100,7 @@ export const accountGetById = query({
78
100
  },
79
101
  });
80
102
 
103
+ /** Create a new account linking a user to an auth provider. */
81
104
  export const accountInsert = mutation({
82
105
  args: {
83
106
  userId: v.id("user"),
@@ -90,6 +113,7 @@ export const accountInsert = mutation({
90
113
  },
91
114
  });
92
115
 
116
+ /** Patch an existing account document with partial data. */
93
117
  export const accountPatch = mutation({
94
118
  args: { accountId: v.id("account"), data: v.any() },
95
119
  handler: async (ctx, { accountId, data }) => {
@@ -97,6 +121,7 @@ export const accountPatch = mutation({
97
121
  },
98
122
  });
99
123
 
124
+ /** Delete an account document. */
100
125
  export const accountDelete = mutation({
101
126
  args: { accountId: v.id("account") },
102
127
  handler: async (ctx, { accountId }) => {
@@ -104,7 +129,11 @@ export const accountDelete = mutation({
104
129
  },
105
130
  });
106
131
 
132
+ // ============================================================================
107
133
  // Sessions
134
+ // ============================================================================
135
+
136
+ /** Create a new session for a user with an expiration time. */
108
137
  export const sessionCreate = mutation({
109
138
  args: { userId: v.id("user"), expirationTime: v.number() },
110
139
  handler: async (ctx, { userId, expirationTime }) => {
@@ -115,6 +144,7 @@ export const sessionCreate = mutation({
115
144
  },
116
145
  });
117
146
 
147
+ /** Retrieve a session by its document ID. */
118
148
  export const sessionGetById = query({
119
149
  args: { sessionId: v.id("session") },
120
150
  handler: async (ctx, { sessionId }) => {
@@ -122,6 +152,7 @@ export const sessionGetById = query({
122
152
  },
123
153
  });
124
154
 
155
+ /** Delete a session. No-op if the session does not exist. */
125
156
  export const sessionDelete = mutation({
126
157
  args: { sessionId: v.id("session") },
127
158
  handler: async (ctx, { sessionId }) => {
@@ -131,6 +162,7 @@ export const sessionDelete = mutation({
131
162
  },
132
163
  });
133
164
 
165
+ /** List all sessions for a user. */
134
166
  export const sessionListByUser = query({
135
167
  args: { userId: v.id("user") },
136
168
  handler: async (ctx, { userId }) => {
@@ -141,7 +173,11 @@ export const sessionListByUser = query({
141
173
  },
142
174
  });
143
175
 
176
+ // ============================================================================
144
177
  // Verifiers
178
+ // ============================================================================
179
+
180
+ /** Create a new PKCE verifier, optionally linked to a session. */
145
181
  export const verifierCreate = mutation({
146
182
  args: { sessionId: v.optional(v.id("session")) },
147
183
  handler: async (ctx, { sessionId }) => {
@@ -149,6 +185,7 @@ export const verifierCreate = mutation({
149
185
  },
150
186
  });
151
187
 
188
+ /** Retrieve a verifier by its document ID. */
152
189
  export const verifierGetById = query({
153
190
  args: { verifierId: v.id("verifier") },
154
191
  handler: async (ctx, { verifierId }) => {
@@ -156,6 +193,7 @@ export const verifierGetById = query({
156
193
  },
157
194
  });
158
195
 
196
+ /** Look up a verifier by its cryptographic signature. */
159
197
  export const verifierGetBySignature = query({
160
198
  args: { signature: v.string() },
161
199
  handler: async (ctx, { signature }) => {
@@ -166,6 +204,7 @@ export const verifierGetBySignature = query({
166
204
  },
167
205
  });
168
206
 
207
+ /** Patch a verifier document with partial data. */
169
208
  export const verifierPatch = mutation({
170
209
  args: { verifierId: v.id("verifier"), data: v.any() },
171
210
  handler: async (ctx, { verifierId, data }) => {
@@ -173,6 +212,7 @@ export const verifierPatch = mutation({
173
212
  },
174
213
  });
175
214
 
215
+ /** Delete a verifier document. */
176
216
  export const verifierDelete = mutation({
177
217
  args: { verifierId: v.id("verifier") },
178
218
  handler: async (ctx, { verifierId }) => {
@@ -180,7 +220,11 @@ export const verifierDelete = mutation({
180
220
  },
181
221
  });
182
222
 
183
- // Verification codes
223
+ // ============================================================================
224
+ // Verification Codes
225
+ // ============================================================================
226
+
227
+ /** Find a verification code by its associated account ID. */
184
228
  export const verificationCodeGetByAccountId = query({
185
229
  args: { accountId: v.id("account") },
186
230
  handler: async (ctx, { accountId }) => {
@@ -191,6 +235,7 @@ export const verificationCodeGetByAccountId = query({
191
235
  },
192
236
  });
193
237
 
238
+ /** Find a verification code by its code string. */
194
239
  export const verificationCodeGetByCode = query({
195
240
  args: { code: v.string() },
196
241
  handler: async (ctx, { code }) => {
@@ -201,6 +246,7 @@ export const verificationCodeGetByCode = query({
201
246
  },
202
247
  });
203
248
 
249
+ /** Create a new verification code for OTP, magic link, or OAuth flows. */
204
250
  export const verificationCodeCreate = mutation({
205
251
  args: {
206
252
  accountId: v.id("account"),
@@ -216,6 +262,7 @@ export const verificationCodeCreate = mutation({
216
262
  },
217
263
  });
218
264
 
265
+ /** Delete a verification code document. */
219
266
  export const verificationCodeDelete = mutation({
220
267
  args: { verificationCodeId: v.id("verification") },
221
268
  handler: async (ctx, { verificationCodeId }) => {
@@ -223,7 +270,11 @@ export const verificationCodeDelete = mutation({
223
270
  },
224
271
  });
225
272
 
226
- // Refresh tokens
273
+ // ============================================================================
274
+ // Refresh Tokens
275
+ // ============================================================================
276
+
277
+ /** Create a new refresh token for a session. */
227
278
  export const refreshTokenCreate = mutation({
228
279
  args: {
229
280
  sessionId: v.id("session"),
@@ -235,6 +286,7 @@ export const refreshTokenCreate = mutation({
235
286
  },
236
287
  });
237
288
 
289
+ /** Retrieve a refresh token by its document ID. */
238
290
  export const refreshTokenGetById = query({
239
291
  args: { refreshTokenId: v.id("token") },
240
292
  handler: async (ctx, { refreshTokenId }) => {
@@ -242,6 +294,7 @@ export const refreshTokenGetById = query({
242
294
  },
243
295
  });
244
296
 
297
+ /** Patch a refresh token document with partial data. */
245
298
  export const refreshTokenPatch = mutation({
246
299
  args: { refreshTokenId: v.id("token"), data: v.any() },
247
300
  handler: async (ctx, { refreshTokenId, data }) => {
@@ -249,6 +302,7 @@ export const refreshTokenPatch = mutation({
249
302
  },
250
303
  });
251
304
 
305
+ /** Get child tokens that were created by exchanging a specific parent token. */
252
306
  export const refreshTokenGetChildren = query({
253
307
  args: {
254
308
  sessionId: v.id("session"),
@@ -266,6 +320,7 @@ export const refreshTokenGetChildren = query({
266
320
  },
267
321
  });
268
322
 
323
+ /** List all refresh tokens for a session. */
269
324
  export const refreshTokenListBySession = query({
270
325
  args: { sessionId: v.id("session") },
271
326
  handler: async (ctx, { sessionId }) => {
@@ -278,6 +333,7 @@ export const refreshTokenListBySession = query({
278
333
  },
279
334
  });
280
335
 
336
+ /** Delete all refresh tokens for a session. */
281
337
  export const refreshTokenDeleteAll = mutation({
282
338
  args: { sessionId: v.id("session") },
283
339
  handler: async (ctx, { sessionId }) => {
@@ -291,6 +347,7 @@ export const refreshTokenDeleteAll = mutation({
291
347
  },
292
348
  });
293
349
 
350
+ /** Get the active (unused) refresh token for a session. */
294
351
  export const refreshTokenGetActive = query({
295
352
  args: { sessionId: v.id("session") },
296
353
  handler: async (ctx, { sessionId }) => {
@@ -303,7 +360,11 @@ export const refreshTokenGetActive = query({
303
360
  },
304
361
  });
305
362
 
306
- // Rate limits
363
+ // ============================================================================
364
+ // Rate Limits
365
+ // ============================================================================
366
+
367
+ /** Look up a rate limit entry by its identifier. */
307
368
  export const rateLimitGet = query({
308
369
  args: { identifier: v.string() },
309
370
  handler: async (ctx, { identifier }) => {
@@ -314,6 +375,7 @@ export const rateLimitGet = query({
314
375
  },
315
376
  });
316
377
 
378
+ /** Create a new rate limit entry. */
317
379
  export const rateLimitCreate = mutation({
318
380
  args: {
319
381
  identifier: v.string(),
@@ -325,6 +387,7 @@ export const rateLimitCreate = mutation({
325
387
  },
326
388
  });
327
389
 
390
+ /** Patch a rate limit entry with partial data. */
328
391
  export const rateLimitPatch = mutation({
329
392
  args: { rateLimitId: v.id("limit"), data: v.any() },
330
393
  handler: async (ctx, { rateLimitId, data }) => {
@@ -332,6 +395,7 @@ export const rateLimitPatch = mutation({
332
395
  },
333
396
  });
334
397
 
398
+ /** Delete a rate limit entry. */
335
399
  export const rateLimitDelete = mutation({
336
400
  args: { rateLimitId: v.id("limit") },
337
401
  handler: async (ctx, { rateLimitId }) => {
@@ -339,269 +403,393 @@ export const rateLimitDelete = mutation({
339
403
  },
340
404
  });
341
405
 
342
- // Singular aliases
343
- export const verificationGetByAccountId = verificationCodeGetByAccountId;
344
- export const verificationGetByCode = verificationCodeGetByCode;
345
- export const verificationCreate = verificationCodeCreate;
346
- export const verificationDelete = verificationCodeDelete;
347
-
348
- export const tokenCreate = refreshTokenCreate;
349
- export const tokenGetById = refreshTokenGetById;
350
- export const tokenPatch = refreshTokenPatch;
351
- export const tokenGetChildren = refreshTokenGetChildren;
352
- export const tokenListBySession = refreshTokenListBySession;
353
- export const tokenDeleteAll = refreshTokenDeleteAll;
354
- export const tokenGetActive = refreshTokenGetActive;
355
-
356
- export const limitGet = rateLimitGet;
357
- export const limitCreate = rateLimitCreate;
358
- export const limitPatch = rateLimitPatch;
359
- export const limitDelete = rateLimitDelete;
360
-
361
- // Organization
362
- export const organizationCreate = mutation({
363
- args: { data: v.any() },
364
- handler: async (ctx, { data }) => {
365
- return await (ctx.db as any).insert("organization", data);
366
- },
367
- });
368
-
369
- export const organizationGet = query({
370
- args: { organizationId: v.id("organization") },
371
- handler: async (ctx, { organizationId }) => {
372
- return await (ctx.db as any).get(organizationId);
373
- },
374
- });
375
-
376
- export const organizationList = query({
377
- args: { ownerUserId: v.optional(v.id("user")) },
378
- handler: async (ctx, { ownerUserId }) => {
379
- if (ownerUserId === undefined) {
380
- return await (ctx.db as any).query("organization").collect();
381
- }
382
- return await (ctx.db as any)
383
- .query("organization")
384
- .withIndex("ownerUserId", (q: any) => q.eq("ownerUserId", ownerUserId))
385
- .collect();
386
- },
387
- });
388
-
389
- export const organizationUpdate = mutation({
390
- args: { organizationId: v.id("organization"), data: v.any() },
391
- handler: async (ctx, { organizationId, data }) => {
392
- await (ctx.db as any).patch(organizationId, data);
393
- },
394
- });
395
-
396
- export const organizationDelete = mutation({
397
- args: { organizationId: v.id("organization") },
398
- handler: async (ctx, { organizationId }) => {
399
- await (ctx.db as any).delete(organizationId);
400
- },
401
- });
406
+ // ============================================================================
407
+ // Groups
408
+ // ============================================================================
402
409
 
403
- // Team
404
- export const teamCreate = mutation({
410
+ /**
411
+ * Create a new group. Groups are hierarchical — set `parentGroupId` to nest
412
+ * under an existing group, or omit it to create a root-level group.
413
+ *
414
+ * @returns The ID of the newly created group.
415
+ */
416
+ export const groupCreate = mutation({
405
417
  args: {
406
- organizationId: v.id("organization"),
407
418
  name: v.string(),
408
419
  slug: v.optional(v.string()),
409
- parentTeamId: v.optional(v.id("team")),
410
- metadata: v.optional(v.any()),
420
+ parentGroupId: v.optional(v.id("group")),
421
+ extend: v.optional(v.any()),
411
422
  },
412
423
  handler: async (ctx, args) => {
413
- return await (ctx.db as any).insert("team", args);
424
+ return await ctx.db.insert("group", args);
414
425
  },
415
426
  });
416
427
 
417
- export const teamGet = query({
418
- args: { teamId: v.id("team") },
419
- handler: async (ctx, { teamId }) => {
420
- return await (ctx.db as any).get(teamId);
428
+ /** Retrieve a group by its document ID. Returns `null` if not found. */
429
+ export const groupGet = query({
430
+ args: { groupId: v.id("group") },
431
+ handler: async (ctx, { groupId }) => {
432
+ return await ctx.db.get(groupId);
421
433
  },
422
434
  });
423
435
 
424
- export const teamListByOrganization = query({
425
- args: { organizationId: v.id("organization") },
426
- handler: async (ctx, { organizationId }) => {
427
- return await (ctx.db as any)
428
- .query("team")
429
- .withIndex("organizationId", (q: any) =>
430
- q.eq("organizationId", organizationId),
431
- )
436
+ /**
437
+ * List groups. When `parentGroupId` is provided, returns children of that
438
+ * group. When omitted, returns all root-level groups (groups with no parent).
439
+ */
440
+ export const groupList = query({
441
+ args: { parentGroupId: v.optional(v.id("group")) },
442
+ handler: async (ctx, { parentGroupId }) => {
443
+ return await ctx.db
444
+ .query("group")
445
+ .withIndex("parentGroupId", (q) => q.eq("parentGroupId", parentGroupId))
432
446
  .collect();
433
447
  },
434
448
  });
435
449
 
436
- export const teamUpdate = mutation({
437
- args: { teamId: v.id("team"), data: v.any() },
438
- handler: async (ctx, { teamId, data }) => {
439
- await (ctx.db as any).patch(teamId, data);
450
+ /** Update a group's fields (name, slug, extend, parentGroupId). */
451
+ export const groupUpdate = mutation({
452
+ args: { groupId: v.id("group"), data: v.any() },
453
+ handler: async (ctx, { groupId, data }) => {
454
+ await ctx.db.patch(groupId, data);
440
455
  },
441
456
  });
442
457
 
443
- export const teamDelete = mutation({
444
- args: { teamId: v.id("team") },
445
- handler: async (ctx, { teamId }) => {
446
- await (ctx.db as any).delete(teamId);
447
- },
448
- });
458
+ /**
459
+ * Delete a group and all of its descendants. This cascades to:
460
+ * - All child groups (recursively)
461
+ * - All members of this group and its descendants
462
+ * - All invites for this group and its descendants
463
+ */
464
+ export const groupDelete = mutation({
465
+ args: { groupId: v.id("group") },
466
+ handler: async (ctx, { groupId }) => {
467
+ const deleteGroup = async (id: typeof groupId) => {
468
+ const children = await ctx.db
469
+ .query("group")
470
+ .withIndex("parentGroupId", (q) => q.eq("parentGroupId", id))
471
+ .collect();
472
+ for (const child of children) {
473
+ await deleteGroup(child._id);
474
+ }
449
475
 
450
- // Team relations
451
- export const teamRelationCreate = mutation({
452
- args: {
453
- organizationId: v.id("organization"),
454
- parentTeamId: v.id("team"),
455
- childTeamId: v.id("team"),
456
- relation: v.optional(v.string()),
457
- },
458
- handler: async (ctx, args) => {
459
- return await (ctx.db as any).insert("teamRelation", args);
460
- },
461
- });
476
+ const members = await ctx.db
477
+ .query("member")
478
+ .withIndex("groupId", (q) => q.eq("groupId", id))
479
+ .collect();
480
+ for (const member of members) {
481
+ await ctx.db.delete(member._id);
482
+ }
462
483
 
463
- export const teamRelationGet = query({
464
- args: { teamRelationId: v.id("teamRelation") },
465
- handler: async (ctx, { teamRelationId }) => {
466
- return await (ctx.db as any).get(teamRelationId);
467
- },
468
- });
484
+ const invites = await ctx.db
485
+ .query("invite")
486
+ .withIndex("groupId", (q) => q.eq("groupId", id))
487
+ .collect();
488
+ for (const invite of invites) {
489
+ await ctx.db.delete(invite._id);
490
+ }
469
491
 
470
- export const teamRelationListByParent = query({
471
- args: {
472
- organizationId: v.id("organization"),
473
- parentTeamId: v.id("team"),
474
- },
475
- handler: async (ctx, { organizationId, parentTeamId }) => {
476
- return await (ctx.db as any)
477
- .query("teamRelation")
478
- .withIndex("organizationIdAndParentTeamId", (q: any) =>
479
- q.eq("organizationId", organizationId).eq("parentTeamId", parentTeamId),
480
- )
481
- .collect();
482
- },
483
- });
492
+ await ctx.db.delete(id);
493
+ };
484
494
 
485
- export const teamRelationDelete = mutation({
486
- args: { teamRelationId: v.id("teamRelation") },
487
- handler: async (ctx, { teamRelationId }) => {
488
- await (ctx.db as any).delete(teamRelationId);
495
+ await deleteGroup(groupId);
489
496
  },
490
497
  });
491
498
 
499
+ // ============================================================================
492
500
  // Members
501
+ // ============================================================================
502
+
503
+ /**
504
+ * Add a user as a member of a group.
505
+ *
506
+ * The `role` field is an application-defined string (e.g. "owner", "admin",
507
+ * "member", "viewer"). The auth component stores it but does not enforce
508
+ * access control — your application defines what each role means.
509
+ *
510
+ * Throws `ConvexError` with code `DUPLICATE_MEMBERSHIP` when the user is
511
+ * already a member of the target group.
512
+ *
513
+ * @returns The ID of the new member record.
514
+ */
493
515
  export const memberAdd = mutation({
494
- args: { data: v.any() },
495
- handler: async (ctx, { data }) => {
496
- return await (ctx.db as any).insert("member", data);
516
+ args: {
517
+ groupId: v.id("group"),
518
+ userId: v.id("user"),
519
+ role: v.optional(v.string()),
520
+ status: v.optional(v.string()),
521
+ extend: v.optional(v.any()),
522
+ },
523
+ handler: async (ctx, args) => {
524
+ const existingMembership = await ctx.db
525
+ .query("member")
526
+ .withIndex("groupIdAndUserId", (q) =>
527
+ q.eq("groupId", args.groupId).eq("userId", args.userId),
528
+ )
529
+ .unique();
530
+ if (existingMembership !== null) {
531
+ throw new ConvexError({
532
+ code: "DUPLICATE_MEMBERSHIP",
533
+ message: "User is already a member of this group",
534
+ groupId: args.groupId,
535
+ userId: args.userId,
536
+ existingMemberId: existingMembership._id,
537
+ });
538
+ }
539
+ return await ctx.db.insert("member", args);
497
540
  },
498
541
  });
499
542
 
500
- export const memberRemove = mutation({
543
+ /** Retrieve a member record by its document ID. Returns `null` if not found. */
544
+ export const memberGet = query({
501
545
  args: { memberId: v.id("member") },
502
546
  handler: async (ctx, { memberId }) => {
503
- await (ctx.db as any).delete(memberId);
547
+ return await ctx.db.get(memberId);
504
548
  },
505
549
  });
506
550
 
551
+ /** List all members of a specific group. */
507
552
  export const memberList = query({
508
- args: {
509
- organizationId: v.id("organization"),
510
- teamId: v.optional(v.id("team")),
553
+ args: { groupId: v.id("group") },
554
+ handler: async (ctx, { groupId }) => {
555
+ return await ctx.db
556
+ .query("member")
557
+ .withIndex("groupId", (q) => q.eq("groupId", groupId))
558
+ .collect();
511
559
  },
512
- handler: async (ctx, { organizationId, teamId }) => {
513
- if (teamId !== undefined) {
514
- return await (ctx.db as any)
515
- .query("member")
516
- .withIndex("teamId", (q: any) => q.eq("teamId", teamId))
517
- .collect();
518
- }
519
- return await (ctx.db as any)
560
+ });
561
+
562
+ /**
563
+ * List all group memberships for a specific user. Returns member records
564
+ * which include the `groupId`, `role`, `status`, and `extend` for each
565
+ * group the user belongs to.
566
+ */
567
+ export const memberListByUser = query({
568
+ args: { userId: v.id("user") },
569
+ handler: async (ctx, { userId }) => {
570
+ return await ctx.db
520
571
  .query("member")
521
- .withIndex("organizationId", (q: any) =>
522
- q.eq("organizationId", organizationId),
523
- )
572
+ .withIndex("userId", (q) => q.eq("userId", userId))
524
573
  .collect();
525
574
  },
526
575
  });
527
576
 
528
- export const memberRoleSet = mutation({
529
- args: { memberId: v.id("member"), role: v.string() },
530
- handler: async (ctx, { memberId, role }) => {
531
- await (ctx.db as any).patch(memberId, { role });
577
+ /**
578
+ * Look up a specific user's membership in a specific group.
579
+ * Returns `null` if the user is not a member of the group.
580
+ */
581
+ export const memberGetByGroupAndUser = query({
582
+ args: { groupId: v.id("group"), userId: v.id("user") },
583
+ handler: async (ctx, { groupId, userId }) => {
584
+ return await ctx.db
585
+ .query("member")
586
+ .withIndex("groupIdAndUserId", (q) =>
587
+ q.eq("groupId", groupId).eq("userId", userId),
588
+ )
589
+ .unique();
532
590
  },
533
591
  });
534
592
 
535
- export const memberRoleGet = query({
593
+ /** Remove a member from a group by deleting the member record. */
594
+ export const memberRemove = mutation({
536
595
  args: { memberId: v.id("member") },
537
596
  handler: async (ctx, { memberId }) => {
538
- const member = await (ctx.db as any).get(memberId);
539
- return member?.role ?? null;
597
+ await ctx.db.delete(memberId);
598
+ },
599
+ });
600
+
601
+ /**
602
+ * Update a member record's fields (role, status, extend).
603
+ *
604
+ * Common usage: `memberUpdate({ memberId, data: { role: "admin" } })`
605
+ */
606
+ export const memberUpdate = mutation({
607
+ args: { memberId: v.id("member"), data: v.any() },
608
+ handler: async (ctx, { memberId, data }) => {
609
+ await ctx.db.patch(memberId, data);
540
610
  },
541
611
  });
542
612
 
613
+ // ============================================================================
543
614
  // Invites
615
+ // ============================================================================
616
+
617
+ /**
618
+ * Create a new platform-level invitation. Optionally set `groupId` to tie
619
+ * the invite to a specific group. The invitation is sent to an email address
620
+ * and includes a hashed token for secure acceptance.
621
+ *
622
+ * Throws `ConvexError` with code `DUPLICATE_INVITE` when a pending invite
623
+ * already exists for the same email and scope:
624
+ * - group invite: same `email` + same `groupId`
625
+ * - platform invite: same `email` with no `groupId`
626
+ *
627
+ * @returns The ID of the new invite record.
628
+ */
544
629
  export const inviteCreate = mutation({
545
- args: { data: v.any() },
546
- handler: async (ctx, { data }) => {
547
- return await (ctx.db as any).insert("invite", data);
630
+ args: {
631
+ groupId: v.optional(v.id("group")),
632
+ invitedByUserId: v.id("user"),
633
+ email: v.string(),
634
+ tokenHash: v.string(),
635
+ role: v.optional(v.string()),
636
+ status: v.union(
637
+ v.literal("pending"),
638
+ v.literal("accepted"),
639
+ v.literal("revoked"),
640
+ v.literal("expired"),
641
+ ),
642
+ expiresTime: v.number(),
643
+ extend: v.optional(v.any()),
644
+ },
645
+ handler: async (ctx, args) => {
646
+ if (args.groupId !== undefined) {
647
+ const existingGroupInvite = await ctx.db
648
+ .query("invite")
649
+ .withIndex("groupIdAndStatus", (q) =>
650
+ q.eq("groupId", args.groupId).eq("status", "pending"),
651
+ )
652
+ .filter((q) => q.eq(q.field("email"), args.email))
653
+ .first();
654
+ if (existingGroupInvite !== null) {
655
+ throw new ConvexError({
656
+ code: "DUPLICATE_INVITE",
657
+ message: "A pending invite already exists for this email in this group",
658
+ email: args.email,
659
+ groupId: args.groupId,
660
+ existingInviteId: existingGroupInvite._id,
661
+ });
662
+ }
663
+ } else {
664
+ const existingPlatformInvite = await ctx.db
665
+ .query("invite")
666
+ .withIndex("emailAndStatus", (q) =>
667
+ q.eq("email", args.email).eq("status", "pending"),
668
+ )
669
+ .filter((q) => q.eq(q.field("groupId"), undefined))
670
+ .first();
671
+ if (existingPlatformInvite !== null) {
672
+ throw new ConvexError({
673
+ code: "DUPLICATE_INVITE",
674
+ message: "A pending platform invite already exists for this email",
675
+ email: args.email,
676
+ existingInviteId: existingPlatformInvite._id,
677
+ });
678
+ }
679
+ }
680
+ return await ctx.db.insert("invite", args);
548
681
  },
549
682
  });
550
683
 
684
+ /** Retrieve an invite by its document ID. Returns `null` if not found. */
551
685
  export const inviteGet = query({
552
686
  args: { inviteId: v.id("invite") },
553
687
  handler: async (ctx, { inviteId }) => {
554
- return await (ctx.db as any).get(inviteId);
688
+ return await ctx.db.get(inviteId);
555
689
  },
556
690
  });
557
691
 
692
+ /**
693
+ * List invites, optionally filtered by group and/or status.
694
+ * Both `groupId` and `status` are optional filters.
695
+ */
558
696
  export const inviteList = query({
559
697
  args: {
560
- organizationId: v.optional(v.id("organization")),
561
- status: v.optional(v.string()),
562
- },
563
- handler: async (ctx, { organizationId, status }) => {
564
- if (organizationId !== undefined && status !== undefined) {
565
- return await (ctx.db as any)
698
+ groupId: v.optional(v.id("group")),
699
+ status: v.optional(
700
+ v.union(
701
+ v.literal("pending"),
702
+ v.literal("accepted"),
703
+ v.literal("revoked"),
704
+ v.literal("expired"),
705
+ ),
706
+ ),
707
+ },
708
+ handler: async (ctx, { groupId, status }) => {
709
+ if (groupId !== undefined && status !== undefined) {
710
+ return await ctx.db
566
711
  .query("invite")
567
- .withIndex("organizationIdAndStatus", (q: any) =>
568
- q.eq("organizationId", organizationId).eq("status", status),
712
+ .withIndex("groupIdAndStatus", (q) =>
713
+ q.eq("groupId", groupId).eq("status", status),
569
714
  )
570
715
  .collect();
571
716
  }
572
- if (organizationId !== undefined) {
573
- return await (ctx.db as any)
717
+ if (groupId !== undefined) {
718
+ return await ctx.db
574
719
  .query("invite")
575
- .withIndex("organizationId", (q: any) =>
576
- q.eq("organizationId", organizationId),
577
- )
720
+ .withIndex("groupId", (q) => q.eq("groupId", groupId))
578
721
  .collect();
579
722
  }
580
723
  if (status !== undefined) {
581
- return await (ctx.db as any)
724
+ return await ctx.db
582
725
  .query("invite")
583
- .filter((q: any) => q.eq(q.field("status"), status))
726
+ .withIndex("status", (q) => q.eq("status", status))
584
727
  .collect();
585
728
  }
586
- return await (ctx.db as any)
587
- .query("invite")
588
- .collect();
729
+ return await ctx.db.query("invite").collect();
589
730
  },
590
731
  });
591
732
 
733
+ /**
734
+ * Accept a pending invitation.
735
+ *
736
+ * Marks the invite as "accepted" and records the acceptance timestamp.
737
+ * Throws a structured `ConvexError` when the invite doesn't exist or is not
738
+ * currently pending.
739
+ *
740
+ * The caller is responsible for creating the corresponding member record.
741
+ */
592
742
  export const inviteAccept = mutation({
593
743
  args: { inviteId: v.id("invite") },
594
744
  handler: async (ctx, { inviteId }) => {
595
- await (ctx.db as any).patch(inviteId, {
745
+ const invite = await ctx.db.get(inviteId);
746
+ if (invite === null) {
747
+ throw new ConvexError({
748
+ code: "INVITE_NOT_FOUND",
749
+ message: "Invite not found",
750
+ inviteId,
751
+ });
752
+ }
753
+ if (invite.status !== "pending") {
754
+ throw new ConvexError({
755
+ code: "INVITE_NOT_PENDING",
756
+ message: `Cannot accept invite with status "${invite.status}"`,
757
+ inviteId,
758
+ currentStatus: invite.status,
759
+ });
760
+ }
761
+ await ctx.db.patch(inviteId, {
596
762
  status: "accepted",
597
763
  acceptedTime: Date.now(),
598
764
  });
599
765
  },
600
766
  });
601
767
 
768
+ /**
769
+ * Revoke a pending invitation.
770
+ *
771
+ * Marks the invite as "revoked". Throws a structured `ConvexError` when the
772
+ * invite doesn't exist or is not currently pending.
773
+ */
602
774
  export const inviteRevoke = mutation({
603
775
  args: { inviteId: v.id("invite") },
604
776
  handler: async (ctx, { inviteId }) => {
605
- await (ctx.db as any).patch(inviteId, { status: "revoked" });
777
+ const invite = await ctx.db.get(inviteId);
778
+ if (invite === null) {
779
+ throw new ConvexError({
780
+ code: "INVITE_NOT_FOUND",
781
+ message: "Invite not found",
782
+ inviteId,
783
+ });
784
+ }
785
+ if (invite.status !== "pending") {
786
+ throw new ConvexError({
787
+ code: "INVITE_NOT_PENDING",
788
+ message: `Cannot revoke invite with status "${invite.status}"`,
789
+ inviteId,
790
+ currentStatus: invite.status,
791
+ });
792
+ }
793
+ await ctx.db.patch(inviteId, { status: "revoked" });
606
794
  },
607
795
  });