@estifanos-sh/convex-auth 0.0.2 → 0.0.4

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 (438) hide show
  1. package/README.md +29 -6
  2. package/dist/bin.js +2 -34
  3. package/dist/browser/index.d.ts +2 -2
  4. package/dist/browser/index.d.ts.map +1 -1
  5. package/dist/browser/index.js +15 -9
  6. package/dist/browser/index.js.map +1 -1
  7. package/dist/client/core/types.d.ts +68 -97
  8. package/dist/client/core/types.d.ts.map +1 -1
  9. package/dist/client/core/types.js.map +1 -1
  10. package/dist/client/errors.d.ts +15 -0
  11. package/dist/client/errors.d.ts.map +1 -0
  12. package/dist/client/errors.js +8 -1
  13. package/dist/client/errors.js.map +1 -1
  14. package/dist/client/factors/device.js +4 -4
  15. package/dist/client/factors/device.js.map +1 -1
  16. package/dist/client/factors/totp.js +20 -12
  17. package/dist/client/factors/totp.js.map +1 -1
  18. package/dist/client/factors/webauthn.js +6 -2
  19. package/dist/client/factors/webauthn.js.map +1 -1
  20. package/dist/client/index.d.ts +6 -3
  21. package/dist/client/index.d.ts.map +1 -1
  22. package/dist/client/index.js +13 -10
  23. package/dist/client/index.js.map +1 -1
  24. package/dist/component/_generated/api.d.ts +13 -3
  25. package/dist/component/_generated/api.d.ts.map +1 -1
  26. package/dist/component/_generated/api.js.map +1 -1
  27. package/dist/component/_generated/component.d.ts +365 -74
  28. package/dist/component/_generated/component.d.ts.map +1 -1
  29. package/dist/component/_generated/dataModel.d.ts +2 -2
  30. package/dist/component/_generated/dataModel.d.ts.map +1 -1
  31. package/dist/component/_generated/server.d.ts +13 -2
  32. package/dist/component/_generated/server.d.ts.map +1 -1
  33. package/dist/component/_generated/server.js +2 -1
  34. package/dist/component/_generated/server.js.map +1 -1
  35. package/dist/component/account.d.ts +52 -38
  36. package/dist/component/account.d.ts.map +1 -1
  37. package/dist/component/account.js +42 -23
  38. package/dist/component/account.js.map +1 -1
  39. package/dist/component/batch.d.ts +3 -0
  40. package/dist/component/batch.d.ts.map +1 -0
  41. package/dist/component/batch.js +17 -0
  42. package/dist/component/batch.js.map +1 -0
  43. package/dist/component/connection/audit.d.ts +30 -27
  44. package/dist/component/connection/audit.d.ts.map +1 -1
  45. package/dist/component/connection/audit.js +16 -23
  46. package/dist/component/connection/audit.js.map +1 -1
  47. package/dist/component/connection/cache.d.ts +6 -6
  48. package/dist/component/connection/cache.js +0 -1
  49. package/dist/component/connection/cache.js.map +1 -1
  50. package/dist/component/connection/domain/verification.d.ts +19 -19
  51. package/dist/component/connection/domain/verification.js +1 -2
  52. package/dist/component/connection/domain/verification.js.map +1 -1
  53. package/dist/component/connection/domain.d.ts +20 -20
  54. package/dist/component/connection/domain.js +1 -2
  55. package/dist/component/connection/domain.js.map +1 -1
  56. package/dist/component/connection/saml/assertion.d.ts +4 -4
  57. package/dist/component/connection/saml/assertion.js +0 -1
  58. package/dist/component/connection/saml/assertion.js.map +1 -1
  59. package/dist/component/connection/saml/request.d.ts +8 -9
  60. package/dist/component/connection/saml/request.d.ts.map +1 -1
  61. package/dist/component/connection/saml/request.js +0 -2
  62. package/dist/component/connection/saml/request.js.map +1 -1
  63. package/dist/component/connection/scim/config.d.ts +13 -13
  64. package/dist/component/connection/scim/config.d.ts.map +1 -1
  65. package/dist/component/connection/scim/config.js +2 -2
  66. package/dist/component/connection/scim/config.js.map +1 -1
  67. package/dist/component/connection/scim/identity.d.ts +122 -61
  68. package/dist/component/connection/scim/identity.d.ts.map +1 -1
  69. package/dist/component/connection/scim/identity.js +408 -60
  70. package/dist/component/connection/scim/identity.js.map +1 -1
  71. package/dist/component/connection/secret.d.ts +15 -15
  72. package/dist/component/connection/secret.d.ts.map +1 -1
  73. package/dist/component/connection/secret.js +2 -2
  74. package/dist/component/connection/secret.js.map +1 -1
  75. package/dist/component/connection/webhook/delivery.d.ts +69 -53
  76. package/dist/component/connection/webhook/delivery.d.ts.map +1 -1
  77. package/dist/component/connection/webhook/delivery.js +129 -172
  78. package/dist/component/connection/webhook/delivery.js.map +1 -1
  79. package/dist/component/connection/webhook/endpoint.d.ts +21 -21
  80. package/dist/component/connection/webhook/endpoint.d.ts.map +1 -1
  81. package/dist/component/connection/webhook/endpoint.js +2 -2
  82. package/dist/component/connection/webhook/endpoint.js.map +1 -1
  83. package/dist/component/connection/webhook/events.d.ts +26 -0
  84. package/dist/component/connection/webhook/events.d.ts.map +1 -0
  85. package/dist/component/connection/webhook/events.js +39 -0
  86. package/dist/component/connection/webhook/events.js.map +1 -0
  87. package/dist/component/connection.d.ts +39 -39
  88. package/dist/component/connection.d.ts.map +1 -1
  89. package/dist/component/connection.js +9 -9
  90. package/dist/component/connection.js.map +1 -1
  91. package/dist/component/context.d.ts +115 -0
  92. package/dist/component/context.d.ts.map +1 -0
  93. package/dist/component/context.js +49 -0
  94. package/dist/component/context.js.map +1 -0
  95. package/dist/component/convex.config.d.ts +2 -2
  96. package/dist/component/crons.d.ts +2 -2
  97. package/dist/component/documents.d.ts +1244 -0
  98. package/dist/component/documents.d.ts.map +1 -0
  99. package/dist/component/documents.js +44 -0
  100. package/dist/component/documents.js.map +1 -0
  101. package/dist/component/event.d.ts +142 -141
  102. package/dist/component/event.d.ts.map +1 -1
  103. package/dist/component/event.js +90 -119
  104. package/dist/component/event.js.map +1 -1
  105. package/dist/component/eventstream.d.ts +30 -0
  106. package/dist/component/eventstream.d.ts.map +1 -0
  107. package/dist/component/eventstream.js +60 -0
  108. package/dist/component/eventstream.js.map +1 -0
  109. package/dist/component/factor/device.d.ts +25 -24
  110. package/dist/component/factor/device.d.ts.map +1 -1
  111. package/dist/component/factor/device.js +7 -4
  112. package/dist/component/factor/device.js.map +1 -1
  113. package/dist/component/factor/passkey.d.ts +173 -85
  114. package/dist/component/factor/passkey.d.ts.map +1 -1
  115. package/dist/component/factor/passkey.js +138 -27
  116. package/dist/component/factor/passkey.js.map +1 -1
  117. package/dist/component/factor/totp.d.ts +56 -50
  118. package/dist/component/factor/totp.d.ts.map +1 -1
  119. package/dist/component/factor/totp.js +6 -5
  120. package/dist/component/factor/totp.js.map +1 -1
  121. package/dist/component/group/active.d.ts +326 -22
  122. package/dist/component/group/active.d.ts.map +1 -1
  123. package/dist/component/group/active.js +22 -13
  124. package/dist/component/group/active.js.map +1 -1
  125. package/dist/component/group/invite.d.ts +30 -30
  126. package/dist/component/group/invite.d.ts.map +1 -1
  127. package/dist/component/group/invite.js +2 -2
  128. package/dist/component/group/invite.js.map +1 -1
  129. package/dist/component/group/member.d.ts +36 -36
  130. package/dist/component/group/member.d.ts.map +1 -1
  131. package/dist/component/group/member.js +5 -3
  132. package/dist/component/group/member.js.map +1 -1
  133. package/dist/component/group.d.ts +49 -64
  134. package/dist/component/group.d.ts.map +1 -1
  135. package/dist/component/group.js +5 -3
  136. package/dist/component/group.js.map +1 -1
  137. package/dist/component/http.d.ts +2 -2
  138. package/dist/component/index.d.ts +0 -10
  139. package/dist/component/index.js +1 -3
  140. package/dist/component/limits.d.ts +5 -4
  141. package/dist/component/limits.d.ts.map +1 -1
  142. package/dist/component/limits.js +7 -4
  143. package/dist/component/limits.js.map +1 -1
  144. package/dist/component/maintenance.d.ts +6 -3
  145. package/dist/component/maintenance.d.ts.map +1 -1
  146. package/dist/component/maintenance.js +45 -6
  147. package/dist/component/maintenance.js.map +1 -1
  148. package/dist/component/model.d.ts +847 -1723
  149. package/dist/component/model.d.ts.map +1 -1
  150. package/dist/component/model.js +28 -317
  151. package/dist/component/model.js.map +1 -1
  152. package/dist/component/modules.d.ts +11 -5
  153. package/dist/component/modules.d.ts.map +1 -1
  154. package/dist/component/modules.js +4 -1
  155. package/dist/component/modules.js.map +1 -1
  156. package/dist/component/oauth/client.d.ts +20 -20
  157. package/dist/component/oauth/client.d.ts.map +1 -1
  158. package/dist/component/oauth/client.js +2 -2
  159. package/dist/component/oauth/client.js.map +1 -1
  160. package/dist/component/oauth/code.d.ts +14 -14
  161. package/dist/component/oauth/code.js +1 -2
  162. package/dist/component/oauth/code.js.map +1 -1
  163. package/dist/component/oauth/refresh.d.ts +20 -20
  164. package/dist/component/oauth/refresh.js +1 -2
  165. package/dist/component/oauth/refresh.js.map +1 -1
  166. package/dist/component/schema.d.ts +1148 -607
  167. package/dist/component/schema.d.ts.map +1 -1
  168. package/dist/component/schema.js +43 -15
  169. package/dist/component/schema.js.map +1 -1
  170. package/dist/component/session.d.ts +68 -32
  171. package/dist/component/session.d.ts.map +1 -1
  172. package/dist/component/session.js +118 -34
  173. package/dist/component/session.js.map +1 -1
  174. package/dist/component/token/continuation.d.ts +47 -18
  175. package/dist/component/token/continuation.d.ts.map +1 -1
  176. package/dist/component/token/continuation.js +76 -18
  177. package/dist/component/token/continuation.js.map +1 -1
  178. package/dist/component/token/enrollment.d.ts +42 -0
  179. package/dist/component/token/enrollment.d.ts.map +1 -0
  180. package/dist/component/token/enrollment.js +57 -0
  181. package/dist/component/token/enrollment.js.map +1 -0
  182. package/dist/component/token/pkce.d.ts +31 -24
  183. package/dist/component/token/pkce.d.ts.map +1 -1
  184. package/dist/component/token/pkce.js +15 -20
  185. package/dist/component/token/pkce.js.map +1 -1
  186. package/dist/component/token/refresh.d.ts +42 -35
  187. package/dist/component/token/refresh.d.ts.map +1 -1
  188. package/dist/component/token/refresh.js +16 -8
  189. package/dist/component/token/refresh.js.map +1 -1
  190. package/dist/component/token/verification.d.ts +34 -30
  191. package/dist/component/token/verification.d.ts.map +1 -1
  192. package/dist/component/token/verification.js +7 -6
  193. package/dist/component/token/verification.js.map +1 -1
  194. package/dist/component/user/email.d.ts +21 -21
  195. package/dist/component/user/email.d.ts.map +1 -1
  196. package/dist/component/user/email.js +2 -2
  197. package/dist/component/user/email.js.map +1 -1
  198. package/dist/component/user/key.d.ts +36 -36
  199. package/dist/component/user/key.d.ts.map +1 -1
  200. package/dist/component/user/key.js +2 -2
  201. package/dist/component/user/key.js.map +1 -1
  202. package/dist/component/user.d.ts +52 -48
  203. package/dist/component/user.d.ts.map +1 -1
  204. package/dist/component/user.js +157 -36
  205. package/dist/component/user.js.map +1 -1
  206. package/dist/expo/index.d.ts +4 -4
  207. package/dist/expo/index.d.ts.map +1 -1
  208. package/dist/expo/index.js +20 -13
  209. package/dist/expo/index.js.map +1 -1
  210. package/dist/node_modules/.pnpm/@opentelemetry_sdk-logs@0.214.0_@opentelemetry_api@1.9.1/node_modules/@opentelemetry/sdk-logs/build/src/export/ReadableLogRecord.d.ts +2 -2
  211. package/dist/node_modules/.pnpm/@opentelemetry_sdk-logs@0.214.0_@opentelemetry_api@1.9.1/node_modules/@opentelemetry/sdk-logs/build/src/export/SdkLogRecord.d.ts +1 -1
  212. package/dist/node_modules/.pnpm/@opentelemetry_sdk-metrics@2.6.1_@opentelemetry_api@1.9.1/node_modules/@opentelemetry/sdk-metrics/build/src/MeterProvider.d.ts +2 -2
  213. package/dist/node_modules/.pnpm/@opentelemetry_sdk-metrics@2.6.1_@opentelemetry_api@1.9.1/node_modules/@opentelemetry/sdk-metrics/build/src/export/MetricData.d.ts +1 -1
  214. package/dist/node_modules/.pnpm/@opentelemetry_sdk-trace-base@2.6.1_@opentelemetry_api@1.9.1/node_modules/@opentelemetry/sdk-trace-base/build/src/Span.d.ts +1 -1
  215. package/dist/node_modules/.pnpm/@opentelemetry_sdk-trace-base@2.6.1_@opentelemetry_api@1.9.1/node_modules/@opentelemetry/sdk-trace-base/build/src/export/ReadableSpan.d.ts +1 -1
  216. package/dist/node_modules/.pnpm/@opentelemetry_sdk-trace-base@2.6.1_@opentelemetry_api@1.9.1/node_modules/@opentelemetry/sdk-trace-base/build/src/types.d.ts +1 -1
  217. package/dist/otel.d.ts +1 -1
  218. package/dist/providers/anonymous.d.ts +11 -5
  219. package/dist/providers/anonymous.d.ts.map +1 -1
  220. package/dist/providers/anonymous.js +4 -1
  221. package/dist/providers/anonymous.js.map +1 -1
  222. package/dist/providers/apple.d.ts +1 -1
  223. package/dist/providers/apple.js +19 -4
  224. package/dist/providers/apple.js.map +1 -1
  225. package/dist/providers/credentials.d.ts +22 -14
  226. package/dist/providers/credentials.d.ts.map +1 -1
  227. package/dist/providers/credentials.js +2 -0
  228. package/dist/providers/credentials.js.map +1 -1
  229. package/dist/providers/custom.d.ts +3 -3
  230. package/dist/providers/custom.d.ts.map +1 -1
  231. package/dist/providers/custom.js.map +1 -1
  232. package/dist/providers/email.d.ts +3 -3
  233. package/dist/providers/email.d.ts.map +1 -1
  234. package/dist/providers/email.js.map +1 -1
  235. package/dist/providers/github.d.ts +1 -1
  236. package/dist/providers/github.d.ts.map +1 -1
  237. package/dist/providers/github.js +9 -4
  238. package/dist/providers/github.js.map +1 -1
  239. package/dist/providers/google.d.ts +1 -1
  240. package/dist/providers/google.d.ts.map +1 -1
  241. package/dist/providers/google.js +9 -4
  242. package/dist/providers/google.js.map +1 -1
  243. package/dist/providers/microsoft.d.ts +1 -1
  244. package/dist/providers/microsoft.d.ts.map +1 -1
  245. package/dist/providers/microsoft.js +12 -4
  246. package/dist/providers/microsoft.js.map +1 -1
  247. package/dist/providers/password.d.ts +117 -15
  248. package/dist/providers/password.d.ts.map +1 -1
  249. package/dist/providers/password.js +71 -26
  250. package/dist/providers/password.js.map +1 -1
  251. package/dist/providers/phone.d.ts +4 -3
  252. package/dist/providers/phone.d.ts.map +1 -1
  253. package/dist/providers/phone.js.map +1 -1
  254. package/dist/providers/webauthn.d.ts.map +1 -1
  255. package/dist/providers/webauthn.js +6 -0
  256. package/dist/providers/webauthn.js.map +1 -1
  257. package/dist/react/index.d.ts +40 -41
  258. package/dist/react/index.d.ts.map +1 -1
  259. package/dist/react/index.js +27 -52
  260. package/dist/react/index.js.map +1 -1
  261. package/dist/server/auth.d.ts +224 -126
  262. package/dist/server/auth.d.ts.map +1 -1
  263. package/dist/server/auth.js +44 -25
  264. package/dist/server/auth.js.map +1 -1
  265. package/dist/server/component/factor/db.js +13 -6
  266. package/dist/server/component/factor/db.js.map +1 -1
  267. package/dist/server/config.d.ts +2 -1
  268. package/dist/server/connection/domain.d.ts +4 -65
  269. package/dist/server/connection/domain.d.ts.map +1 -1
  270. package/dist/server/connection/domain.js +29 -5
  271. package/dist/server/connection/domain.js.map +1 -1
  272. package/dist/server/connection/group/service.js +7 -5
  273. package/dist/server/connection/group/service.js.map +1 -1
  274. package/dist/server/connection/http.js +136 -291
  275. package/dist/server/connection/http.js.map +1 -1
  276. package/dist/server/connection/oidc.js +8 -17
  277. package/dist/server/connection/oidc.js.map +1 -1
  278. package/dist/server/connection/policy/domain.js +4 -1
  279. package/dist/server/connection/policy/domain.js.map +1 -1
  280. package/dist/server/connection/policy.js +14 -13
  281. package/dist/server/connection/policy.js.map +1 -1
  282. package/dist/server/connection/provision.js +5 -19
  283. package/dist/server/connection/provision.js.map +1 -1
  284. package/dist/server/connection/saml/encoding.js +2 -6
  285. package/dist/server/connection/saml/encoding.js.map +1 -1
  286. package/dist/server/connection/saml/signature.js +2 -2
  287. package/dist/server/connection/saml/signature.js.map +1 -1
  288. package/dist/server/connection/saml/xmldsig.js +1 -1
  289. package/dist/server/connection/saml/xmldsig.js.map +1 -1
  290. package/dist/server/connection/webhook.js +4 -36
  291. package/dist/server/connection/webhook.js.map +1 -1
  292. package/dist/server/context.d.ts +2 -2
  293. package/dist/server/context.d.ts.map +1 -1
  294. package/dist/server/context.js +35 -24
  295. package/dist/server/context.js.map +1 -1
  296. package/dist/server/contract.d.ts.map +1 -1
  297. package/dist/server/contract.js +6 -14
  298. package/dist/server/contract.js.map +1 -1
  299. package/dist/server/core.js +46 -1
  300. package/dist/server/core.js.map +1 -1
  301. package/dist/server/cors.d.ts +2 -2
  302. package/dist/server/cors.d.ts.map +1 -1
  303. package/dist/server/crypto.js.map +1 -1
  304. package/dist/server/db.js +6 -2
  305. package/dist/server/db.js.map +1 -1
  306. package/dist/server/device.js +1 -1
  307. package/dist/server/domains/account.d.ts +9 -1
  308. package/dist/server/domains/account.d.ts.map +1 -1
  309. package/dist/server/domains/account.js +21 -9
  310. package/dist/server/domains/account.js.map +1 -1
  311. package/dist/server/domains/context.js +34 -0
  312. package/dist/server/domains/context.js.map +1 -0
  313. package/dist/server/domains/factor.d.ts +3 -2
  314. package/dist/server/domains/factor.d.ts.map +1 -1
  315. package/dist/server/domains/factor.js.map +1 -1
  316. package/dist/server/domains/group.js.map +1 -1
  317. package/dist/server/domains/invite.js.map +1 -1
  318. package/dist/server/domains/key.js.map +1 -1
  319. package/dist/server/domains/member.js.map +1 -1
  320. package/dist/server/domains/session.js.map +1 -1
  321. package/dist/server/domains/user.js.map +1 -1
  322. package/dist/server/env.js +2 -22
  323. package/dist/server/env.js.map +1 -1
  324. package/dist/server/events.d.ts +3 -1
  325. package/dist/server/events.d.ts.map +1 -1
  326. package/dist/server/events.js +6 -6
  327. package/dist/server/events.js.map +1 -1
  328. package/dist/server/facade.d.ts +1 -33
  329. package/dist/server/facade.d.ts.map +1 -1
  330. package/dist/server/facade.js +1 -2
  331. package/dist/server/facade.js.map +1 -1
  332. package/dist/server/http.d.ts +1 -0
  333. package/dist/server/http.d.ts.map +1 -1
  334. package/dist/server/http.js +4 -4
  335. package/dist/server/http.js.map +1 -1
  336. package/dist/server/identity/claims.js +1 -1
  337. package/dist/server/identity/convex.d.ts +2 -0
  338. package/dist/server/identity/convex.d.ts.map +1 -1
  339. package/dist/server/index.d.ts +6 -9
  340. package/dist/server/index.js +2 -4
  341. package/dist/server/limits.js +23 -2
  342. package/dist/server/limits.js.map +1 -1
  343. package/dist/server/mutations/calls.js +3 -2
  344. package/dist/server/mutations/credentials/signin.js +7 -3
  345. package/dist/server/mutations/credentials/signin.js.map +1 -1
  346. package/dist/server/mutations/enrollment.js +87 -0
  347. package/dist/server/mutations/enrollment.js.map +1 -0
  348. package/dist/server/mutations/invalidate.js +5 -10
  349. package/dist/server/mutations/invalidate.js.map +1 -1
  350. package/dist/server/mutations/oauth.js +3 -3
  351. package/dist/server/mutations/oauth.js.map +1 -1
  352. package/dist/server/mutations/refresh.js +1 -0
  353. package/dist/server/mutations/refresh.js.map +1 -1
  354. package/dist/server/mutations/register.js +52 -21
  355. package/dist/server/mutations/register.js.map +1 -1
  356. package/dist/server/mutations/retrieve.js +4 -1
  357. package/dist/server/mutations/retrieve.js.map +1 -1
  358. package/dist/server/mutations/signin.js +2 -2
  359. package/dist/server/mutations/signin.js.map +1 -1
  360. package/dist/server/mutations/store.js +6 -1
  361. package/dist/server/mutations/store.js.map +1 -1
  362. package/dist/server/mutations/verify.js +6 -4
  363. package/dist/server/mutations/verify.js.map +1 -1
  364. package/dist/server/oauth/client.d.ts +1 -1
  365. package/dist/server/oauth/client.js.map +1 -1
  366. package/dist/server/oauth/code.d.ts +1 -1
  367. package/dist/server/oauth/code.js.map +1 -1
  368. package/dist/server/oauth/factory.js +89 -13
  369. package/dist/server/oauth/factory.js.map +1 -1
  370. package/dist/server/oauth/handlers.js +2 -2
  371. package/dist/server/oauth/runtime.js +24 -9
  372. package/dist/server/oauth/runtime.js.map +1 -1
  373. package/dist/server/preload.d.ts.map +1 -1
  374. package/dist/server/preload.js +14 -11
  375. package/dist/server/preload.js.map +1 -1
  376. package/dist/server/runtime.d.ts +236 -148
  377. package/dist/server/runtime.d.ts.map +1 -1
  378. package/dist/server/runtime.js +92 -44
  379. package/dist/server/runtime.js.map +1 -1
  380. package/dist/server/session/lifecycle.d.ts +2 -1
  381. package/dist/server/session/lifecycle.d.ts.map +1 -1
  382. package/dist/server/session/lifecycle.js +29 -10
  383. package/dist/server/session/lifecycle.js.map +1 -1
  384. package/dist/server/signin/flow.js +2 -2
  385. package/dist/server/signin/flow.js.map +1 -1
  386. package/dist/server/telemetry.js +1 -1
  387. package/dist/server/tokens.js +6 -3
  388. package/dist/server/tokens.js.map +1 -1
  389. package/dist/server/totp.js +4 -3
  390. package/dist/server/totp.js.map +1 -1
  391. package/dist/server/types.d.ts +131 -45
  392. package/dist/server/types.d.ts.map +1 -1
  393. package/dist/server/user/account.js +0 -1
  394. package/dist/server/user/account.js.map +1 -1
  395. package/dist/server/validators.d.ts +930 -944
  396. package/dist/server/validators.d.ts.map +1 -1
  397. package/dist/server/validators.js +66 -39
  398. package/dist/server/validators.js.map +1 -1
  399. package/dist/server/webauthn.js +60 -29
  400. package/dist/server/webauthn.js.map +1 -1
  401. package/dist/shared/brand.d.ts +3 -7
  402. package/dist/shared/brand.d.ts.map +1 -1
  403. package/dist/shared/params.d.ts +78 -0
  404. package/dist/shared/params.d.ts.map +1 -0
  405. package/dist/shared/results.d.ts +13 -1
  406. package/dist/shared/results.d.ts.map +1 -1
  407. package/dist/svelte/context.svelte.d.ts +23 -12
  408. package/dist/svelte/context.svelte.d.ts.map +1 -1
  409. package/dist/svelte/context.svelte.js +20 -13
  410. package/dist/svelte/index.d.ts +1 -4
  411. package/dist/svelte/index.d.ts.map +1 -1
  412. package/dist/svelte/index.js +1 -4
  413. package/dist/test.d.ts +1204 -611
  414. package/dist/test.d.ts.map +1 -1
  415. package/dist/test.js +50 -1
  416. package/dist/test.js.map +1 -1
  417. package/package.json +5 -14
  418. package/dist/_generated/component.d.ts +0 -3074
  419. package/dist/_generated/component.d.ts.map +0 -1
  420. package/dist/component/functions.d.ts +0 -9
  421. package/dist/component/functions.d.ts.map +0 -1
  422. package/dist/component/functions.js +0 -3
  423. package/dist/core/index.d.ts +0 -533
  424. package/dist/core/index.d.ts.map +0 -1
  425. package/dist/core/index.js +0 -86
  426. package/dist/core/index.js.map +0 -1
  427. package/dist/model.js.map +0 -1
  428. package/dist/server/env.d.ts +0 -28
  429. package/dist/server/env.d.ts.map +0 -1
  430. package/dist/svelte/AuthLoading.svelte +0 -13
  431. package/dist/svelte/AuthLoading.svelte.d.ts +0 -8
  432. package/dist/svelte/AuthLoading.svelte.d.ts.map +0 -1
  433. package/dist/svelte/SignedIn.svelte +0 -14
  434. package/dist/svelte/SignedIn.svelte.d.ts +0 -8
  435. package/dist/svelte/SignedIn.svelte.d.ts.map +0 -1
  436. package/dist/svelte/SignedOut.svelte +0 -13
  437. package/dist/svelte/SignedOut.svelte.d.ts +0 -8
  438. package/dist/svelte/SignedOut.svelte.d.ts.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"key.js","names":["schema"],"sources":["../../../src/component/user/key.ts"],"sourcesContent":["/**\n * `component.user.key.*` — API keys (programmatic access credentials,\n * a sub-resource of user).\n *\n * Reads collapse into one overloaded `get`.\n *\n * @module\n */\n\nimport { paginationOptsValidator } from \"convex/server\";\nimport { ConvexError, v } from \"convex/values\";\nimport { stream } from \"convex-helpers/server/stream\";\nimport { ErrorCode } from \"../../shared/codes\";\n\nimport { mutation, query } from \"../functions\";\nimport schema from \"../schema\";\nimport { vApiKeyDoc, vApiKeyRateLimit, vApiKeyScope, vPaginated } from \"../model\";\n\ntype ApiKeyRateLimit = { maxRequests: number; windowMs: number };\ntype ApiKeyUsePatch = {\n rateLimitState?: { attemptsLeft: number; lastAttemptTime: number };\n lastUsedAt?: number;\n};\n\nfunction isValidRateLimit(rateLimit: ApiKeyRateLimit): boolean {\n return (\n Number.isSafeInteger(rateLimit.maxRequests) &&\n rateLimit.maxRequests > 0 &&\n Number.isFinite(rateLimit.windowMs) &&\n rateLimit.windowMs > 0\n );\n}\n\nfunction assertValidRateLimit(rateLimit: ApiKeyRateLimit | undefined): void {\n if (rateLimit !== undefined && !isValidRateLimit(rateLimit)) {\n throw new ConvexError({\n code: ErrorCode.INVALID_PARAMETERS,\n message:\n \"API key rate limits require a positive integer maxRequests and positive finite windowMs.\",\n });\n }\n}\n\n/**\n * Read an API key by id, or by `hashedKey` when given. Returns `null` when no\n * selector matches.\n */\nexport const get = query({\n args: {\n id: v.optional(v.id(\"ApiKey\")),\n hashedKey: v.optional(v.string()),\n },\n returns: v.union(vApiKeyDoc, v.null()),\n handler: async (ctx, args) => {\n if (args.hashedKey !== undefined) {\n return await ctx.db\n .query(\"ApiKey\")\n .withIndex(\"hashed_key\", (q) => q.eq(\"hashedKey\", args.hashedKey!))\n .first();\n }\n if (args.id === undefined) return null;\n return await ctx.db.get(\"ApiKey\", args.id);\n },\n});\n\n/** List API keys, paginated, with optional `where` filters and ordering. */\nexport const list = query({\n args: {\n where: v.optional(\n v.object({\n userId: v.optional(v.id(\"User\")),\n revoked: v.optional(v.boolean()),\n name: v.optional(v.string()),\n prefix: v.optional(v.string()),\n }),\n ),\n paginationOpts: paginationOptsValidator,\n orderBy: v.optional(\n v.union(\n v.literal(\"_creationTime\"),\n v.literal(\"name\"),\n v.literal(\"lastUsedAt\"),\n v.literal(\"expiresAt\"),\n v.literal(\"revoked\"),\n ),\n ),\n order: v.optional(v.union(v.literal(\"asc\"), v.literal(\"desc\"))),\n },\n returns: vPaginated(vApiKeyDoc),\n handler: async (ctx, args) => {\n const where = args.where ?? {};\n const order = args.order ?? \"desc\";\n\n const base = stream(ctx.db, schema).query(\"ApiKey\");\n let q;\n if (where.userId !== undefined) {\n q = base.withIndex(\"user_id\", (idx) => idx.eq(\"userId\", where.userId!));\n } else {\n q = base;\n }\n\n return await q\n .order(order)\n .filterWith(\n async (d) =>\n (where.revoked === undefined || d.revoked === where.revoked) &&\n (where.name === undefined || d.name === where.name) &&\n (where.prefix === undefined || d.prefix === where.prefix),\n )\n .paginate(args.paginationOpts);\n },\n});\n\n/** Insert a new API key (stamped `createdAt`, `revoked: false`). */\nexport const create = mutation({\n args: {\n userId: v.id(\"User\"),\n prefix: v.string(),\n hashedKey: v.string(),\n name: v.string(),\n scopes: v.array(\n v.object({\n resource: v.string(),\n actions: v.array(v.string()),\n }),\n ),\n rateLimit: v.optional(vApiKeyRateLimit),\n expiresAt: v.optional(v.number()),\n extend: v.optional(v.any()),\n },\n returns: v.id(\"ApiKey\"),\n handler: async (ctx, args) => {\n assertValidRateLimit(args.rateLimit);\n return await ctx.db.insert(\"ApiKey\", {\n ...args,\n createdAt: Date.now(),\n revoked: false,\n });\n },\n});\n\n/** Patch fields on an API key; throws `KEY_NOT_FOUND` if it does not exist. */\nexport const update = mutation({\n args: {\n id: v.id(\"ApiKey\"),\n patch: v.object({\n name: v.optional(v.string()),\n scopes: v.optional(v.array(vApiKeyScope)),\n rateLimit: v.optional(vApiKeyRateLimit),\n revoked: v.optional(v.boolean()),\n }),\n },\n returns: v.null(),\n handler: async (ctx, { id: keyId, patch }) => {\n assertValidRateLimit(patch.rateLimit);\n const key = await ctx.db.get(\"ApiKey\", keyId);\n if (key === null) {\n throw new ConvexError({\n code: ErrorCode.KEY_NOT_FOUND,\n message: \"API key not found\",\n keyId,\n });\n }\n await ctx.db.patch(\"ApiKey\", keyId, patch);\n return null;\n },\n});\n\n/**\n * Atomically enforce an API key's rate limit and refresh `lastUsedAt`, folding\n * the read → check → decrement of the token bucket into a SINGLE transaction on\n * the `ApiKey` row.\n *\n * The facade previously split that read (`key.get`) from the decrement\n * (`key.update`) across two transactions, so two concurrent verifications could\n * both read the same starting `rateLimitState`, both decide \"not limited\", and\n * both persist a decrement of the *same* state — the true atomic decrement was\n * lost and a burst could slip past the limit. Performing the whole\n * check-and-decrement here closes that window: the row's transaction serializes\n * concurrent callers, so each consumes its own token.\n *\n * The verification decision is made from the same transactional snapshot as\n * the rate-limit update. In particular, the mutation re-checks deletion,\n * revocation, expiration, and the current scopes instead of trusting the\n * facade's earlier hash lookup. A successful result returns the current owner\n * and scopes so a concurrent scope reduction cannot authenticate with stale\n * permissions.\n *\n * Returns `{ status: \"limited\" }` WITHOUT writing when the bucket is empty\n * (mirroring the facade's prior behavior of throwing before its update on the\n * limited branch); otherwise decrements the bucket (when configured) and\n * refreshes `lastUsedAt`. The token bucket refills linearly over the configured\n * window (the same algorithm as sign-in rate limiting); the math is inlined\n * here because component code cannot import from the server bundle.\n *\n * `lastUsedAt` is coarsened by the optional `coarsenMs` window: it is only\n * rewritten when unset or older than the window, so read-only bearer traffic on\n * a hot key stays read-only (no per-request write). When a rate-limit decrement\n * forces a write anyway, a fresh `lastUsedAt` is simply left as-is. Omitting\n * `coarsenMs` (or passing `0`) disables coarsening and refreshes every call.\n */\nexport const recordUse = mutation({\n args: { id: v.id(\"ApiKey\"), coarsenMs: v.optional(v.number()) },\n returns: v.union(\n v.object({ status: v.literal(\"invalid\") }),\n v.object({ status: v.literal(\"revoked\") }),\n v.object({ status: v.literal(\"expired\") }),\n v.object({ status: v.literal(\"limited\") }),\n v.object({\n status: v.literal(\"verified\"),\n keyId: v.id(\"ApiKey\"),\n userId: v.id(\"User\"),\n scopes: v.array(vApiKeyScope),\n }),\n ),\n handler: async (ctx, { id: keyId, coarsenMs }) => {\n // Use the mutation's trusted transaction clock for validity and bucket math.\n const now = Date.now();\n const key = await ctx.db.get(\"ApiKey\", keyId);\n if (key === null) {\n return { status: \"invalid\" as const };\n }\n if (key.revoked) {\n return { status: \"revoked\" as const };\n }\n if (key.expiresAt !== undefined && key.expiresAt < now) {\n return { status: \"expired\" as const };\n }\n const patch: ApiKeyUsePatch = {};\n if (key.rateLimit) {\n const state = key.rateLimitState;\n if (!state) {\n patch.rateLimitState = {\n attemptsLeft: key.rateLimit.maxRequests - 1,\n lastAttemptTime: now,\n };\n } else {\n const elapsed = Math.max(0, now - state.lastAttemptTime);\n const refillRate = key.rateLimit.maxRequests / key.rateLimit.windowMs;\n const refilled = Math.min(\n key.rateLimit.maxRequests,\n state.attemptsLeft + elapsed * refillRate,\n );\n if (refilled < 1) {\n // Bucket empty: reject WITHOUT writing (matches the prior facade,\n // which threw before its single `update`, leaving state untouched).\n return { status: \"limited\" as const };\n }\n patch.rateLimitState = { attemptsLeft: refilled - 1, lastAttemptTime: now };\n }\n }\n // Coarsen the `lastUsedAt` touch: only stamp it when unset or older than the\n // caller's sampling window. With `coarsenMs` unset/0 the window is 0, so it\n // refreshes every call.\n const window = coarsenMs ?? 0;\n const lastUsedAt = typeof key.lastUsedAt === \"number\" ? key.lastUsedAt : 0;\n if (now - lastUsedAt >= window) {\n patch.lastUsedAt = now;\n }\n // Nothing to persist — no rate-limit decrement and `lastUsedAt` still fresh:\n // the hot path stays read-only.\n if (patch.rateLimitState === undefined && patch.lastUsedAt === undefined) {\n return {\n status: \"verified\" as const,\n keyId: key._id,\n userId: key.userId,\n scopes: key.scopes,\n };\n }\n await ctx.db.patch(\"ApiKey\", keyId, patch);\n return {\n status: \"verified\" as const,\n keyId: key._id,\n userId: key.userId,\n scopes: key.scopes,\n };\n },\n});\n\n/**\n * Atomically revoke an active API key and create its replacement.\n *\n * The caller pre-generates the replacement secret and passes only its hash and\n * display prefix. Reading the old row, validating its state, revoking it, and\n * inserting the replacement happen in one transaction, so concurrent rotations\n * cannot both mint a usable successor and an insert failure cannot strand the\n * caller with a revoked key and no replacement.\n */\nexport const rotate = mutation({\n args: {\n id: v.id(\"ApiKey\"),\n prefix: v.string(),\n hashedKey: v.string(),\n name: v.optional(v.string()),\n expiresAt: v.optional(v.number()),\n },\n returns: v.union(\n v.object({ status: v.literal(\"invalid\") }),\n v.object({ status: v.literal(\"revoked\") }),\n v.object({ status: v.literal(\"invalid_rate_limit\") }),\n v.object({\n status: v.literal(\"rotated\"),\n id: v.id(\"ApiKey\"),\n userId: v.id(\"User\"),\n name: v.string(),\n }),\n ),\n handler: async (ctx, args) => {\n const existing = await ctx.db.get(\"ApiKey\", args.id);\n if (existing === null) {\n return { status: \"invalid\" as const };\n }\n if (existing.revoked) {\n return { status: \"revoked\" as const };\n }\n if (existing.rateLimit !== undefined && !isValidRateLimit(existing.rateLimit)) {\n return { status: \"invalid_rate_limit\" as const };\n }\n\n const name = args.name ?? existing.name;\n await ctx.db.patch(\"ApiKey\", existing._id, { revoked: true });\n const id = await ctx.db.insert(\"ApiKey\", {\n userId: existing.userId,\n prefix: args.prefix,\n hashedKey: args.hashedKey,\n name,\n scopes: existing.scopes,\n rateLimit: existing.rateLimit,\n expiresAt: args.expiresAt,\n createdAt: Date.now(),\n revoked: false,\n extend: existing.extend,\n });\n return {\n status: \"rotated\" as const,\n id,\n userId: existing.userId,\n name,\n };\n },\n});\n\n/** Delete an API key; throws `KEY_NOT_FOUND` if it does not exist. */\nconst remove = mutation({\n args: { id: v.id(\"ApiKey\") },\n returns: v.null(),\n handler: async (ctx, { id: keyId }) => {\n const key = await ctx.db.get(\"ApiKey\", keyId);\n if (key === null) {\n throw new ConvexError({\n code: ErrorCode.KEY_NOT_FOUND,\n message: \"API key not found\",\n keyId,\n });\n }\n await ctx.db.delete(\"ApiKey\", keyId);\n return null;\n },\n});\n\nexport { remove };\n"],"mappings":"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"}
1
+ {"version":3,"file":"key.js","names":["schema"],"sources":["../../../src/component/user/key.ts"],"sourcesContent":["/**\n * `component.user.key.*` — API keys (programmatic access credentials,\n * a sub-resource of user).\n *\n * Reads collapse into one overloaded `get`.\n *\n * @module\n */\n\nimport { paginationOptsValidator } from \"convex/server\";\nimport { ConvexError, v } from \"convex/values\";\nimport { stream } from \"convex-helpers/server/stream\";\nimport { ErrorCode } from \"../../shared/codes\";\n\nimport { mutation, query } from \"../_generated/server\";\nimport schema from \"../schema\";\nimport { vApiKeyDoc } from \"../documents\";\nimport { vApiKeyRateLimit, vApiKeyScope, vPaginated } from \"../model\";\n\ntype ApiKeyRateLimit = { maxRequests: number; windowMs: number };\ntype ApiKeyUsePatch = {\n rateLimitState?: { attemptsLeft: number; lastAttemptTime: number };\n lastUsedAt?: number;\n};\n\nfunction isValidRateLimit(rateLimit: ApiKeyRateLimit): boolean {\n return (\n Number.isSafeInteger(rateLimit.maxRequests) &&\n rateLimit.maxRequests > 0 &&\n Number.isFinite(rateLimit.windowMs) &&\n rateLimit.windowMs > 0\n );\n}\n\nfunction assertValidRateLimit(rateLimit: ApiKeyRateLimit | undefined): void {\n if (rateLimit !== undefined && !isValidRateLimit(rateLimit)) {\n throw new ConvexError({\n code: ErrorCode.INVALID_PARAMETERS,\n message:\n \"API key rate limits require a positive integer maxRequests and positive finite windowMs.\",\n });\n }\n}\n\n/**\n * Read an API key by id, or by `hashedKey` when given. Returns `null` when no\n * selector matches.\n */\nexport const get = query({\n args: {\n id: v.optional(v.id(\"ApiKey\")),\n hashedKey: v.optional(v.string()),\n },\n returns: v.union(vApiKeyDoc, v.null()),\n handler: async (ctx, args) => {\n if (args.hashedKey !== undefined) {\n return await ctx.db\n .query(\"ApiKey\")\n .withIndex(\"hashed_key\", (q) => q.eq(\"hashedKey\", args.hashedKey!))\n .first();\n }\n if (args.id === undefined) return null;\n return await ctx.db.get(\"ApiKey\", args.id);\n },\n});\n\n/** List API keys, paginated, with optional `where` filters and ordering. */\nexport const list = query({\n args: {\n where: v.optional(\n v.object({\n userId: v.optional(v.id(\"User\")),\n revoked: v.optional(v.boolean()),\n name: v.optional(v.string()),\n prefix: v.optional(v.string()),\n }),\n ),\n paginationOpts: paginationOptsValidator,\n orderBy: v.optional(\n v.union(\n v.literal(\"_creationTime\"),\n v.literal(\"name\"),\n v.literal(\"lastUsedAt\"),\n v.literal(\"expiresAt\"),\n v.literal(\"revoked\"),\n ),\n ),\n order: v.optional(v.union(v.literal(\"asc\"), v.literal(\"desc\"))),\n },\n returns: vPaginated(vApiKeyDoc),\n handler: async (ctx, args) => {\n const where = args.where ?? {};\n const order = args.order ?? \"desc\";\n\n const base = stream(ctx.db, schema).query(\"ApiKey\");\n let q;\n if (where.userId !== undefined) {\n q = base.withIndex(\"user_id\", (idx) => idx.eq(\"userId\", where.userId!));\n } else {\n q = base;\n }\n\n return await q\n .order(order)\n .filterWith(\n async (d) =>\n (where.revoked === undefined || d.revoked === where.revoked) &&\n (where.name === undefined || d.name === where.name) &&\n (where.prefix === undefined || d.prefix === where.prefix),\n )\n .paginate(args.paginationOpts);\n },\n});\n\n/** Insert a new API key (stamped `createdAt`, `revoked: false`). */\nexport const create = mutation({\n args: {\n userId: v.id(\"User\"),\n prefix: v.string(),\n hashedKey: v.string(),\n name: v.string(),\n scopes: v.array(\n v.object({\n resource: v.string(),\n actions: v.array(v.string()),\n }),\n ),\n rateLimit: v.optional(vApiKeyRateLimit),\n expiresAt: v.optional(v.number()),\n extend: v.optional(v.any()),\n },\n returns: v.id(\"ApiKey\"),\n handler: async (ctx, args) => {\n assertValidRateLimit(args.rateLimit);\n return await ctx.db.insert(\"ApiKey\", {\n ...args,\n createdAt: Date.now(),\n revoked: false,\n });\n },\n});\n\n/** Patch fields on an API key; throws `KEY_NOT_FOUND` if it does not exist. */\nexport const update = mutation({\n args: {\n id: v.id(\"ApiKey\"),\n patch: v.object({\n name: v.optional(v.string()),\n scopes: v.optional(v.array(vApiKeyScope)),\n rateLimit: v.optional(vApiKeyRateLimit),\n revoked: v.optional(v.boolean()),\n }),\n },\n returns: v.null(),\n handler: async (ctx, { id: keyId, patch }) => {\n assertValidRateLimit(patch.rateLimit);\n const key = await ctx.db.get(\"ApiKey\", keyId);\n if (key === null) {\n throw new ConvexError({\n code: ErrorCode.KEY_NOT_FOUND,\n message: \"API key not found\",\n keyId,\n });\n }\n await ctx.db.patch(\"ApiKey\", keyId, patch);\n return null;\n },\n});\n\n/**\n * Atomically enforce an API key's rate limit and refresh `lastUsedAt`, folding\n * the read → check → decrement of the token bucket into a SINGLE transaction on\n * the `ApiKey` row.\n *\n * The facade previously split that read (`key.get`) from the decrement\n * (`key.update`) across two transactions, so two concurrent verifications could\n * both read the same starting `rateLimitState`, both decide \"not limited\", and\n * both persist a decrement of the *same* state — the true atomic decrement was\n * lost and a burst could slip past the limit. Performing the whole\n * check-and-decrement here closes that window: the row's transaction serializes\n * concurrent callers, so each consumes its own token.\n *\n * The verification decision is made from the same transactional snapshot as\n * the rate-limit update. In particular, the mutation re-checks deletion,\n * revocation, expiration, and the current scopes instead of trusting the\n * facade's earlier hash lookup. A successful result returns the current owner\n * and scopes so a concurrent scope reduction cannot authenticate with stale\n * permissions.\n *\n * Returns `{ status: \"limited\" }` WITHOUT writing when the bucket is empty\n * (mirroring the facade's prior behavior of throwing before its update on the\n * limited branch); otherwise decrements the bucket (when configured) and\n * refreshes `lastUsedAt`. The token bucket refills linearly over the configured\n * window (the same algorithm as sign-in rate limiting); the math is inlined\n * here because component code cannot import from the server bundle.\n *\n * `lastUsedAt` is coarsened by the optional `coarsenMs` window: it is only\n * rewritten when unset or older than the window, so read-only bearer traffic on\n * a hot key stays read-only (no per-request write). When a rate-limit decrement\n * forces a write anyway, a fresh `lastUsedAt` is simply left as-is. Omitting\n * `coarsenMs` (or passing `0`) disables coarsening and refreshes every call.\n */\nexport const recordUse = mutation({\n args: { id: v.id(\"ApiKey\"), coarsenMs: v.optional(v.number()) },\n returns: v.union(\n v.object({ status: v.literal(\"invalid\") }),\n v.object({ status: v.literal(\"revoked\") }),\n v.object({ status: v.literal(\"expired\") }),\n v.object({ status: v.literal(\"limited\") }),\n v.object({\n status: v.literal(\"verified\"),\n keyId: v.id(\"ApiKey\"),\n userId: v.id(\"User\"),\n scopes: v.array(vApiKeyScope),\n }),\n ),\n handler: async (ctx, { id: keyId, coarsenMs }) => {\n // Use the mutation's trusted transaction clock for validity and bucket math.\n const now = Date.now();\n const key = await ctx.db.get(\"ApiKey\", keyId);\n if (key === null) {\n return { status: \"invalid\" as const };\n }\n if (key.revoked) {\n return { status: \"revoked\" as const };\n }\n if (key.expiresAt !== undefined && key.expiresAt < now) {\n return { status: \"expired\" as const };\n }\n const patch: ApiKeyUsePatch = {};\n if (key.rateLimit) {\n const state = key.rateLimitState;\n if (!state) {\n patch.rateLimitState = {\n attemptsLeft: key.rateLimit.maxRequests - 1,\n lastAttemptTime: now,\n };\n } else {\n const elapsed = Math.max(0, now - state.lastAttemptTime);\n const refillRate = key.rateLimit.maxRequests / key.rateLimit.windowMs;\n const refilled = Math.min(\n key.rateLimit.maxRequests,\n state.attemptsLeft + elapsed * refillRate,\n );\n if (refilled < 1) {\n // Bucket empty: reject WITHOUT writing (matches the prior facade,\n // which threw before its single `update`, leaving state untouched).\n return { status: \"limited\" as const };\n }\n patch.rateLimitState = { attemptsLeft: refilled - 1, lastAttemptTime: now };\n }\n }\n // Coarsen the `lastUsedAt` touch: only stamp it when unset or older than the\n // caller's sampling window. With `coarsenMs` unset/0 the window is 0, so it\n // refreshes every call.\n const window = coarsenMs ?? 0;\n const lastUsedAt = typeof key.lastUsedAt === \"number\" ? key.lastUsedAt : 0;\n if (now - lastUsedAt >= window) {\n patch.lastUsedAt = now;\n }\n // Nothing to persist — no rate-limit decrement and `lastUsedAt` still fresh:\n // the hot path stays read-only.\n if (patch.rateLimitState === undefined && patch.lastUsedAt === undefined) {\n return {\n status: \"verified\" as const,\n keyId: key._id,\n userId: key.userId,\n scopes: key.scopes,\n };\n }\n await ctx.db.patch(\"ApiKey\", keyId, patch);\n return {\n status: \"verified\" as const,\n keyId: key._id,\n userId: key.userId,\n scopes: key.scopes,\n };\n },\n});\n\n/**\n * Atomically revoke an active API key and create its replacement.\n *\n * The caller pre-generates the replacement secret and passes only its hash and\n * display prefix. Reading the old row, validating its state, revoking it, and\n * inserting the replacement happen in one transaction, so concurrent rotations\n * cannot both mint a usable successor and an insert failure cannot strand the\n * caller with a revoked key and no replacement.\n */\nexport const rotate = mutation({\n args: {\n id: v.id(\"ApiKey\"),\n prefix: v.string(),\n hashedKey: v.string(),\n name: v.optional(v.string()),\n expiresAt: v.optional(v.number()),\n },\n returns: v.union(\n v.object({ status: v.literal(\"invalid\") }),\n v.object({ status: v.literal(\"revoked\") }),\n v.object({ status: v.literal(\"invalid_rate_limit\") }),\n v.object({\n status: v.literal(\"rotated\"),\n id: v.id(\"ApiKey\"),\n userId: v.id(\"User\"),\n name: v.string(),\n }),\n ),\n handler: async (ctx, args) => {\n const existing = await ctx.db.get(\"ApiKey\", args.id);\n if (existing === null) {\n return { status: \"invalid\" as const };\n }\n if (existing.revoked) {\n return { status: \"revoked\" as const };\n }\n if (existing.rateLimit !== undefined && !isValidRateLimit(existing.rateLimit)) {\n return { status: \"invalid_rate_limit\" as const };\n }\n\n const name = args.name ?? existing.name;\n await ctx.db.patch(\"ApiKey\", existing._id, { revoked: true });\n const id = await ctx.db.insert(\"ApiKey\", {\n userId: existing.userId,\n prefix: args.prefix,\n hashedKey: args.hashedKey,\n name,\n scopes: existing.scopes,\n rateLimit: existing.rateLimit,\n expiresAt: args.expiresAt,\n createdAt: Date.now(),\n revoked: false,\n extend: existing.extend,\n });\n return {\n status: \"rotated\" as const,\n id,\n userId: existing.userId,\n name,\n };\n },\n});\n\n/** Delete an API key; throws `KEY_NOT_FOUND` if it does not exist. */\nconst remove = mutation({\n args: { id: v.id(\"ApiKey\") },\n returns: v.null(),\n handler: async (ctx, { id: keyId }) => {\n const key = await ctx.db.get(\"ApiKey\", keyId);\n if (key === null) {\n throw new ConvexError({\n code: ErrorCode.KEY_NOT_FOUND,\n message: \"API key not found\",\n keyId,\n });\n }\n await ctx.db.delete(\"ApiKey\", keyId);\n return null;\n },\n});\n\nexport { remove };\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
- import * as convex_values2654 from "convex/values";
2
- import * as convex_server141 from "convex/server";
1
+ import * as convex_values1665 from "convex/values";
2
+ import * as convex_server115 from "convex/server";
3
3
 
4
4
  //#region src/component/user.d.ts
5
5
  declare namespace user_d_exports {
@@ -18,7 +18,7 @@ declare namespace user_d_exports {
18
18
  * selector:
19
19
  *
20
20
  * - `{ id }` → `Doc<"User"> | null`
21
- * - `{ ids }` → `(Doc<"User"> | null)[]` (order preserved, deduped)
21
+ * - `{ ids }` → `(Doc<"User"> | null)[]` (order preserved, deduped; max 100 IDs)
22
22
  * - `{ verifiedEmail }`→ `Doc<"User"> | null` (exactly-one-or-null)
23
23
  * - `{ verifiedPhone }`→ `Doc<"User"> | null` (exactly-one-or-null)
24
24
  *
@@ -29,50 +29,52 @@ declare namespace user_d_exports {
29
29
  * await ctx.runQuery(component.user.get, { verifiedEmail: "a@b.com" });
30
30
  * ```
31
31
  */
32
- declare const get: convex_server141.RegisteredQuery<"public", {
32
+ declare const get: convex_server115.RegisteredQuery<"public", {
33
+ id?: convex_values1665.GenericId<"User"> | undefined;
33
34
  verifiedEmail?: string | undefined;
34
- id?: convex_values2654.GenericId<"User"> | undefined;
35
- ids?: convex_values2654.GenericId<"User">[] | undefined;
35
+ ids?: convex_values1665.GenericId<"User">[] | undefined;
36
36
  verifiedPhone?: string | undefined;
37
37
  }, Promise<{
38
- _id: convex_values2654.GenericId<"User">;
38
+ _id: convex_values1665.GenericId<"User">;
39
39
  _creationTime: number;
40
- name?: string | undefined;
41
40
  email?: string | undefined;
42
- image?: string | undefined;
43
41
  phone?: string | undefined;
42
+ extend?: any;
43
+ name?: string | undefined;
44
44
  firstName?: string | undefined;
45
45
  lastName?: string | undefined;
46
+ image?: string | undefined;
46
47
  emailVerificationTime?: number | undefined;
47
48
  phoneVerificationTime?: number | undefined;
48
49
  isAnonymous?: boolean | undefined;
49
- lastActiveGroup?: convex_values2654.GenericId<"Group"> | undefined;
50
- extend?: any;
50
+ lastActiveGroup?: convex_values1665.GenericId<"Group"> | undefined;
51
+ sessionEpoch: number;
51
52
  } | ({
52
- _id: convex_values2654.GenericId<"User">;
53
+ _id: convex_values1665.GenericId<"User">;
53
54
  _creationTime: number;
54
- name?: string | undefined;
55
55
  email?: string | undefined;
56
- image?: string | undefined;
57
56
  phone?: string | undefined;
57
+ extend?: any;
58
+ name?: string | undefined;
58
59
  firstName?: string | undefined;
59
60
  lastName?: string | undefined;
61
+ image?: string | undefined;
60
62
  emailVerificationTime?: number | undefined;
61
63
  phoneVerificationTime?: number | undefined;
62
64
  isAnonymous?: boolean | undefined;
63
- lastActiveGroup?: convex_values2654.GenericId<"Group"> | undefined;
64
- extend?: any;
65
+ lastActiveGroup?: convex_values1665.GenericId<"Group"> | undefined;
66
+ sessionEpoch: number;
65
67
  } | null)[] | null>>;
66
68
  /** List users, paginated, with optional `where` filters and ordering. */
67
- declare const list: convex_server141.RegisteredQuery<"public", {
68
- order?: "asc" | "desc" | undefined;
69
+ declare const list: convex_server115.RegisteredQuery<"public", {
69
70
  where?: {
70
- name?: string | undefined;
71
71
  email?: string | undefined;
72
72
  phone?: string | undefined;
73
+ name?: string | undefined;
73
74
  isAnonymous?: boolean | undefined;
74
75
  } | undefined;
75
- orderBy?: "name" | "email" | "_creationTime" | "phone" | undefined;
76
+ orderBy?: "_creationTime" | "email" | "phone" | "name" | undefined;
77
+ order?: "asc" | "desc" | undefined;
76
78
  paginationOpts: {
77
79
  id?: number;
78
80
  endCursor?: string | null;
@@ -81,74 +83,76 @@ declare const list: convex_server141.RegisteredQuery<"public", {
81
83
  numItems: number;
82
84
  cursor: string | null;
83
85
  };
84
- }, Promise<convex_server141.PaginationResult<{
85
- _id: convex_values2654.GenericId<"User">;
86
+ }, Promise<convex_server115.PaginationResult<{
87
+ _id: convex_values1665.GenericId<"User">;
86
88
  _creationTime: number;
87
- name?: string | undefined;
88
89
  email?: string | undefined;
89
- image?: string | undefined;
90
90
  phone?: string | undefined;
91
+ extend?: any;
92
+ name?: string | undefined;
91
93
  firstName?: string | undefined;
92
94
  lastName?: string | undefined;
95
+ image?: string | undefined;
93
96
  emailVerificationTime?: number | undefined;
94
97
  phoneVerificationTime?: number | undefined;
95
98
  isAnonymous?: boolean | undefined;
96
- lastActiveGroup?: convex_values2654.GenericId<"Group"> | undefined;
97
- extend?: any;
99
+ lastActiveGroup?: convex_values1665.GenericId<"Group"> | undefined;
100
+ sessionEpoch: number;
98
101
  }>>>;
99
102
  /** Insert a new user. */
100
- declare const create: convex_server141.RegisteredMutation<"public", {
103
+ declare const create: convex_server115.RegisteredMutation<"public", {
101
104
  data: {
102
- name?: string | undefined;
103
105
  email?: string | undefined;
104
- image?: string | undefined;
105
106
  phone?: string | undefined;
107
+ extend?: any;
108
+ name?: string | undefined;
106
109
  firstName?: string | undefined;
107
110
  lastName?: string | undefined;
111
+ image?: string | undefined;
108
112
  emailVerificationTime?: number | undefined;
109
113
  phoneVerificationTime?: number | undefined;
110
114
  isAnonymous?: boolean | undefined;
111
- lastActiveGroup?: convex_values2654.GenericId<"Group"> | undefined;
112
- extend?: any;
115
+ lastActiveGroup?: convex_values1665.GenericId<"Group"> | undefined;
113
116
  };
114
- }, Promise<convex_values2654.GenericId<"User">>>;
117
+ }, Promise<convex_values1665.GenericId<"User">>>;
115
118
  /** Insert a user, or patch it when `id` is supplied. Returns the user id. */
116
- declare const upsert: convex_server141.RegisteredMutation<"public", {
117
- id?: convex_values2654.GenericId<"User"> | undefined;
119
+ declare const upsert: convex_server115.RegisteredMutation<"public", {
120
+ id?: convex_values1665.GenericId<"User"> | undefined;
118
121
  data: {
119
- name?: string | undefined;
120
122
  email?: string | undefined;
121
- image?: string | undefined;
122
123
  phone?: string | undefined;
124
+ extend?: any;
125
+ name?: string | undefined;
123
126
  firstName?: string | undefined;
124
127
  lastName?: string | undefined;
128
+ image?: string | undefined;
125
129
  emailVerificationTime?: number | undefined;
126
130
  phoneVerificationTime?: number | undefined;
127
131
  isAnonymous?: boolean | undefined;
128
- lastActiveGroup?: convex_values2654.GenericId<"Group"> | undefined;
129
- extend?: any;
132
+ lastActiveGroup?: convex_values1665.GenericId<"Group"> | undefined;
130
133
  };
131
- }, Promise<convex_values2654.GenericId<"User">>>;
134
+ }, Promise<convex_values1665.GenericId<"User">>>;
132
135
  /** Patch fields on a user. */
133
- declare const update: convex_server141.RegisteredMutation<"public", {
134
- id: convex_values2654.GenericId<"User">;
136
+ declare const update: convex_server115.RegisteredMutation<"public", {
137
+ id: convex_values1665.GenericId<"User">;
135
138
  patch: {
136
- name?: string | undefined;
137
139
  email?: string | undefined;
138
- image?: string | undefined;
139
140
  phone?: string | undefined;
141
+ extend?: any;
142
+ name?: string | undefined;
140
143
  firstName?: string | undefined;
141
144
  lastName?: string | undefined;
145
+ image?: string | undefined;
142
146
  emailVerificationTime?: number | undefined;
143
147
  phoneVerificationTime?: number | undefined;
144
148
  isAnonymous?: boolean | undefined;
145
- lastActiveGroup?: convex_values2654.GenericId<"Group"> | undefined;
146
- extend?: any;
149
+ lastActiveGroup?: convex_values1665.GenericId<"Group"> | undefined;
150
+ sessionEpoch?: number | undefined;
147
151
  };
148
152
  }, Promise<null>>;
149
- /** Delete a user and all auth-owned child rows, bounded per table. */
150
- declare const remove: convex_server141.RegisteredMutation<"public", {
151
- id: convex_values2654.GenericId<"User">;
153
+ /** Delete a user and every auth-owned credential row in one bounded transaction. */
154
+ declare const remove: convex_server115.RegisteredMutation<"public", {
155
+ id: convex_values1665.GenericId<"User">;
152
156
  }, Promise<null>>;
153
157
  //#endregion
154
158
  export { user_d_exports };
@@ -1 +1 @@
1
- {"version":3,"file":"user.d.ts","names":[],"sources":["../../src/component/user.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;AA2CA;;;;;;;;;;;cAAa,GAAA,mBAAG,eAAA;;OAqCd,iBAAA,CAAA,SAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAGW,IAAA,mBAAI,eAAA;;;;;;;;;;;;;;;;;;OAsDf,iBAAA,CAAA,SAAA;;;;;;;;;;;;;;;cAGW,MAAA,mBAAM,kBAAA;;;;;;;;;;;sBAMjB,iBAAA,CAAA,SAAA;;;;;cAGW,MAAA,mBAAM,kBAAA;OAUjB,iBAAA,CAAA,SAAA;;;;;;;;;;;;;;;;cAGW,MAAA,mBAAM,kBAAA;MAOjB,iBAAA,CAAA,SAAA;;;;;;;;;;;;;;;;cAGI,MAAA,mBAAM,kBAAA;MAwFV,iBAAA,CAAA,SAAA;AAAA"}
1
+ {"version":3,"file":"user.d.ts","names":[],"sources":["../../src/component/user.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;AA2DA;;;;;;;;;;;cAAa,GAAA,mBAAG,eAAA;OAsCd,iBAAA,CAAA,SAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAGW,IAAA,mBAAI,eAAA;;;;;;;;;;;;;;;;;;OAsDf,iBAAA,CAAA,SAAA;;;;;;;;;;;;;;;;cAGW,MAAA,mBAAM,kBAAA;;;;;;;;;;;;sBAMjB,iBAAA,CAAA,SAAA;EAAA;AAAA;;cAGW,MAAA,mBAAM,kBAAA;OAUjB,iBAAA,CAAA,SAAA;;;;;;;;;;;;;;;;cAGW,MAAA,mBAAM,kBAAA;MAOjB,iBAAA,CAAA,SAAA;;;;;;;;;;;;;;;;;cAGI,MAAA,mBAAM,kBAAA;MA8RV,iBAAA,CAAA,SAAA;AAAA"}
@@ -1,11 +1,11 @@
1
1
  import { ErrorCode } from "../shared/codes.js";
2
- import { vPaginated, vUserDoc } from "./model.js";
2
+ import { vPaginated } from "./model.js";
3
3
  import schema_default from "./schema.js";
4
4
  import { mutation, query } from "./_generated/server.js";
5
- import "./functions.js";
5
+ import { vUserDoc } from "./documents.js";
6
+ import { assertBatchSelectorSize } from "./batch.js";
6
7
  import { ConvexError, v } from "convex/values";
7
8
  import { paginationOptsValidator } from "convex/server";
8
- import { partial } from "convex-helpers/validators";
9
9
  import { stream } from "convex-helpers/server/stream";
10
10
 
11
11
  //#region src/component/user.ts
@@ -16,17 +16,28 @@ import { stream } from "convex-helpers/server/stream";
16
16
  *
17
17
  * @module
18
18
  */
19
- const vUserInsertData = v.object(schema_default.tables.User.validator.fields);
20
- const vUserPatchData = v.object(partial(schema_default.tables.User.validator.fields));
21
- const CASCADE_MAX = 1e3;
22
- const tooMany = (count) => count > CASCADE_MAX;
19
+ const vUserInsertData = schema_default.tables.User.validator.omit("sessionEpoch");
20
+ const vUserPatchData = schema_default.tables.User.validator.partial();
21
+ /**
22
+ * Keep one complete user cascade within a single safe transaction.
23
+ */
24
+ const CASCADE_MAX = 100;
25
+ function assertCascadeSize(count) {
26
+ if (count > CASCADE_MAX) throw new ConvexError({
27
+ code: ErrorCode.CASCADE_TOO_LARGE,
28
+ message: `User cascade has more than ${CASCADE_MAX} dependent rows; delete child rows in bounded batches, then retry.`
29
+ });
30
+ }
31
+ function uniqueRows(rows) {
32
+ return Array.from(new Map(rows.map((row) => [row._id, row])).values());
33
+ }
23
34
  /**
24
35
  * Read a user by identity. One overloaded function (single Convex
25
36
  * function with a unioned `args`/`returns`). Accepts exactly one
26
37
  * selector:
27
38
  *
28
39
  * - `{ id }` → `Doc<"User"> | null`
29
- * - `{ ids }` → `(Doc<"User"> | null)[]` (order preserved, deduped)
40
+ * - `{ ids }` → `(Doc<"User"> | null)[]` (order preserved, deduped; max 100 IDs)
30
41
  * - `{ verifiedEmail }`→ `Doc<"User"> | null` (exactly-one-or-null)
31
42
  * - `{ verifiedPhone }`→ `Doc<"User"> | null` (exactly-one-or-null)
32
43
  *
@@ -39,14 +50,15 @@ const tooMany = (count) => count > CASCADE_MAX;
39
50
  */
40
51
  const get = query({
41
52
  args: {
42
- id: v.optional(v.id("User")),
43
- ids: v.optional(v.array(v.id("User"))),
53
+ id: v.optional(schema_default.id("User")),
54
+ ids: v.optional(v.array(schema_default.id("User"))),
44
55
  verifiedEmail: v.optional(v.string()),
45
56
  verifiedPhone: v.optional(v.string())
46
57
  },
47
58
  returns: v.union(vUserDoc, v.null(), v.array(v.union(vUserDoc, v.null()))),
48
59
  handler: async (ctx, args) => {
49
60
  if (args.ids !== void 0) {
61
+ assertBatchSelectorSize(args.ids, "ids");
50
62
  if (args.ids.length === 0) return [];
51
63
  const unique = Array.from(new Set(args.ids));
52
64
  const docs = await Promise.all(unique.map((id) => ctx.db.get("User", id)));
@@ -94,30 +106,36 @@ const list = query({
94
106
  /** Insert a new user. */
95
107
  const create = mutation({
96
108
  args: { data: vUserInsertData },
97
- returns: v.id("User"),
109
+ returns: schema_default.id("User"),
98
110
  handler: async (ctx, { data }) => {
99
- return await ctx.db.insert("User", data);
111
+ return await ctx.db.insert("User", {
112
+ ...data,
113
+ sessionEpoch: 0
114
+ });
100
115
  }
101
116
  });
102
117
  /** Insert a user, or patch it when `id` is supplied. Returns the user id. */
103
118
  const upsert = mutation({
104
119
  args: {
105
- id: v.optional(v.id("User")),
120
+ id: v.optional(schema_default.id("User")),
106
121
  data: vUserInsertData
107
122
  },
108
- returns: v.id("User"),
123
+ returns: schema_default.id("User"),
109
124
  handler: async (ctx, { id, data }) => {
110
125
  if (id !== void 0) {
111
126
  await ctx.db.patch("User", id, data);
112
127
  return id;
113
128
  }
114
- return await ctx.db.insert("User", data);
129
+ return await ctx.db.insert("User", {
130
+ ...data,
131
+ sessionEpoch: 0
132
+ });
115
133
  }
116
134
  });
117
135
  /** Patch fields on a user. */
118
136
  const update = mutation({
119
137
  args: {
120
- id: v.id("User"),
138
+ id: schema_default.id("User"),
121
139
  patch: vUserPatchData
122
140
  },
123
141
  returns: v.null(),
@@ -126,29 +144,122 @@ const update = mutation({
126
144
  return null;
127
145
  }
128
146
  });
129
- /** Delete a user and all auth-owned child rows, bounded per table. */
147
+ /** Delete a user and every auth-owned credential row in one bounded transaction. */
130
148
  const remove = mutation({
131
- args: { id: v.id("User") },
149
+ args: { id: schema_default.id("User") },
132
150
  returns: v.null(),
133
151
  handler: async (ctx, { id: userId }) => {
134
152
  if (await ctx.db.get("User", userId) === null) return null;
135
- const [sessions, accounts, keys, members, passkeys, totps] = await Promise.all([
153
+ const [sessions, accounts, keys, members, passkeys, totps, emails, devicesByUser, scimIdentities, continuations, oauthCodesByUser, oauthGrantsByUser, oauthClients, invitesByAuthor, invitesByAcceptor, webhooks] = await Promise.all([
136
154
  ctx.db.query("Session").withIndex("user_id", (q) => q.eq("userId", userId)).take(CASCADE_MAX + 1),
137
155
  ctx.db.query("Account").withIndex("user_id_provider", (q) => q.eq("userId", userId)).take(CASCADE_MAX + 1),
138
156
  ctx.db.query("ApiKey").withIndex("user_id", (q) => q.eq("userId", userId)).take(CASCADE_MAX + 1),
139
157
  ctx.db.query("GroupMember").withIndex("user_id", (q) => q.eq("userId", userId)).take(CASCADE_MAX + 1),
140
158
  ctx.db.query("Passkey").withIndex("user_id", (q) => q.eq("userId", userId)).take(CASCADE_MAX + 1),
141
- ctx.db.query("TotpFactor").withIndex("user_id", (q) => q.eq("userId", userId)).take(CASCADE_MAX + 1)
159
+ ctx.db.query("TotpFactor").withIndex("user_id", (q) => q.eq("userId", userId)).take(CASCADE_MAX + 1),
160
+ ctx.db.query("UserEmail").withIndex("user_id", (q) => q.eq("userId", userId)).take(CASCADE_MAX + 1),
161
+ ctx.db.query("DeviceCode").withIndex("user_id", (q) => q.eq("userId", userId)).take(CASCADE_MAX + 1),
162
+ ctx.db.query("GroupConnectionScimIdentity").withIndex("user_id", (q) => q.eq("userId", userId)).take(CASCADE_MAX + 1),
163
+ ctx.db.query("AuthContinuation").withIndex("user_id_provider_operation_expiration_time", (q) => q.eq("subject.userId", userId)).take(CASCADE_MAX + 1),
164
+ ctx.db.query("OAuthCode").withIndex("user_id", (q) => q.eq("userId", userId)).take(CASCADE_MAX + 1),
165
+ ctx.db.query("OAuthRefreshGrant").withIndex("user_id", (q) => q.eq("userId", userId)).take(CASCADE_MAX + 1),
166
+ ctx.db.query("OAuthClient").withIndex("created_by", (q) => q.eq("createdBy", userId)).take(CASCADE_MAX + 1),
167
+ ctx.db.query("GroupInvite").withIndex("invited_by_user_id_status", (q) => q.eq("invitedByUserId", userId)).take(CASCADE_MAX + 1),
168
+ ctx.db.query("GroupInvite").withIndex("accepted_by_user_id", (q) => q.eq("acceptedByUserId", userId)).take(CASCADE_MAX + 1),
169
+ ctx.db.query("GroupWebhookEndpoint").withIndex("created_by_user_id", (q) => q.eq("createdByUserId", userId)).take(CASCADE_MAX + 1)
142
170
  ]);
143
- if (tooMany(sessions.length) || tooMany(accounts.length) || tooMany(keys.length) || tooMany(members.length) || tooMany(passkeys.length) || tooMany(totps.length)) throw new ConvexError({
144
- code: ErrorCode.CASCADE_TOO_LARGE,
145
- message: `User has more than ${CASCADE_MAX} child rows in one or more tables; cascade delete is not safe in a single mutation. Delete child rows in bounded batches, then retry.`
146
- });
147
- const refreshTokens = sessions.length > 0 ? (await Promise.all(sessions.map((s) => ctx.db.query("RefreshToken").withIndex("session_id", (q) => q.eq("sessionId", s._id)).take(CASCADE_MAX + 1)))).flat() : [];
148
- if (tooMany(refreshTokens.length)) throw new ConvexError({
149
- code: ErrorCode.CASCADE_TOO_LARGE,
150
- message: `User has more than ${CASCADE_MAX} refresh tokens across sessions; cascade delete is not safe in a single mutation.`
151
- });
171
+ assertCascadeSize([
172
+ ...sessions,
173
+ ...accounts,
174
+ ...keys,
175
+ ...members,
176
+ ...passkeys,
177
+ ...totps,
178
+ ...emails,
179
+ ...devicesByUser,
180
+ ...scimIdentities,
181
+ ...continuations,
182
+ ...oauthCodesByUser,
183
+ ...oauthGrantsByUser,
184
+ ...oauthClients,
185
+ ...invitesByAuthor,
186
+ ...invitesByAcceptor,
187
+ ...webhooks
188
+ ].length);
189
+ let remaining = CASCADE_MAX - [
190
+ ...sessions,
191
+ ...accounts,
192
+ ...keys,
193
+ ...members,
194
+ ...passkeys,
195
+ ...totps,
196
+ ...emails,
197
+ ...devicesByUser,
198
+ ...scimIdentities,
199
+ ...continuations,
200
+ ...oauthCodesByUser,
201
+ ...oauthGrantsByUser,
202
+ ...oauthClients,
203
+ ...invitesByAuthor,
204
+ ...invitesByAcceptor,
205
+ ...webhooks
206
+ ].length;
207
+ async function collect(read) {
208
+ const rows = await read(remaining + 1);
209
+ if (rows.length > remaining) assertCascadeSize(CASCADE_MAX + 1);
210
+ remaining -= rows.length;
211
+ return rows;
212
+ }
213
+ const refreshTokens = [];
214
+ const devicesBySession = [];
215
+ const verificationCodes = [];
216
+ const resetsByAccount = [];
217
+ const resetsByContinuation = [];
218
+ const verifiersBySession = [];
219
+ const verifiersByContinuation = [];
220
+ for (const session of sessions) {
221
+ refreshTokens.push(...await collect((limit) => ctx.db.query("RefreshToken").withIndex("session_id", (q) => q.eq("sessionId", session._id)).take(limit)));
222
+ devicesBySession.push(...await collect((limit) => ctx.db.query("DeviceCode").withIndex("session_id", (q) => q.eq("sessionId", session._id)).take(limit)));
223
+ verifiersBySession.push(...await collect((limit) => ctx.db.query("AuthVerifier").withIndex("session_id", (q) => q.eq("sessionId", session._id)).take(limit)));
224
+ }
225
+ for (const account of accounts) {
226
+ verificationCodes.push(...await collect((limit) => ctx.db.query("VerificationCode").withIndex("account_id", (q) => q.eq("accountId", account._id)).take(limit)));
227
+ resetsByAccount.push(...await collect((limit) => ctx.db.query("PasswordReset").withIndex("account_id", (q) => q.eq("accountId", account._id)).take(limit)));
228
+ }
229
+ for (const continuation of continuations) {
230
+ resetsByContinuation.push(...await collect((limit) => ctx.db.query("PasswordReset").withIndex("continuation_id", (q) => q.eq("continuationId", continuation._id)).take(limit)));
231
+ verifiersByContinuation.push(...await collect((limit) => ctx.db.query("AuthVerifier").withIndex("continuation_id", (q) => q.eq("continuationId", continuation._id)).take(limit)));
232
+ }
233
+ const grants = oauthGrantsByUser;
234
+ const oauthTokens = [];
235
+ for (const grant of grants) oauthTokens.push(...await collect((limit) => ctx.db.query("OAuthRefreshToken").withIndex("grant_id", (q) => q.eq("grantId", grant._id)).take(limit)));
236
+ const devices = uniqueRows([...devicesByUser, ...devicesBySession]);
237
+ const resets = uniqueRows([...resetsByAccount, ...resetsByContinuation]);
238
+ const verifiers = uniqueRows([...verifiersBySession, ...verifiersByContinuation]);
239
+ const oauthCodes = oauthCodesByUser;
240
+ const invites = uniqueRows([...invitesByAuthor, ...invitesByAcceptor]);
241
+ assertCascadeSize([
242
+ ...sessions,
243
+ ...accounts,
244
+ ...keys,
245
+ ...members,
246
+ ...passkeys,
247
+ ...totps,
248
+ ...emails,
249
+ ...devices,
250
+ ...scimIdentities,
251
+ ...continuations,
252
+ ...oauthCodes,
253
+ ...grants,
254
+ ...oauthClients,
255
+ ...invites,
256
+ ...webhooks,
257
+ ...refreshTokens,
258
+ ...verificationCodes,
259
+ ...resets,
260
+ ...verifiers,
261
+ ...oauthTokens
262
+ ].length);
152
263
  await Promise.all([
153
264
  ...sessions.map((s) => ctx.db.delete("Session", s._id)),
154
265
  ...refreshTokens.map((r) => ctx.db.delete("RefreshToken", r._id)),
@@ -156,14 +267,24 @@ const remove = mutation({
156
267
  ...keys.map((k) => ctx.db.delete("ApiKey", k._id)),
157
268
  ...members.map((m) => ctx.db.delete("GroupMember", m._id)),
158
269
  ...passkeys.map((p) => ctx.db.delete("Passkey", p._id)),
159
- ...totps.map((t) => ctx.db.delete("TotpFactor", t._id))
270
+ ...totps.map((t) => ctx.db.delete("TotpFactor", t._id)),
271
+ ...emails.map((email) => ctx.db.delete("UserEmail", email._id)),
272
+ ...devices.map((device) => ctx.db.delete("DeviceCode", device._id)),
273
+ ...scimIdentities.map((identity) => ctx.db.delete("GroupConnectionScimIdentity", identity._id)),
274
+ ...continuations.map((continuation) => ctx.db.delete("AuthContinuation", continuation._id)),
275
+ ...oauthCodes.map((code) => ctx.db.delete("OAuthCode", code._id)),
276
+ ...grants.map((grant) => ctx.db.delete("OAuthRefreshGrant", grant._id)),
277
+ ...oauthTokens.map((token) => ctx.db.delete("OAuthRefreshToken", token._id)),
278
+ ...verificationCodes.map((code) => ctx.db.delete("VerificationCode", code._id)),
279
+ ...resets.map((reset) => ctx.db.delete("PasswordReset", reset._id)),
280
+ ...verifiers.map((verifier) => ctx.db.delete("AuthVerifier", verifier._id)),
281
+ ...oauthClients.map((client) => ctx.db.patch("OAuthClient", client._id, { createdBy: void 0 })),
282
+ ...invites.map((invite) => ctx.db.patch("GroupInvite", invite._id, {
283
+ invitedByUserId: invite.invitedByUserId === userId ? void 0 : invite.invitedByUserId,
284
+ acceptedByUserId: invite.acceptedByUserId === userId ? void 0 : invite.acceptedByUserId
285
+ })),
286
+ ...webhooks.map((webhook) => ctx.db.patch("GroupWebhookEndpoint", webhook._id, { createdByUserId: void 0 }))
160
287
  ]);
161
- const ownedEmails = await ctx.db.query("UserEmail").withIndex("user_id", (q) => q.eq("userId", userId)).take(CASCADE_MAX + 1);
162
- if (tooMany(ownedEmails.length)) throw new ConvexError({
163
- code: ErrorCode.CASCADE_TOO_LARGE,
164
- message: `User has more than ${CASCADE_MAX} emails; cascade delete is not safe in a single mutation.`
165
- });
166
- await Promise.all(ownedEmails.map((e) => ctx.db.delete("UserEmail", e._id)));
167
288
  await ctx.db.delete("User", userId);
168
289
  return null;
169
290
  }