@effect-auth/core 0.1.0-alpha.16 → 0.1.0-alpha.18

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 (607) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +308 -133
  3. package/dist/AbuseProtection.d.ts +100 -0
  4. package/dist/AbuseProtection.d.ts.map +1 -0
  5. package/dist/AbuseProtection.js +71 -0
  6. package/dist/AbuseProtection.js.map +1 -0
  7. package/dist/AccessGrant.d.ts +344 -0
  8. package/dist/AccessGrant.d.ts.map +1 -0
  9. package/dist/AccessGrant.js +687 -0
  10. package/dist/AccessGrant.js.map +1 -0
  11. package/dist/ApiKey.d.ts +0 -12
  12. package/dist/ApiKey.d.ts.map +1 -1
  13. package/dist/ApiKey.js +36 -27
  14. package/dist/ApiKey.js.map +1 -1
  15. package/dist/Assurance.d.ts +415 -0
  16. package/dist/Assurance.d.ts.map +1 -0
  17. package/dist/Assurance.js +764 -0
  18. package/dist/Assurance.js.map +1 -0
  19. package/dist/AtomicPlanExecutor.d.ts +28 -0
  20. package/dist/AtomicPlanExecutor.d.ts.map +1 -0
  21. package/dist/AtomicPlanExecutor.js +5 -0
  22. package/dist/AtomicPlanExecutor.js.map +1 -0
  23. package/dist/AuditLog.d.ts +2314 -52
  24. package/dist/AuditLog.d.ts.map +1 -1
  25. package/dist/AuditLog.js +511 -33
  26. package/dist/AuditLog.js.map +1 -1
  27. package/dist/AuthFlow.d.ts +128 -38
  28. package/dist/AuthFlow.d.ts.map +1 -1
  29. package/dist/AuthFlow.js +611 -263
  30. package/dist/AuthFlow.js.map +1 -1
  31. package/dist/AuthRateLimit.d.ts +96 -0
  32. package/dist/AuthRateLimit.d.ts.map +1 -0
  33. package/dist/AuthRateLimit.js +212 -0
  34. package/dist/AuthRateLimit.js.map +1 -0
  35. package/dist/Challenge.d.ts +0 -3
  36. package/dist/Challenge.d.ts.map +1 -1
  37. package/dist/Challenge.js +1 -4
  38. package/dist/Challenge.js.map +1 -1
  39. package/dist/Client.d.ts +205 -10
  40. package/dist/Client.d.ts.map +1 -1
  41. package/dist/Client.js +397 -16
  42. package/dist/Client.js.map +1 -1
  43. package/dist/CloudflareTurnstile.d.ts +28 -0
  44. package/dist/CloudflareTurnstile.d.ts.map +1 -0
  45. package/dist/CloudflareTurnstile.js +205 -0
  46. package/dist/CloudflareTurnstile.js.map +1 -0
  47. package/dist/Crypto.d.ts +0 -3
  48. package/dist/Crypto.d.ts.map +1 -1
  49. package/dist/Crypto.js +1 -4
  50. package/dist/Crypto.js.map +1 -1
  51. package/dist/DelegatedAccessStorageMigrations.d.ts +7 -0
  52. package/dist/DelegatedAccessStorageMigrations.d.ts.map +1 -0
  53. package/dist/DelegatedAccessStorageMigrations.js +159 -0
  54. package/dist/DelegatedAccessStorageMigrations.js.map +1 -0
  55. package/dist/DisposableEmailDomains.d.ts +19 -0
  56. package/dist/DisposableEmailDomains.d.ts.map +1 -0
  57. package/dist/DisposableEmailDomains.js +85 -0
  58. package/dist/DisposableEmailDomains.js.map +1 -0
  59. package/dist/DomainVerification.d.ts +0 -9
  60. package/dist/DomainVerification.d.ts.map +1 -1
  61. package/dist/DomainVerification.js +36 -22
  62. package/dist/DomainVerification.js.map +1 -1
  63. package/dist/DrizzleBunSqliteStorage.d.ts +18 -0
  64. package/dist/DrizzleBunSqliteStorage.d.ts.map +1 -0
  65. package/dist/DrizzleBunSqliteStorage.js +11 -0
  66. package/dist/DrizzleBunSqliteStorage.js.map +1 -0
  67. package/dist/DrizzleD1SqliteStorage.d.ts +4 -4
  68. package/dist/DrizzleD1SqliteStorage.d.ts.map +1 -1
  69. package/dist/DrizzleD1SqliteStorage.js +5 -5
  70. package/dist/DrizzleD1SqliteStorage.js.map +1 -1
  71. package/dist/DrizzleDurableObjectSqliteStorage.d.ts +19 -0
  72. package/dist/DrizzleDurableObjectSqliteStorage.d.ts.map +1 -0
  73. package/dist/DrizzleDurableObjectSqliteStorage.js +14 -0
  74. package/dist/DrizzleDurableObjectSqliteStorage.js.map +1 -0
  75. package/dist/DrizzleEffectSqliteStorage.d.ts +13 -4
  76. package/dist/DrizzleEffectSqliteStorage.d.ts.map +1 -1
  77. package/dist/DrizzleEffectSqliteStorage.js +71 -4
  78. package/dist/DrizzleEffectSqliteStorage.js.map +1 -1
  79. package/dist/DrizzleNodeSqliteStorage.d.ts +18 -0
  80. package/dist/DrizzleNodeSqliteStorage.d.ts.map +1 -0
  81. package/dist/DrizzleNodeSqliteStorage.js +11 -0
  82. package/dist/DrizzleNodeSqliteStorage.js.map +1 -0
  83. package/dist/DrizzleWasmSqliteStorage.d.ts +23 -0
  84. package/dist/DrizzleWasmSqliteStorage.d.ts.map +1 -0
  85. package/dist/DrizzleWasmSqliteStorage.js +15 -0
  86. package/dist/DrizzleWasmSqliteStorage.js.map +1 -0
  87. package/dist/EffectQbPostgresDelegatedAccessStorage.d.ts +34 -0
  88. package/dist/EffectQbPostgresDelegatedAccessStorage.d.ts.map +1 -0
  89. package/dist/EffectQbPostgresDelegatedAccessStorage.js +499 -0
  90. package/dist/EffectQbPostgresDelegatedAccessStorage.js.map +1 -0
  91. package/dist/EffectQbPostgresStorage.d.ts +171 -0
  92. package/dist/EffectQbPostgresStorage.d.ts.map +1 -0
  93. package/dist/EffectQbPostgresStorage.js +4280 -0
  94. package/dist/EffectQbPostgresStorage.js.map +1 -0
  95. package/dist/EffectQbSqliteDelegatedAccessStorage.d.ts +112 -0
  96. package/dist/EffectQbSqliteDelegatedAccessStorage.d.ts.map +1 -0
  97. package/dist/EffectQbSqliteDelegatedAccessStorage.js +602 -0
  98. package/dist/EffectQbSqliteDelegatedAccessStorage.js.map +1 -0
  99. package/dist/EffectQbSqliteStorage.d.ts +181 -23
  100. package/dist/EffectQbSqliteStorage.d.ts.map +1 -1
  101. package/dist/EffectQbSqliteStorage.js +1728 -135
  102. package/dist/EffectQbSqliteStorage.js.map +1 -1
  103. package/dist/EmailAuth.d.ts +65 -0
  104. package/dist/EmailAuth.d.ts.map +1 -0
  105. package/dist/EmailAuth.js +171 -0
  106. package/dist/EmailAuth.js.map +1 -0
  107. package/dist/EmailIdentityAuth.d.ts +30 -0
  108. package/dist/EmailIdentityAuth.d.ts.map +1 -0
  109. package/dist/EmailIdentityAuth.js +142 -0
  110. package/dist/EmailIdentityAuth.js.map +1 -0
  111. package/dist/EmailOtp.d.ts +10 -11
  112. package/dist/EmailOtp.d.ts.map +1 -1
  113. package/dist/EmailOtp.js +44 -81
  114. package/dist/EmailOtp.js.map +1 -1
  115. package/dist/EmailRisk.d.ts +48 -0
  116. package/dist/EmailRisk.d.ts.map +1 -0
  117. package/dist/EmailRisk.js +33 -0
  118. package/dist/EmailRisk.js.map +1 -0
  119. package/dist/EmailVerification.d.ts +10 -16
  120. package/dist/EmailVerification.d.ts.map +1 -1
  121. package/dist/EmailVerification.js +85 -42
  122. package/dist/EmailVerification.js.map +1 -1
  123. package/dist/Guard.d.ts +11 -0
  124. package/dist/Guard.d.ts.map +1 -0
  125. package/dist/Guard.js +11 -0
  126. package/dist/Guard.js.map +1 -0
  127. package/dist/HibpPwnedPasswords.d.ts +56 -0
  128. package/dist/HibpPwnedPasswords.d.ts.map +1 -0
  129. package/dist/HibpPwnedPasswords.js +232 -0
  130. package/dist/HibpPwnedPasswords.js.map +1 -0
  131. package/dist/HttpApi/AdminPermissionDefinition.d.ts +4 -0
  132. package/dist/HttpApi/AdminPermissionDefinition.d.ts.map +1 -0
  133. package/dist/HttpApi/AdminPermissionDefinition.js +4 -0
  134. package/dist/HttpApi/AdminPermissionDefinition.js.map +1 -0
  135. package/dist/HttpApi/AdminSecurityTimeline.d.ts +4 -0
  136. package/dist/HttpApi/AdminSecurityTimeline.d.ts.map +1 -0
  137. package/dist/HttpApi/AdminSecurityTimeline.js +4 -0
  138. package/dist/HttpApi/AdminSecurityTimeline.js.map +1 -0
  139. package/dist/HttpApi/AdminSession.d.ts +4 -0
  140. package/dist/HttpApi/AdminSession.d.ts.map +1 -0
  141. package/dist/HttpApi/AdminSession.js +4 -0
  142. package/dist/HttpApi/AdminSession.js.map +1 -0
  143. package/dist/HttpApi/AdminTrustedDevice.d.ts +4 -0
  144. package/dist/HttpApi/AdminTrustedDevice.d.ts.map +1 -0
  145. package/dist/HttpApi/AdminTrustedDevice.js +4 -0
  146. package/dist/HttpApi/AdminTrustedDevice.js.map +1 -0
  147. package/dist/HttpApi/Api.d.ts +879 -877
  148. package/dist/HttpApi/Api.d.ts.map +1 -1
  149. package/dist/HttpApi/Api.js +2160 -896
  150. package/dist/HttpApi/Api.js.map +1 -1
  151. package/dist/HttpApi/ApiKey.d.ts +4 -0
  152. package/dist/HttpApi/ApiKey.d.ts.map +1 -0
  153. package/dist/HttpApi/ApiKey.js +4 -0
  154. package/dist/HttpApi/ApiKey.js.map +1 -0
  155. package/dist/HttpApi/Audit.d.ts +6 -0
  156. package/dist/HttpApi/Audit.d.ts.map +1 -0
  157. package/dist/HttpApi/Audit.js +10 -0
  158. package/dist/HttpApi/Audit.js.map +1 -0
  159. package/dist/HttpApi/ClientApi.d.ts +917 -0
  160. package/dist/HttpApi/ClientApi.d.ts.map +1 -0
  161. package/dist/HttpApi/ClientApi.js +101 -0
  162. package/dist/HttpApi/ClientApi.js.map +1 -0
  163. package/dist/HttpApi/EmailAuth.d.ts +6 -0
  164. package/dist/HttpApi/EmailAuth.d.ts.map +1 -0
  165. package/dist/HttpApi/EmailAuth.js +6 -0
  166. package/dist/HttpApi/EmailAuth.js.map +1 -0
  167. package/dist/HttpApi/EmailGuards.d.ts +115 -0
  168. package/dist/HttpApi/EmailGuards.d.ts.map +1 -0
  169. package/dist/HttpApi/EmailGuards.js +191 -0
  170. package/dist/HttpApi/EmailGuards.js.map +1 -0
  171. package/dist/HttpApi/EmailOtp.d.ts +6 -0
  172. package/dist/HttpApi/EmailOtp.d.ts.map +1 -0
  173. package/dist/HttpApi/EmailOtp.js +6 -0
  174. package/dist/HttpApi/EmailOtp.js.map +1 -0
  175. package/dist/HttpApi/EmailVerification.d.ts +6 -0
  176. package/dist/HttpApi/EmailVerification.d.ts.map +1 -0
  177. package/dist/HttpApi/EmailVerification.js +6 -0
  178. package/dist/HttpApi/EmailVerification.js.map +1 -0
  179. package/dist/HttpApi/Endpoints.d.ts +629 -301
  180. package/dist/HttpApi/Endpoints.d.ts.map +1 -1
  181. package/dist/HttpApi/Endpoints.js +178 -120
  182. package/dist/HttpApi/Endpoints.js.map +1 -1
  183. package/dist/HttpApi/Errors.d.ts +26 -7
  184. package/dist/HttpApi/Errors.d.ts.map +1 -1
  185. package/dist/HttpApi/Errors.js +26 -4
  186. package/dist/HttpApi/Errors.js.map +1 -1
  187. package/dist/HttpApi/Identity.d.ts +6 -0
  188. package/dist/HttpApi/Identity.d.ts.map +1 -0
  189. package/dist/HttpApi/Identity.js +6 -0
  190. package/dist/HttpApi/Identity.js.map +1 -0
  191. package/dist/HttpApi/IdentityGuards.d.ts +31 -0
  192. package/dist/HttpApi/IdentityGuards.d.ts.map +1 -0
  193. package/dist/HttpApi/IdentityGuards.js +33 -0
  194. package/dist/HttpApi/IdentityGuards.js.map +1 -0
  195. package/dist/HttpApi/Jwt.d.ts +4 -0
  196. package/dist/HttpApi/Jwt.d.ts.map +1 -0
  197. package/dist/HttpApi/Jwt.js +4 -0
  198. package/dist/HttpApi/Jwt.js.map +1 -0
  199. package/dist/HttpApi/JwtDiscovery.d.ts +4 -0
  200. package/dist/HttpApi/JwtDiscovery.d.ts.map +1 -0
  201. package/dist/HttpApi/JwtDiscovery.js +4 -0
  202. package/dist/HttpApi/JwtDiscovery.js.map +1 -0
  203. package/dist/HttpApi/LoginApproval.d.ts +5 -0
  204. package/dist/HttpApi/LoginApproval.d.ts.map +1 -0
  205. package/dist/HttpApi/LoginApproval.js +5 -0
  206. package/dist/HttpApi/LoginApproval.js.map +1 -0
  207. package/dist/HttpApi/LoginNotification.d.ts +5 -0
  208. package/dist/HttpApi/LoginNotification.d.ts.map +1 -0
  209. package/dist/HttpApi/LoginNotification.js +5 -0
  210. package/dist/HttpApi/LoginNotification.js.map +1 -0
  211. package/dist/HttpApi/MagicLink.d.ts +6 -0
  212. package/dist/HttpApi/MagicLink.d.ts.map +1 -0
  213. package/dist/HttpApi/MagicLink.js +6 -0
  214. package/dist/HttpApi/MagicLink.js.map +1 -0
  215. package/dist/HttpApi/Mfa.d.ts +5 -0
  216. package/dist/HttpApi/Mfa.d.ts.map +1 -0
  217. package/dist/HttpApi/Mfa.js +5 -0
  218. package/dist/HttpApi/Mfa.js.map +1 -0
  219. package/dist/HttpApi/Middleware.d.ts +14 -0
  220. package/dist/HttpApi/Middleware.d.ts.map +1 -0
  221. package/dist/HttpApi/Middleware.js +5 -0
  222. package/dist/HttpApi/Middleware.js.map +1 -0
  223. package/dist/HttpApi/OAuth.d.ts +4 -0
  224. package/dist/HttpApi/OAuth.d.ts.map +1 -0
  225. package/dist/HttpApi/OAuth.js +4 -0
  226. package/dist/HttpApi/OAuth.js.map +1 -0
  227. package/dist/HttpApi/OAuthProviderAuthorization.d.ts +4 -0
  228. package/dist/HttpApi/OAuthProviderAuthorization.d.ts.map +1 -0
  229. package/dist/HttpApi/OAuthProviderAuthorization.js +4 -0
  230. package/dist/HttpApi/OAuthProviderAuthorization.js.map +1 -0
  231. package/dist/HttpApi/OAuthToken.d.ts +4 -0
  232. package/dist/HttpApi/OAuthToken.d.ts.map +1 -0
  233. package/dist/HttpApi/OAuthToken.js +4 -0
  234. package/dist/HttpApi/OAuthToken.js.map +1 -0
  235. package/dist/HttpApi/OidcDiscovery.d.ts +4 -0
  236. package/dist/HttpApi/OidcDiscovery.d.ts.map +1 -0
  237. package/dist/HttpApi/OidcDiscovery.js +4 -0
  238. package/dist/HttpApi/OidcDiscovery.js.map +1 -0
  239. package/dist/HttpApi/Operations/AdminPermissionDefinition.d.ts +28 -0
  240. package/dist/HttpApi/Operations/AdminPermissionDefinition.d.ts.map +1 -0
  241. package/dist/HttpApi/Operations/AdminPermissionDefinition.js +5 -0
  242. package/dist/HttpApi/Operations/AdminPermissionDefinition.js.map +1 -0
  243. package/dist/HttpApi/Operations/AdminSession.d.ts +16 -0
  244. package/dist/HttpApi/Operations/AdminSession.d.ts.map +1 -0
  245. package/dist/HttpApi/Operations/AdminSession.js +5 -0
  246. package/dist/HttpApi/Operations/AdminSession.js.map +1 -0
  247. package/dist/HttpApi/Operations/ApiKey.d.ts +16 -0
  248. package/dist/HttpApi/Operations/ApiKey.d.ts.map +1 -0
  249. package/dist/HttpApi/Operations/ApiKey.js +5 -0
  250. package/dist/HttpApi/Operations/ApiKey.js.map +1 -0
  251. package/dist/HttpApi/Operations/Discovery.d.ts +19 -0
  252. package/dist/HttpApi/Operations/Discovery.d.ts.map +1 -0
  253. package/dist/HttpApi/Operations/Discovery.js +7 -0
  254. package/dist/HttpApi/Operations/Discovery.js.map +1 -0
  255. package/dist/HttpApi/Operations/EmailAuth.d.ts +12 -0
  256. package/dist/HttpApi/Operations/EmailAuth.d.ts.map +1 -0
  257. package/dist/HttpApi/Operations/EmailAuth.js +5 -0
  258. package/dist/HttpApi/Operations/EmailAuth.js.map +1 -0
  259. package/dist/HttpApi/Operations/EmailOtp.d.ts +14 -0
  260. package/dist/HttpApi/Operations/EmailOtp.d.ts.map +1 -0
  261. package/dist/HttpApi/Operations/EmailOtp.js +5 -0
  262. package/dist/HttpApi/Operations/EmailOtp.js.map +1 -0
  263. package/dist/HttpApi/Operations/EmailVerification.d.ts +14 -0
  264. package/dist/HttpApi/Operations/EmailVerification.d.ts.map +1 -0
  265. package/dist/HttpApi/Operations/EmailVerification.js +5 -0
  266. package/dist/HttpApi/Operations/EmailVerification.js.map +1 -0
  267. package/dist/HttpApi/Operations/HttpOperation.d.ts +4 -0
  268. package/dist/HttpApi/Operations/HttpOperation.d.ts.map +1 -0
  269. package/dist/HttpApi/Operations/HttpOperation.js +2 -0
  270. package/dist/HttpApi/Operations/HttpOperation.js.map +1 -0
  271. package/dist/HttpApi/Operations/Identity.d.ts +22 -0
  272. package/dist/HttpApi/Operations/Identity.d.ts.map +1 -0
  273. package/dist/HttpApi/Operations/Identity.js +5 -0
  274. package/dist/HttpApi/Operations/Identity.js.map +1 -0
  275. package/dist/HttpApi/Operations/Jwt.d.ts +14 -0
  276. package/dist/HttpApi/Operations/Jwt.d.ts.map +1 -0
  277. package/dist/HttpApi/Operations/Jwt.js +5 -0
  278. package/dist/HttpApi/Operations/Jwt.js.map +1 -0
  279. package/dist/HttpApi/Operations/LoginApproval.d.ts +16 -0
  280. package/dist/HttpApi/Operations/LoginApproval.d.ts.map +1 -0
  281. package/dist/HttpApi/Operations/LoginApproval.js +5 -0
  282. package/dist/HttpApi/Operations/LoginApproval.js.map +1 -0
  283. package/dist/HttpApi/Operations/MagicLink.d.ts +14 -0
  284. package/dist/HttpApi/Operations/MagicLink.d.ts.map +1 -0
  285. package/dist/HttpApi/Operations/MagicLink.js +5 -0
  286. package/dist/HttpApi/Operations/MagicLink.js.map +1 -0
  287. package/dist/HttpApi/Operations/Mfa.d.ts +24 -0
  288. package/dist/HttpApi/Operations/Mfa.d.ts.map +1 -0
  289. package/dist/HttpApi/Operations/Mfa.js +5 -0
  290. package/dist/HttpApi/Operations/Mfa.js.map +1 -0
  291. package/dist/HttpApi/Operations/OAuth.d.ts +20 -0
  292. package/dist/HttpApi/Operations/OAuth.d.ts.map +1 -0
  293. package/dist/HttpApi/Operations/OAuth.js +5 -0
  294. package/dist/HttpApi/Operations/OAuth.js.map +1 -0
  295. package/dist/HttpApi/Operations/OAuthProviderAuthorization.d.ts +12 -0
  296. package/dist/HttpApi/Operations/OAuthProviderAuthorization.d.ts.map +1 -0
  297. package/dist/HttpApi/Operations/OAuthProviderAuthorization.js +5 -0
  298. package/dist/HttpApi/Operations/OAuthProviderAuthorization.js.map +1 -0
  299. package/dist/HttpApi/Operations/OAuthToken.d.ts +16 -0
  300. package/dist/HttpApi/Operations/OAuthToken.d.ts.map +1 -0
  301. package/dist/HttpApi/Operations/OAuthToken.js +5 -0
  302. package/dist/HttpApi/Operations/OAuthToken.js.map +1 -0
  303. package/dist/HttpApi/Operations/Passkey.d.ts +22 -0
  304. package/dist/HttpApi/Operations/Passkey.d.ts.map +1 -0
  305. package/dist/HttpApi/Operations/Passkey.js +5 -0
  306. package/dist/HttpApi/Operations/Passkey.js.map +1 -0
  307. package/dist/HttpApi/Operations/Password.d.ts +22 -0
  308. package/dist/HttpApi/Operations/Password.d.ts.map +1 -0
  309. package/dist/HttpApi/Operations/Password.js +5 -0
  310. package/dist/HttpApi/Operations/Password.js.map +1 -0
  311. package/dist/HttpApi/Operations/RecoveryCodes.d.ts +20 -0
  312. package/dist/HttpApi/Operations/RecoveryCodes.d.ts.map +1 -0
  313. package/dist/HttpApi/Operations/RecoveryCodes.js +5 -0
  314. package/dist/HttpApi/Operations/RecoveryCodes.js.map +1 -0
  315. package/dist/HttpApi/Operations/RefreshToken.d.ts +12 -0
  316. package/dist/HttpApi/Operations/RefreshToken.d.ts.map +1 -0
  317. package/dist/HttpApi/Operations/RefreshToken.js +5 -0
  318. package/dist/HttpApi/Operations/RefreshToken.js.map +1 -0
  319. package/dist/HttpApi/Operations/Security.d.ts +12 -0
  320. package/dist/HttpApi/Operations/Security.d.ts.map +1 -0
  321. package/dist/HttpApi/Operations/Security.js +5 -0
  322. package/dist/HttpApi/Operations/Security.js.map +1 -0
  323. package/dist/HttpApi/Operations/SecurityTimeline.d.ts +19 -0
  324. package/dist/HttpApi/Operations/SecurityTimeline.d.ts.map +1 -0
  325. package/dist/HttpApi/Operations/SecurityTimeline.js +7 -0
  326. package/dist/HttpApi/Operations/SecurityTimeline.js.map +1 -0
  327. package/dist/HttpApi/Operations/Session.d.ts +22 -0
  328. package/dist/HttpApi/Operations/Session.d.ts.map +1 -0
  329. package/dist/HttpApi/Operations/Session.js +5 -0
  330. package/dist/HttpApi/Operations/Session.js.map +1 -0
  331. package/dist/HttpApi/Operations/StepUp.d.ts +22 -0
  332. package/dist/HttpApi/Operations/StepUp.d.ts.map +1 -0
  333. package/dist/HttpApi/Operations/StepUp.js +5 -0
  334. package/dist/HttpApi/Operations/StepUp.js.map +1 -0
  335. package/dist/HttpApi/Operations/Totp.d.ts +20 -0
  336. package/dist/HttpApi/Operations/Totp.d.ts.map +1 -0
  337. package/dist/HttpApi/Operations/Totp.js +5 -0
  338. package/dist/HttpApi/Operations/Totp.js.map +1 -0
  339. package/dist/HttpApi/Operations/TrustedDevice.d.ts +23 -0
  340. package/dist/HttpApi/Operations/TrustedDevice.d.ts.map +1 -0
  341. package/dist/HttpApi/Operations/TrustedDevice.js +7 -0
  342. package/dist/HttpApi/Operations/TrustedDevice.js.map +1 -0
  343. package/dist/HttpApi/Operations/index.d.ts +27 -0
  344. package/dist/HttpApi/Operations/index.d.ts.map +1 -0
  345. package/dist/HttpApi/Operations/index.js +27 -0
  346. package/dist/HttpApi/Operations/index.js.map +1 -0
  347. package/dist/HttpApi/Passkey.d.ts +5 -0
  348. package/dist/HttpApi/Passkey.d.ts.map +1 -0
  349. package/dist/HttpApi/Passkey.js +5 -0
  350. package/dist/HttpApi/Passkey.js.map +1 -0
  351. package/dist/HttpApi/Password.d.ts +220 -0
  352. package/dist/HttpApi/Password.d.ts.map +1 -0
  353. package/dist/HttpApi/Password.js +237 -0
  354. package/dist/HttpApi/Password.js.map +1 -0
  355. package/dist/HttpApi/PasswordContract.d.ts +307 -0
  356. package/dist/HttpApi/PasswordContract.d.ts.map +1 -0
  357. package/dist/HttpApi/PasswordContract.js +63 -0
  358. package/dist/HttpApi/PasswordContract.js.map +1 -0
  359. package/dist/HttpApi/PasswordGuards.d.ts +82 -0
  360. package/dist/HttpApi/PasswordGuards.d.ts.map +1 -0
  361. package/dist/HttpApi/PasswordGuards.js +179 -0
  362. package/dist/HttpApi/PasswordGuards.js.map +1 -0
  363. package/dist/HttpApi/RecoveryCodes.d.ts +5 -0
  364. package/dist/HttpApi/RecoveryCodes.d.ts.map +1 -0
  365. package/dist/HttpApi/RecoveryCodes.js +5 -0
  366. package/dist/HttpApi/RecoveryCodes.js.map +1 -0
  367. package/dist/HttpApi/RefreshToken.d.ts +4 -0
  368. package/dist/HttpApi/RefreshToken.d.ts.map +1 -0
  369. package/dist/HttpApi/RefreshToken.js +4 -0
  370. package/dist/HttpApi/RefreshToken.js.map +1 -0
  371. package/dist/HttpApi/RequestGuards.d.ts +42 -0
  372. package/dist/HttpApi/RequestGuards.d.ts.map +1 -0
  373. package/dist/HttpApi/RequestGuards.js +194 -0
  374. package/dist/HttpApi/RequestGuards.js.map +1 -0
  375. package/dist/HttpApi/Schemas.d.ts +361 -84
  376. package/dist/HttpApi/Schemas.d.ts.map +1 -1
  377. package/dist/HttpApi/Schemas.js +190 -27
  378. package/dist/HttpApi/Schemas.js.map +1 -1
  379. package/dist/HttpApi/Security.d.ts +12 -93
  380. package/dist/HttpApi/Security.d.ts.map +1 -1
  381. package/dist/HttpApi/Security.js +65 -356
  382. package/dist/HttpApi/Security.js.map +1 -1
  383. package/dist/HttpApi/SecurityTimeline.d.ts +4 -0
  384. package/dist/HttpApi/SecurityTimeline.d.ts.map +1 -0
  385. package/dist/HttpApi/SecurityTimeline.js +4 -0
  386. package/dist/HttpApi/SecurityTimeline.js.map +1 -0
  387. package/dist/HttpApi/Session.d.ts +5 -0
  388. package/dist/HttpApi/Session.d.ts.map +1 -0
  389. package/dist/HttpApi/Session.js +5 -0
  390. package/dist/HttpApi/Session.js.map +1 -0
  391. package/dist/HttpApi/SessionGuards.d.ts +15 -0
  392. package/dist/HttpApi/SessionGuards.d.ts.map +1 -0
  393. package/dist/HttpApi/SessionGuards.js +37 -0
  394. package/dist/HttpApi/SessionGuards.js.map +1 -0
  395. package/dist/HttpApi/StepUp.d.ts +5 -0
  396. package/dist/HttpApi/StepUp.d.ts.map +1 -0
  397. package/dist/HttpApi/StepUp.js +5 -0
  398. package/dist/HttpApi/StepUp.js.map +1 -0
  399. package/dist/HttpApi/Totp.d.ts +5 -0
  400. package/dist/HttpApi/Totp.d.ts.map +1 -0
  401. package/dist/HttpApi/Totp.js +5 -0
  402. package/dist/HttpApi/Totp.js.map +1 -0
  403. package/dist/HttpApi/Transport.d.ts +4 -6
  404. package/dist/HttpApi/Transport.d.ts.map +1 -1
  405. package/dist/HttpApi/Transport.js +62 -36
  406. package/dist/HttpApi/Transport.js.map +1 -1
  407. package/dist/HttpApi/TrustedDevice.d.ts +4 -0
  408. package/dist/HttpApi/TrustedDevice.d.ts.map +1 -0
  409. package/dist/HttpApi/TrustedDevice.js +4 -0
  410. package/dist/HttpApi/TrustedDevice.js.map +1 -0
  411. package/dist/HttpApi/index.d.ts +9 -3
  412. package/dist/HttpApi/index.d.ts.map +1 -1
  413. package/dist/HttpApi/index.js +8 -2
  414. package/dist/HttpApi/index.js.map +1 -1
  415. package/dist/Identity.d.ts +138 -0
  416. package/dist/Identity.d.ts.map +1 -0
  417. package/dist/Identity.js +105 -0
  418. package/dist/Identity.js.map +1 -0
  419. package/dist/IdentityManagement.d.ts +189 -0
  420. package/dist/IdentityManagement.d.ts.map +1 -0
  421. package/dist/IdentityManagement.js +317 -0
  422. package/dist/IdentityManagement.js.map +1 -0
  423. package/dist/IncidentAction.d.ts +1 -4
  424. package/dist/IncidentAction.d.ts.map +1 -1
  425. package/dist/IncidentAction.js +15 -9
  426. package/dist/IncidentAction.js.map +1 -1
  427. package/dist/Internal.d.ts +2 -0
  428. package/dist/Internal.d.ts.map +1 -1
  429. package/dist/Internal.js +30 -0
  430. package/dist/Internal.js.map +1 -1
  431. package/dist/Invitation.d.ts +362 -0
  432. package/dist/Invitation.d.ts.map +1 -0
  433. package/dist/Invitation.js +633 -0
  434. package/dist/Invitation.js.map +1 -0
  435. package/dist/IpQualityScore.d.ts +38 -0
  436. package/dist/IpQualityScore.d.ts.map +1 -0
  437. package/dist/IpQualityScore.js +215 -0
  438. package/dist/IpQualityScore.js.map +1 -0
  439. package/dist/Jwt.d.ts +8 -29
  440. package/dist/Jwt.d.ts.map +1 -1
  441. package/dist/Jwt.js +67 -56
  442. package/dist/Jwt.js.map +1 -1
  443. package/dist/LoginApproval.d.ts +15 -30
  444. package/dist/LoginApproval.d.ts.map +1 -1
  445. package/dist/LoginApproval.js +47 -41
  446. package/dist/LoginApproval.js.map +1 -1
  447. package/dist/LoginNotification.d.ts +7 -11
  448. package/dist/LoginNotification.d.ts.map +1 -1
  449. package/dist/LoginNotification.js +30 -17
  450. package/dist/LoginNotification.js.map +1 -1
  451. package/dist/LoginRisk.d.ts +11 -13
  452. package/dist/LoginRisk.d.ts.map +1 -1
  453. package/dist/LoginRisk.js +66 -26
  454. package/dist/LoginRisk.js.map +1 -1
  455. package/dist/LoginRiskEnrichment.d.ts +145 -0
  456. package/dist/LoginRiskEnrichment.d.ts.map +1 -0
  457. package/dist/LoginRiskEnrichment.js +220 -0
  458. package/dist/LoginRiskEnrichment.js.map +1 -0
  459. package/dist/MagicLink.d.ts +9 -8
  460. package/dist/MagicLink.d.ts.map +1 -1
  461. package/dist/MagicLink.js +40 -73
  462. package/dist/MagicLink.js.map +1 -1
  463. package/dist/Mailer.d.ts +19 -12
  464. package/dist/Mailer.d.ts.map +1 -1
  465. package/dist/Mailer.js +10 -12
  466. package/dist/Mailer.js.map +1 -1
  467. package/dist/OAuth.d.ts +93 -96
  468. package/dist/OAuth.d.ts.map +1 -1
  469. package/dist/OAuth.js +404 -217
  470. package/dist/OAuth.js.map +1 -1
  471. package/dist/Passkey.d.ts +15 -17
  472. package/dist/Passkey.d.ts.map +1 -1
  473. package/dist/Passkey.js +86 -39
  474. package/dist/Passkey.js.map +1 -1
  475. package/dist/PasskeyBrowser.d.ts +39 -0
  476. package/dist/PasskeyBrowser.d.ts.map +1 -0
  477. package/dist/PasskeyBrowser.js +206 -0
  478. package/dist/PasskeyBrowser.js.map +1 -0
  479. package/dist/PasskeySimpleWebAuthn.d.ts.map +1 -1
  480. package/dist/PasskeySimpleWebAuthn.js +19 -7
  481. package/dist/PasskeySimpleWebAuthn.js.map +1 -1
  482. package/dist/Password.d.ts +71 -37
  483. package/dist/Password.d.ts.map +1 -1
  484. package/dist/Password.js +295 -81
  485. package/dist/Password.js.map +1 -1
  486. package/dist/PasswordRisk.d.ts +63 -0
  487. package/dist/PasswordRisk.d.ts.map +1 -0
  488. package/dist/PasswordRisk.js +37 -0
  489. package/dist/PasswordRisk.js.map +1 -0
  490. package/dist/Permission.d.ts +308 -0
  491. package/dist/Permission.d.ts.map +1 -0
  492. package/dist/Permission.js +827 -0
  493. package/dist/Permission.js.map +1 -0
  494. package/dist/Policy.d.ts +31 -18
  495. package/dist/Policy.d.ts.map +1 -1
  496. package/dist/Policy.js +58 -14
  497. package/dist/Policy.js.map +1 -1
  498. package/dist/PostgresDelegatedAccessStorageMigrations.d.ts +7 -0
  499. package/dist/PostgresDelegatedAccessStorageMigrations.d.ts.map +1 -0
  500. package/dist/PostgresDelegatedAccessStorageMigrations.js +119 -0
  501. package/dist/PostgresDelegatedAccessStorageMigrations.js.map +1 -0
  502. package/dist/PostgresStorageMigrations.d.ts +15 -0
  503. package/dist/PostgresStorageMigrations.d.ts.map +1 -0
  504. package/dist/PostgresStorageMigrations.js +620 -0
  505. package/dist/PostgresStorageMigrations.js.map +1 -0
  506. package/dist/Privacy.d.ts +0 -3
  507. package/dist/Privacy.d.ts.map +1 -1
  508. package/dist/Privacy.js +2 -5
  509. package/dist/Privacy.js.map +1 -1
  510. package/dist/RateLimiter.d.ts +0 -3
  511. package/dist/RateLimiter.d.ts.map +1 -1
  512. package/dist/RateLimiter.js +1 -4
  513. package/dist/RateLimiter.js.map +1 -1
  514. package/dist/RecoveryCode.d.ts +83 -11
  515. package/dist/RecoveryCode.d.ts.map +1 -1
  516. package/dist/RecoveryCode.js +273 -75
  517. package/dist/RecoveryCode.js.map +1 -1
  518. package/dist/RecoveryPolicy.d.ts +81 -0
  519. package/dist/RecoveryPolicy.d.ts.map +1 -0
  520. package/dist/RecoveryPolicy.js +119 -0
  521. package/dist/RecoveryPolicy.js.map +1 -0
  522. package/dist/RefreshToken.d.ts +0 -9
  523. package/dist/RefreshToken.d.ts.map +1 -1
  524. package/dist/RefreshToken.js +31 -22
  525. package/dist/RefreshToken.js.map +1 -1
  526. package/dist/SecurityTimeline.d.ts +1 -4
  527. package/dist/SecurityTimeline.d.ts.map +1 -1
  528. package/dist/SecurityTimeline.js +110 -17
  529. package/dist/SecurityTimeline.js.map +1 -1
  530. package/dist/Sessions.d.ts +124 -29
  531. package/dist/Sessions.d.ts.map +1 -1
  532. package/dist/Sessions.js +626 -94
  533. package/dist/Sessions.js.map +1 -1
  534. package/dist/StepUp.d.ts +132 -11
  535. package/dist/StepUp.d.ts.map +1 -1
  536. package/dist/StepUp.js +387 -19
  537. package/dist/StepUp.js.map +1 -1
  538. package/dist/Storage.d.ts +89 -23
  539. package/dist/Storage.d.ts.map +1 -1
  540. package/dist/Storage.js +10 -12
  541. package/dist/Storage.js.map +1 -1
  542. package/dist/StorageMigrations.d.ts +6 -1
  543. package/dist/StorageMigrations.d.ts.map +1 -1
  544. package/dist/StorageMigrations.js +224 -0
  545. package/dist/StorageMigrations.js.map +1 -1
  546. package/dist/StorageSchemaGenerator.d.ts +486 -0
  547. package/dist/StorageSchemaGenerator.d.ts.map +1 -0
  548. package/dist/StorageSchemaGenerator.js +1022 -0
  549. package/dist/StorageSchemaGenerator.js.map +1 -0
  550. package/dist/StrongFactor.d.ts +64 -0
  551. package/dist/StrongFactor.d.ts.map +1 -0
  552. package/dist/StrongFactor.js +100 -0
  553. package/dist/StrongFactor.js.map +1 -0
  554. package/dist/Testing.js +2 -2
  555. package/dist/Testing.js.map +1 -1
  556. package/dist/Totp.d.ts +88 -13
  557. package/dist/Totp.d.ts.map +1 -1
  558. package/dist/Totp.js +192 -34
  559. package/dist/Totp.js.map +1 -1
  560. package/dist/TrustedDevice.d.ts +0 -9
  561. package/dist/TrustedDevice.d.ts.map +1 -1
  562. package/dist/TrustedDevice.js +15 -17
  563. package/dist/TrustedDevice.js.map +1 -1
  564. package/dist/WaitUntil.d.ts +0 -3
  565. package/dist/WaitUntil.d.ts.map +1 -1
  566. package/dist/WaitUntil.js +1 -4
  567. package/dist/WaitUntil.js.map +1 -1
  568. package/dist/Webhook.d.ts +42 -12
  569. package/dist/Webhook.d.ts.map +1 -1
  570. package/dist/Webhook.js +165 -26
  571. package/dist/Webhook.js.map +1 -1
  572. package/dist/alchemy/cloudflare/Email.d.ts.map +1 -1
  573. package/dist/alchemy/cloudflare/Email.js +9 -4
  574. package/dist/alchemy/cloudflare/Email.js.map +1 -1
  575. package/dist/cloudflare/Email.js +1 -1
  576. package/dist/cloudflare/Email.js.map +1 -1
  577. package/dist/cloudflare/RequestMetadata.d.ts +49 -0
  578. package/dist/cloudflare/RequestMetadata.d.ts.map +1 -0
  579. package/dist/cloudflare/RequestMetadata.js +71 -0
  580. package/dist/cloudflare/RequestMetadata.js.map +1 -0
  581. package/dist/index.d.ts +0 -35
  582. package/dist/index.d.ts.map +1 -1
  583. package/dist/index.js +0 -35
  584. package/dist/index.js.map +1 -1
  585. package/dist/internal/AuthFlowFinalizer.d.ts +31 -0
  586. package/dist/internal/AuthFlowFinalizer.d.ts.map +1 -0
  587. package/dist/internal/AuthFlowFinalizer.js +182 -0
  588. package/dist/internal/AuthFlowFinalizer.js.map +1 -0
  589. package/migrations/0001_auth_storage.sql +1 -0
  590. package/migrations/0006_auth_totp_factor.sql +1 -0
  591. package/migrations/0024_auth_permission.sql +39 -0
  592. package/migrations/0025_auth_permission_definition.sql +19 -0
  593. package/migrations/0026_auth_user_identity.sql +59 -0
  594. package/migrations/0027_auth_audit_log_storage_order.sql +46 -0
  595. package/migrations/0028_auth_oauth_client_secret_key.sql +39 -0
  596. package/migrations/delegated-access/0001_auth_delegated_access.sql +152 -0
  597. package/migrations/postgres/0001_auth_core.sql +77 -0
  598. package/migrations/postgres/0002_auth_security_oauth.sql +65 -0
  599. package/migrations/postgres/0003_auth_credentials_tokens.sql +43 -0
  600. package/migrations/postgres/0004_auth_security_history.sql +103 -0
  601. package/migrations/postgres/0005_auth_webhooks.sql +29 -0
  602. package/migrations/postgres/0006_auth_domain_timeline.sql +50 -0
  603. package/migrations/postgres/0007_auth_oauth_clients.sql +85 -0
  604. package/migrations/postgres/0008_auth_oauth_tokens.sql +63 -0
  605. package/migrations/postgres/0009_auth_permissions.sql +66 -0
  606. package/migrations/postgres/delegated-access/0001_auth_delegated_access.sql +112 -0
  607. package/package.json +301 -2
@@ -1,34 +1,59 @@
1
1
  import { Data, Effect, Layer, Option, Redacted } from "effect";
2
- import { Column, Query, Scalar, Table } from "effect-qb";
3
- import { Executor, Query as SqliteQuery } from "effect-qb/sqlite";
4
- import { AuditLogStore, AuditLogStoreError, auditEventActorUserId, auditEventUserId, filterAuditLogEvents, } from "./AuditLog.js";
2
+ import { Column, Executor as EffectQbExecutor, Query, Scalar, Table, } from "effect-qb";
3
+ import { Executor, Query as SqliteQuery, Renderer } from "effect-qb/sqlite";
5
4
  import { ApiKeyId, ApiKeyPrefix, ApiKeySecretHash, ApiKeyStore, ApiKeyStoreError, } from "./ApiKey.js";
5
+ import { deriveAssurance, deriveMfaVerifiedAt, parseAuthenticationEvents, } from "./Assurance.js";
6
+ import { AtomicPlanError } from "./AtomicPlanExecutor.js";
7
+ import { AuditLogStore, AuditLogStoreError, auditEventActorUserId, auditEventUserId, decodeAuditEvent, filterAuditLogEvents, } from "./AuditLog.js";
6
8
  import { DomainName, DomainVerificationId, DomainVerificationProofToken, DomainVerificationStore, DomainVerificationStoreError, } from "./DomainVerification.js";
7
- import { AuthFlowId, ChallengeId, CredentialId, Email, OAuthAccountId, OAuthClientId, OAuthProviderId, SessionId, UnixMillis, UserId, } from "./Identifiers.js";
9
+ import { AuthFlowId, ChallengeId, CredentialId, Email, IdentityId, OAuthAccountId, OAuthClientId, OAuthProviderId, PermissionId, RoleId, SessionId, UnixMillis, UserId, } from "./Identifiers.js";
10
+ import { isIdentityLoginEligible } from "./Identity.js";
8
11
  import { currentUnixMillis } from "./Internal.js";
9
12
  import { JwtError, JwtRevocationStore } from "./Jwt.js";
10
13
  import { LoginApprovalReviewError, LoginApprovalReviewStore, } from "./LoginApproval.js";
11
14
  import { LoginRiskContext, LoginRiskHistory, LoginRiskHistoryError, LoginRiskSignal, maxLoginRiskLevel, } from "./LoginRisk.js";
12
- import { OAuthAccountStore, OAuthAccountStoreError, OAuthAuthorizationCodeStore, OAuthAuthorizationCodeStoreError, OAuthClientStore, OAuthClientStoreError, OAuthConsentStore, OAuthConsentStoreError, OAuthClientSecretHash, OAuthClientSecretPrefix, OAuthClientSecretStore, OAuthClientSecretStoreError, OAuthCodeChallenge, OAuthProviderModeTokenStore, OAuthProviderModeTokenStoreError, OAuthProviderTokenVault, OAuthProviderTokenVaultError, } from "./OAuth.js";
15
+ import { OAuthAccountStore, OAuthAccountStoreError, OAuthIdentityBridgeConflictError, OAuthIdentityBridgeStore, OAuthIdentityBridgeStoreError, OAuthAuthorizationCodeStore, OAuthAuthorizationCodeStoreError, OAuthClientStore, OAuthClientStoreError, OAuthConsentStore, OAuthConsentStoreError, OAuthClientSecretHash, OAuthClientSecretPrefix, OAuthClientSecretStore, OAuthClientSecretStoreError, OAuthCodeChallenge, OAuthProviderModeTokenStore, OAuthProviderModeTokenStoreError, OAuthProviderTokenVault, OAuthProviderTokenVaultError, } from "./OAuth.js";
13
16
  import { PasskeyCredentialId, PasskeyCredentialStore, PasskeyCredentialStoreError, } from "./Passkey.js";
14
- import { RecoveryCodeHash, RecoveryCodeStore, RecoveryCodeStoreError, } from "./RecoveryCode.js";
17
+ import { canonicalPermissionScope, normalizePermissionDefinitionListLimit, PermissionStore, PermissionStoreError, } from "./Permission.js";
18
+ import { RecoveryCodeHash, RecoveryCodeLoginCommitStore, RecoveryCodeLoginCommitStoreError, RecoveryCodeStore, RecoveryCodeStoreError, RecoveryCodeSessionRotateStore, RecoveryCodeSessionRotateStoreError, } from "./RecoveryCode.js";
15
19
  import { RefreshTokenFamilyId, RefreshTokenId, RefreshTokenSecretHash, RefreshTokenStore, RefreshTokenStoreError, } from "./RefreshToken.js";
16
20
  import { filterSecurityTimelineEvents, SecurityTimelineEventId, SecurityTimelineStore, SecurityTimelineStoreError, } from "./SecurityTimeline.js";
17
- import { CredentialStore, SessionStore, StorageError, UserStore, VerificationStore, } from "./Storage.js";
18
- import { TotpFactorStore, TotpFactorStoreError, TotpSecret, } from "./Totp.js";
21
+ import { CredentialStore, IdentityConflictError, IdentityStore, RegistrationConflictError, RegistrationFailureError, RegistrationStore, SessionStore, StorageError, UserStore, VerificationStore, } from "./Storage.js";
22
+ import { TotpFactorStore, TotpFactorStoreError, TotpLoginCommitStore, TotpLoginCommitStoreError, TotpSessionRotateStore, TotpSessionRotateStoreError, TotpSecret, } from "./Totp.js";
19
23
  import { filterTrustedDevicesForUser, TrustedDeviceError, TrustedDeviceStore, } from "./TrustedDevice.js";
20
24
  import { filterDueWebhookOutboxRecords, WebhookOutboxItemId, WebhookOutboxStore, WebhookOutboxStoreError, WebhookReplayStore, WebhookReplayStoreError, } from "./Webhook.js";
25
+ const boundColumnName = (expression) => {
26
+ const state = expression[Symbol.for("effect-qb/BoundColumn")];
27
+ if (typeof state?.columnName !== "string") {
28
+ throw new TypeError("Atomic SQLite storage requires bound table columns");
29
+ }
30
+ return state.columnName;
31
+ };
21
32
  export class D1EffectQbError extends Data.TaggedError("D1EffectQbError") {
22
33
  }
23
34
  const makeAuthUserTable = (name) => Table.make(name, {
24
35
  id: Column.text().pipe(Column.primaryKey),
25
- email: Column.text(),
26
- email_verified: Column.int(),
27
36
  created_at: Column.int(),
28
37
  updated_at: Column.int(),
29
38
  disabled_at: Column.int().pipe(Column.nullable),
30
39
  metadata: Column.text().pipe(Column.nullable),
31
40
  });
41
+ const makeAuthUserIdentityTable = (name) => Table.make(name, {
42
+ id: Column.text().pipe(Column.primaryKey),
43
+ user_id: Column.text(),
44
+ scope_type: Column.text(),
45
+ scope_id: Column.text(),
46
+ kind: Column.text(),
47
+ value: Column.text(),
48
+ normalized_value: Column.text(),
49
+ verified_at: Column.int().pipe(Column.nullable),
50
+ is_primary_login: Column.int(),
51
+ created_at: Column.int(),
52
+ updated_at: Column.int(),
53
+ revoked_at: Column.int().pipe(Column.nullable),
54
+ replaced_by_id: Column.text().pipe(Column.nullable),
55
+ metadata: Column.text().pipe(Column.nullable),
56
+ });
32
57
  const makeAuthCredentialTable = (name) => Table.make(name, {
33
58
  id: Column.text().pipe(Column.primaryKey),
34
59
  user_id: Column.text(),
@@ -46,6 +71,7 @@ const makeAuthSessionTable = (name) => Table.make(name, {
46
71
  created_at: Column.int(),
47
72
  expires_at: Column.int(),
48
73
  auth_time: Column.int(),
74
+ authentication_events: Column.text(),
49
75
  aal: Column.text(),
50
76
  amr: Column.text(),
51
77
  mfa_verified_at: Column.int().pipe(Column.nullable),
@@ -65,6 +91,7 @@ const makeAuthVerificationTable = (name) => Table.make(name, {
65
91
  consumed_at: Column.int().pipe(Column.nullable),
66
92
  });
67
93
  const makeAuthAuditLogTable = (name) => Table.make(name, {
94
+ storage_id: Column.int().pipe(Column.primaryKey),
68
95
  id: Column.text().pipe(Column.nullable),
69
96
  type: Column.text(),
70
97
  user_id: Column.text().pipe(Column.nullable),
@@ -142,6 +169,7 @@ const makeAuthTotpFactorTable = (name) => Table.make(name, {
142
169
  created_at: Column.int(),
143
170
  confirmed_at: Column.int().pipe(Column.nullable),
144
171
  last_used_at: Column.int().pipe(Column.nullable),
172
+ last_accepted_counter: Column.int().pipe(Column.nullable),
145
173
  revoked_at: Column.int().pipe(Column.nullable),
146
174
  metadata: Column.text().pipe(Column.nullable),
147
175
  });
@@ -250,7 +278,7 @@ const makeAuthOAuthAuthorizationCodeTable = (name) => Table.make(name, {
250
278
  metadata: Column.text().pipe(Column.nullable),
251
279
  });
252
280
  const makeAuthOAuthClientSecretTable = (name) => Table.make(name, {
253
- prefix: Column.text().pipe(Column.primaryKey),
281
+ prefix: Column.text(),
254
282
  client_id: Column.text(),
255
283
  secret_hash: Column.text(),
256
284
  authentication_methods: Column.text(),
@@ -320,6 +348,52 @@ const makeAuthWebhookReplayTable = (name) => Table.make(name, {
320
348
  expires_at: Column.int(),
321
349
  created_at: Column.int(),
322
350
  });
351
+ const makeAuthPermissionDefinitionTable = (name) => Table.make(name, {
352
+ id: Column.text().pipe(Column.primaryKey),
353
+ description: Column.text().pipe(Column.nullable),
354
+ scope_type_present: Column.int(),
355
+ scope_type: Column.text(),
356
+ created_at: Column.int(),
357
+ updated_at: Column.int(),
358
+ disabled_at: Column.int().pipe(Column.nullable),
359
+ deleted_at: Column.int().pipe(Column.nullable),
360
+ });
361
+ const makeAuthRoleDefinitionTable = (name) => Table.make(name, {
362
+ id: Column.text().pipe(Column.primaryKey),
363
+ description: Column.text().pipe(Column.nullable),
364
+ created_at: Column.int(),
365
+ updated_at: Column.int(),
366
+ disabled_at: Column.int().pipe(Column.nullable),
367
+ deleted_at: Column.int().pipe(Column.nullable),
368
+ });
369
+ const makeAuthPermissionGrantTable = (name) => Table.make(name, {
370
+ subject_type: Column.text(),
371
+ subject_id: Column.text(),
372
+ permission_id: Column.text(),
373
+ scope_type: Column.text(),
374
+ scope_id_present: Column.int(),
375
+ scope_id: Column.text(),
376
+ expires_at: Column.int().pipe(Column.nullable),
377
+ metadata: Column.text().pipe(Column.nullable),
378
+ revoked_at: Column.int().pipe(Column.nullable),
379
+ });
380
+ const makeAuthRoleGrantTable = (name) => Table.make(name, {
381
+ subject_type: Column.text(),
382
+ subject_id: Column.text(),
383
+ role_id: Column.text(),
384
+ scope_type: Column.text(),
385
+ scope_id_present: Column.int(),
386
+ scope_id: Column.text(),
387
+ expires_at: Column.int().pipe(Column.nullable),
388
+ metadata: Column.text().pipe(Column.nullable),
389
+ revoked_at: Column.int().pipe(Column.nullable),
390
+ });
391
+ const makeAuthRolePermissionTable = (name) => Table.make(name, {
392
+ role_id: Column.text(),
393
+ permission_id: Column.text(),
394
+ scope_type_present: Column.int(),
395
+ scope_type: Column.text(),
396
+ });
323
397
  const authSqliteTablesTypeId = Symbol.for("@effect-auth/core/EffectQbSqliteStorage/AuthSqliteTables");
324
398
  const jsonEncode = (value) => value === undefined ? null : JSON.stringify(value);
325
399
  const jsonDecode = (value) => {
@@ -346,14 +420,50 @@ const decodeBoolean = (value) => {
346
420
  };
347
421
  const mutationValues = (value) => value;
348
422
  const queryInsert = (table, values) => Query.insert(table, values);
423
+ const queryInsertMany = (table, values) => Query.from(Query.values(values))(Query.insert(table));
349
424
  const queryUpdate = (table, values) => Query.update(table, values);
350
425
  const queryEq = (left, right) => Query.eq(left, right);
426
+ const queryNeq = (left, right) => Query.neq(left, right);
351
427
  const queryGt = (left, right) => Query.gt(left, right);
352
428
  const queryLt = (left, right) => Query.lt(left, right);
353
429
  const queryLte = (left, right) => Query.lte(left, right);
354
430
  const queryIsNull = (value) => Query.isNull(value);
431
+ const queryIsNotNull = (value) => Query.isNotNull(value);
432
+ const queryIsNotDistinctFrom = (left, right) => Query.isNotDistinctFrom(left, right);
433
+ const queryAnd = (...values) => Query.and(...values);
434
+ const queryOr = (...values) => Query.or(...values);
435
+ const queryNot = (value) => Query.not(value);
436
+ const queryExists = (plan) => Query.exists(plan);
437
+ const queryScalar = (plan) => Query.scalar(plan);
438
+ const queryChanges = () => Query.call("changes");
439
+ const queryChangedOne = () => queryEq(queryChanges(), 1);
440
+ const queryWithoutNullRefinement = (value) => Query.cast(value, value[Scalar.TypeId].dbType);
355
441
  const queryIn = (value, first, ...rest) => Query.in(value, first, ...rest);
442
+ const aliasTable = (table, alias) => Table.alias(table, alias);
443
+ const aliasColumn = (table, column) => table[boundColumnName(column)];
444
+ const queryAstTypeId = Symbol.for("effect-qb/QueryAst");
445
+ const targetMutationExpression = (table, columnName, value) => {
446
+ if (value === null) {
447
+ return Query.literal(null);
448
+ }
449
+ const plan = queryUpdate(table, { [columnName]: value });
450
+ const ast = plan[queryAstTypeId];
451
+ const assignment = ast?.set?.find((candidate) => candidate.columnName === columnName);
452
+ if (assignment === undefined) {
453
+ throw new TypeError(`Invalid SQLite mutation column: ${columnName}`);
454
+ }
455
+ return assignment.value;
456
+ };
457
+ const queryInsertWhere = (table, values, predicate) => {
458
+ const selection = Object.fromEntries(Object.entries(values).map(([key, value]) => [
459
+ key,
460
+ targetMutationExpression(table, key, value),
461
+ ]));
462
+ const select = Query.where(predicate)(Query.select(selection));
463
+ return Query.from(select)(Query.insert(table));
464
+ };
356
465
  const executePlan = (executor, plan) => executor.execute(plan);
466
+ const executeAtomicPlans = (atomic, plans) => atomic.execute(plans);
357
467
  const omitUndefined = (record) => Object.fromEntries(Object.entries(record).filter(([, value]) => value !== undefined));
358
468
  const sessionMetadataEnvelopeKey = "__effectAuthSession";
359
469
  const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
@@ -426,13 +536,31 @@ const encodeSessionMetadata = (row) => {
426
536
  };
427
537
  const decodeDefaultUserRow = (row) => omitUndefined({
428
538
  id: UserId(row.id),
429
- email: Email(row.email),
430
- emailVerified: decodeBoolean(row.emailVerified),
431
539
  createdAt: UnixMillis(Number(row.createdAt)),
432
540
  updatedAt: UnixMillis(Number(row.updatedAt)),
433
541
  disabledAt: optionalUnixMillis(row.disabledAt),
434
542
  metadata: jsonDecode(row.metadata),
435
543
  });
544
+ const decodeDefaultUserIdentityRow = (row) => omitUndefined({
545
+ id: IdentityId(row.id),
546
+ userId: UserId(row.userId),
547
+ scope: row.scopeType === "global"
548
+ ? { type: "global" }
549
+ : { type: "tenant", tenantId: row.scopeId },
550
+ kind: row.kind,
551
+ value: row.value,
552
+ normalizedValue: row.normalizedValue,
553
+ verifiedAt: optionalUnixMillis(row.verifiedAt),
554
+ isPrimaryLogin: decodeBoolean(row.isPrimaryLogin),
555
+ createdAt: UnixMillis(Number(row.createdAt)),
556
+ updatedAt: UnixMillis(Number(row.updatedAt)),
557
+ revokedAt: optionalUnixMillis(row.revokedAt),
558
+ replacedById: row.replacedById === null ? undefined : IdentityId(row.replacedById),
559
+ metadata: jsonDecode(row.metadata),
560
+ });
561
+ const decodeUserIdentityRow = (tables, row) => tables.identity.decode === undefined
562
+ ? decodeDefaultUserIdentityRow(row)
563
+ : tables.identity.decode(row);
436
564
  const decodeUserRow = (tables, row) => tables.user.decode === undefined
437
565
  ? decodeDefaultUserRow(row)
438
566
  : tables.user.decode(row);
@@ -450,6 +578,34 @@ const decodePasswordCredentialRow = (tables, row) => tables.credential.decodePas
450
578
  : tables.credential.decodePassword(row);
451
579
  const decodeDefaultSessionRow = (row) => {
452
580
  const sessionMetadata = decodeSessionMetadata(row.metadata);
581
+ const authenticationEvents = parseAuthenticationEvents(jsonDecode(row.authenticationEvents));
582
+ if (authenticationEvents.some((event) => event.type === "custom")) {
583
+ return omitUndefined({
584
+ id: SessionId(row.id),
585
+ userId: UserId(row.userId),
586
+ secretHash: row.secretHash,
587
+ createdAt: UnixMillis(Number(row.createdAt)),
588
+ expiresAt: UnixMillis(Number(row.expiresAt)),
589
+ authTime: UnixMillis(Number(row.authTime)),
590
+ authenticationEvents,
591
+ aal: row.aal,
592
+ amr: decodeAmr(row.amr),
593
+ mfaVerifiedAt: optionalUnixMillis(row.mfaVerifiedAt),
594
+ claims: sessionMetadata.claims,
595
+ metadata: sessionMetadata.metadata,
596
+ revokedAt: optionalUnixMillis(row.revokedAt),
597
+ lastSeenAt: optionalUnixMillis(row.lastSeenAt),
598
+ rotatedAt: optionalUnixMillis(row.rotatedAt),
599
+ });
600
+ }
601
+ const assurance = deriveAssurance(authenticationEvents);
602
+ const mfaVerifiedAt = deriveMfaVerifiedAt(authenticationEvents);
603
+ if (row.aal !== assurance.level ||
604
+ JSON.stringify(decodeAmr(row.amr)) !== JSON.stringify(assurance.amr) ||
605
+ optionalUnixMillis(row.mfaVerifiedAt) !== mfaVerifiedAt ||
606
+ Number(row.authTime) !== Number(assurance.authenticatedAt)) {
607
+ throw new Error("Session assurance summary does not match authentication events");
608
+ }
453
609
  return omitUndefined({
454
610
  id: SessionId(row.id),
455
611
  userId: UserId(row.userId),
@@ -457,6 +613,7 @@ const decodeDefaultSessionRow = (row) => {
457
613
  createdAt: UnixMillis(Number(row.createdAt)),
458
614
  expiresAt: UnixMillis(Number(row.expiresAt)),
459
615
  authTime: UnixMillis(Number(row.authTime)),
616
+ authenticationEvents,
460
617
  aal: row.aal,
461
618
  amr: decodeAmr(row.amr),
462
619
  mfaVerifiedAt: optionalUnixMillis(row.mfaVerifiedAt),
@@ -467,9 +624,13 @@ const decodeDefaultSessionRow = (row) => {
467
624
  rotatedAt: optionalUnixMillis(row.rotatedAt),
468
625
  });
469
626
  };
470
- const decodeSessionRow = (tables, row) => tables.session.decode === undefined
471
- ? decodeDefaultSessionRow(row)
472
- : tables.session.decode(row);
627
+ const decodeSessionRow = (tables, row) => {
628
+ const decoded = tables.session.decode === undefined
629
+ ? decodeDefaultSessionRow(row)
630
+ : tables.session.decode(row);
631
+ assertSessionAssurance(decoded);
632
+ return decoded;
633
+ };
473
634
  const decodeDefaultVerificationRow = (row) => omitUndefined({
474
635
  id: ChallengeId(row.id),
475
636
  type: row.type,
@@ -483,7 +644,7 @@ const decodeDefaultVerificationRow = (row) => omitUndefined({
483
644
  const decodeVerificationRow = (tables, row) => tables.verification.decode === undefined
484
645
  ? decodeDefaultVerificationRow(row)
485
646
  : tables.verification.decode(row);
486
- const decodeDefaultAuditLogEvent = (row) => jsonDecode(row.event);
647
+ const decodeDefaultAuditLogEvent = (row) => decodeAuditEvent(jsonDecode(row.event));
487
648
  const decodeAuditLogEvent = (tables, row) => tables.auditLog.decode === undefined
488
649
  ? decodeDefaultAuditLogEvent(row)
489
650
  : tables.auditLog.decode(row);
@@ -523,7 +684,9 @@ const decodeDefaultLoginRiskHistoryRecord = (row) => omitUndefined({
523
684
  locationKey: row.locationKey ?? undefined,
524
685
  country: row.country ?? undefined,
525
686
  region: row.region ?? undefined,
526
- latitude: row.latitudeMicro === null ? undefined : Number(row.latitudeMicro) / 1_000_000,
687
+ latitude: row.latitudeMicro === null
688
+ ? undefined
689
+ : Number(row.latitudeMicro) / 1_000_000,
527
690
  longitude: row.longitudeMicro === null
528
691
  ? undefined
529
692
  : Number(row.longitudeMicro) / 1_000_000,
@@ -546,7 +709,9 @@ const decodeTrustedDeviceRow = (tables, row) => tables.trustedDevice.decode ===
546
709
  : tables.trustedDevice.decode(row);
547
710
  const decodePasskeyTransports = (value) => {
548
711
  const decoded = jsonDecode(value);
549
- return Array.isArray(decoded) ? decoded : undefined;
712
+ return Array.isArray(decoded)
713
+ ? decoded
714
+ : undefined;
550
715
  };
551
716
  const decodeDefaultPasskeyCredentialRow = (row) => omitUndefined({
552
717
  id: CredentialId(row.id),
@@ -576,6 +741,9 @@ const decodeDefaultTotpFactorRow = (row) => omitUndefined({
576
741
  createdAt: UnixMillis(Number(row.createdAt)),
577
742
  confirmedAt: optionalUnixMillis(row.confirmedAt),
578
743
  lastUsedAt: optionalUnixMillis(row.lastUsedAt),
744
+ lastAcceptedCounter: row.lastAcceptedCounter === null
745
+ ? undefined
746
+ : Number(row.lastAcceptedCounter),
579
747
  revokedAt: optionalUnixMillis(row.revokedAt),
580
748
  metadata: jsonDecode(row.metadata),
581
749
  });
@@ -712,7 +880,8 @@ const decodeOAuthConsentRow = (tables, row) => tables.oauthConsent.decode === un
712
880
  : tables.oauthConsent.decode(row);
713
881
  const oauthConsentCoversScopes = (record, scopes = []) => scopes.every((scope) => record.scopes.includes(scope));
714
882
  const isOAuthConsentActive = (record, input) => record.revokedAt === undefined &&
715
- (record.expiresAt === undefined || Number(record.expiresAt) > Number(input.now)) &&
883
+ (record.expiresAt === undefined ||
884
+ Number(record.expiresAt) > Number(input.now)) &&
716
885
  oauthConsentCoversScopes(record, input.scopes);
717
886
  const oauthClientRedirectUriValidationResult = (client, input) => {
718
887
  if (client === undefined) {
@@ -740,7 +909,9 @@ const decodeDefaultOAuthAuthorizationCodeRow = (row) => omitUndefined({
740
909
  subject: row.subject,
741
910
  redirectUri: row.redirectUri,
742
911
  scopes: decodeStringArray(row.scopes),
743
- codeChallenge: row.codeChallenge === null ? undefined : OAuthCodeChallenge(row.codeChallenge),
912
+ codeChallenge: row.codeChallenge === null
913
+ ? undefined
914
+ : OAuthCodeChallenge(row.codeChallenge),
744
915
  codeChallengeMethod: row.codeChallengeMethod === null
745
916
  ? undefined
746
917
  : row.codeChallengeMethod,
@@ -840,6 +1011,82 @@ const decodeDefaultWebhookOutboxRecord = (row) => omitUndefined({
840
1011
  const decodeWebhookOutboxRecord = (tables, row) => tables.webhookOutbox.decode === undefined
841
1012
  ? decodeDefaultWebhookOutboxRecord(row)
842
1013
  : tables.webhookOutbox.decode(row);
1014
+ const decodeDefaultPermissionDefinition = (row) => omitUndefined({
1015
+ id: PermissionId(row.id),
1016
+ description: row.description ?? undefined,
1017
+ scopeType: decodeBoolean(row.scopeTypePresent) ? row.scopeType : undefined,
1018
+ createdAt: UnixMillis(Number(row.createdAt)),
1019
+ updatedAt: UnixMillis(Number(row.updatedAt)),
1020
+ disabledAt: optionalUnixMillis(row.disabledAt),
1021
+ deletedAt: optionalUnixMillis(row.deletedAt),
1022
+ });
1023
+ const decodePermissionDefinition = (tables, row) => tables.permissionDefinition.decode === undefined
1024
+ ? decodeDefaultPermissionDefinition(row)
1025
+ : tables.permissionDefinition.decode(row);
1026
+ const decodeDefaultRoleDefinition = (row) => omitUndefined({
1027
+ id: RoleId(row.id),
1028
+ description: row.description ?? undefined,
1029
+ createdAt: UnixMillis(Number(row.createdAt)),
1030
+ updatedAt: UnixMillis(Number(row.updatedAt)),
1031
+ disabledAt: optionalUnixMillis(row.disabledAt),
1032
+ deletedAt: optionalUnixMillis(row.deletedAt),
1033
+ });
1034
+ const decodeRoleDefinition = (tables, row) => tables.roleDefinition.decode === undefined
1035
+ ? decodeDefaultRoleDefinition(row)
1036
+ : tables.roleDefinition.decode(row);
1037
+ const decodePermissionScope = (scopeType, scopeIdPresent, scopeId) => {
1038
+ const hasId = decodeBoolean(scopeIdPresent);
1039
+ if (scopeType === "global" && !hasId) {
1040
+ return undefined;
1041
+ }
1042
+ return hasId ? { type: scopeType, id: scopeId } : { type: scopeType };
1043
+ };
1044
+ const decodeDefaultPermissionGrant = (row) => omitUndefined({
1045
+ subject: { type: row.subjectType, id: row.subjectId },
1046
+ permission: PermissionId(row.permissionId),
1047
+ scope: decodePermissionScope(row.scopeType, row.scopeIdPresent, row.scopeId),
1048
+ expiresAt: optionalUnixMillis(row.expiresAt),
1049
+ metadata: jsonDecode(row.metadata),
1050
+ revokedAt: optionalUnixMillis(row.revokedAt),
1051
+ });
1052
+ const decodePermissionGrant = (tables, row) => {
1053
+ const decoded = tables.permissionGrant.decode === undefined
1054
+ ? decodeDefaultPermissionGrant(row)
1055
+ : tables.permissionGrant.decode(row);
1056
+ const { scope: _scope, ...withoutScope } = decoded;
1057
+ const scope = canonicalPermissionScope(decoded.scope);
1058
+ return {
1059
+ ...withoutScope,
1060
+ ...(scope === undefined ? {} : { scope }),
1061
+ };
1062
+ };
1063
+ const decodeDefaultRoleGrant = (row) => omitUndefined({
1064
+ subject: { type: row.subjectType, id: row.subjectId },
1065
+ role: RoleId(row.roleId),
1066
+ scope: decodePermissionScope(row.scopeType, row.scopeIdPresent, row.scopeId),
1067
+ expiresAt: optionalUnixMillis(row.expiresAt),
1068
+ metadata: jsonDecode(row.metadata),
1069
+ revokedAt: optionalUnixMillis(row.revokedAt),
1070
+ });
1071
+ const decodeRoleGrant = (tables, row) => {
1072
+ const decoded = tables.roleGrant.decode === undefined
1073
+ ? decodeDefaultRoleGrant(row)
1074
+ : tables.roleGrant.decode(row);
1075
+ const { scope: _scope, ...withoutScope } = decoded;
1076
+ const scope = canonicalPermissionScope(decoded.scope);
1077
+ return {
1078
+ ...withoutScope,
1079
+ ...(scope === undefined ? {} : { scope }),
1080
+ };
1081
+ };
1082
+ const decodeDefaultRolePermission = (row) => omitUndefined({
1083
+ role: RoleId(row.roleId),
1084
+ permission: PermissionId(row.permissionId),
1085
+ scopeType: decodeBoolean(row.scopeTypePresent) ? row.scopeType : undefined,
1086
+ });
1087
+ const decodeRolePermission = (tables, row) => tables.rolePermission.decode === undefined
1088
+ ? decodeDefaultRolePermission(row)
1089
+ : tables.rolePermission.decode(row);
843
1090
  const providerTokenVaultTokens = (record) => omitUndefined({
844
1091
  accessTokenCiphertext: record.accessTokenCiphertext,
845
1092
  refreshTokenCiphertext: record.refreshTokenCiphertext,
@@ -850,13 +1097,50 @@ const providerTokenVaultTokens = (record) => omitUndefined({
850
1097
  });
851
1098
  const userInsert = (row) => ({
852
1099
  id: row.id,
853
- email: row.email,
854
- email_verified: row.emailVerified ? 1 : 0,
855
1100
  created_at: Number(row.createdAt),
856
1101
  updated_at: Number(row.updatedAt),
857
1102
  disabled_at: row.disabledAt === undefined ? null : Number(row.disabledAt),
858
1103
  metadata: jsonEncode(row.metadata),
859
1104
  });
1105
+ const identityScopeId = (scope) => scope.type === "global" ? "global" : scope.tenantId;
1106
+ const userIdentityInsert = (row) => ({
1107
+ id: row.id,
1108
+ user_id: row.userId,
1109
+ scope_type: row.scope.type,
1110
+ scope_id: identityScopeId(row.scope),
1111
+ kind: row.kind,
1112
+ value: row.value,
1113
+ normalized_value: row.normalizedValue,
1114
+ verified_at: row.verifiedAt === undefined ? null : Number(row.verifiedAt),
1115
+ is_primary_login: row.isPrimaryLogin === true ? 1 : 0,
1116
+ created_at: Number(row.createdAt),
1117
+ updated_at: Number(row.createdAt),
1118
+ revoked_at: null,
1119
+ replaced_by_id: null,
1120
+ metadata: jsonEncode(row.metadata),
1121
+ });
1122
+ const userIdentityVerify = (input) => ({
1123
+ verified_at: Number(input.verifiedAt),
1124
+ updated_at: Number(input.verifiedAt),
1125
+ });
1126
+ const userIdentitySetPrimary = (input, primary) => ({
1127
+ is_primary_login: primary ? 1 : 0,
1128
+ updated_at: Number(input.updatedAt),
1129
+ });
1130
+ const userIdentityReplace = (input) => ({
1131
+ is_primary_login: 0,
1132
+ revoked_at: Number(input.replacedAt),
1133
+ replaced_by_id: input.replacement.id,
1134
+ updated_at: Number(input.replacedAt),
1135
+ });
1136
+ const userIdentityRevoke = (input, _row, metadata) => ({
1137
+ is_primary_login: 0,
1138
+ revoked_at: Number(input.revokedAt),
1139
+ updated_at: Number(input.revokedAt),
1140
+ metadata: input.reason === undefined
1141
+ ? metadata
1142
+ : Query.call("json_set", Query.coalesce(metadata, "{}"), "$.revokeReason", input.reason),
1143
+ });
860
1144
  const passwordCredentialInsert = (row) => ({
861
1145
  id: row.id,
862
1146
  user_id: row.userId,
@@ -871,21 +1155,40 @@ const passwordCredentialUpdatePassword = (input) => ({
871
1155
  password_hash: input.passwordHash,
872
1156
  updated_at: Number(input.updatedAt),
873
1157
  });
874
- const sessionInsert = (row) => ({
875
- id: row.id,
876
- user_id: row.userId,
877
- secret_hash: row.secretHash,
878
- created_at: Number(row.createdAt),
879
- expires_at: Number(row.expiresAt),
880
- auth_time: Number(row.authTime),
881
- aal: row.aal,
882
- amr: JSON.stringify(row.amr),
883
- mfa_verified_at: row.mfaVerifiedAt === undefined ? null : Number(row.mfaVerifiedAt),
884
- metadata: encodeSessionMetadata(row),
885
- revoked_at: null,
886
- last_seen_at: null,
887
- rotated_at: null,
888
- });
1158
+ const assertSessionAssurance = (row) => {
1159
+ const authenticationEvents = parseAuthenticationEvents(row.authenticationEvents);
1160
+ // Sessions owns registry-aware validation for custom evidence.
1161
+ if (authenticationEvents.some((event) => event.type === "custom")) {
1162
+ return;
1163
+ }
1164
+ const assurance = deriveAssurance(authenticationEvents);
1165
+ if (assurance.authenticatedAt === undefined ||
1166
+ row.aal !== assurance.level ||
1167
+ JSON.stringify(row.amr) !== JSON.stringify(assurance.amr) ||
1168
+ row.mfaVerifiedAt !== deriveMfaVerifiedAt(authenticationEvents) ||
1169
+ Number(row.authTime) !== Number(assurance.authenticatedAt)) {
1170
+ throw new Error("Session assurance summary does not match authentication events");
1171
+ }
1172
+ };
1173
+ const sessionInsert = (row) => {
1174
+ assertSessionAssurance(row);
1175
+ return {
1176
+ id: row.id,
1177
+ user_id: row.userId,
1178
+ secret_hash: row.secretHash,
1179
+ created_at: Number(row.createdAt),
1180
+ expires_at: Number(row.expiresAt),
1181
+ auth_time: Number(row.authTime),
1182
+ authentication_events: JSON.stringify(row.authenticationEvents),
1183
+ aal: row.aal,
1184
+ amr: JSON.stringify(row.amr),
1185
+ mfa_verified_at: row.mfaVerifiedAt === undefined ? null : Number(row.mfaVerifiedAt),
1186
+ metadata: encodeSessionMetadata(row),
1187
+ revoked_at: null,
1188
+ last_seen_at: null,
1189
+ rotated_at: null,
1190
+ };
1191
+ };
889
1192
  const verificationInsert = (row) => ({
890
1193
  id: row.id,
891
1194
  type: row.type,
@@ -946,10 +1249,6 @@ const loginRiskHistoryInsert = (row) => ({
946
1249
  risk_level: row.riskLevel ?? null,
947
1250
  created_at: Number(row.createdAt),
948
1251
  });
949
- const userMarkEmailVerifiedUpdate = (input) => ({
950
- email_verified: 1,
951
- updated_at: Number(input.updatedAt),
952
- });
953
1252
  const sessionRefreshUpdate = (input) => omitUndefined({
954
1253
  expires_at: Number(input.expiresAt),
955
1254
  last_seen_at: input.lastSeenAt === undefined ? undefined : Number(input.lastSeenAt),
@@ -960,14 +1259,28 @@ const sessionRotateUpdate = (input) => omitUndefined({
960
1259
  expires_at: input.expiresAt === undefined ? undefined : Number(input.expiresAt),
961
1260
  });
962
1261
  const sessionUpdateClaimsUpdate = (row, input) => omitUndefined({
963
- aal: input.aal,
964
- amr: input.amr === undefined ? undefined : JSON.stringify(input.amr),
965
- mfa_verified_at: input.mfaVerifiedAt === undefined ? undefined : Number(input.mfaVerifiedAt),
966
1262
  metadata: encodeSessionMetadata({
967
1263
  ...row,
968
1264
  claims: input.claims ?? row.claims,
969
1265
  }),
970
1266
  });
1267
+ const sessionAppendEvidenceUpdate = (input) => {
1268
+ assertSessionAssurance(input);
1269
+ return {
1270
+ authentication_events: JSON.stringify(input.authenticationEvents),
1271
+ auth_time: Number(input.authTime),
1272
+ aal: input.aal,
1273
+ amr: JSON.stringify(input.amr),
1274
+ mfa_verified_at: input.mfaVerifiedAt === undefined ? null : Number(input.mfaVerifiedAt),
1275
+ };
1276
+ };
1277
+ const sessionAssureAndRotateUpdate = (row, input) => ({
1278
+ ...sessionAppendEvidenceUpdate(input),
1279
+ ...sessionRotateUpdate(input),
1280
+ ...(input.claims === undefined
1281
+ ? {}
1282
+ : { metadata: encodeSessionMetadata({ ...row, claims: input.claims }) }),
1283
+ });
971
1284
  const sessionRevokeUpdate = (input) => ({
972
1285
  revoked_at: Number(input.revokedAt),
973
1286
  });
@@ -1032,6 +1345,7 @@ const totpFactorInsert = (row) => ({
1032
1345
  created_at: Number(row.createdAt),
1033
1346
  confirmed_at: row.confirmedAt === undefined ? null : Number(row.confirmedAt),
1034
1347
  last_used_at: row.lastUsedAt === undefined ? null : Number(row.lastUsedAt),
1348
+ last_accepted_counter: row.lastAcceptedCounter ?? null,
1035
1349
  revoked_at: row.revokedAt === undefined ? null : Number(row.revokedAt),
1036
1350
  metadata: jsonEncode(row.metadata),
1037
1351
  });
@@ -1039,8 +1353,9 @@ const totpFactorConfirm = (input) => omitUndefined({
1039
1353
  confirmed_at: Number(input.confirmedAt),
1040
1354
  metadata: input.metadata === undefined ? undefined : jsonEncode(input.metadata),
1041
1355
  });
1042
- const totpFactorMarkUsed = (input) => omitUndefined({
1356
+ const totpFactorAcceptCounter = (input) => omitUndefined({
1043
1357
  last_used_at: Number(input.lastUsedAt),
1358
+ last_accepted_counter: input.counter,
1044
1359
  metadata: input.metadata === undefined ? undefined : jsonEncode(input.metadata),
1045
1360
  });
1046
1361
  const totpFactorRevoke = (input, row) => ({
@@ -1060,6 +1375,14 @@ const recoveryCodeMarkUsed = (input) => omitUndefined({
1060
1375
  used_at: Number(input.usedAt),
1061
1376
  metadata: input.metadata === undefined ? undefined : jsonEncode(input.metadata),
1062
1377
  });
1378
+ const recoveryCodeReplaceActive = (input, metadata) => ({
1379
+ revoked_at: Number(input.revokedAt),
1380
+ ...(input.revokeReason === undefined
1381
+ ? {}
1382
+ : {
1383
+ metadata: Query.call("json_set", Query.coalesce(metadata, "{}"), "$.revokeReason", input.revokeReason),
1384
+ }),
1385
+ });
1063
1386
  const recoveryCodeRevoke = (input, row) => ({
1064
1387
  revoked_at: Number(input.revokedAt),
1065
1388
  metadata: jsonEncode(metadataWithReason(row.metadata, "revokeReason", input.reason)),
@@ -1145,7 +1468,9 @@ const oauthProviderTokenVaultValues = (row) => ({
1145
1468
  refresh_token_ciphertext: row.refreshTokenCiphertext === undefined
1146
1469
  ? null
1147
1470
  : Redacted.value(row.refreshTokenCiphertext),
1148
- id_token_ciphertext: row.idTokenCiphertext === undefined ? null : Redacted.value(row.idTokenCiphertext),
1471
+ id_token_ciphertext: row.idTokenCiphertext === undefined
1472
+ ? null
1473
+ : Redacted.value(row.idTokenCiphertext),
1149
1474
  token_type: row.tokenType,
1150
1475
  scopes: jsonEncode(row.scopes),
1151
1476
  expires_at: row.expiresAt === undefined ? null : Number(row.expiresAt),
@@ -1321,6 +1646,101 @@ const webhookReplayUpdate = (input) => ({
1321
1646
  expires_at: Number(input.expiresAt),
1322
1647
  created_at: Number(input.now),
1323
1648
  });
1649
+ const permissionScopeValues = (scope) => ({
1650
+ scope_type: scope?.type ?? "global",
1651
+ scope_id_present: scope?.id === undefined ? 0 : 1,
1652
+ scope_id: scope?.id ?? "",
1653
+ });
1654
+ const permissionGrantValues = (input) => ({
1655
+ subject_type: input.subject.type,
1656
+ subject_id: input.subject.id,
1657
+ permission_id: input.permission,
1658
+ ...permissionScopeValues(input.scope),
1659
+ expires_at: input.expiresAt === undefined ? null : Number(input.expiresAt),
1660
+ metadata: jsonEncode(input.metadata),
1661
+ revoked_at: null,
1662
+ });
1663
+ const permissionGrantUpdate = (input) => ({
1664
+ expires_at: input.expiresAt === undefined ? null : Number(input.expiresAt),
1665
+ metadata: jsonEncode(input.metadata),
1666
+ revoked_at: null,
1667
+ });
1668
+ const permissionGrantRevoke = (_input, revokedAt) => ({ revoked_at: Number(revokedAt) });
1669
+ const roleGrantValues = (input) => ({
1670
+ subject_type: input.subject.type,
1671
+ subject_id: input.subject.id,
1672
+ role_id: input.role,
1673
+ ...permissionScopeValues(input.scope),
1674
+ expires_at: input.expiresAt === undefined ? null : Number(input.expiresAt),
1675
+ metadata: jsonEncode(input.metadata),
1676
+ revoked_at: null,
1677
+ });
1678
+ const roleGrantUpdate = (input) => ({
1679
+ expires_at: input.expiresAt === undefined ? null : Number(input.expiresAt),
1680
+ metadata: jsonEncode(input.metadata),
1681
+ revoked_at: null,
1682
+ });
1683
+ const roleGrantRevoke = (_input, revokedAt) => ({
1684
+ revoked_at: Number(revokedAt),
1685
+ });
1686
+ const rolePermissionValues = (input) => ({
1687
+ role_id: input.role,
1688
+ permission_id: input.permission,
1689
+ scope_type_present: input.scopeType === undefined ? 0 : 1,
1690
+ scope_type: input.scopeType ?? "",
1691
+ });
1692
+ const permissionDefinitionValues = (definition) => ({
1693
+ id: definition.id,
1694
+ description: definition.description ?? null,
1695
+ scope_type_present: definition.scopeType === undefined ? 0 : 1,
1696
+ scope_type: definition.scopeType ?? "",
1697
+ created_at: Number(definition.createdAt),
1698
+ updated_at: Number(definition.updatedAt),
1699
+ disabled_at: definition.disabledAt === undefined ? null : Number(definition.disabledAt),
1700
+ deleted_at: definition.deletedAt === undefined ? null : Number(definition.deletedAt),
1701
+ });
1702
+ const permissionDefinitionUpdate = (input) => ({
1703
+ ...(input.description === undefined
1704
+ ? {}
1705
+ : { description: input.description }),
1706
+ ...(input.scopeType === undefined
1707
+ ? {}
1708
+ : {
1709
+ scope_type_present: input.scopeType === null ? 0 : 1,
1710
+ scope_type: input.scopeType ?? "",
1711
+ }),
1712
+ updated_at: Number(input.updatedAt),
1713
+ });
1714
+ const permissionDefinitionSetDisabled = (input) => ({
1715
+ disabled_at: input.disabledAt === undefined ? null : Number(input.disabledAt),
1716
+ updated_at: Number(input.updatedAt),
1717
+ });
1718
+ const permissionDefinitionDelete = (input) => ({
1719
+ deleted_at: Number(input.deletedAt),
1720
+ updated_at: Number(input.updatedAt),
1721
+ });
1722
+ const roleDefinitionValues = (definition) => ({
1723
+ id: definition.id,
1724
+ description: definition.description ?? null,
1725
+ created_at: Number(definition.createdAt),
1726
+ updated_at: Number(definition.updatedAt),
1727
+ disabled_at: definition.disabledAt === undefined ? null : Number(definition.disabledAt),
1728
+ deleted_at: definition.deletedAt === undefined ? null : Number(definition.deletedAt),
1729
+ });
1730
+ const roleDefinitionUpdate = (input) => ({
1731
+ ...(input.description === undefined
1732
+ ? {}
1733
+ : { description: input.description }),
1734
+ updated_at: Number(input.updatedAt),
1735
+ });
1736
+ const roleDefinitionSetDisabled = (input) => ({
1737
+ disabled_at: input.disabledAt === undefined ? null : Number(input.disabledAt),
1738
+ updated_at: Number(input.updatedAt),
1739
+ });
1740
+ const roleDefinitionDelete = (input) => ({
1741
+ deleted_at: Number(input.deletedAt),
1742
+ updated_at: Number(input.updatedAt),
1743
+ });
1324
1744
  const jwtRevocationUpdate = (record) => ({
1325
1745
  revoked_at: Number(record.revokedAt),
1326
1746
  expires_at: record.expiresAt === undefined ? null : Number(record.expiresAt),
@@ -1329,6 +1749,7 @@ const jwtRevocationUpdate = (record) => ({
1329
1749
  const authSqliteTableName = (name, defaultName) => name ?? defaultName;
1330
1750
  const makeInternalAuthSqliteTables = (names = {}) => {
1331
1751
  const user = makeAuthUserTable(names.user ?? "auth_user");
1752
+ const identity = makeAuthUserIdentityTable(names.identity ?? "auth_user_identity");
1332
1753
  const credential = makeAuthCredentialTable(names.credential ?? "auth_credential");
1333
1754
  const session = makeAuthSessionTable(names.session ?? "auth_session");
1334
1755
  const verification = makeAuthVerificationTable(names.verification ?? "auth_verification");
@@ -1353,20 +1774,46 @@ const makeInternalAuthSqliteTables = (names = {}) => {
1353
1774
  const securityTimeline = makeAuthSecurityTimelineTable(names.securityTimeline ?? "auth_security_timeline");
1354
1775
  const webhookOutbox = makeAuthWebhookOutboxTable(names.webhookOutbox ?? "auth_webhook_outbox");
1355
1776
  const webhookReplay = makeAuthWebhookReplayTable(names.webhookReplay ?? "auth_webhook_replay");
1777
+ const permissionDefinition = makeAuthPermissionDefinitionTable(names.permissionDefinition ?? "auth_permission_definition");
1778
+ const roleDefinition = makeAuthRoleDefinitionTable(names.roleDefinition ?? "auth_role_definition");
1779
+ const permissionGrant = makeAuthPermissionGrantTable(names.permissionGrant ?? "auth_permission_grant");
1780
+ const roleGrant = makeAuthRoleGrantTable(names.roleGrant ?? "auth_role_grant");
1781
+ const rolePermission = makeAuthRolePermissionTable(names.rolePermission ?? "auth_role_permission");
1356
1782
  return {
1357
1783
  user: {
1358
1784
  table: user,
1359
1785
  select: {
1360
1786
  id: user.id,
1361
- email: user.email,
1362
- emailVerified: user.email_verified,
1363
1787
  createdAt: user.created_at,
1364
1788
  updatedAt: user.updated_at,
1365
1789
  disabledAt: user.disabled_at,
1366
1790
  metadata: user.metadata,
1367
1791
  },
1368
1792
  insert: userInsert,
1369
- markEmailVerified: userMarkEmailVerifiedUpdate,
1793
+ },
1794
+ identity: {
1795
+ table: identity,
1796
+ select: {
1797
+ id: identity.id,
1798
+ userId: identity.user_id,
1799
+ scopeType: identity.scope_type,
1800
+ scopeId: identity.scope_id,
1801
+ kind: identity.kind,
1802
+ value: identity.value,
1803
+ normalizedValue: identity.normalized_value,
1804
+ verifiedAt: identity.verified_at,
1805
+ isPrimaryLogin: identity.is_primary_login,
1806
+ createdAt: identity.created_at,
1807
+ updatedAt: identity.updated_at,
1808
+ revokedAt: identity.revoked_at,
1809
+ replacedById: identity.replaced_by_id,
1810
+ metadata: identity.metadata,
1811
+ },
1812
+ insert: userIdentityInsert,
1813
+ verify: userIdentityVerify,
1814
+ setPrimary: userIdentitySetPrimary,
1815
+ replace: userIdentityReplace,
1816
+ revoke: userIdentityRevoke,
1370
1817
  },
1371
1818
  credential: {
1372
1819
  table: credential,
@@ -1392,6 +1839,7 @@ const makeInternalAuthSqliteTables = (names = {}) => {
1392
1839
  createdAt: session.created_at,
1393
1840
  expiresAt: session.expires_at,
1394
1841
  authTime: session.auth_time,
1842
+ authenticationEvents: session.authentication_events,
1395
1843
  aal: session.aal,
1396
1844
  amr: session.amr,
1397
1845
  mfaVerifiedAt: session.mfa_verified_at,
@@ -1404,6 +1852,8 @@ const makeInternalAuthSqliteTables = (names = {}) => {
1404
1852
  refresh: sessionRefreshUpdate,
1405
1853
  rotate: sessionRotateUpdate,
1406
1854
  updateClaims: sessionUpdateClaimsUpdate,
1855
+ appendEvidence: sessionAppendEvidenceUpdate,
1856
+ assureAndRotate: sessionAssureAndRotateUpdate,
1407
1857
  revoke: sessionRevokeUpdate,
1408
1858
  },
1409
1859
  verification: {
@@ -1424,6 +1874,7 @@ const makeInternalAuthSqliteTables = (names = {}) => {
1424
1874
  auditLog: {
1425
1875
  table: auditLog,
1426
1876
  select: {
1877
+ storageOrder: auditLog.storage_id,
1427
1878
  id: auditLog.id,
1428
1879
  type: auditLog.type,
1429
1880
  userId: auditLog.user_id,
@@ -1526,12 +1977,13 @@ const makeInternalAuthSqliteTables = (names = {}) => {
1526
1977
  createdAt: totpFactor.created_at,
1527
1978
  confirmedAt: totpFactor.confirmed_at,
1528
1979
  lastUsedAt: totpFactor.last_used_at,
1980
+ lastAcceptedCounter: totpFactor.last_accepted_counter,
1529
1981
  revokedAt: totpFactor.revoked_at,
1530
1982
  metadata: totpFactor.metadata,
1531
1983
  },
1532
1984
  insert: totpFactorInsert,
1533
1985
  confirm: totpFactorConfirm,
1534
- markUsed: totpFactorMarkUsed,
1986
+ acceptCounter: totpFactorAcceptCounter,
1535
1987
  revoke: totpFactorRevoke,
1536
1988
  },
1537
1989
  recoveryCode: {
@@ -1547,6 +1999,7 @@ const makeInternalAuthSqliteTables = (names = {}) => {
1547
1999
  },
1548
2000
  insert: recoveryCodeInsert,
1549
2001
  markUsed: recoveryCodeMarkUsed,
2002
+ replaceActive: recoveryCodeReplaceActive,
1550
2003
  revoke: recoveryCodeRevoke,
1551
2004
  },
1552
2005
  apiKey: {
@@ -1799,6 +2252,84 @@ const makeInternalAuthSqliteTables = (names = {}) => {
1799
2252
  insert: webhookReplayInsert,
1800
2253
  update: webhookReplayUpdate,
1801
2254
  },
2255
+ permissionDefinition: {
2256
+ table: permissionDefinition,
2257
+ conflictTarget: "id",
2258
+ select: {
2259
+ id: permissionDefinition.id,
2260
+ description: permissionDefinition.description,
2261
+ scopeTypePresent: permissionDefinition.scope_type_present,
2262
+ scopeType: permissionDefinition.scope_type,
2263
+ createdAt: permissionDefinition.created_at,
2264
+ updatedAt: permissionDefinition.updated_at,
2265
+ disabledAt: permissionDefinition.disabled_at,
2266
+ deletedAt: permissionDefinition.deleted_at,
2267
+ },
2268
+ insert: permissionDefinitionValues,
2269
+ update: permissionDefinitionUpdate,
2270
+ setDisabled: permissionDefinitionSetDisabled,
2271
+ delete: permissionDefinitionDelete,
2272
+ },
2273
+ roleDefinition: {
2274
+ table: roleDefinition,
2275
+ conflictTarget: "id",
2276
+ select: {
2277
+ id: roleDefinition.id,
2278
+ description: roleDefinition.description,
2279
+ createdAt: roleDefinition.created_at,
2280
+ updatedAt: roleDefinition.updated_at,
2281
+ disabledAt: roleDefinition.disabled_at,
2282
+ deletedAt: roleDefinition.deleted_at,
2283
+ },
2284
+ insert: roleDefinitionValues,
2285
+ update: roleDefinitionUpdate,
2286
+ setDisabled: roleDefinitionSetDisabled,
2287
+ delete: roleDefinitionDelete,
2288
+ },
2289
+ permissionGrant: {
2290
+ table: permissionGrant,
2291
+ select: {
2292
+ subjectType: permissionGrant.subject_type,
2293
+ subjectId: permissionGrant.subject_id,
2294
+ permissionId: permissionGrant.permission_id,
2295
+ scopeType: permissionGrant.scope_type,
2296
+ scopeIdPresent: permissionGrant.scope_id_present,
2297
+ scopeId: permissionGrant.scope_id,
2298
+ expiresAt: permissionGrant.expires_at,
2299
+ metadata: permissionGrant.metadata,
2300
+ revokedAt: permissionGrant.revoked_at,
2301
+ },
2302
+ insert: permissionGrantValues,
2303
+ update: permissionGrantUpdate,
2304
+ revoke: permissionGrantRevoke,
2305
+ },
2306
+ roleGrant: {
2307
+ table: roleGrant,
2308
+ select: {
2309
+ subjectType: roleGrant.subject_type,
2310
+ subjectId: roleGrant.subject_id,
2311
+ roleId: roleGrant.role_id,
2312
+ scopeType: roleGrant.scope_type,
2313
+ scopeIdPresent: roleGrant.scope_id_present,
2314
+ scopeId: roleGrant.scope_id,
2315
+ expiresAt: roleGrant.expires_at,
2316
+ metadata: roleGrant.metadata,
2317
+ revokedAt: roleGrant.revoked_at,
2318
+ },
2319
+ insert: roleGrantValues,
2320
+ update: roleGrantUpdate,
2321
+ revoke: roleGrantRevoke,
2322
+ },
2323
+ rolePermission: {
2324
+ table: rolePermission,
2325
+ select: {
2326
+ roleId: rolePermission.role_id,
2327
+ permissionId: rolePermission.permission_id,
2328
+ scopeTypePresent: rolePermission.scope_type_present,
2329
+ scopeType: rolePermission.scope_type,
2330
+ },
2331
+ insert: rolePermissionValues,
2332
+ },
1802
2333
  };
1803
2334
  };
1804
2335
  const asAuthSqliteTables = (tables) => tables;
@@ -1806,6 +2337,7 @@ export const defineEffectQbAuthTables = (tables) => asAuthSqliteTables(tables);
1806
2337
  export const makeAuthSqliteTables = (names = {}) => asAuthSqliteTables(makeInternalAuthSqliteTables(names));
1807
2338
  export const authSqliteTables = makeAuthSqliteTables();
1808
2339
  const selectUserColumns = (tables) => tables.user.select;
2340
+ const selectIdentityColumns = (tables) => tables.identity.select;
1809
2341
  const selectCredentialColumns = (tables) => tables.credential.select;
1810
2342
  const selectSessionColumns = (tables) => tables.session.select;
1811
2343
  const selectVerificationColumns = (tables) => tables.verification.select;
@@ -1830,7 +2362,16 @@ const selectDomainVerificationColumns = (tables) => tables.domainVerification.se
1830
2362
  const selectSecurityTimelineColumns = (tables) => tables.securityTimeline.select;
1831
2363
  const selectWebhookOutboxColumns = (tables) => tables.webhookOutbox.select;
1832
2364
  const selectWebhookReplayColumns = (tables) => tables.webhookReplay.select;
2365
+ const selectPermissionDefinitionColumns = (tables) => tables.permissionDefinition.select;
2366
+ const selectRoleDefinitionColumns = (tables) => tables.roleDefinition.select;
2367
+ const selectPermissionGrantColumns = (tables) => tables.permissionGrant.select;
2368
+ const selectRoleGrantColumns = (tables) => tables.roleGrant.select;
2369
+ const selectRolePermissionColumns = (tables) => tables.rolePermission.select;
1833
2370
  const storageError = (entity, operation, cause) => StorageError.fromUnknown(entity, operation, cause);
2371
+ const decodeSessionResult = (operation, decode) => Effect.try({
2372
+ try: decode,
2373
+ catch: (cause) => storageError("session", operation, cause),
2374
+ });
1834
2375
  const auditLogStoreError = (operation, cause) => new AuditLogStoreError({
1835
2376
  operation,
1836
2377
  message: `Audit log ${operation} failed`,
@@ -1935,22 +2476,36 @@ const webhookReplayStoreError = (operation, cause) => new WebhookReplayStoreErro
1935
2476
  message: `Webhook replay ${operation} failed`,
1936
2477
  cause,
1937
2478
  });
2479
+ const permissionStoreError = (operation, cause) => new PermissionStoreError({
2480
+ operation,
2481
+ message: `Permission store ${operation} failed`,
2482
+ cause,
2483
+ });
1938
2484
  const findUserById = (executor, tables, id, operation = "find") => executePlan(executor, Query.select(selectUserColumns(tables)).pipe(Query.from(tables.user.table), Query.where(queryEq(tables.user.select.id, id)))).pipe(Effect.mapError((cause) => storageError("user", operation, cause)), Effect.map((rows) => rows[0] === undefined
1939
2485
  ? Option.none()
1940
2486
  : Option.some(decodeUserRow(tables, rows[0]))));
1941
- const findUserByEmail = (executor, tables, email, operation = "find") => executePlan(executor, Query.select(selectUserColumns(tables)).pipe(Query.from(tables.user.table), Query.where(queryEq(tables.user.select.email, email)))).pipe(Effect.mapError((cause) => storageError("user", operation, cause)), Effect.map((rows) => rows[0] === undefined
2487
+ const findIdentity = (executor, tables, where, operation = "find") => executePlan(executor, where.reduce((query, [column, value]) => query.pipe(Query.where(queryEq(column, value))), Query.select(selectIdentityColumns(tables)).pipe(Query.from(tables.identity.table)))).pipe(Effect.mapError((cause) => storageError("identity", operation, cause)), Effect.map((rows) => rows[0] === undefined
1942
2488
  ? Option.none()
1943
- : Option.some(decodeUserRow(tables, rows[0]))));
2489
+ : Option.some(decodeUserIdentityRow(tables, rows[0]))));
1944
2490
  const findPasswordCredentialByUserId = (executor, tables, userId, operation = "find") => executePlan(executor, Query.select(selectCredentialColumns(tables)).pipe(Query.from(tables.credential.table), Query.where(queryEq(tables.credential.select.userId, userId)), Query.where(queryEq(tables.credential.select.type, "password")))).pipe(Effect.mapError((cause) => storageError("credential", operation, cause)), Effect.map((rows) => rows[0] === undefined
1945
2491
  ? Option.none()
1946
2492
  : Option.some(decodePasswordCredentialRow(tables, rows[0]))));
1947
2493
  const findJwtRevocation = (executor, tables, jwtId, operation = "find") => executePlan(executor, Query.select(selectJwtRevocationColumns(tables)).pipe(Query.from(tables.jwtRevocation.table), Query.where(queryEq(tables.jwtRevocation.select.jwtId, jwtId)))).pipe(Effect.mapError((cause) => jwtRevocationStoreError(operation, cause)), Effect.map((rows) => rows[0] === undefined
1948
2494
  ? Option.none()
1949
2495
  : Option.some(decodeJwtRevocationRow(tables, rows[0]))));
1950
- const findSessionById = (executor, tables, id, operation = "find") => executePlan(executor, Query.select(selectSessionColumns(tables)).pipe(Query.from(tables.session.table), Query.where(queryEq(tables.session.select.id, id)))).pipe(Effect.mapError((cause) => storageError("session", operation, cause)), Effect.map((rows) => rows[0] === undefined
2496
+ const findSessionById = (executor, tables, id, operation = "find") => executePlan(executor, Query.select(selectSessionColumns(tables)).pipe(Query.from(tables.session.table), Query.where(queryEq(tables.session.select.id, id)))).pipe(Effect.mapError((cause) => storageError("session", operation, cause)), Effect.flatMap((rows) => decodeSessionResult(operation, () => rows[0] === undefined
1951
2497
  ? Option.none()
1952
- : Option.some(decodeSessionRow(tables, rows[0]))));
1953
- const listSessionsByUser = (executor, tables, input, operation = "listByUser") => executePlan(executor, Query.select(selectSessionColumns(tables)).pipe(Query.from(tables.session.table), Query.where(queryEq(tables.session.select.userId, input.userId)))).pipe(Effect.mapError((cause) => storageError("session", operation, cause)), Effect.map((rows) => rows.map((row) => decodeSessionRow(tables, row))));
2498
+ : Option.some(decodeSessionRow(tables, rows[0])))));
2499
+ const findSessionForMutation = (executor, tables, id, operation) => executePlan(executor, Query.select(selectSessionColumns(tables)).pipe(Query.from(tables.session.table), Query.where(queryEq(tables.session.select.id, id)))).pipe(Effect.mapError((cause) => storageError("session", operation, cause)), Effect.flatMap((rows) => decodeSessionResult(operation, () => {
2500
+ const [selected] = rows;
2501
+ return selected === undefined
2502
+ ? Option.none()
2503
+ : Option.some({
2504
+ row: decodeSessionRow(tables, selected),
2505
+ authenticationEvents: selected.authenticationEvents,
2506
+ });
2507
+ })));
2508
+ const listSessionsByUser = (executor, tables, input, operation = "listByUser") => executePlan(executor, Query.select(selectSessionColumns(tables)).pipe(Query.from(tables.session.table), Query.where(queryEq(tables.session.select.userId, input.userId)))).pipe(Effect.mapError((cause) => storageError("session", operation, cause)), Effect.flatMap((rows) => decodeSessionResult(operation, () => rows.map((row) => decodeSessionRow(tables, row)))));
1954
2509
  const findVerificationById = (executor, tables, id, operation = "find") => executePlan(executor, Query.select(selectVerificationColumns(tables)).pipe(Query.from(tables.verification.table), Query.where(queryEq(tables.verification.select.id, id)))).pipe(Effect.mapError((cause) => storageError("verification", operation, cause)), Effect.map((rows) => rows[0] === undefined
1955
2510
  ? Option.none()
1956
2511
  : Option.some(decodeVerificationRow(tables, rows[0]))));
@@ -2022,7 +2577,9 @@ const findOAuthClientSecretByClientIdAndPrefix = (executor, tables, input, opera
2022
2577
  const findOAuthProviderModeTokenByHash = (executor, tables, input, operation = "find") => executePlan(executor, Query.select(selectOAuthProviderModeTokenColumns(tables)).pipe(Query.from(tables.oauthProviderModeToken.table), Query.where(queryEq(tables.oauthProviderModeToken.select.tokenHash, input.tokenHash)))).pipe(Effect.mapError((cause) => oauthProviderModeTokenStoreError(operation, cause)), Effect.map((rows) => rows[0] === undefined
2023
2578
  ? Option.none()
2024
2579
  : Option.some(decodeOAuthProviderModeTokenRow(tables, rows[0]))));
2025
- const deleteOAuthProviderModeTokenRowsBefore = (executor, tables, column, before) => executePlan(executor, Query.delete(tables.oauthProviderModeToken.table).pipe(Query.where(queryLt(column, Number(before))), Query.returning({ tokenHash: tables.oauthProviderModeToken.select.tokenHash }))).pipe(Effect.map((rows) => rows.length));
2580
+ const deleteOAuthProviderModeTokenRowsBefore = (executor, tables, column, before) => executePlan(executor, Query.delete(tables.oauthProviderModeToken.table).pipe(Query.where(queryLt(column, Number(before))), Query.returning({
2581
+ tokenHash: tables.oauthProviderModeToken.select.tokenHash,
2582
+ }))).pipe(Effect.map((rows) => rows.length));
2026
2583
  const deleteOAuthProviderModeRetiredTokens = (executor, tables, input) => Effect.gen(function* () {
2027
2584
  const expiredDeleted = input.beforeExpiresAt === undefined
2028
2585
  ? 0
@@ -2096,7 +2653,14 @@ const listAuditLogRecords = (executor, tables, input = {}) => {
2096
2653
  const query = input.types === undefined
2097
2654
  ? byOccurredAt
2098
2655
  : byOccurredAt.pipe(Query.where(queryIn(tables.auditLog.select.type, input.types[0], ...input.types.slice(1))));
2099
- return executePlan(executor, query).pipe(Effect.mapError((cause) => auditLogStoreError("list", cause)), Effect.map((rows) => filterAuditLogEvents(rows.map((row) => decodeAuditLogEvent(tables, row)), input)));
2656
+ let ordered = query.pipe(Query.orderBy(tables.auditLog.select.occurredAt, "desc"), Query.orderBy(Query.coalesce(tables.auditLog.select.id, Query.literal("")), "desc"));
2657
+ if (tables.auditLog.select.storageOrder !== undefined) {
2658
+ ordered = ordered.pipe(Query.orderBy(tables.auditLog.select.storageOrder, "asc"));
2659
+ }
2660
+ return executePlan(executor, ordered).pipe(Effect.mapError((cause) => auditLogStoreError("list", cause)), Effect.flatMap((rows) => Effect.try({
2661
+ try: () => filterAuditLogEvents(rows.map((row) => decodeAuditLogEvent(tables, row)), input),
2662
+ catch: (cause) => auditLogStoreError("list", cause),
2663
+ })));
2100
2664
  };
2101
2665
  const deleteAuditLogRecordsBefore = (executor, tables, input) => {
2102
2666
  const base = Query.delete(tables.auditLog.table).pipe(Query.where(queryLt(tables.auditLog.select.occurredAt, Number(input.beforeOccurredAt))));
@@ -2132,7 +2696,9 @@ const distanceKm = (first, second) => {
2132
2696
  Math.cos(firstLatitude) *
2133
2697
  Math.cos(secondLatitude) *
2134
2698
  Math.sin(longitudeDelta / 2) ** 2;
2135
- return (2 * earthRadiusKm * Math.atan2(Math.sqrt(haversine), Math.sqrt(1 - haversine)));
2699
+ return (2 *
2700
+ earthRadiusKm *
2701
+ Math.atan2(Math.sqrt(haversine), Math.sqrt(1 - haversine)));
2136
2702
  };
2137
2703
  const hasCoordinates = (value) => value.latitude !== undefined &&
2138
2704
  Number.isFinite(value.latitude) &&
@@ -2215,18 +2781,287 @@ const deleteLoginRiskHistoryRecordsBefore = (executor, tables, input) => {
2215
2781
  };
2216
2782
  export const makeEffectQbSqliteUserStore = (executor, options = {}) => {
2217
2783
  const tables = options.tables ?? authSqliteTables;
2218
- return UserStore.make({
2784
+ return UserStore.of({
2219
2785
  findById: (id) => findUserById(executor, tables, id),
2220
- findByEmail: (email) => findUserByEmail(executor, tables, email),
2221
2786
  insert: (row) => executePlan(executor, queryInsert(tables.user.table, mutationValues(tables.user.insert(row)))).pipe(Effect.mapError((cause) => storageError("user", "insert", cause)), Effect.asVoid),
2222
- markEmailVerified: (input) => executePlan(executor, queryUpdate(tables.user.table, mutationValues(tables.user.markEmailVerified(input))).pipe(Query.where(queryEq(tables.user.select.id, input.userId)), Query.returning(selectUserColumns(tables)))).pipe(Effect.mapError((cause) => storageError("user", "markEmailVerified", cause)), Effect.map((rows) => rows[0] === undefined
2787
+ });
2788
+ };
2789
+ const identityConflict = (row) => new IdentityConflictError({
2790
+ scope: row.scope,
2791
+ kind: row.kind,
2792
+ normalizedValue: row.normalizedValue,
2793
+ });
2794
+ const sqliteConstraintDiscriminator = (cause) => {
2795
+ const pending = [cause];
2796
+ const seen = new Set();
2797
+ while (pending.length > 0) {
2798
+ const current = pending.pop();
2799
+ if (current === undefined || current === null || seen.has(current)) {
2800
+ continue;
2801
+ }
2802
+ seen.add(current);
2803
+ if (typeof current === "string") {
2804
+ if (current === "UniqueViolation" ||
2805
+ /\bUNIQUE constraint failed\b/i.test(current) ||
2806
+ /\bPRIMARY KEY constraint failed\b/i.test(current)) {
2807
+ return true;
2808
+ }
2809
+ continue;
2810
+ }
2811
+ if (typeof current !== "object") {
2812
+ continue;
2813
+ }
2814
+ const record = current;
2815
+ const { code, errno, message, name, _tag } = record;
2816
+ if (_tag === "UniqueViolation" || name === "UniqueViolation") {
2817
+ return true;
2818
+ }
2819
+ if (code === "SQLITE_CONSTRAINT_UNIQUE" ||
2820
+ code === "SQLITE_CONSTRAINT_PRIMARYKEY") {
2821
+ return true;
2822
+ }
2823
+ if (errno === 2067 || errno === 1555) {
2824
+ return true;
2825
+ }
2826
+ if (typeof message === "string" &&
2827
+ (/\bUNIQUE constraint failed\b/i.test(message) ||
2828
+ /\bPRIMARY KEY constraint failed\b/i.test(message))) {
2829
+ return true;
2830
+ }
2831
+ pending.push(record.cause, record.reason, record.error, record.raw);
2832
+ if (Array.isArray(record.failures)) {
2833
+ pending.push(...record.failures);
2834
+ }
2835
+ if (Array.isArray(record.reasons)) {
2836
+ pending.push(...record.reasons);
2837
+ }
2838
+ }
2839
+ return false;
2840
+ };
2841
+ const isAtomicExecuteConflict = (cause, statement) => cause instanceof AtomicPlanError &&
2842
+ cause.phase === "execute" &&
2843
+ cause.statement === statement &&
2844
+ sqliteConstraintDiscriminator(cause.cause) === true;
2845
+ export const makeEffectQbSqliteIdentityStore = (executor, atomic, options = {}) => {
2846
+ const tables = options.tables ?? authSqliteTables;
2847
+ const byId = (id, operation = "find") => findIdentity(executor, tables, [[tables.identity.select.id, id]], operation);
2848
+ const insert = (row) => executePlan(executor, queryInsert(tables.identity.table, mutationValues(tables.identity.insert(row)))).pipe(Effect.mapError(() => identityConflict(row)), Effect.asVoid);
2849
+ return IdentityStore.of({
2850
+ findById: byId,
2851
+ findByKindAndNormalizedValue: (input) => executePlan(executor, Query.select(selectIdentityColumns(tables)).pipe(Query.from(tables.identity.table), Query.where(queryEq(tables.identity.select.scopeType, input.scope.type)), Query.where(queryEq(tables.identity.select.scopeId, identityScopeId(input.scope))), Query.where(queryEq(tables.identity.select.kind, input.kind)), Query.where(queryEq(tables.identity.select.normalizedValue, input.normalizedValue)), Query.where(queryIsNull(tables.identity.select.revokedAt)))).pipe(Effect.mapError((cause) => storageError("identity", "find", cause)), Effect.map((rows) => rows[0] === undefined
2223
2852
  ? Option.none()
2224
- : Option.some(decodeUserRow(tables, rows[0])))),
2853
+ : Option.some(decodeUserIdentityRow(tables, rows[0])))),
2854
+ listByUser: (input) => {
2855
+ let query = Query.select(selectIdentityColumns(tables)).pipe(Query.from(tables.identity.table), Query.where(queryEq(tables.identity.select.userId, input.userId)));
2856
+ if (input.includeRevoked !== true) {
2857
+ query = query.pipe(Query.where(queryIsNull(tables.identity.select.revokedAt)));
2858
+ }
2859
+ return executePlan(executor, query).pipe(Effect.mapError((cause) => storageError("identity", "find", cause)), Effect.map((rows) => rows.map((row) => decodeUserIdentityRow(tables, row))));
2860
+ },
2861
+ insert,
2862
+ markVerified: (input) => executePlan(executor, queryUpdate(tables.identity.table, mutationValues(tables.identity.verify(input))).pipe(Query.where(queryEq(tables.identity.select.id, input.id)), Query.where(queryEq(tables.identity.select.normalizedValue, input.expectedNormalizedValue)), Query.where(queryIsNull(tables.identity.select.revokedAt)), Query.returning(selectIdentityColumns(tables)))).pipe(Effect.mapError((cause) => storageError("identity", "update", cause)), Effect.map((rows) => rows[0]
2863
+ ? Option.some(decodeUserIdentityRow(tables, rows[0]))
2864
+ : Option.none())),
2865
+ setPrimaryLogin: (input) => {
2866
+ const candidate = aliasTable(tables.identity.table, "primary_candidate");
2867
+ const candidateColumn = (column) => aliasColumn(candidate, column);
2868
+ const candidateExists = queryExists(Query.select({ value: Query.literal(1) }).pipe(Query.from(candidate), Query.where(queryEq(candidateColumn(tables.identity.select.id), input.identityId)), Query.where(queryEq(candidateColumn(tables.identity.select.userId), input.userId)), Query.where(queryEq(candidateColumn(tables.identity.select.updatedAt), Number(input.expectedUpdatedAt))), Query.where(queryIsNull(candidateColumn(tables.identity.select.revokedAt))), Query.where(queryOr(queryNeq(candidateColumn(tables.identity.select.kind), "email"), queryIsNotNull(candidateColumn(tables.identity.select.verifiedAt))))));
2869
+ const primaryColumnName = boundColumnName(tables.identity.select.isPrimaryLogin);
2870
+ const values = {
2871
+ ...mutationValues(tables.identity.setPrimary(input, false)),
2872
+ [primaryColumnName]: Query.case()
2873
+ .when(queryEq(tables.identity.select.id, input.identityId), targetMutationExpression(tables.identity.table, primaryColumnName, 1))
2874
+ .else(targetMutationExpression(tables.identity.table, primaryColumnName, 0)),
2875
+ };
2876
+ const plan = queryUpdate(tables.identity.table, values).pipe(Query.where(queryEq(tables.identity.select.userId, input.userId)), Query.where(queryIsNull(tables.identity.select.revokedAt)), Query.where(candidateExists), Query.returning(selectIdentityColumns(tables)));
2877
+ return executeAtomicPlans(atomic, [plan]).pipe(Effect.mapError((cause) => storageError("identity", "update", cause)), Effect.map(([rows]) => {
2878
+ const selected = rows.find((row) => row.id === input.identityId);
2879
+ return selected === undefined
2880
+ ? Option.none()
2881
+ : Option.some(decodeUserIdentityRow(tables, selected));
2882
+ }));
2883
+ },
2884
+ replace: (input) => byId(input.id, "update").pipe(Effect.flatMap((found) => {
2885
+ if (Option.isNone(found) ||
2886
+ found.value.userId !== input.userId ||
2887
+ input.replacement.userId !== input.userId) {
2888
+ return Effect.succeed(Option.none());
2889
+ }
2890
+ const row = {
2891
+ ...input.replacement,
2892
+ isPrimaryLogin: input.replacement.isPrimaryLogin ?? found.value.isPrimaryLogin,
2893
+ };
2894
+ const active = aliasTable(tables.identity.table, "active_identity");
2895
+ const activeColumn = (column) => aliasColumn(active, column);
2896
+ const usableCount = queryScalar(Query.select({
2897
+ count: Query.count(activeColumn(tables.identity.select.id)),
2898
+ }).pipe(Query.from(active), Query.where(queryEq(activeColumn(tables.identity.select.userId), input.userId)), Query.where(queryIsNull(activeColumn(tables.identity.select.revokedAt))), Query.where(queryOr(queryNeq(activeColumn(tables.identity.select.kind), "email"), queryIsNotNull(activeColumn(tables.identity.select.verifiedAt))))));
2899
+ const updatePlanBase = queryUpdate(tables.identity.table, mutationValues(tables.identity.replace(input))).pipe(Query.where(queryEq(tables.identity.select.id, input.id)), Query.where(queryEq(tables.identity.select.userId, input.userId)), Query.where(queryEq(tables.identity.select.updatedAt, Number(input.expectedUpdatedAt))), Query.where(queryIsNull(tables.identity.select.revokedAt)));
2900
+ const updatePlan = (isIdentityLoginEligible(found.value) &&
2901
+ !isIdentityLoginEligible(row)
2902
+ ? updatePlanBase.pipe(Query.where(queryGt(usableCount, 1)))
2903
+ : updatePlanBase).pipe(Query.returning({
2904
+ ...selectIdentityColumns(tables),
2905
+ revokedAt: queryWithoutNullRefinement(tables.identity.select.revokedAt),
2906
+ replacedById: queryWithoutNullRefinement(tables.identity.select.replacedById),
2907
+ }));
2908
+ const insertPlan = queryInsertWhere(tables.identity.table, mutationValues(tables.identity.insert(row)), queryChangedOne()).pipe(Query.returning(selectIdentityColumns(tables)));
2909
+ return executeAtomicPlans(atomic, [
2910
+ updatePlan,
2911
+ insertPlan,
2912
+ ]).pipe(Effect.mapError((cause) => isAtomicExecuteConflict(cause, 1)
2913
+ ? identityConflict(row)
2914
+ : storageError("identity", "update", cause)), Effect.map(([updated, inserted]) => inserted[0] === undefined || updated[0] === undefined
2915
+ ? Option.none()
2916
+ : Option.some({
2917
+ previous: decodeUserIdentityRow(tables, updated[0]),
2918
+ replacement: decodeUserIdentityRow(tables, inserted[0]),
2919
+ })));
2920
+ })),
2921
+ revoke: (input) => byId(input.id, "update").pipe(Effect.flatMap((found) => {
2922
+ if (Option.isNone(found)) {
2923
+ return Effect.succeed(Option.none());
2924
+ }
2925
+ const active = aliasTable(tables.identity.table, "active_identity");
2926
+ const activeColumn = (column) => aliasColumn(active, column);
2927
+ const usableCount = queryScalar(Query.select({
2928
+ count: Query.count(activeColumn(tables.identity.select.id)),
2929
+ }).pipe(Query.from(active), Query.where(queryEq(activeColumn(tables.identity.select.userId), input.userId)), Query.where(queryIsNull(activeColumn(tables.identity.select.revokedAt))), Query.where(queryOr(queryNeq(activeColumn(tables.identity.select.kind), "email"), queryIsNotNull(activeColumn(tables.identity.select.verifiedAt))))));
2930
+ const revokeValues = mutationValues(tables.identity.revoke(input, found.value, tables.identity.select.metadata));
2931
+ const revokePlan = queryUpdate(tables.identity.table, revokeValues).pipe(Query.where(queryEq(tables.identity.select.id, input.id)), Query.where(queryEq(tables.identity.select.userId, input.userId)), Query.where(queryEq(tables.identity.select.updatedAt, Number(input.expectedUpdatedAt))), Query.where(queryIsNull(tables.identity.select.revokedAt)), Query.where(queryOr(queryAnd(queryEq(tables.identity.select.kind, "email"), queryIsNull(tables.identity.select.verifiedAt)), queryGt(usableCount, 1))), Query.returning({
2932
+ ...selectIdentityColumns(tables),
2933
+ revokedAt: queryWithoutNullRefinement(tables.identity.select.revokedAt),
2934
+ metadata: queryWithoutNullRefinement(tables.identity.select.metadata),
2935
+ }));
2936
+ const next = aliasTable(tables.identity.table, "next_identity");
2937
+ const nextColumn = (column) => aliasColumn(next, column);
2938
+ const nextId = queryScalar(Query.select({ id: nextColumn(tables.identity.select.id) }).pipe(Query.from(next), Query.where(queryEq(nextColumn(tables.identity.select.userId), input.userId)), Query.where(queryIsNull(nextColumn(tables.identity.select.revokedAt))), Query.where(queryOr(queryNeq(nextColumn(tables.identity.select.kind), "email"), queryIsNotNull(nextColumn(tables.identity.select.verifiedAt)))), Query.orderBy(nextColumn(tables.identity.select.createdAt), "asc"), Query.orderBy(nextColumn(tables.identity.select.id), "asc"), Query.limit(1)));
2939
+ const primary = aliasTable(tables.identity.table, "existing_primary");
2940
+ const primaryColumn = (column) => aliasColumn(primary, column);
2941
+ const primaryExists = queryExists(Query.select({ value: Query.literal(1) }).pipe(Query.from(primary), Query.where(queryEq(primaryColumn(tables.identity.select.userId), input.userId)), Query.where(queryIsNull(primaryColumn(tables.identity.select.revokedAt))), Query.where(queryEq(primaryColumn(tables.identity.select.isPrimaryLogin), 1))));
2942
+ const promotePlan = queryUpdate(tables.identity.table, mutationValues(tables.identity.setPrimary({
2943
+ userId: input.userId,
2944
+ identityId: input.id,
2945
+ expectedUpdatedAt: input.expectedUpdatedAt,
2946
+ updatedAt: input.revokedAt,
2947
+ }, true))).pipe(Query.where(queryEq(tables.identity.select.id, nextId)), Query.where(queryNot(primaryExists)), Query.where(queryChangedOne()));
2948
+ return executeAtomicPlans(atomic, [
2949
+ revokePlan,
2950
+ promotePlan,
2951
+ ]).pipe(Effect.mapError((cause) => storageError("identity", "update", cause)), Effect.map(([rows]) => rows[0] === undefined
2952
+ ? Option.none()
2953
+ : Option.some(decodeUserIdentityRow(tables, rows[0]))));
2954
+ })),
2955
+ });
2956
+ };
2957
+ export const makeEffectQbSqliteRegistrationStore = (executor, atomic, options = {}) => {
2958
+ const tables = options.tables ?? authSqliteTables;
2959
+ const identityStore = makeEffectQbSqliteIdentityStore(executor, atomic, options);
2960
+ const credentialStore = makeEffectQbSqliteCredentialStore(executor, options);
2961
+ const classify = (input, cause) => {
2962
+ const failure = () => Effect.fail(new RegistrationFailureError({
2963
+ message: "Atomic registration failed",
2964
+ cause,
2965
+ }));
2966
+ if (cause instanceof AtomicPlanError && cause.phase !== "execute") {
2967
+ return failure();
2968
+ }
2969
+ const statement = cause instanceof AtomicPlanError ? (cause.statement ?? -1) : -1;
2970
+ if (statement >= 0 && !isAtomicExecuteConflict(cause, statement)) {
2971
+ return failure();
2972
+ }
2973
+ if (statement < 0) {
2974
+ return Effect.gen(function* () {
2975
+ if (Option.isSome(yield* findUserById(executor, options.tables ?? authSqliteTables, input.user.id))) {
2976
+ return yield* new RegistrationConflictError({ reason: "user-id" });
2977
+ }
2978
+ if (Option.isSome(yield* identityStore.findById(input.identity.id))) {
2979
+ return yield* new RegistrationConflictError({
2980
+ reason: "identity-id",
2981
+ });
2982
+ }
2983
+ if (Option.isSome(yield* identityStore.findByKindAndNormalizedValue({
2984
+ scope: input.identity.scope,
2985
+ kind: input.identity.kind,
2986
+ normalizedValue: input.identity.normalizedValue,
2987
+ }))) {
2988
+ return yield* new RegistrationConflictError({
2989
+ reason: "identity-value",
2990
+ });
2991
+ }
2992
+ if (input.passwordCredential !== undefined) {
2993
+ const credential = yield* credentialStore.findPasswordByUserId(input.user.id);
2994
+ if (Option.isSome(credential)) {
2995
+ return yield* new RegistrationConflictError({
2996
+ reason: credential.value.id === input.passwordCredential.id
2997
+ ? "credential-id"
2998
+ : "password-credential",
2999
+ });
3000
+ }
3001
+ }
3002
+ return yield* new RegistrationFailureError({
3003
+ message: "Atomic registration failed",
3004
+ cause,
3005
+ });
3006
+ }).pipe(Effect.catch((error) => error instanceof RegistrationConflictError ||
3007
+ error instanceof RegistrationFailureError
3008
+ ? Effect.fail(error)
3009
+ : Effect.fail(new RegistrationFailureError({
3010
+ message: "Registration conflict classification failed",
3011
+ cause: error,
3012
+ }))));
3013
+ }
3014
+ if (statement === 0) {
3015
+ return Effect.fail(new RegistrationConflictError({ reason: "user-id" }));
3016
+ }
3017
+ if (statement === 1) {
3018
+ return identityStore.findById(input.identity.id).pipe(Effect.flatMap((found) => Option.isSome(found)
3019
+ ? Effect.fail(new RegistrationConflictError({ reason: "identity-id" }))
3020
+ : Effect.fail(new RegistrationConflictError({ reason: "identity-value" }))), Effect.catch(() => Effect.fail(new RegistrationConflictError({ reason: "identity-value" }))));
3021
+ }
3022
+ if (statement === 2 && input.passwordCredential !== undefined) {
3023
+ return credentialStore.findPasswordByUserId(input.user.id).pipe(Effect.flatMap((found) => Effect.fail(new RegistrationConflictError({
3024
+ reason: Option.isSome(found) &&
3025
+ found.value.id === input.passwordCredential?.id
3026
+ ? "credential-id"
3027
+ : "password-credential",
3028
+ }))), Effect.catch(() => Effect.fail(new RegistrationConflictError({ reason: "password-credential" }))));
3029
+ }
3030
+ return failure();
3031
+ };
3032
+ return RegistrationStore.of({
3033
+ create: (input) => {
3034
+ if (input.identity.userId !== input.user.id ||
3035
+ (input.passwordCredential !== undefined &&
3036
+ input.passwordCredential.userId !== input.user.id)) {
3037
+ return Effect.fail(new RegistrationConflictError({ reason: "user-id" }));
3038
+ }
3039
+ const userPlan = queryInsert(tables.user.table, mutationValues(tables.user.insert(input.user)));
3040
+ const identityPlan = queryInsert(tables.identity.table, mutationValues(tables.identity.insert(input.identity)));
3041
+ const plans = input.passwordCredential === undefined
3042
+ ? [userPlan, identityPlan]
3043
+ : [
3044
+ userPlan,
3045
+ identityPlan,
3046
+ queryInsert(tables.credential.table, mutationValues(tables.credential.insertPassword(input.passwordCredential))),
3047
+ ];
3048
+ return atomic.execute(plans).pipe(Effect.map(() => ({
3049
+ user: input.user,
3050
+ identity: {
3051
+ ...input.identity,
3052
+ isPrimaryLogin: input.identity.isPrimaryLogin ?? false,
3053
+ updatedAt: input.identity.createdAt,
3054
+ },
3055
+ ...(input.passwordCredential === undefined
3056
+ ? {}
3057
+ : { passwordCredential: input.passwordCredential }),
3058
+ })), Effect.catch((error) => classify(input, error)));
3059
+ },
2225
3060
  });
2226
3061
  };
2227
3062
  export const makeEffectQbSqliteCredentialStore = (executor, options = {}) => {
2228
3063
  const tables = options.tables ?? authSqliteTables;
2229
- return CredentialStore.make({
3064
+ return CredentialStore.of({
2230
3065
  findPasswordByUserId: (userId) => findPasswordCredentialByUserId(executor, tables, userId),
2231
3066
  insertPassword: (row) => executePlan(executor, queryInsert(tables.credential.table, mutationValues(tables.credential.insertPassword(row)))).pipe(Effect.mapError((cause) => storageError("credential", "insert", cause)), Effect.asVoid),
2232
3067
  updatePassword: (input) => executePlan(executor, queryUpdate(tables.credential.table, mutationValues(tables.credential.updatePassword(input))).pipe(Query.where(queryEq(tables.credential.select.userId, input.userId)), Query.where(queryEq(tables.credential.select.type, "password")), Query.where(queryIsNull(tables.credential.select.revokedAt)), Query.returning({ id: tables.credential.select.id }))).pipe(Effect.mapError((cause) => storageError("credential", "updatePassword", cause)), Effect.flatMap((rows) => rows[0] === undefined
@@ -2236,29 +3071,44 @@ export const makeEffectQbSqliteCredentialStore = (executor, options = {}) => {
2236
3071
  };
2237
3072
  export const makeEffectQbSqliteSessionStore = (executor, options = {}) => {
2238
3073
  const tables = options.tables ?? authSqliteTables;
2239
- return SessionStore.make({
3074
+ return SessionStore.of({
2240
3075
  findById: (id) => findSessionById(executor, tables, id),
2241
3076
  listByUser: (input) => listSessionsByUser(executor, tables, input),
2242
- insert: (row) => executePlan(executor, queryInsert(tables.session.table, mutationValues(tables.session.insert(row)))).pipe(Effect.mapError((cause) => storageError("session", "insert", cause)), Effect.asVoid),
2243
- refresh: (input) => {
2244
- const query = queryUpdate(tables.session.table, mutationValues(tables.session.refresh(input))).pipe(Query.where(queryEq(tables.session.select.id, input.sessionId)), Query.returning(selectSessionColumns(tables)));
2245
- return executePlan(executor, query).pipe(Effect.mapError((cause) => storageError("session", "refresh", cause)), Effect.map((rows) => rows[0] === undefined
3077
+ insert: (row) => decodeSessionResult("insert", () => parseAuthenticationEvents(row.authenticationEvents)).pipe(Effect.flatMap(() => executePlan(executor, queryInsert(tables.session.table, mutationValues(tables.session.insert(row))))), Effect.mapError((cause) => storageError("session", "insert", cause)), Effect.asVoid),
3078
+ refresh: (input) => findSessionForMutation(executor, tables, input.sessionId, "refresh").pipe(Effect.flatMap((existing) => Option.isNone(existing)
3079
+ ? Effect.succeed(Option.none())
3080
+ : executePlan(executor, queryUpdate(tables.session.table, mutationValues(tables.session.refresh(input))).pipe(Query.where(queryEq(tables.session.select.id, input.sessionId)), Query.where(queryEq(tables.session.select.authenticationEvents, existing.value.authenticationEvents)), Query.returning(selectSessionColumns(tables)))).pipe(Effect.mapError((cause) => storageError("session", "refresh", cause)), Effect.flatMap((rows) => decodeSessionResult("refresh", () => rows[0] === undefined
2246
3081
  ? Option.none()
2247
- : Option.some(decodeSessionRow(tables, rows[0]))));
2248
- },
2249
- rotate: (input) => {
2250
- const query = queryUpdate(tables.session.table, mutationValues(tables.session.rotate(input))).pipe(Query.where(queryEq(tables.session.select.id, input.sessionId)), Query.returning(selectSessionColumns(tables)));
2251
- return executePlan(executor, query).pipe(Effect.mapError((cause) => storageError("session", "rotate", cause)), Effect.map((rows) => rows[0] === undefined
3082
+ : Option.some(decodeSessionRow(tables, rows[0]))))))),
3083
+ rotate: (input) => findSessionForMutation(executor, tables, input.sessionId, "rotate").pipe(Effect.flatMap((existing) => Option.isNone(existing)
3084
+ ? Effect.succeed(Option.none())
3085
+ : executePlan(executor, queryUpdate(tables.session.table, mutationValues(tables.session.rotate(input))).pipe(Query.where(queryEq(tables.session.select.id, input.sessionId)), Query.where(queryEq(tables.session.select.authenticationEvents, existing.value.authenticationEvents)), Query.returning(selectSessionColumns(tables)))).pipe(Effect.mapError((cause) => storageError("session", "rotate", cause)), Effect.flatMap((rows) => decodeSessionResult("rotate", () => rows[0] === undefined
2252
3086
  ? Option.none()
2253
- : Option.some(decodeSessionRow(tables, rows[0]))));
2254
- },
2255
- updateClaims: (input) => findSessionById(executor, tables, input.sessionId, "updateClaims").pipe(Effect.flatMap((rowOption) => {
3087
+ : Option.some(decodeSessionRow(tables, rows[0]))))))),
3088
+ updateClaims: (input) => findSessionForMutation(executor, tables, input.sessionId, "updateClaims").pipe(Effect.flatMap((selected) => {
3089
+ if (Option.isNone(selected)) {
3090
+ return Effect.succeed(Option.none());
3091
+ }
3092
+ return executePlan(executor, queryUpdate(tables.session.table, mutationValues(tables.session.updateClaims(selected.value.row, input))).pipe(Query.where(queryEq(tables.session.select.id, input.sessionId)), Query.where(queryEq(tables.session.select.authenticationEvents, selected.value.authenticationEvents)), Query.returning(selectSessionColumns(tables)))).pipe(Effect.mapError((cause) => storageError("session", "updateClaims", cause)), Effect.flatMap((rows) => decodeSessionResult("updateClaims", () => rows[0] === undefined
3093
+ ? Option.none()
3094
+ : Option.some(decodeSessionRow(tables, rows[0])))));
3095
+ })),
3096
+ appendEvidence: (input) => decodeSessionResult("appendEvidence", () => parseAuthenticationEvents(input.authenticationEvents)).pipe(Effect.flatMap((authenticationEvents) => findSessionForMutation(executor, tables, input.sessionId, "appendEvidence").pipe(Effect.flatMap((existing) => Option.isNone(existing)
3097
+ ? Effect.succeed(Option.none())
3098
+ : executePlan(executor, queryUpdate(tables.session.table, mutationValues(tables.session.appendEvidence({
3099
+ ...input,
3100
+ authenticationEvents,
3101
+ }))).pipe(Query.where(queryEq(tables.session.select.id, input.sessionId)), Query.where(queryEq(tables.session.select.authenticationEvents, existing.value.authenticationEvents)), Query.returning(selectSessionColumns(tables)))).pipe(Effect.mapError((cause) => storageError("session", "appendEvidence", cause)), Effect.flatMap((rows) => decodeSessionResult("appendEvidence", () => rows[0] === undefined
3102
+ ? Option.none()
3103
+ : Option.some(decodeSessionRow(tables, rows[0]))))))))),
3104
+ assureAndRotate: (input) => decodeSessionResult("assureAndRotate", () => parseAuthenticationEvents(input.authenticationEvents)).pipe(Effect.flatMap(() => findSessionById(executor, tables, input.sessionId, "assureAndRotate")), Effect.flatMap((rowOption) => {
2256
3105
  if (Option.isNone(rowOption)) {
2257
3106
  return Effect.succeed(Option.none());
2258
3107
  }
2259
- return executePlan(executor, queryUpdate(tables.session.table, mutationValues(tables.session.updateClaims(rowOption.value, input))).pipe(Query.where(queryEq(tables.session.select.id, input.sessionId)), Query.returning(selectSessionColumns(tables)))).pipe(Effect.mapError((cause) => storageError("session", "updateClaims", cause)), Effect.map((rows) => rows[0] === undefined
3108
+ const query = queryUpdate(tables.session.table, mutationValues(tables.session.assureAndRotate(rowOption.value, input))).pipe(Query.where(queryEq(tables.session.select.id, input.sessionId)), Query.where(queryEq(tables.session.select.secretHash, input.expectedSecretHash)), Query.where(queryEq(tables.session.select.authenticationEvents, JSON.stringify(input.expectedAuthenticationEvents))), Query.where(queryIsNull(tables.session.select.revokedAt)), Query.where(queryGt(tables.session.select.expiresAt, Number(input.now))), Query.returning(selectSessionColumns(tables)));
3109
+ return executePlan(executor, query).pipe(Effect.mapError((cause) => storageError("session", "assureAndRotate", cause)), Effect.flatMap((rows) => decodeSessionResult("assureAndRotate", () => rows[0] === undefined
2260
3110
  ? Option.none()
2261
- : Option.some(decodeSessionRow(tables, rows[0]))));
3111
+ : Option.some(decodeSessionRow(tables, rows[0])))));
2262
3112
  })),
2263
3113
  revoke: (input) => executePlan(executor, queryUpdate(tables.session.table, mutationValues(tables.session.revoke(input))).pipe(Query.where(queryEq(tables.session.select.id, input.sessionId)))).pipe(Effect.mapError((cause) => storageError("session", "revoke", cause)), Effect.asVoid),
2264
3114
  revokeAllForUser: (input) => executePlan(executor, queryUpdate(tables.session.table, mutationValues(tables.session.revoke(input))).pipe(Query.where(queryEq(tables.session.select.userId, input.userId)))).pipe(Effect.mapError((cause) => storageError("session", "revokeAllForUser", cause)), Effect.asVoid),
@@ -2266,7 +3116,7 @@ export const makeEffectQbSqliteSessionStore = (executor, options = {}) => {
2266
3116
  };
2267
3117
  export const makeEffectQbSqliteVerificationStore = (executor, options = {}) => {
2268
3118
  const tables = options.tables ?? authSqliteTables;
2269
- return VerificationStore.make({
3119
+ return VerificationStore.of({
2270
3120
  findById: (id) => findVerificationById(executor, tables, id),
2271
3121
  insert: (row) => executePlan(executor, queryInsert(tables.verification.table, mutationValues(tables.verification.insert(row)))).pipe(Effect.mapError((cause) => storageError("verification", "insert", cause)), Effect.asVoid),
2272
3122
  consume: (input) => {
@@ -2294,10 +3144,13 @@ export const makeEffectQbSqliteVerificationStore = (executor, options = {}) => {
2294
3144
  };
2295
3145
  export const makeEffectQbSqliteAuditLogStore = (executor, options = {}) => {
2296
3146
  const tables = options.tables ?? authSqliteTables;
2297
- return AuditLogStore.make({
2298
- insert: (event) => Effect.gen(function* () {
3147
+ return AuditLogStore.of({
3148
+ insertMany: (events) => Effect.gen(function* () {
3149
+ if (events.length === 0) {
3150
+ return;
3151
+ }
2299
3152
  const createdAt = yield* currentUnixMillis;
2300
- yield* executePlan(executor, queryInsert(tables.auditLog.table, mutationValues(tables.auditLog.insert(event, createdAt)))).pipe(Effect.mapError((cause) => auditLogStoreError("insert", cause)), Effect.asVoid);
3153
+ yield* executePlan(executor, queryInsertMany(tables.auditLog.table, events.map((event) => mutationValues(tables.auditLog.insert(event, createdAt))))).pipe(Effect.mapError((cause) => auditLogStoreError("insertMany", cause)), Effect.asVoid);
2301
3154
  }),
2302
3155
  list: (input = {}) => listAuditLogRecords(executor, tables, input),
2303
3156
  deleteBefore: (input) => deleteAuditLogRecordsBefore(executor, tables, input),
@@ -2305,7 +3158,7 @@ export const makeEffectQbSqliteAuditLogStore = (executor, options = {}) => {
2305
3158
  };
2306
3159
  export const makeEffectQbSqliteLoginApprovalReviewStore = (executor, options = {}) => {
2307
3160
  const tables = options.tables ?? authSqliteTables;
2308
- return LoginApprovalReviewStore.make({
3161
+ return LoginApprovalReviewStore.of({
2309
3162
  insertPending: (input) => executePlan(executor, queryInsert(tables.loginApprovalReview.table, mutationValues(tables.loginApprovalReview.insertPending(input)))).pipe(Effect.mapError((cause) => loginApprovalReviewStoreError("insert", cause)), Effect.as(input)),
2310
3163
  findByApprovalChallengeId: (input) => findLoginApprovalReviewByApprovalChallengeId(executor, tables, input),
2311
3164
  approve: (input) => executePlan(executor, queryUpdate(tables.loginApprovalReview.table, mutationValues(tables.loginApprovalReview.approve(input))).pipe(Query.where(queryEq(tables.loginApprovalReview.select.approvalChallengeId, input.approvalChallengeId)), Query.where(queryEq(tables.loginApprovalReview.select.status, "pending")), Query.returning(selectLoginApprovalReviewColumns(tables)))).pipe(Effect.mapError((cause) => loginApprovalReviewStoreError("approve", cause)), Effect.map((rows) => rows[0] === undefined
@@ -2318,7 +3171,7 @@ export const makeEffectQbSqliteLoginApprovalReviewStore = (executor, options = {
2318
3171
  };
2319
3172
  export const makeEffectQbSqliteLoginRiskHistoryStore = (executor, options = {}) => {
2320
3173
  const tables = options.tables ?? authSqliteTables;
2321
- return LoginRiskHistory.make({
3174
+ return LoginRiskHistory.of({
2322
3175
  lookup: (input) => lookupLoginRiskHistory(executor, tables, input, options.loginRiskHistory),
2323
3176
  record: (input) => Effect.gen(function* () {
2324
3177
  const createdAt = input.createdAt ?? (yield* currentUnixMillis);
@@ -2329,15 +3182,21 @@ export const makeEffectQbSqliteLoginRiskHistoryStore = (executor, options = {})
2329
3182
  };
2330
3183
  export const makeEffectQbSqliteTrustedDeviceStore = (executor, options = {}) => {
2331
3184
  const tables = options.tables ?? authSqliteTables;
2332
- return TrustedDeviceStore.make({
3185
+ const updateExisting = (input, existing) => executePlan(executor, queryUpdate(tables.trustedDevice.table, mutationValues(tables.trustedDevice.update(input, existing))).pipe(Query.where(queryEq(tables.trustedDevice.select.userId, input.userId)), Query.where(queryEq(tables.trustedDevice.select.tokenHash, input.tokenHash)), Query.where(queryLte(tables.trustedDevice.select.lastSeenAt, Number(input.now))))).pipe(Effect.mapError((cause) => trustedDeviceStoreError("trust", cause)), Effect.asVoid);
3186
+ const updateAfterInsertConflict = (input, originalError) => findTrustedDeviceIncludingExpired(executor, tables, input).pipe(Effect.flatMap((existing) => Option.isSome(existing)
3187
+ ? updateExisting(input, existing.value)
3188
+ : Effect.fail(trustedDeviceStoreError("trust", originalError))));
3189
+ return TrustedDeviceStore.of({
2333
3190
  find: (input) => findTrustedDevice(executor, tables, input),
2334
3191
  listForUser: (input) => listTrustedDevicesForUser(executor, tables, input),
2335
3192
  revokeForUser: (input) => executePlan(executor, Query.delete(tables.trustedDevice.table).pipe(Query.where(queryEq(tables.trustedDevice.select.userId, input.userId)), Query.where(queryEq(tables.trustedDevice.select.tokenHash, input.tokenHash)))).pipe(Effect.mapError((cause) => trustedDeviceStoreError("revoke", cause)), Effect.asVoid),
2336
3193
  upsert: (input) => findTrustedDeviceIncludingExpired(executor, tables, input).pipe(Effect.flatMap((existing) => {
2337
3194
  if (Option.isNone(existing)) {
2338
- return executePlan(executor, queryInsert(tables.trustedDevice.table, mutationValues(tables.trustedDevice.insert(input, input.now)))).pipe(Effect.mapError((cause) => trustedDeviceStoreError("trust", cause)), Effect.asVoid);
3195
+ return executePlan(executor, queryInsert(tables.trustedDevice.table, mutationValues(tables.trustedDevice.insert(input, input.now)))).pipe(Effect.catch((error) => sqliteConstraintDiscriminator(error)
3196
+ ? updateAfterInsertConflict(input, error)
3197
+ : Effect.fail(trustedDeviceStoreError("trust", error))), Effect.asVoid);
2339
3198
  }
2340
- return executePlan(executor, queryUpdate(tables.trustedDevice.table, mutationValues(tables.trustedDevice.update(input, existing.value))).pipe(Query.where(queryEq(tables.trustedDevice.select.userId, input.userId)), Query.where(queryEq(tables.trustedDevice.select.tokenHash, input.tokenHash)))).pipe(Effect.mapError((cause) => trustedDeviceStoreError("trust", cause)), Effect.asVoid);
3199
+ return updateExisting(input, existing.value);
2341
3200
  }), Effect.flatMap(() => findTrustedDeviceIncludingExpired(executor, tables, input)), Effect.flatMap((stored) => Option.isSome(stored)
2342
3201
  ? Effect.succeed(stored.value)
2343
3202
  : Effect.fail(trustedDeviceStoreError("trust", "missing row")))),
@@ -2345,7 +3204,7 @@ export const makeEffectQbSqliteTrustedDeviceStore = (executor, options = {}) =>
2345
3204
  };
2346
3205
  export const makeEffectQbSqlitePasskeyCredentialStore = (executor, options = {}) => {
2347
3206
  const tables = options.tables ?? authSqliteTables;
2348
- return PasskeyCredentialStore.make({
3207
+ return PasskeyCredentialStore.of({
2349
3208
  insert: (row) => executePlan(executor, queryInsert(tables.passkeyCredential.table, mutationValues(tables.passkeyCredential.insert(row)))).pipe(Effect.mapError((cause) => passkeyCredentialStoreError("insert", cause)), Effect.asVoid),
2350
3209
  findByCredentialId: (credentialId) => findPasskeyCredentialByCredentialId(executor, tables, credentialId),
2351
3210
  listByUser: (input) => {
@@ -2355,7 +3214,7 @@ export const makeEffectQbSqlitePasskeyCredentialStore = (executor, options = {})
2355
3214
  : base.pipe(Query.where(queryIsNull(tables.passkeyCredential.select.revokedAt)));
2356
3215
  return executePlan(executor, query).pipe(Effect.mapError((cause) => passkeyCredentialStoreError("list", cause)), Effect.map((rows) => rows.map((row) => decodePasskeyCredentialRow(tables, row))));
2357
3216
  },
2358
- updateSignCount: (input) => executePlan(executor, queryUpdate(tables.passkeyCredential.table, mutationValues(tables.passkeyCredential.updateSignCount(input))).pipe(Query.where(queryEq(tables.passkeyCredential.select.credentialId, input.credentialId)), Query.where(queryIsNull(tables.passkeyCredential.select.revokedAt)), Query.returning(selectPasskeyCredentialColumns(tables)))).pipe(Effect.mapError((cause) => passkeyCredentialStoreError("update-sign-count", cause)), Effect.map((rows) => rows[0] === undefined
3217
+ updateSignCount: (input) => executePlan(executor, queryUpdate(tables.passkeyCredential.table, mutationValues(tables.passkeyCredential.updateSignCount(input))).pipe(Query.where(queryEq(tables.passkeyCredential.select.credentialId, input.credentialId)), Query.where(queryEq(tables.passkeyCredential.select.signCount, input.expectedSignCount)), Query.where(queryIsNull(tables.passkeyCredential.select.revokedAt)), Query.returning(selectPasskeyCredentialColumns(tables)))).pipe(Effect.mapError((cause) => passkeyCredentialStoreError("update-sign-count", cause)), Effect.map((rows) => rows[0] === undefined
2359
3218
  ? Option.none()
2360
3219
  : Option.some(decodePasskeyCredentialRow(tables, rows[0])))),
2361
3220
  revoke: (input) => findPasskeyCredentialByCredentialId(executor, tables, input.credentialId, "revoke").pipe(Effect.flatMap((row) => {
@@ -2368,7 +3227,7 @@ export const makeEffectQbSqlitePasskeyCredentialStore = (executor, options = {})
2368
3227
  };
2369
3228
  export const makeEffectQbSqliteTotpFactorStore = (executor, options = {}) => {
2370
3229
  const tables = options.tables ?? authSqliteTables;
2371
- return TotpFactorStore.make({
3230
+ return TotpFactorStore.of({
2372
3231
  insert: (row) => executePlan(executor, queryInsert(tables.totpFactor.table, mutationValues(tables.totpFactor.insert(row)))).pipe(Effect.mapError((cause) => totpFactorStoreError("insert", cause)), Effect.asVoid),
2373
3232
  findById: (id) => findTotpFactorById(executor, tables, id),
2374
3233
  listByUser: (input) => {
@@ -2384,9 +3243,35 @@ export const makeEffectQbSqliteTotpFactorStore = (executor, options = {}) => {
2384
3243
  confirm: (input) => executePlan(executor, queryUpdate(tables.totpFactor.table, mutationValues(tables.totpFactor.confirm(input))).pipe(Query.where(queryEq(tables.totpFactor.select.id, input.id)), Query.where(queryIsNull(tables.totpFactor.select.revokedAt)), Query.returning(selectTotpFactorColumns(tables)))).pipe(Effect.mapError((cause) => totpFactorStoreError("confirm", cause)), Effect.map((rows) => rows[0] === undefined
2385
3244
  ? Option.none()
2386
3245
  : Option.some(decodeTotpFactorRow(tables, rows[0])))),
2387
- markUsed: (input) => executePlan(executor, queryUpdate(tables.totpFactor.table, mutationValues(tables.totpFactor.markUsed(input))).pipe(Query.where(queryEq(tables.totpFactor.select.id, input.id)), Query.where(queryIsNull(tables.totpFactor.select.revokedAt)), Query.where(queryGt(tables.totpFactor.select.confirmedAt, 0)), Query.returning(selectTotpFactorColumns(tables)))).pipe(Effect.mapError((cause) => totpFactorStoreError("mark-used", cause)), Effect.map((rows) => rows[0] === undefined
2388
- ? Option.none()
2389
- : Option.some(decodeTotpFactorRow(tables, rows[0])))),
3246
+ acceptCounter: (input) => {
3247
+ const attempt = (initial) => executePlan(executor, queryUpdate(tables.totpFactor.table, mutationValues(tables.totpFactor.acceptCounter(input))).pipe(Query.where(queryEq(tables.totpFactor.select.id, input.id)), Query.where(queryIsNull(tables.totpFactor.select.revokedAt)), Query.where(queryGt(tables.totpFactor.select.confirmedAt, 0)), Query.where(initial
3248
+ ? queryIsNull(tables.totpFactor.select.lastAcceptedCounter)
3249
+ : queryLt(tables.totpFactor.select.lastAcceptedCounter, input.counter)), Query.returning({ id: tables.totpFactor.select.id })));
3250
+ return Effect.gen(function* () {
3251
+ let rows = yield* attempt(true).pipe(Effect.mapError((cause) => totpFactorStoreError("accept-counter", cause)));
3252
+ if (rows[0] === undefined) {
3253
+ rows = yield* attempt(false).pipe(Effect.mapError((cause) => totpFactorStoreError("accept-counter", cause)));
3254
+ }
3255
+ if (rows[0] !== undefined) {
3256
+ const factor = yield* findTotpFactorById(executor, tables, input.id, "accept-counter");
3257
+ if (Option.isNone(factor)) {
3258
+ return { _tag: "Unavailable" };
3259
+ }
3260
+ return {
3261
+ _tag: "Accepted",
3262
+ factor: factor.value,
3263
+ };
3264
+ }
3265
+ const factor = yield* findTotpFactorById(executor, tables, input.id, "accept-counter");
3266
+ return Option.isSome(factor) &&
3267
+ factor.value.confirmedAt !== undefined &&
3268
+ factor.value.revokedAt === undefined &&
3269
+ factor.value.lastAcceptedCounter !== undefined &&
3270
+ factor.value.lastAcceptedCounter >= input.counter
3271
+ ? { _tag: "Replay" }
3272
+ : { _tag: "Unavailable" };
3273
+ });
3274
+ },
2390
3275
  revoke: (input) => findTotpFactorById(executor, tables, input.id, "revoke").pipe(Effect.flatMap((row) => {
2391
3276
  if (Option.isNone(row)) {
2392
3277
  return Effect.void;
@@ -2395,9 +3280,9 @@ export const makeEffectQbSqliteTotpFactorStore = (executor, options = {}) => {
2395
3280
  })),
2396
3281
  });
2397
3282
  };
2398
- export const makeEffectQbSqliteRecoveryCodeStore = (executor, options = {}) => {
3283
+ export const makeEffectQbSqliteRecoveryCodeStore = (executor, atomic, options = {}) => {
2399
3284
  const tables = options.tables ?? authSqliteTables;
2400
- return RecoveryCodeStore.make({
3285
+ return RecoveryCodeStore.of({
2401
3286
  insertMany: (rows) => Effect.forEach(rows, (row) => executePlan(executor, queryInsert(tables.recoveryCode.table, mutationValues(tables.recoveryCode.insert(row)))).pipe(Effect.mapError((cause) => recoveryCodeStoreError("insert", cause)), Effect.asVoid), { discard: true }),
2402
3287
  findById: (id) => findRecoveryCodeById(executor, tables, id),
2403
3288
  listByUser: (input) => {
@@ -2413,6 +3298,13 @@ export const makeEffectQbSqliteRecoveryCodeStore = (executor, options = {}) => {
2413
3298
  markUsed: (input) => executePlan(executor, queryUpdate(tables.recoveryCode.table, mutationValues(tables.recoveryCode.markUsed(input))).pipe(Query.where(queryEq(tables.recoveryCode.select.id, input.id)), Query.where(queryIsNull(tables.recoveryCode.select.usedAt)), Query.where(queryIsNull(tables.recoveryCode.select.revokedAt)), Query.returning({ id: tables.recoveryCode.select.id }))).pipe(Effect.mapError((cause) => recoveryCodeStoreError("mark-used", cause)), Effect.flatMap((rows) => rows[0] === undefined
2414
3299
  ? Effect.succeed(Option.none())
2415
3300
  : findRecoveryCodeById(executor, tables, input.id, "mark-used"))),
3301
+ replaceActiveForUser: (input) => {
3302
+ const plans = [
3303
+ queryUpdate(tables.recoveryCode.table, mutationValues(tables.recoveryCode.replaceActive(input, tables.recoveryCode.select.metadata))).pipe(Query.where(queryEq(tables.recoveryCode.select.userId, input.userId)), Query.where(queryIsNull(tables.recoveryCode.select.usedAt)), Query.where(queryIsNull(tables.recoveryCode.select.revokedAt))),
3304
+ ...input.rows.map((row) => queryInsert(tables.recoveryCode.table, mutationValues(tables.recoveryCode.insert(row)))),
3305
+ ];
3306
+ return atomic.execute(plans).pipe(Effect.mapError((cause) => recoveryCodeStoreError("replace-active", cause)), Effect.asVoid);
3307
+ },
2416
3308
  revoke: (input) => findRecoveryCodeById(executor, tables, input.id, "revoke").pipe(Effect.flatMap((row) => {
2417
3309
  if (Option.isNone(row)) {
2418
3310
  return Effect.void;
@@ -2421,9 +3313,219 @@ export const makeEffectQbSqliteRecoveryCodeStore = (executor, options = {}) => {
2421
3313
  })),
2422
3314
  });
2423
3315
  };
3316
+ export const makeEffectQbSqliteRecoveryCodeSessionRotateStore = (executor, atomic, options = {}) => {
3317
+ const tables = options.tables ?? authSqliteTables;
3318
+ return RecoveryCodeSessionRotateStore.of({
3319
+ consumeAndRotate: (input) => findSessionById(executor, tables, input.sessionId, "assureAndRotate").pipe(Effect.flatMap((current) => {
3320
+ if (Option.isNone(current) || current.value.userId !== input.userId) {
3321
+ return Effect.succeed(Option.none());
3322
+ }
3323
+ try {
3324
+ assertSessionAssurance(input);
3325
+ }
3326
+ catch (error) {
3327
+ return Effect.fail(new RecoveryCodeSessionRotateStoreError({
3328
+ message: "Session assurance validation failed",
3329
+ cause: error,
3330
+ }));
3331
+ }
3332
+ const metadata = input.claims === undefined
3333
+ ? undefined
3334
+ : encodeSessionMetadata({
3335
+ ...current.value,
3336
+ claims: input.claims,
3337
+ });
3338
+ const sessionExists = queryExists(Query.select({ value: Query.literal(1) }).pipe(Query.from(tables.session.table), Query.where(queryEq(tables.session.select.id, input.sessionId)), Query.where(queryEq(tables.session.select.userId, input.userId)), Query.where(queryEq(tables.session.select.secretHash, input.expectedSecretHash)), Query.where(queryEq(tables.session.select.authenticationEvents, JSON.stringify(input.expectedAuthenticationEvents))), Query.where(queryIsNull(tables.session.select.revokedAt)), Query.where(queryGt(tables.session.select.expiresAt, Number(input.now)))));
3339
+ const consumePlan = queryUpdate(tables.recoveryCode.table, mutationValues(tables.recoveryCode.markUsed({
3340
+ id: input.codeId,
3341
+ usedAt: input.usedAt,
3342
+ ...(input.codeMetadata === undefined
3343
+ ? {}
3344
+ : { metadata: input.codeMetadata }),
3345
+ }))).pipe(Query.where(queryEq(tables.recoveryCode.select.id, input.codeId)), Query.where(queryEq(tables.recoveryCode.select.userId, input.userId)), Query.where(queryIsNull(tables.recoveryCode.select.usedAt)), Query.where(queryIsNull(tables.recoveryCode.select.revokedAt)), Query.where(sessionExists));
3346
+ const sessionValues = mutationValues(tables.session.assureAndRotate(current.value, input));
3347
+ if (metadata !== undefined) {
3348
+ sessionValues[boundColumnName(tables.session.select.metadata)] =
3349
+ metadata;
3350
+ }
3351
+ const rotatePlan = queryUpdate(tables.session.table, sessionValues).pipe(Query.where(queryEq(tables.session.select.id, input.sessionId)), Query.where(queryEq(tables.session.select.userId, input.userId)), Query.where(queryEq(tables.session.select.secretHash, input.expectedSecretHash)), Query.where(queryEq(tables.session.select.authenticationEvents, JSON.stringify(input.expectedAuthenticationEvents))), Query.where(queryIsNull(tables.session.select.revokedAt)), Query.where(queryGt(tables.session.select.expiresAt, Number(input.now))), Query.where(queryChangedOne()), Query.returning({ id: tables.session.select.id }));
3352
+ return executeAtomicPlans(atomic, [
3353
+ consumePlan,
3354
+ rotatePlan,
3355
+ ]).pipe(Effect.mapError((cause) => new RecoveryCodeSessionRotateStoreError({
3356
+ message: "Atomic recovery session rotation failed",
3357
+ cause,
3358
+ })), Effect.flatMap((results) => results[1].length === 0
3359
+ ? Effect.succeed(Option.none())
3360
+ : findSessionById(executor, tables, input.sessionId, "assureAndRotate").pipe(Effect.mapError((cause) => new RecoveryCodeSessionRotateStoreError({
3361
+ message: "Failed to load rotated session",
3362
+ cause,
3363
+ })))));
3364
+ }), Effect.mapError((cause) => cause instanceof RecoveryCodeSessionRotateStoreError
3365
+ ? cause
3366
+ : new RecoveryCodeSessionRotateStoreError({
3367
+ message: "Failed to load current session",
3368
+ cause,
3369
+ }))),
3370
+ });
3371
+ };
3372
+ export const makeEffectQbSqliteTotpSessionRotateStore = (executor, atomic, options = {}) => {
3373
+ const tables = options.tables ?? authSqliteTables;
3374
+ return TotpSessionRotateStore.of({
3375
+ acceptAndRotate: (input) => findSessionById(executor, tables, input.sessionId, "assureAndRotate").pipe(Effect.flatMap((current) => {
3376
+ if (Option.isNone(current) || current.value.userId !== input.userId) {
3377
+ return Effect.succeed(Option.none());
3378
+ }
3379
+ try {
3380
+ assertSessionAssurance(input);
3381
+ }
3382
+ catch (error) {
3383
+ return Effect.fail(new TotpSessionRotateStoreError({
3384
+ message: "Session assurance validation failed",
3385
+ cause: error,
3386
+ }));
3387
+ }
3388
+ const metadata = input.claims === undefined
3389
+ ? undefined
3390
+ : encodeSessionMetadata({
3391
+ ...current.value,
3392
+ claims: input.claims,
3393
+ });
3394
+ const sessionExists = queryExists(Query.select({ value: Query.literal(1) }).pipe(Query.from(tables.session.table), Query.where(queryEq(tables.session.select.id, input.sessionId)), Query.where(queryEq(tables.session.select.userId, input.userId)), Query.where(queryEq(tables.session.select.secretHash, input.expectedSecretHash)), Query.where(queryEq(tables.session.select.authenticationEvents, JSON.stringify(input.expectedAuthenticationEvents))), Query.where(queryIsNull(tables.session.select.revokedAt)), Query.where(queryGt(tables.session.select.expiresAt, Number(input.now)))));
3395
+ const acceptPlan = queryUpdate(tables.totpFactor.table, mutationValues(tables.totpFactor.acceptCounter({
3396
+ id: input.factorId,
3397
+ counter: input.counter,
3398
+ lastUsedAt: input.lastUsedAt,
3399
+ ...(input.factorMetadata === undefined
3400
+ ? {}
3401
+ : { metadata: input.factorMetadata }),
3402
+ }))).pipe(Query.where(queryEq(tables.totpFactor.select.id, input.factorId)), Query.where(queryEq(tables.totpFactor.select.userId, input.userId)), Query.where(queryIsNotNull(tables.totpFactor.select.confirmedAt)), Query.where(queryIsNull(tables.totpFactor.select.revokedAt)), Query.where(queryOr(queryIsNull(tables.totpFactor.select.lastAcceptedCounter), queryLt(tables.totpFactor.select.lastAcceptedCounter, input.counter))), Query.where(sessionExists));
3403
+ const sessionValues = mutationValues(tables.session.assureAndRotate(current.value, input));
3404
+ if (metadata !== undefined) {
3405
+ sessionValues[boundColumnName(tables.session.select.metadata)] =
3406
+ metadata;
3407
+ }
3408
+ const rotatePlan = queryUpdate(tables.session.table, sessionValues).pipe(Query.where(queryEq(tables.session.select.id, input.sessionId)), Query.where(queryEq(tables.session.select.userId, input.userId)), Query.where(queryEq(tables.session.select.secretHash, input.expectedSecretHash)), Query.where(queryEq(tables.session.select.authenticationEvents, JSON.stringify(input.expectedAuthenticationEvents))), Query.where(queryIsNull(tables.session.select.revokedAt)), Query.where(queryGt(tables.session.select.expiresAt, Number(input.now))), Query.where(queryChangedOne()), Query.returning({ id: tables.session.select.id }));
3409
+ return executeAtomicPlans(atomic, [
3410
+ acceptPlan,
3411
+ rotatePlan,
3412
+ ]).pipe(Effect.mapError((cause) => new TotpSessionRotateStoreError({
3413
+ message: "Atomic TOTP session rotation failed",
3414
+ cause,
3415
+ })), Effect.flatMap((results) => results[1].length === 0
3416
+ ? Effect.succeed(Option.none())
3417
+ : findSessionById(executor, tables, input.sessionId, "assureAndRotate").pipe(Effect.mapError((cause) => new TotpSessionRotateStoreError({
3418
+ message: "Failed to load rotated session",
3419
+ cause,
3420
+ })))));
3421
+ }), Effect.mapError((cause) => cause instanceof TotpSessionRotateStoreError
3422
+ ? cause
3423
+ : new TotpSessionRotateStoreError({
3424
+ message: "Failed to load current session",
3425
+ cause,
3426
+ }))),
3427
+ });
3428
+ };
3429
+ export const makeEffectQbSqliteTotpLoginCommitStore = (atomic, options = {}) => {
3430
+ const tables = options.tables ?? authSqliteTables;
3431
+ return TotpLoginCommitStore.of({
3432
+ commit: (input) => {
3433
+ const { verification, session } = input;
3434
+ if (verification.type !== "auth-flow-state" ||
3435
+ verification.subject !== input.userId ||
3436
+ (session === undefined && input.continuation !== "login-approval") ||
3437
+ (session !== undefined && input.continuation !== undefined) ||
3438
+ (session !== undefined && session.userId !== input.userId)) {
3439
+ return Effect.succeed(false);
3440
+ }
3441
+ const factorExists = queryExists(Query.select({ value: Query.literal(1) }).pipe(Query.from(tables.totpFactor.table), Query.where(queryEq(tables.totpFactor.select.id, input.factorId)), Query.where(queryEq(tables.totpFactor.select.userId, input.userId)), Query.where(queryIsNotNull(tables.totpFactor.select.confirmedAt)), Query.where(queryIsNull(tables.totpFactor.select.revokedAt)), Query.where(queryOr(queryIsNull(tables.totpFactor.select.lastAcceptedCounter), queryLt(tables.totpFactor.select.lastAcceptedCounter, input.counter)))));
3442
+ const verificationPlan = queryUpdate(tables.verification.table, mutationValues(tables.verification.consume({
3443
+ id: verification.id,
3444
+ type: verification.type,
3445
+ consumedAt: input.consumedAt,
3446
+ }))).pipe(Query.where(queryEq(tables.verification.select.id, verification.id)), Query.where(queryEq(tables.verification.select.type, verification.type)), Query.where(queryEq(tables.verification.select.subject, verification.subject)), Query.where(queryIsNotDistinctFrom(tables.verification.select.secretHash, verification.secretHash ?? null)), Query.where(queryEq(tables.verification.select.createdAt, Number(verification.createdAt))), Query.where(queryEq(tables.verification.select.expiresAt, Number(verification.expiresAt))), Query.where(queryIsNotDistinctFrom(tables.verification.select.metadata, jsonEncode(verification.metadata))), Query.where(queryIsNull(tables.verification.select.consumedAt)), Query.where(queryGt(tables.verification.select.expiresAt, Number(input.consumedAt))), Query.where(factorExists));
3447
+ const factorPlan = queryUpdate(tables.totpFactor.table, mutationValues(tables.totpFactor.acceptCounter({
3448
+ id: input.factorId,
3449
+ counter: input.counter,
3450
+ lastUsedAt: input.lastUsedAt,
3451
+ ...(input.factorMetadata === undefined
3452
+ ? {}
3453
+ : { metadata: input.factorMetadata }),
3454
+ }))).pipe(Query.where(queryEq(tables.totpFactor.select.id, input.factorId)), Query.where(queryEq(tables.totpFactor.select.userId, input.userId)), Query.where(queryIsNotNull(tables.totpFactor.select.confirmedAt)), Query.where(queryIsNull(tables.totpFactor.select.revokedAt)), Query.where(queryOr(queryIsNull(tables.totpFactor.select.lastAcceptedCounter), queryLt(tables.totpFactor.select.lastAcceptedCounter, input.counter))), Query.where(queryChangedOne()), Query.returning({ id: tables.totpFactor.select.id }));
3455
+ if (session === undefined) {
3456
+ return executeAtomicPlans(atomic, [
3457
+ verificationPlan,
3458
+ factorPlan,
3459
+ ]).pipe(Effect.map((results) => results[1].length === 1), Effect.mapError((cause) => new TotpLoginCommitStoreError({
3460
+ message: "Atomic TOTP login commit failed",
3461
+ cause,
3462
+ })));
3463
+ }
3464
+ const sessionExists = queryExists(Query.select({ value: Query.literal(1) }).pipe(Query.from(tables.session.table), Query.where(queryEq(tables.session.select.id, session.id))));
3465
+ const sessionVerificationPlan = verificationPlan.pipe(Query.where(queryNot(sessionExists)));
3466
+ const sessionPlan = queryInsertWhere(tables.session.table, mutationValues(tables.session.insert(session)), queryChangedOne()).pipe(Query.returning({ id: tables.session.select.id }));
3467
+ return executeAtomicPlans(atomic, [
3468
+ sessionVerificationPlan,
3469
+ factorPlan,
3470
+ sessionPlan,
3471
+ ]).pipe(Effect.map((results) => results[2].length === 1), Effect.mapError((cause) => new TotpLoginCommitStoreError({
3472
+ message: "Atomic TOTP login commit failed",
3473
+ cause,
3474
+ })));
3475
+ },
3476
+ });
3477
+ };
3478
+ export const makeEffectQbSqliteRecoveryCodeLoginCommitStore = (atomic, options = {}) => {
3479
+ const tables = options.tables ?? authSqliteTables;
3480
+ return RecoveryCodeLoginCommitStore.of({
3481
+ commit: (input) => {
3482
+ const { verification, session } = input;
3483
+ if (verification.type !== "auth-flow-state" ||
3484
+ verification.subject !== input.userId ||
3485
+ (session === undefined && input.continuation !== "login-approval") ||
3486
+ (session !== undefined && input.continuation !== undefined) ||
3487
+ (session !== undefined && session.userId !== input.userId)) {
3488
+ return Effect.succeed(false);
3489
+ }
3490
+ const codeExists = queryExists(Query.select({ value: Query.literal(1) }).pipe(Query.from(tables.recoveryCode.table), Query.where(queryEq(tables.recoveryCode.select.id, input.codeId)), Query.where(queryEq(tables.recoveryCode.select.userId, input.userId)), Query.where(queryIsNull(tables.recoveryCode.select.usedAt)), Query.where(queryIsNull(tables.recoveryCode.select.revokedAt))));
3491
+ const verificationPlan = queryUpdate(tables.verification.table, mutationValues(tables.verification.consume({
3492
+ id: verification.id,
3493
+ type: verification.type,
3494
+ consumedAt: input.consumedAt,
3495
+ }))).pipe(Query.where(queryEq(tables.verification.select.id, verification.id)), Query.where(queryEq(tables.verification.select.type, verification.type)), Query.where(queryEq(tables.verification.select.subject, verification.subject)), Query.where(queryIsNotDistinctFrom(tables.verification.select.secretHash, verification.secretHash ?? null)), Query.where(queryEq(tables.verification.select.createdAt, Number(verification.createdAt))), Query.where(queryEq(tables.verification.select.expiresAt, Number(verification.expiresAt))), Query.where(queryIsNotDistinctFrom(tables.verification.select.metadata, jsonEncode(verification.metadata))), Query.where(queryIsNull(tables.verification.select.consumedAt)), Query.where(queryGt(tables.verification.select.expiresAt, Number(input.consumedAt))), Query.where(codeExists));
3496
+ const codePlan = queryUpdate(tables.recoveryCode.table, mutationValues(tables.recoveryCode.markUsed({
3497
+ id: input.codeId,
3498
+ usedAt: input.usedAt,
3499
+ ...(input.codeMetadata === undefined
3500
+ ? {}
3501
+ : { metadata: input.codeMetadata }),
3502
+ }))).pipe(Query.where(queryEq(tables.recoveryCode.select.id, input.codeId)), Query.where(queryEq(tables.recoveryCode.select.userId, input.userId)), Query.where(queryIsNull(tables.recoveryCode.select.usedAt)), Query.where(queryIsNull(tables.recoveryCode.select.revokedAt)), Query.where(queryChangedOne()), Query.returning({ id: tables.recoveryCode.select.id }));
3503
+ if (session === undefined) {
3504
+ return executeAtomicPlans(atomic, [
3505
+ verificationPlan,
3506
+ codePlan,
3507
+ ]).pipe(Effect.map((results) => results[1].length === 1), Effect.mapError((cause) => new RecoveryCodeLoginCommitStoreError({
3508
+ message: "Atomic recovery-code login commit failed",
3509
+ cause,
3510
+ })));
3511
+ }
3512
+ const sessionExists = queryExists(Query.select({ value: Query.literal(1) }).pipe(Query.from(tables.session.table), Query.where(queryEq(tables.session.select.id, session.id))));
3513
+ const sessionVerificationPlan = verificationPlan.pipe(Query.where(queryNot(sessionExists)));
3514
+ const sessionPlan = queryInsertWhere(tables.session.table, mutationValues(tables.session.insert(session)), queryChangedOne()).pipe(Query.returning({ id: tables.session.select.id }));
3515
+ return executeAtomicPlans(atomic, [
3516
+ sessionVerificationPlan,
3517
+ codePlan,
3518
+ sessionPlan,
3519
+ ]).pipe(Effect.map((results) => results[2].length === 1), Effect.mapError((cause) => new RecoveryCodeLoginCommitStoreError({
3520
+ message: "Atomic recovery-code login commit failed",
3521
+ cause,
3522
+ })));
3523
+ },
3524
+ });
3525
+ };
2424
3526
  export const makeEffectQbSqliteApiKeyStore = (executor, options = {}) => {
2425
3527
  const tables = options.tables ?? authSqliteTables;
2426
- return ApiKeyStore.make({
3528
+ return ApiKeyStore.of({
2427
3529
  insert: (row) => executePlan(executor, queryInsert(tables.apiKey.table, mutationValues(tables.apiKey.insert(row)))).pipe(Effect.mapError((cause) => apiKeyStoreError("insert", cause)), Effect.asVoid),
2428
3530
  findById: (id) => findApiKeyById(executor, tables, id),
2429
3531
  findByPrefix: (prefix) => findApiKeyByPrefix(executor, tables, prefix),
@@ -2445,9 +3547,9 @@ export const makeEffectQbSqliteApiKeyStore = (executor, options = {}) => {
2445
3547
  })),
2446
3548
  });
2447
3549
  };
2448
- export const makeEffectQbSqliteRefreshTokenStore = (executor, options = {}) => {
3550
+ export const makeEffectQbSqliteRefreshTokenStore = (executor, atomic, options = {}) => {
2449
3551
  const tables = options.tables ?? authSqliteTables;
2450
- return RefreshTokenStore.make({
3552
+ return RefreshTokenStore.of({
2451
3553
  insert: (row) => executePlan(executor, queryInsert(tables.refreshToken.table, mutationValues(tables.refreshToken.insert(row)))).pipe(Effect.mapError((cause) => refreshTokenStoreError("insert", cause)), Effect.asVoid),
2452
3554
  findById: (id) => findRefreshTokenById(executor, tables, id),
2453
3555
  findBySecretHash: (hash) => findRefreshTokenBySecretHash(executor, tables, hash),
@@ -2465,21 +3567,28 @@ export const makeEffectQbSqliteRefreshTokenStore = (executor, options = {}) => {
2465
3567
  : base.pipe(Query.where(queryIsNull(tables.refreshToken.select.revokedAt)));
2466
3568
  return executePlan(executor, query).pipe(Effect.mapError((cause) => refreshTokenStoreError("list", cause)), Effect.map((rows) => rows.map((row) => decodeRefreshTokenRow(tables, row))));
2467
3569
  },
2468
- rotate: (input) => executePlan(executor, queryUpdate(tables.refreshToken.table, mutationValues(tables.refreshToken.rotate(input))).pipe(Query.where(queryEq(tables.refreshToken.select.id, input.id)), Query.where(queryIsNull(tables.refreshToken.select.rotatedAt)), Query.where(queryIsNull(tables.refreshToken.select.revokedAt)), Query.returning({ id: tables.refreshToken.select.id }))).pipe(Effect.mapError((cause) => refreshTokenStoreError("rotate", cause)), Effect.flatMap((rows) => {
2469
- const [updated] = rows;
2470
- if (updated === undefined) {
2471
- return Effect.succeed(Option.none());
2472
- }
2473
- return findRefreshTokenById(executor, tables, RefreshTokenId(updated.id), "rotate").pipe(Effect.flatMap((previous) => {
2474
- if (Option.isNone(previous)) {
2475
- return Effect.succeed(Option.none());
2476
- }
2477
- return executePlan(executor, queryInsert(tables.refreshToken.table, mutationValues(tables.refreshToken.insert(input.replacement)))).pipe(Effect.mapError((cause) => refreshTokenStoreError("rotate", cause)), Effect.as(Option.some({
2478
- previous: previous.value,
2479
- replacement: input.replacement,
2480
- })));
3570
+ rotate: (input) => {
3571
+ const { replacement } = input;
3572
+ const rotatePlan = queryUpdate(tables.refreshToken.table, mutationValues(tables.refreshToken.rotate(input))).pipe(Query.where(queryEq(tables.refreshToken.select.id, input.id)), Query.where(queryIsNull(tables.refreshToken.select.rotatedAt)), Query.where(queryIsNull(tables.refreshToken.select.revokedAt)), Query.returning({
3573
+ ...selectRefreshTokenColumns(tables),
3574
+ lastUsedAt: queryWithoutNullRefinement(tables.refreshToken.select.lastUsedAt),
3575
+ rotatedAt: queryWithoutNullRefinement(tables.refreshToken.select.rotatedAt),
3576
+ replacedById: queryWithoutNullRefinement(tables.refreshToken.select.replacedById),
2481
3577
  }));
2482
- })),
3578
+ const replacementPlan = queryInsertWhere(tables.refreshToken.table, mutationValues(tables.refreshToken.insert(replacement)), queryChangedOne()).pipe(Query.returning({ id: tables.refreshToken.select.id }));
3579
+ return executeAtomicPlans(atomic, [
3580
+ rotatePlan,
3581
+ replacementPlan,
3582
+ ]).pipe(Effect.mapError((cause) => refreshTokenStoreError("rotate", cause)), Effect.map(([previousRows, replacementRows]) => {
3583
+ const [previous] = previousRows;
3584
+ return previous === undefined || replacementRows.length === 0
3585
+ ? Option.none()
3586
+ : Option.some({
3587
+ previous: decodeRefreshTokenRow(tables, previous),
3588
+ replacement,
3589
+ });
3590
+ }));
3591
+ },
2483
3592
  markReuseDetected: (input) => executePlan(executor, queryUpdate(tables.refreshToken.table, mutationValues(tables.refreshToken.markReuseDetected(input))).pipe(Query.where(queryEq(tables.refreshToken.select.id, input.id)), Query.returning(selectRefreshTokenColumns(tables)))).pipe(Effect.mapError((cause) => refreshTokenStoreError("reuse", cause)), Effect.map((rows) => rows[0] === undefined
2484
3593
  ? Option.none()
2485
3594
  : Option.some(decodeRefreshTokenRow(tables, rows[0])))),
@@ -2494,7 +3603,7 @@ export const makeEffectQbSqliteRefreshTokenStore = (executor, options = {}) => {
2494
3603
  };
2495
3604
  export const makeEffectQbSqliteJwtRevocationStore = (executor, options = {}) => {
2496
3605
  const tables = options.tables ?? authSqliteTables;
2497
- return JwtRevocationStore.make({
3606
+ return JwtRevocationStore.of({
2498
3607
  revoke: (record) => findJwtRevocation(executor, tables, record.jwtId, "revoke").pipe(Effect.flatMap((existing) => {
2499
3608
  if (Option.isNone(existing)) {
2500
3609
  return executePlan(executor, queryInsert(tables.jwtRevocation.table, mutationValues(tables.jwtRevocation.insert(record))));
@@ -2508,7 +3617,7 @@ export const makeEffectQbSqliteJwtRevocationStore = (executor, options = {}) =>
2508
3617
  };
2509
3618
  export const makeEffectQbSqliteOAuthAccountStore = (executor, options = {}) => {
2510
3619
  const tables = options.tables ?? authSqliteTables;
2511
- return OAuthAccountStore.make({
3620
+ return OAuthAccountStore.of({
2512
3621
  insert: (row) => executePlan(executor, queryInsert(tables.oauthAccount.table, mutationValues(tables.oauthAccount.insert(row)))).pipe(Effect.mapError((cause) => oauthAccountStoreError("insert", cause)), Effect.asVoid),
2513
3622
  findById: (id) => findOAuthAccountById(executor, tables, id),
2514
3623
  findByProviderAccount: (input) => findOAuthAccountByProviderAccount(executor, tables, input),
@@ -2532,9 +3641,90 @@ export const makeEffectQbSqliteOAuthAccountStore = (executor, options = {}) => {
2532
3641
  })),
2533
3642
  });
2534
3643
  };
3644
+ export const makeEffectQbSqliteOAuthIdentityBridgeStore = (executor, atomic, options = {}) => {
3645
+ const tables = options.tables ?? authSqliteTables;
3646
+ const identities = makeEffectQbSqliteIdentityStore(executor, atomic, options);
3647
+ const accounts = makeEffectQbSqliteOAuthAccountStore(executor, options);
3648
+ const classify = (input, cause) => Effect.gen(function* () {
3649
+ if (Option.isSome(yield* findUserById(executor, tables, input.user.id))) {
3650
+ return yield* new OAuthIdentityBridgeConflictError({
3651
+ reason: "user-id",
3652
+ });
3653
+ }
3654
+ if (input.identity !== undefined) {
3655
+ if (Option.isSome(yield* identities.findById(input.identity.id))) {
3656
+ return yield* new OAuthIdentityBridgeConflictError({
3657
+ reason: "identity-id",
3658
+ });
3659
+ }
3660
+ if (Option.isSome(yield* identities.findByKindAndNormalizedValue({
3661
+ scope: input.identity.scope,
3662
+ kind: input.identity.kind,
3663
+ normalizedValue: input.identity.normalizedValue,
3664
+ }))) {
3665
+ return yield* new OAuthIdentityBridgeConflictError({
3666
+ reason: "identity-value",
3667
+ });
3668
+ }
3669
+ }
3670
+ if (Option.isSome(yield* accounts.findById(input.account.id))) {
3671
+ return yield* new OAuthIdentityBridgeConflictError({
3672
+ reason: "oauth-account-id",
3673
+ });
3674
+ }
3675
+ if (Option.isSome(yield* accounts.findByProviderAccount({
3676
+ providerId: input.account.providerId,
3677
+ providerAccountId: input.account.providerAccountId,
3678
+ includeUnlinked: true,
3679
+ }))) {
3680
+ return yield* new OAuthIdentityBridgeConflictError({
3681
+ reason: "provider-account",
3682
+ });
3683
+ }
3684
+ return yield* new OAuthIdentityBridgeStoreError({
3685
+ message: "Atomic OAuth identity creation failed",
3686
+ cause,
3687
+ });
3688
+ }).pipe(Effect.catch((error) => error instanceof OAuthIdentityBridgeConflictError ||
3689
+ error instanceof OAuthIdentityBridgeStoreError
3690
+ ? Effect.fail(error)
3691
+ : Effect.fail(new OAuthIdentityBridgeStoreError({
3692
+ message: "OAuth identity conflict classification failed",
3693
+ cause: error,
3694
+ }))));
3695
+ return OAuthIdentityBridgeStore.of({
3696
+ create: (input) => {
3697
+ if (input.account.userId !== input.user.id ||
3698
+ (input.identity !== undefined &&
3699
+ input.identity.userId !== input.user.id)) {
3700
+ return Effect.fail(new OAuthIdentityBridgeConflictError({ reason: "user-id" }));
3701
+ }
3702
+ const plans = [
3703
+ queryInsert(tables.user.table, mutationValues(tables.user.insert(input.user))),
3704
+ ];
3705
+ if (input.identity !== undefined) {
3706
+ plans.push(queryInsert(tables.identity.table, mutationValues(tables.identity.insert(input.identity))));
3707
+ }
3708
+ plans.push(queryInsert(tables.oauthAccount.table, mutationValues(tables.oauthAccount.insert(input.account))));
3709
+ return atomic.execute(plans).pipe(Effect.map(() => ({
3710
+ user: input.user,
3711
+ ...(input.identity === undefined
3712
+ ? {}
3713
+ : {
3714
+ identity: {
3715
+ ...input.identity,
3716
+ isPrimaryLogin: input.identity.isPrimaryLogin ?? false,
3717
+ updatedAt: input.identity.createdAt,
3718
+ },
3719
+ }),
3720
+ account: input.account,
3721
+ })), Effect.catch((error) => classify(input, error)));
3722
+ },
3723
+ });
3724
+ };
2535
3725
  export const makeEffectQbSqliteOAuthClientStore = (executor, options = {}) => {
2536
3726
  const tables = options.tables ?? authSqliteTables;
2537
- return OAuthClientStore.make({
3727
+ return OAuthClientStore.of({
2538
3728
  upsert: (record) => executePlan(executor, queryInsert(tables.oauthClient.table, mutationValues(tables.oauthClient.insert(record))).pipe(Query.onConflict("id", {
2539
3729
  update: mutationValues(tables.oauthClient.update(record)),
2540
3730
  }))).pipe(Effect.mapError((cause) => oauthClientStoreError("upsert", cause)), Effect.asVoid),
@@ -2551,7 +3741,7 @@ export const makeEffectQbSqliteOAuthClientStore = (executor, options = {}) => {
2551
3741
  };
2552
3742
  export const makeEffectQbSqliteOAuthConsentStore = (executor, options = {}) => {
2553
3743
  const tables = options.tables ?? authSqliteTables;
2554
- return OAuthConsentStore.make({
3744
+ return OAuthConsentStore.of({
2555
3745
  upsert: (record) => executePlan(executor, queryInsert(tables.oauthConsent.table, mutationValues(tables.oauthConsent.insert(record))).pipe(Query.onConflict("id", {
2556
3746
  update: mutationValues(tables.oauthConsent.update(record)),
2557
3747
  }))).pipe(Effect.mapError((cause) => oauthConsentStoreError("upsert", cause)), Effect.asVoid),
@@ -2575,12 +3765,14 @@ export const makeEffectQbSqliteOAuthConsentStore = (executor, options = {}) => {
2575
3765
  };
2576
3766
  export const makeEffectQbSqliteOAuthAuthorizationCodeStore = (executor, options = {}) => {
2577
3767
  const tables = options.tables ?? authSqliteTables;
2578
- return OAuthAuthorizationCodeStore.make({
3768
+ return OAuthAuthorizationCodeStore.of({
2579
3769
  upsert: (record) => executePlan(executor, queryInsert(tables.oauthAuthorizationCode.table, mutationValues(tables.oauthAuthorizationCode.insert(record))).pipe(Query.onConflict("code_hash", {
2580
3770
  update: mutationValues(tables.oauthAuthorizationCode.update(record)),
2581
3771
  }))).pipe(Effect.mapError((cause) => oauthAuthorizationCodeStoreError("upsert", cause)), Effect.asVoid),
2582
3772
  findByHash: (input) => findOAuthAuthorizationCodeByHash(executor, tables, input),
2583
- markConsumed: (input) => executePlan(executor, queryUpdate(tables.oauthAuthorizationCode.table, mutationValues(tables.oauthAuthorizationCode.markConsumed(input))).pipe(Query.where(queryEq(tables.oauthAuthorizationCode.select.codeHash, input.codeHash)), Query.where(queryIsNull(tables.oauthAuthorizationCode.select.consumedAt)), Query.returning({ codeHash: tables.oauthAuthorizationCode.select.codeHash }))).pipe(Effect.mapError((cause) => oauthAuthorizationCodeStoreError("mark-consumed", cause)), Effect.flatMap((rows) => {
3773
+ markConsumed: (input) => executePlan(executor, queryUpdate(tables.oauthAuthorizationCode.table, mutationValues(tables.oauthAuthorizationCode.markConsumed(input))).pipe(Query.where(queryEq(tables.oauthAuthorizationCode.select.codeHash, input.codeHash)), Query.where(queryIsNull(tables.oauthAuthorizationCode.select.consumedAt)), Query.returning({
3774
+ codeHash: tables.oauthAuthorizationCode.select.codeHash,
3775
+ }))).pipe(Effect.mapError((cause) => oauthAuthorizationCodeStoreError("mark-consumed", cause)), Effect.flatMap((rows) => {
2584
3776
  const [consumed] = rows;
2585
3777
  if (consumed !== undefined) {
2586
3778
  return findOAuthAuthorizationCodeByHash(executor, tables, { codeHash: consumed.codeHash }, "mark-consumed").pipe(Effect.map((record) => Option.isNone(record)
@@ -2601,8 +3793,8 @@ export const makeEffectQbSqliteOAuthAuthorizationCodeStore = (executor, options
2601
3793
  };
2602
3794
  export const makeEffectQbSqliteOAuthClientSecretStore = (executor, options = {}) => {
2603
3795
  const tables = options.tables ?? authSqliteTables;
2604
- return OAuthClientSecretStore.make({
2605
- upsert: (record) => executePlan(executor, queryInsert(tables.oauthClientSecret.table, mutationValues(tables.oauthClientSecret.insert(record))).pipe(Query.onConflict("prefix", {
3796
+ return OAuthClientSecretStore.of({
3797
+ upsert: (record) => executePlan(executor, queryInsert(tables.oauthClientSecret.table, mutationValues(tables.oauthClientSecret.insert(record))).pipe(Query.onConflict(["client_id", "prefix"], {
2606
3798
  update: mutationValues(tables.oauthClientSecret.update(record)),
2607
3799
  }))).pipe(Effect.mapError((cause) => oauthClientSecretStoreError("upsert", cause)), Effect.asVoid),
2608
3800
  findByClientIdAndPrefix: (input) => findOAuthClientSecretByClientIdAndPrefix(executor, tables, input),
@@ -2621,7 +3813,7 @@ export const makeEffectQbSqliteOAuthClientSecretStore = (executor, options = {})
2621
3813
  };
2622
3814
  export const makeEffectQbSqliteOAuthProviderModeTokenStore = (executor, options = {}) => {
2623
3815
  const tables = options.tables ?? authSqliteTables;
2624
- return OAuthProviderModeTokenStore.make({
3816
+ return OAuthProviderModeTokenStore.of({
2625
3817
  upsert: (record) => executePlan(executor, queryInsert(tables.oauthProviderModeToken.table, mutationValues(tables.oauthProviderModeToken.insert(record))).pipe(Query.onConflict("token_hash", {
2626
3818
  update: mutationValues(tables.oauthProviderModeToken.update(record)),
2627
3819
  }))).pipe(Effect.mapError((cause) => oauthProviderModeTokenStoreError("upsert", cause)), Effect.asVoid),
@@ -2646,7 +3838,7 @@ export const makeEffectQbSqliteOAuthProviderModeTokenStore = (executor, options
2646
3838
  };
2647
3839
  export const makeEffectQbSqliteOAuthProviderTokenVault = (executor, options = {}) => {
2648
3840
  const tables = options.tables ?? authSqliteTables;
2649
- return OAuthProviderTokenVault.make({
3841
+ return OAuthProviderTokenVault.of({
2650
3842
  upsert: (record) => findOAuthProviderTokenVaultRecordByAccountId(executor, tables, { accountId: record.accountId }, "upsert", true).pipe(Effect.flatMap((existing) => Option.isNone(existing)
2651
3843
  ? executePlan(executor, queryInsert(tables.oauthProviderTokenVault.table, mutationValues(tables.oauthProviderTokenVault.insert(record))))
2652
3844
  : executePlan(executor, queryUpdate(tables.oauthProviderTokenVault.table, mutationValues(tables.oauthProviderTokenVault.update(record))).pipe(Query.where(queryEq(tables.oauthProviderTokenVault.select.accountId, record.accountId))))), Effect.mapError((cause) => cause instanceof OAuthProviderTokenVaultError
@@ -2671,7 +3863,7 @@ export const makeEffectQbSqliteOAuthProviderTokenVault = (executor, options = {}
2671
3863
  };
2672
3864
  export const makeEffectQbSqliteDomainVerificationStore = (executor, options = {}) => {
2673
3865
  const tables = options.tables ?? authSqliteTables;
2674
- return DomainVerificationStore.make({
3866
+ return DomainVerificationStore.of({
2675
3867
  upsert: (record) => findDomainVerificationByOwnerAndDomain(executor, tables, { ownerId: record.ownerId, domain: record.domain }, "upsert").pipe(Effect.flatMap((existing) => Option.isNone(existing)
2676
3868
  ? executePlan(executor, queryInsert(tables.domainVerification.table, mutationValues(tables.domainVerification.insert(record))))
2677
3869
  : executePlan(executor, queryUpdate(tables.domainVerification.table, mutationValues(tables.domainVerification.update(record))).pipe(Query.where(queryEq(tables.domainVerification.select.ownerId, record.ownerId)), Query.where(queryEq(tables.domainVerification.select.domain, record.domain))))), Effect.mapError((cause) => cause instanceof DomainVerificationStoreError
@@ -2683,8 +3875,8 @@ export const makeEffectQbSqliteDomainVerificationStore = (executor, options = {}
2683
3875
  const base = Query.select(selectDomainVerificationColumns(tables)).pipe(Query.from(tables.domainVerification.table), Query.where(queryEq(tables.domainVerification.select.ownerId, input.ownerId)));
2684
3876
  const query = input.includeRevoked === true
2685
3877
  ? base
2686
- : base.pipe(Query.where(queryIsNull(tables.domainVerification.select.revokedAt)));
2687
- return executePlan(executor, query).pipe(Effect.mapError((cause) => domainVerificationStoreError("list", cause)), Effect.map((rows) => rows.map((row) => decodeDomainVerificationRecord(tables, row))));
3878
+ : base.pipe(Query.where(queryNeq(tables.domainVerification.select.status, "revoked")));
3879
+ return executePlan(executor, query.pipe(Query.orderBy(tables.domainVerification.select.createdAt, "asc"), Query.orderBy(tables.domainVerification.select.id, "asc"))).pipe(Effect.mapError((cause) => domainVerificationStoreError("list", cause)), Effect.map((rows) => rows.map((row) => decodeDomainVerificationRecord(tables, row))));
2688
3880
  },
2689
3881
  markVerified: (input) => executePlan(executor, queryUpdate(tables.domainVerification.table, mutationValues(tables.domainVerification.markVerified(input))).pipe(Query.where(queryEq(tables.domainVerification.select.id, input.id)), Query.where(queryEq(tables.domainVerification.select.status, "pending")), Query.returning(selectDomainVerificationColumns(tables)))).pipe(Effect.mapError((cause) => domainVerificationStoreError("mark-verified", cause)), Effect.map((rows) => rows[0] === undefined
2690
3882
  ? Option.none()
@@ -2699,7 +3891,7 @@ export const makeEffectQbSqliteDomainVerificationStore = (executor, options = {}
2699
3891
  };
2700
3892
  export const makeEffectQbSqliteSecurityTimelineStore = (executor, options = {}) => {
2701
3893
  const tables = options.tables ?? authSqliteTables;
2702
- return SecurityTimelineStore.make({
3894
+ return SecurityTimelineStore.of({
2703
3895
  insert: (event) => executePlan(executor, queryInsert(tables.securityTimeline.table, mutationValues(tables.securityTimeline.insert(event)))).pipe(Effect.mapError((cause) => securityTimelineStoreError("insert", cause)), Effect.asVoid),
2704
3896
  listByUser: (input) => listSecurityTimelineByUser(executor, tables, input),
2705
3897
  deleteBefore: (input) => deleteSecurityTimelineBefore(executor, tables, input),
@@ -2707,7 +3899,7 @@ export const makeEffectQbSqliteSecurityTimelineStore = (executor, options = {})
2707
3899
  };
2708
3900
  export const makeEffectQbSqliteWebhookOutboxStore = (executor, options = {}) => {
2709
3901
  const tables = options.tables ?? authSqliteTables;
2710
- return WebhookOutboxStore.make({
3902
+ return WebhookOutboxStore.of({
2711
3903
  enqueue: (input) => Effect.gen(function* () {
2712
3904
  const now = input.now ?? (yield* currentUnixMillis);
2713
3905
  const record = {
@@ -2726,7 +3918,8 @@ export const makeEffectQbSqliteWebhookOutboxStore = (executor, options = {}) =>
2726
3918
  claimDue: (input) => claimDueWebhookOutboxRecords(executor, tables, input),
2727
3919
  markDelivered: (input) => Effect.gen(function* () {
2728
3920
  const existing = yield* findWebhookOutboxRecordById(executor, tables, input.id, "markDelivered");
2729
- if (Option.isNone(existing) || existing.value.status === "dead_lettered") {
3921
+ if (Option.isNone(existing) ||
3922
+ existing.value.status === "dead_lettered") {
2730
3923
  return;
2731
3924
  }
2732
3925
  const deliveredAt = input.deliveredAt ?? (yield* currentUnixMillis);
@@ -2764,7 +3957,7 @@ export const makeEffectQbSqliteWebhookOutboxStore = (executor, options = {}) =>
2764
3957
  };
2765
3958
  export const makeEffectQbSqliteWebhookReplayStore = (executor, options = {}) => {
2766
3959
  const tables = options.tables ?? authSqliteTables;
2767
- return WebhookReplayStore.make({
3960
+ return WebhookReplayStore.of({
2768
3961
  claim: (input) => Effect.gen(function* () {
2769
3962
  const now = input.now ?? (yield* currentUnixMillis);
2770
3963
  const claim = { ...input, now };
@@ -2777,8 +3970,297 @@ export const makeEffectQbSqliteWebhookReplayStore = (executor, options = {}) =>
2777
3970
  deleteExpired: (input = {}) => deleteExpiredWebhookReplayRecords(executor, tables, input),
2778
3971
  });
2779
3972
  };
2780
- export const makeEffectQbSqliteStorage = (executor, options = {}) => ({
3973
+ const permissionScopeKey = (scope) => [
3974
+ scope?.type ?? "global",
3975
+ scope?.id === undefined ? 0 : 1,
3976
+ scope?.id ?? "",
3977
+ ];
3978
+ const comparePermissionText = (left, right) => left === right
3979
+ ? 0
3980
+ : left === undefined
3981
+ ? -1
3982
+ : right === undefined
3983
+ ? 1
3984
+ : left < right
3985
+ ? -1
3986
+ : 1;
3987
+ const comparePermissionScopes = (left, right) => {
3988
+ if (left === undefined || right === undefined) {
3989
+ return left === right ? 0 : left === undefined ? -1 : 1;
3990
+ }
3991
+ return (comparePermissionText(left.type, right.type) ||
3992
+ comparePermissionText(left.id, right.id));
3993
+ };
3994
+ const permissionScopesMatchExactly = (left, right) => {
3995
+ const [leftType, leftIdPresent, leftId] = permissionScopeKey(left);
3996
+ const [rightType, rightIdPresent, rightId] = permissionScopeKey(right);
3997
+ return (leftType === rightType &&
3998
+ leftIdPresent === rightIdPresent &&
3999
+ leftId === rightId);
4000
+ };
4001
+ const permissionGrantIsActive = (grant, at) => grant.revokedAt === undefined &&
4002
+ (grant.expiresAt === undefined || Number(grant.expiresAt) > Number(at));
4003
+ const comparePermissionGrantRecords = (left, right) => comparePermissionText(left.subject.type, right.subject.type) ||
4004
+ comparePermissionText(left.subject.id, right.subject.id) ||
4005
+ comparePermissionText(left.permission, right.permission) ||
4006
+ comparePermissionScopes(left.scope, right.scope);
4007
+ const compareRoleGrantRecords = (left, right) => comparePermissionText(left.subject.type, right.subject.type) ||
4008
+ comparePermissionText(left.subject.id, right.subject.id) ||
4009
+ comparePermissionText(left.role, right.role) ||
4010
+ comparePermissionScopes(left.scope, right.scope);
4011
+ const compareRolePermissionRecords = (left, right) => comparePermissionText(left.role, right.role) ||
4012
+ comparePermissionText(left.permission, right.permission) ||
4013
+ comparePermissionText(left.scopeType, right.scopeType);
4014
+ export const makeEffectQbSqlitePermissionStore = (executor, options = {}) => {
4015
+ const tables = options.tables ?? authSqliteTables;
4016
+ const permissionGrantWhere = (input) => {
4017
+ const { scope: _scope, ...withoutScope } = input;
4018
+ const scope = canonicalPermissionScope(input.scope);
4019
+ const canonicalInput = {
4020
+ ...withoutScope,
4021
+ ...(scope === undefined ? {} : { scope }),
4022
+ };
4023
+ const [scopeType, scopeIdPresent, scopeId] = permissionScopeKey(scope);
4024
+ return queryUpdate(tables.permissionGrant.table, mutationValues(tables.permissionGrant.revoke(canonicalInput, input.revokedAt ?? UnixMillis(Date.now())))).pipe(Query.where(queryEq(tables.permissionGrant.select.subjectType, input.subject.type)), Query.where(queryEq(tables.permissionGrant.select.subjectId, input.subject.id)), Query.where(queryEq(tables.permissionGrant.select.permissionId, input.permission)), Query.where(queryEq(tables.permissionGrant.select.scopeType, scopeType)), Query.where(queryEq(tables.permissionGrant.select.scopeIdPresent, scopeIdPresent)), Query.where(queryEq(tables.permissionGrant.select.scopeId, scopeId)));
4025
+ };
4026
+ const roleGrantWhere = (input) => {
4027
+ const { scope: _scope, ...withoutScope } = input;
4028
+ const scope = canonicalPermissionScope(input.scope);
4029
+ const canonicalInput = {
4030
+ ...withoutScope,
4031
+ ...(scope === undefined ? {} : { scope }),
4032
+ };
4033
+ const [scopeType, scopeIdPresent, scopeId] = permissionScopeKey(scope);
4034
+ return queryUpdate(tables.roleGrant.table, mutationValues(tables.roleGrant.revoke(canonicalInput, input.revokedAt ?? UnixMillis(Date.now())))).pipe(Query.where(queryEq(tables.roleGrant.select.subjectType, input.subject.type)), Query.where(queryEq(tables.roleGrant.select.subjectId, input.subject.id)), Query.where(queryEq(tables.roleGrant.select.roleId, input.role)), Query.where(queryEq(tables.roleGrant.select.scopeType, scopeType)), Query.where(queryEq(tables.roleGrant.select.scopeIdPresent, scopeIdPresent)), Query.where(queryEq(tables.roleGrant.select.scopeId, scopeId)));
4035
+ };
4036
+ const checkResult = (operation, allowed) => executePlan(executor, Query.select({ allowed: allowed })).pipe(Effect.mapError((cause) => permissionStoreError(operation, cause)), Effect.map((rows) => decodeBoolean(rows[0]?.allowed)));
4037
+ const findPermissionDefinition = (id, operation = "find_permission_definition") => executePlan(executor, Query.select(selectPermissionDefinitionColumns(tables)).pipe(Query.from(tables.permissionDefinition.table), Query.where(queryEq(tables.permissionDefinition.select.id, id)))).pipe(Effect.mapError((cause) => permissionStoreError(operation, cause)), Effect.map((rows) => rows[0] === undefined
4038
+ ? Option.none()
4039
+ : Option.some(decodePermissionDefinition(tables, rows[0]))));
4040
+ const findRoleDefinition = (id, operation = "find_role_definition") => executePlan(executor, Query.select(selectRoleDefinitionColumns(tables)).pipe(Query.from(tables.roleDefinition.table), Query.where(queryEq(tables.roleDefinition.select.id, id)))).pipe(Effect.mapError((cause) => permissionStoreError(operation, cause)), Effect.map((rows) => rows[0] === undefined
4041
+ ? Option.none()
4042
+ : Option.some(decodeRoleDefinition(tables, rows[0]))));
4043
+ const mutatePermissionDefinition = (operation, input, values) => {
4044
+ const returning = operation === "delete_permission_definition"
4045
+ ? {
4046
+ ...selectPermissionDefinitionColumns(tables),
4047
+ deletedAt: queryWithoutNullRefinement(tables.permissionDefinition.select.deletedAt),
4048
+ }
4049
+ : selectPermissionDefinitionColumns(tables);
4050
+ return executePlan(executor, queryUpdate(tables.permissionDefinition.table, mutationValues(values)).pipe(Query.where(queryEq(tables.permissionDefinition.select.id, input.id)), Query.where(queryEq(tables.permissionDefinition.select.updatedAt, Number(input.expectedUpdatedAt))), Query.where(queryIsNull(tables.permissionDefinition.select.deletedAt)), Query.returning(returning))).pipe(Effect.mapError((cause) => permissionStoreError(operation, cause)), Effect.map((rows) => rows[0] === undefined
4051
+ ? Option.none()
4052
+ : Option.some(decodePermissionDefinition(tables, rows[0]))));
4053
+ };
4054
+ const mutateRoleDefinition = (operation, input, values) => {
4055
+ const returning = operation === "delete_role_definition"
4056
+ ? {
4057
+ ...selectRoleDefinitionColumns(tables),
4058
+ deletedAt: queryWithoutNullRefinement(tables.roleDefinition.select.deletedAt),
4059
+ }
4060
+ : selectRoleDefinitionColumns(tables);
4061
+ return executePlan(executor, queryUpdate(tables.roleDefinition.table, mutationValues(values)).pipe(Query.where(queryEq(tables.roleDefinition.select.id, input.id)), Query.where(queryEq(tables.roleDefinition.select.updatedAt, Number(input.expectedUpdatedAt))), Query.where(queryIsNull(tables.roleDefinition.select.deletedAt)), Query.returning(returning))).pipe(Effect.mapError((cause) => permissionStoreError(operation, cause)), Effect.map((rows) => rows[0] === undefined
4062
+ ? Option.none()
4063
+ : Option.some(decodeRoleDefinition(tables, rows[0]))));
4064
+ };
4065
+ return PermissionStore.of({
4066
+ createPermissionDefinition: (definition) => executePlan(executor, queryInsert(tables.permissionDefinition.table, mutationValues(tables.permissionDefinition.insert(definition))).pipe(SqliteQuery.onConflict(tables.permissionDefinition.conflictTarget), Query.returning(selectPermissionDefinitionColumns(tables)))).pipe(Effect.mapError((cause) => permissionStoreError("create_permission_definition", cause)), Effect.map((rows) => rows.length > 0)),
4067
+ findPermissionDefinition: (id) => findPermissionDefinition(id),
4068
+ listPermissionDefinitions: (input = {}) => {
4069
+ const base = Query.select(selectPermissionDefinitionColumns(tables)).pipe(Query.from(tables.permissionDefinition.table));
4070
+ const withoutDisabled = input.includeDisabled === true
4071
+ ? base
4072
+ : base.pipe(Query.where(queryIsNull(tables.permissionDefinition.select.disabledAt)));
4073
+ const withoutDeleted = input.includeDeleted === true
4074
+ ? withoutDisabled
4075
+ : withoutDisabled.pipe(Query.where(queryIsNull(tables.permissionDefinition.select.deletedAt)));
4076
+ const after = input.after === undefined
4077
+ ? withoutDeleted
4078
+ : withoutDeleted.pipe(Query.where(queryGt(tables.permissionDefinition.select.id, input.after)));
4079
+ const query = after.pipe(Query.orderBy(tables.permissionDefinition.select.id, "asc"), Query.limit(normalizePermissionDefinitionListLimit(input.limit)));
4080
+ return executePlan(executor, query).pipe(Effect.mapError((cause) => permissionStoreError("list_permission_definitions", cause)), Effect.map((rows) => rows.map((row) => decodePermissionDefinition(tables, row))));
4081
+ },
4082
+ updatePermissionDefinition: (input) => mutatePermissionDefinition("update_permission_definition", input, tables.permissionDefinition.update(input)),
4083
+ setPermissionDefinitionDisabled: (input) => mutatePermissionDefinition("set_permission_definition_disabled", input, tables.permissionDefinition.setDisabled(input)),
4084
+ deletePermissionDefinition: (input) => mutatePermissionDefinition("delete_permission_definition", input, tables.permissionDefinition.delete(input)),
4085
+ createRoleDefinition: (definition) => executePlan(executor, queryInsert(tables.roleDefinition.table, mutationValues(tables.roleDefinition.insert(definition))).pipe(SqliteQuery.onConflict(tables.roleDefinition.conflictTarget), Query.returning(selectRoleDefinitionColumns(tables)))).pipe(Effect.mapError((cause) => permissionStoreError("create_role_definition", cause)), Effect.map((rows) => rows.length > 0)),
4086
+ findRoleDefinition: (id) => findRoleDefinition(id),
4087
+ listRoleDefinitions: (input = {}) => {
4088
+ const base = Query.select(selectRoleDefinitionColumns(tables)).pipe(Query.from(tables.roleDefinition.table));
4089
+ const withoutDisabled = input.includeDisabled === true
4090
+ ? base
4091
+ : base.pipe(Query.where(queryIsNull(tables.roleDefinition.select.disabledAt)));
4092
+ const query = input.includeDeleted === true
4093
+ ? withoutDisabled
4094
+ : withoutDisabled.pipe(Query.where(queryIsNull(tables.roleDefinition.select.deletedAt)));
4095
+ return executePlan(executor, query).pipe(Effect.mapError((cause) => permissionStoreError("list_role_definitions", cause)), Effect.map((rows) => rows
4096
+ .map((row) => decodeRoleDefinition(tables, row))
4097
+ .sort((left, right) => left.id < right.id ? -1 : left.id > right.id ? 1 : 0)));
4098
+ },
4099
+ updateRoleDefinition: (input) => mutateRoleDefinition("update_role_definition", input, tables.roleDefinition.update(input)),
4100
+ setRoleDefinitionDisabled: (input) => mutateRoleDefinition("set_role_definition_disabled", input, tables.roleDefinition.setDisabled(input)),
4101
+ deleteRoleDefinition: (input) => mutateRoleDefinition("delete_role_definition", input, tables.roleDefinition.delete(input)),
4102
+ grantPermission: (input) => Effect.suspend(() => {
4103
+ const { scope: _scope, ...withoutScope } = input;
4104
+ const scope = canonicalPermissionScope(input.scope);
4105
+ const grant = {
4106
+ ...withoutScope,
4107
+ ...(scope === undefined ? {} : { scope }),
4108
+ };
4109
+ return executePlan(executor, queryInsert(tables.permissionGrant.table, mutationValues(tables.permissionGrant.insert(grant))).pipe(SqliteQuery.onConflict([
4110
+ "subject_type",
4111
+ "subject_id",
4112
+ "permission_id",
4113
+ "scope_type",
4114
+ "scope_id_present",
4115
+ "scope_id",
4116
+ ], { update: mutationValues(tables.permissionGrant.update(grant)) }))).pipe(Effect.mapError((cause) => permissionStoreError("grant_permission", cause)), Effect.asVoid);
4117
+ }),
4118
+ revokePermission: (input) => Effect.suspend(() => executePlan(executor, permissionGrantWhere(input)).pipe(Effect.mapError((cause) => permissionStoreError("revoke_permission", cause)), Effect.asVoid)),
4119
+ listPermissionGrants: (input = {}) => Effect.suspend(() => {
4120
+ const at = input.at ?? UnixMillis(Date.now());
4121
+ const activity = input.activity ?? "active";
4122
+ const base = Query.select(selectPermissionGrantColumns(tables)).pipe(Query.from(tables.permissionGrant.table));
4123
+ const bySubjectType = input.subject === undefined
4124
+ ? base
4125
+ : base.pipe(Query.where(queryEq(tables.permissionGrant.select.subjectType, input.subject.type)));
4126
+ const bySubject = input.subject === undefined
4127
+ ? bySubjectType
4128
+ : bySubjectType.pipe(Query.where(queryEq(tables.permissionGrant.select.subjectId, input.subject.id)));
4129
+ const byPermission = input.permission === undefined
4130
+ ? bySubject
4131
+ : bySubject.pipe(Query.where(queryEq(tables.permissionGrant.select.permissionId, input.permission)));
4132
+ const [scopeType, scopeIdPresent, scopeId] = permissionScopeKey(input.scope);
4133
+ const query = input.scope === undefined
4134
+ ? byPermission
4135
+ : byPermission.pipe(Query.where(queryEq(tables.permissionGrant.select.scopeType, scopeType)), Query.where(queryEq(tables.permissionGrant.select.scopeIdPresent, scopeIdPresent)), Query.where(queryEq(tables.permissionGrant.select.scopeId, scopeId)));
4136
+ return executePlan(executor, query).pipe(Effect.mapError((cause) => permissionStoreError("list_permission_grants", cause)), Effect.flatMap((rows) => Effect.try({
4137
+ try: () => rows
4138
+ .map((row) => decodePermissionGrant(tables, row))
4139
+ .filter((grant) => (input.subject === undefined ||
4140
+ (grant.subject.type === input.subject.type &&
4141
+ grant.subject.id === input.subject.id)) &&
4142
+ (input.permission === undefined ||
4143
+ grant.permission === input.permission) &&
4144
+ (input.scope === undefined ||
4145
+ permissionScopesMatchExactly(grant.scope, input.scope)) &&
4146
+ (activity === "all" ||
4147
+ permissionGrantIsActive(grant, at) ===
4148
+ (activity === "active")))
4149
+ .sort(comparePermissionGrantRecords),
4150
+ catch: (cause) => permissionStoreError("list_permission_grants", cause),
4151
+ })));
4152
+ }),
4153
+ grantRole: (input) => Effect.suspend(() => {
4154
+ const { scope: _scope, ...withoutScope } = input;
4155
+ const scope = canonicalPermissionScope(input.scope);
4156
+ const grant = {
4157
+ ...withoutScope,
4158
+ ...(scope === undefined ? {} : { scope }),
4159
+ };
4160
+ return executePlan(executor, queryInsert(tables.roleGrant.table, mutationValues(tables.roleGrant.insert(grant))).pipe(SqliteQuery.onConflict([
4161
+ "subject_type",
4162
+ "subject_id",
4163
+ "role_id",
4164
+ "scope_type",
4165
+ "scope_id_present",
4166
+ "scope_id",
4167
+ ], { update: mutationValues(tables.roleGrant.update(grant)) }))).pipe(Effect.mapError((cause) => permissionStoreError("grant_role", cause)), Effect.asVoid);
4168
+ }),
4169
+ revokeRole: (input) => Effect.suspend(() => executePlan(executor, roleGrantWhere(input)).pipe(Effect.mapError((cause) => permissionStoreError("revoke_role", cause)), Effect.asVoid)),
4170
+ listRoleGrants: (input = {}) => Effect.suspend(() => {
4171
+ const at = input.at ?? UnixMillis(Date.now());
4172
+ const activity = input.activity ?? "active";
4173
+ const base = Query.select(selectRoleGrantColumns(tables)).pipe(Query.from(tables.roleGrant.table));
4174
+ const bySubjectType = input.subject === undefined
4175
+ ? base
4176
+ : base.pipe(Query.where(queryEq(tables.roleGrant.select.subjectType, input.subject.type)));
4177
+ const bySubject = input.subject === undefined
4178
+ ? bySubjectType
4179
+ : bySubjectType.pipe(Query.where(queryEq(tables.roleGrant.select.subjectId, input.subject.id)));
4180
+ const byRole = input.role === undefined
4181
+ ? bySubject
4182
+ : bySubject.pipe(Query.where(queryEq(tables.roleGrant.select.roleId, input.role)));
4183
+ const [scopeType, scopeIdPresent, scopeId] = permissionScopeKey(input.scope);
4184
+ const query = input.scope === undefined
4185
+ ? byRole
4186
+ : byRole.pipe(Query.where(queryEq(tables.roleGrant.select.scopeType, scopeType)), Query.where(queryEq(tables.roleGrant.select.scopeIdPresent, scopeIdPresent)), Query.where(queryEq(tables.roleGrant.select.scopeId, scopeId)));
4187
+ return executePlan(executor, query).pipe(Effect.mapError((cause) => permissionStoreError("list_role_grants", cause)), Effect.flatMap((rows) => Effect.try({
4188
+ try: () => rows
4189
+ .map((row) => decodeRoleGrant(tables, row))
4190
+ .filter((grant) => (input.subject === undefined ||
4191
+ (grant.subject.type === input.subject.type &&
4192
+ grant.subject.id === input.subject.id)) &&
4193
+ (input.role === undefined || grant.role === input.role) &&
4194
+ (input.scope === undefined ||
4195
+ permissionScopesMatchExactly(grant.scope, input.scope)) &&
4196
+ (activity === "all" ||
4197
+ permissionGrantIsActive(grant, at) ===
4198
+ (activity === "active")))
4199
+ .sort(compareRoleGrantRecords),
4200
+ catch: (cause) => permissionStoreError("list_role_grants", cause),
4201
+ })));
4202
+ }),
4203
+ assignRolePermission: (input) => executePlan(executor, queryInsert(tables.rolePermission.table, mutationValues(tables.rolePermission.insert(input))).pipe(SqliteQuery.onConflict([
4204
+ "role_id",
4205
+ "permission_id",
4206
+ "scope_type_present",
4207
+ "scope_type",
4208
+ ]))).pipe(Effect.mapError((cause) => permissionStoreError("assign_role_permission", cause)), Effect.asVoid),
4209
+ removeRolePermission: (input) => executePlan(executor, Query.delete(tables.rolePermission.table).pipe(Query.where(queryEq(tables.rolePermission.select.roleId, input.role)), Query.where(queryEq(tables.rolePermission.select.permissionId, input.permission)), Query.where(queryEq(tables.rolePermission.select.scopeTypePresent, input.scopeType === undefined ? 0 : 1)), Query.where(queryEq(tables.rolePermission.select.scopeType, input.scopeType ?? "")))).pipe(Effect.mapError((cause) => permissionStoreError("remove_role_permission", cause)), Effect.asVoid),
4210
+ listRolePermissions: (input = {}) => {
4211
+ const base = Query.select(selectRolePermissionColumns(tables)).pipe(Query.from(tables.rolePermission.table));
4212
+ const byRole = input.role === undefined
4213
+ ? base
4214
+ : base.pipe(Query.where(queryEq(tables.rolePermission.select.roleId, input.role)));
4215
+ const byPermission = input.permission === undefined
4216
+ ? byRole
4217
+ : byRole.pipe(Query.where(queryEq(tables.rolePermission.select.permissionId, input.permission)));
4218
+ const byScopePresence = input.scopeType === undefined
4219
+ ? byPermission
4220
+ : byPermission.pipe(Query.where(queryEq(tables.rolePermission.select.scopeTypePresent, input.scopeType === null ? 0 : 1)));
4221
+ const query = input.scopeType === undefined || input.scopeType === null
4222
+ ? byScopePresence
4223
+ : byScopePresence.pipe(Query.where(queryEq(tables.rolePermission.select.scopeType, input.scopeType)));
4224
+ return executePlan(executor, query).pipe(Effect.mapError((cause) => permissionStoreError("list_role_permissions", cause)), Effect.flatMap((rows) => Effect.try({
4225
+ try: () => rows
4226
+ .map((row) => decodeRolePermission(tables, row))
4227
+ .filter((rolePermission) => (input.role === undefined ||
4228
+ rolePermission.role === input.role) &&
4229
+ (input.permission === undefined ||
4230
+ rolePermission.permission === input.permission) &&
4231
+ (input.scopeType === undefined ||
4232
+ (input.scopeType === null
4233
+ ? rolePermission.scopeType === undefined
4234
+ : rolePermission.scopeType === input.scopeType)))
4235
+ .sort(compareRolePermissionRecords),
4236
+ catch: (cause) => permissionStoreError("list_role_permissions", cause),
4237
+ })));
4238
+ },
4239
+ hasPermission: (input) => Effect.suspend(() => {
4240
+ const now = Date.now();
4241
+ const [scopeType, scopeIdPresent, scopeId] = permissionScopeKey(input.scope);
4242
+ const permissionScope = queryOr(queryAnd(queryEq(tables.permissionGrant.select.scopeType, "global"), queryEq(tables.permissionGrant.select.scopeIdPresent, 0)), queryAnd(queryEq(tables.permissionGrant.select.scopeType, scopeType), queryEq(tables.permissionGrant.select.scopeIdPresent, scopeIdPresent), queryEq(tables.permissionGrant.select.scopeId, scopeId)));
4243
+ const direct = queryExists(Query.select({ value: Query.literal(1) }).pipe(Query.from(tables.permissionGrant.table), Query.where(queryEq(tables.permissionGrant.select.subjectType, input.subject.type)), Query.where(queryEq(tables.permissionGrant.select.subjectId, input.subject.id)), Query.where(queryEq(tables.permissionGrant.select.permissionId, input.permission)), Query.where(queryIsNull(tables.permissionGrant.select.revokedAt)), Query.where(queryOr(queryIsNull(tables.permissionGrant.select.expiresAt), queryGt(tables.permissionGrant.select.expiresAt, now))), Query.where(permissionScope)));
4244
+ const roleGrant = aliasTable(tables.roleGrant.table, "role_grant");
4245
+ const roleGrantColumn = (column) => aliasColumn(roleGrant, column);
4246
+ const rolePermission = aliasTable(tables.rolePermission.table, "role_permission");
4247
+ const rolePermissionColumn = (column) => aliasColumn(rolePermission, column);
4248
+ const rolePermissionExists = queryExists(Query.select({ value: Query.literal(1) }).pipe(Query.from(rolePermission), Query.where(queryEq(rolePermissionColumn(tables.rolePermission.select.roleId), roleGrantColumn(tables.roleGrant.select.roleId))), Query.where(queryEq(rolePermissionColumn(tables.rolePermission.select.permissionId), input.permission)), Query.where(queryOr(queryEq(rolePermissionColumn(tables.rolePermission.select.scopeTypePresent), 0), queryAnd(queryEq(rolePermissionColumn(tables.rolePermission.select.scopeTypePresent), 1), queryEq(rolePermissionColumn(tables.rolePermission.select.scopeType), scopeType))))));
4249
+ const role = queryExists(Query.select({ value: Query.literal(1) }).pipe(Query.from(roleGrant), Query.where(queryEq(roleGrantColumn(tables.roleGrant.select.subjectType), input.subject.type)), Query.where(queryEq(roleGrantColumn(tables.roleGrant.select.subjectId), input.subject.id)), Query.where(queryIsNull(roleGrantColumn(tables.roleGrant.select.revokedAt))), Query.where(queryOr(queryIsNull(roleGrantColumn(tables.roleGrant.select.expiresAt)), queryGt(roleGrantColumn(tables.roleGrant.select.expiresAt), now))), Query.where(queryOr(queryAnd(queryEq(roleGrantColumn(tables.roleGrant.select.scopeType), "global"), queryEq(roleGrantColumn(tables.roleGrant.select.scopeIdPresent), 0)), queryAnd(queryEq(roleGrantColumn(tables.roleGrant.select.scopeType), scopeType), queryEq(roleGrantColumn(tables.roleGrant.select.scopeIdPresent), scopeIdPresent), queryEq(roleGrantColumn(tables.roleGrant.select.scopeId), scopeId)))), Query.where(rolePermissionExists)));
4250
+ return checkResult("has_permission", queryOr(direct, role));
4251
+ }),
4252
+ hasRole: (input) => Effect.suspend(() => {
4253
+ const now = Date.now();
4254
+ const [scopeType, scopeIdPresent, scopeId] = permissionScopeKey(input.scope);
4255
+ const allowed = queryExists(Query.select({ value: Query.literal(1) }).pipe(Query.from(tables.roleGrant.table), Query.where(queryEq(tables.roleGrant.select.subjectType, input.subject.type)), Query.where(queryEq(tables.roleGrant.select.subjectId, input.subject.id)), Query.where(queryEq(tables.roleGrant.select.roleId, input.role)), Query.where(queryIsNull(tables.roleGrant.select.revokedAt)), Query.where(queryOr(queryIsNull(tables.roleGrant.select.expiresAt), queryGt(tables.roleGrant.select.expiresAt, now))), Query.where(queryOr(queryAnd(queryEq(tables.roleGrant.select.scopeType, "global"), queryEq(tables.roleGrant.select.scopeIdPresent, 0)), queryAnd(queryEq(tables.roleGrant.select.scopeType, scopeType), queryEq(tables.roleGrant.select.scopeIdPresent, scopeIdPresent), queryEq(tables.roleGrant.select.scopeId, scopeId))))));
4256
+ return checkResult("has_role", allowed);
4257
+ }),
4258
+ });
4259
+ };
4260
+ export const makeEffectQbSqliteStorage = (executor, atomic, options = {}) => ({
2781
4261
  userStore: makeEffectQbSqliteUserStore(executor, options),
4262
+ identityStore: makeEffectQbSqliteIdentityStore(executor, atomic, options),
4263
+ registrationStore: makeEffectQbSqliteRegistrationStore(executor, atomic, options),
2782
4264
  credentialStore: makeEffectQbSqliteCredentialStore(executor, options),
2783
4265
  sessionStore: makeEffectQbSqliteSessionStore(executor, options),
2784
4266
  verificationStore: makeEffectQbSqliteVerificationStore(executor, options),
@@ -2788,11 +4270,16 @@ export const makeEffectQbSqliteStorage = (executor, options = {}) => ({
2788
4270
  trustedDeviceStore: makeEffectQbSqliteTrustedDeviceStore(executor, options),
2789
4271
  passkeyCredentialStore: makeEffectQbSqlitePasskeyCredentialStore(executor, options),
2790
4272
  totpFactorStore: makeEffectQbSqliteTotpFactorStore(executor, options),
2791
- recoveryCodeStore: makeEffectQbSqliteRecoveryCodeStore(executor, options),
4273
+ totpSessionRotateStore: makeEffectQbSqliteTotpSessionRotateStore(executor, atomic, options),
4274
+ totpLoginCommitStore: makeEffectQbSqliteTotpLoginCommitStore(atomic, options),
4275
+ recoveryCodeStore: makeEffectQbSqliteRecoveryCodeStore(executor, atomic, options),
4276
+ recoveryCodeSessionRotateStore: makeEffectQbSqliteRecoveryCodeSessionRotateStore(executor, atomic, options),
4277
+ recoveryCodeLoginCommitStore: makeEffectQbSqliteRecoveryCodeLoginCommitStore(atomic, options),
2792
4278
  apiKeyStore: makeEffectQbSqliteApiKeyStore(executor, options),
2793
- refreshTokenStore: makeEffectQbSqliteRefreshTokenStore(executor, options),
4279
+ refreshTokenStore: makeEffectQbSqliteRefreshTokenStore(executor, atomic, options),
2794
4280
  jwtRevocationStore: makeEffectQbSqliteJwtRevocationStore(executor, options),
2795
4281
  oauthAccountStore: makeEffectQbSqliteOAuthAccountStore(executor, options),
4282
+ oauthIdentityBridgeStore: makeEffectQbSqliteOAuthIdentityBridgeStore(executor, atomic, options),
2796
4283
  oauthClientStore: makeEffectQbSqliteOAuthClientStore(executor, options),
2797
4284
  oauthConsentStore: makeEffectQbSqliteOAuthConsentStore(executor, options),
2798
4285
  oauthAuthorizationCodeStore: makeEffectQbSqliteOAuthAuthorizationCodeStore(executor, options),
@@ -2803,8 +4290,9 @@ export const makeEffectQbSqliteStorage = (executor, options = {}) => ({
2803
4290
  securityTimelineStore: makeEffectQbSqliteSecurityTimelineStore(executor, options),
2804
4291
  webhookOutboxStore: makeEffectQbSqliteWebhookOutboxStore(executor, options),
2805
4292
  webhookReplayStore: makeEffectQbSqliteWebhookReplayStore(executor, options),
4293
+ permissionStore: makeEffectQbSqlitePermissionStore(executor, options),
2806
4294
  });
2807
- export const EffectQbSqliteAuthStorageLive = (executor, options = {}) => Layer.mergeAll(Layer.succeed(UserStore)(makeEffectQbSqliteUserStore(executor, options)), Layer.succeed(CredentialStore)(makeEffectQbSqliteCredentialStore(executor, options)), Layer.succeed(SessionStore)(makeEffectQbSqliteSessionStore(executor, options)), Layer.succeed(VerificationStore)(makeEffectQbSqliteVerificationStore(executor, options)), Layer.succeed(AuditLogStore)(makeEffectQbSqliteAuditLogStore(executor, options)), Layer.succeed(LoginApprovalReviewStore)(makeEffectQbSqliteLoginApprovalReviewStore(executor, options)), Layer.succeed(LoginRiskHistory)(makeEffectQbSqliteLoginRiskHistoryStore(executor, options)), Layer.succeed(TrustedDeviceStore)(makeEffectQbSqliteTrustedDeviceStore(executor, options)), Layer.succeed(PasskeyCredentialStore)(makeEffectQbSqlitePasskeyCredentialStore(executor, options)), Layer.succeed(TotpFactorStore)(makeEffectQbSqliteTotpFactorStore(executor, options)), Layer.succeed(RecoveryCodeStore)(makeEffectQbSqliteRecoveryCodeStore(executor, options)), Layer.succeed(ApiKeyStore)(makeEffectQbSqliteApiKeyStore(executor, options)), Layer.succeed(RefreshTokenStore)(makeEffectQbSqliteRefreshTokenStore(executor, options)), Layer.succeed(JwtRevocationStore)(makeEffectQbSqliteJwtRevocationStore(executor, options)), Layer.succeed(OAuthAccountStore)(makeEffectQbSqliteOAuthAccountStore(executor, options)), Layer.succeed(OAuthClientStore)(makeEffectQbSqliteOAuthClientStore(executor, options)), Layer.succeed(OAuthConsentStore)(makeEffectQbSqliteOAuthConsentStore(executor, options)), Layer.succeed(OAuthAuthorizationCodeStore)(makeEffectQbSqliteOAuthAuthorizationCodeStore(executor, options)), Layer.succeed(OAuthClientSecretStore)(makeEffectQbSqliteOAuthClientSecretStore(executor, options)), Layer.succeed(OAuthProviderModeTokenStore)(makeEffectQbSqliteOAuthProviderModeTokenStore(executor, options)), Layer.succeed(OAuthProviderTokenVault)(makeEffectQbSqliteOAuthProviderTokenVault(executor, options)), Layer.succeed(DomainVerificationStore)(makeEffectQbSqliteDomainVerificationStore(executor, options)), Layer.succeed(SecurityTimelineStore)(makeEffectQbSqliteSecurityTimelineStore(executor, options)), Layer.succeed(WebhookOutboxStore)(makeEffectQbSqliteWebhookOutboxStore(executor, options)), Layer.succeed(WebhookReplayStore)(makeEffectQbSqliteWebhookReplayStore(executor, options)));
4295
+ export const EffectQbSqliteAuthStorageLive = (executor, atomic, options = {}) => Layer.mergeAll(Layer.succeed(UserStore)(makeEffectQbSqliteUserStore(executor, options)), Layer.succeed(IdentityStore)(makeEffectQbSqliteIdentityStore(executor, atomic, options)), Layer.succeed(RegistrationStore)(makeEffectQbSqliteRegistrationStore(executor, atomic, options)), Layer.succeed(CredentialStore)(makeEffectQbSqliteCredentialStore(executor, options)), Layer.succeed(SessionStore)(makeEffectQbSqliteSessionStore(executor, options)), Layer.succeed(VerificationStore)(makeEffectQbSqliteVerificationStore(executor, options)), Layer.succeed(AuditLogStore)(makeEffectQbSqliteAuditLogStore(executor, options)), Layer.succeed(LoginApprovalReviewStore)(makeEffectQbSqliteLoginApprovalReviewStore(executor, options)), Layer.succeed(LoginRiskHistory)(makeEffectQbSqliteLoginRiskHistoryStore(executor, options)), Layer.succeed(TrustedDeviceStore)(makeEffectQbSqliteTrustedDeviceStore(executor, options)), Layer.succeed(PasskeyCredentialStore)(makeEffectQbSqlitePasskeyCredentialStore(executor, options)), Layer.succeed(TotpFactorStore)(makeEffectQbSqliteTotpFactorStore(executor, options)), Layer.succeed(TotpSessionRotateStore)(makeEffectQbSqliteTotpSessionRotateStore(executor, atomic, options)), Layer.succeed(TotpLoginCommitStore)(makeEffectQbSqliteTotpLoginCommitStore(atomic, options)), Layer.succeed(RecoveryCodeStore)(makeEffectQbSqliteRecoveryCodeStore(executor, atomic, options)), Layer.succeed(RecoveryCodeSessionRotateStore)(makeEffectQbSqliteRecoveryCodeSessionRotateStore(executor, atomic, options)), Layer.succeed(RecoveryCodeLoginCommitStore)(makeEffectQbSqliteRecoveryCodeLoginCommitStore(atomic, options)), Layer.succeed(ApiKeyStore)(makeEffectQbSqliteApiKeyStore(executor, options)), Layer.succeed(RefreshTokenStore)(makeEffectQbSqliteRefreshTokenStore(executor, atomic, options)), Layer.succeed(JwtRevocationStore)(makeEffectQbSqliteJwtRevocationStore(executor, options)), Layer.succeed(OAuthAccountStore)(makeEffectQbSqliteOAuthAccountStore(executor, options)), Layer.succeed(OAuthIdentityBridgeStore)(makeEffectQbSqliteOAuthIdentityBridgeStore(executor, atomic, options)), Layer.succeed(OAuthClientStore)(makeEffectQbSqliteOAuthClientStore(executor, options)), Layer.succeed(OAuthConsentStore)(makeEffectQbSqliteOAuthConsentStore(executor, options)), Layer.succeed(OAuthAuthorizationCodeStore)(makeEffectQbSqliteOAuthAuthorizationCodeStore(executor, options)), Layer.succeed(OAuthClientSecretStore)(makeEffectQbSqliteOAuthClientSecretStore(executor, options)), Layer.succeed(OAuthProviderModeTokenStore)(makeEffectQbSqliteOAuthProviderModeTokenStore(executor, options)), Layer.succeed(OAuthProviderTokenVault)(makeEffectQbSqliteOAuthProviderTokenVault(executor, options)), Layer.succeed(DomainVerificationStore)(makeEffectQbSqliteDomainVerificationStore(executor, options)), Layer.succeed(SecurityTimelineStore)(makeEffectQbSqliteSecurityTimelineStore(executor, options)), Layer.succeed(WebhookOutboxStore)(makeEffectQbSqliteWebhookOutboxStore(executor, options)), Layer.succeed(WebhookReplayStore)(makeEffectQbSqliteWebhookReplayStore(executor, options)), Layer.succeed(PermissionStore)(makeEffectQbSqlitePermissionStore(executor, options)));
2808
4296
  export const makeD1EffectQbSqliteExecutor = (database) => Executor.make({
2809
4297
  driver: Executor.driver((query) => Effect.tryPromise({
2810
4298
  try: () => database
@@ -2821,5 +4309,110 @@ export const makeD1EffectQbSqliteExecutor = (database) => Executor.make({
2821
4309
  return Effect.succeed([...(result.results ?? [])]);
2822
4310
  }))),
2823
4311
  });
2824
- export const D1EffectQbSqliteAuthStorageLive = (database, options = {}) => EffectQbSqliteAuthStorageLive(makeD1EffectQbSqliteExecutor(database), options);
4312
+ export const makeD1EffectQbSqliteAtomicPlanExecutor = (database) => {
4313
+ const execute = (plans) => {
4314
+ if (plans.length === 0) {
4315
+ return Effect.succeed([]);
4316
+ }
4317
+ return Effect.try({
4318
+ try: () => plans.map((plan, statement) => {
4319
+ try {
4320
+ return {
4321
+ plan,
4322
+ rendered: Renderer.sqlite.render(plan),
4323
+ };
4324
+ }
4325
+ catch (error) {
4326
+ throw new AtomicPlanError({
4327
+ statement,
4328
+ phase: "render",
4329
+ commitState: "not-committed",
4330
+ cause: error,
4331
+ });
4332
+ }
4333
+ }),
4334
+ catch: (cause) => cause instanceof AtomicPlanError
4335
+ ? cause
4336
+ : new AtomicPlanError({
4337
+ phase: "render",
4338
+ commitState: "not-committed",
4339
+ cause,
4340
+ }),
4341
+ }).pipe(Effect.flatMap((renderedPlans) => Effect.tryPromise({
4342
+ try: () => {
4343
+ const statements = renderedPlans.map(({ rendered }, statement) => {
4344
+ try {
4345
+ return database.prepare(rendered.sql).bind(...rendered.params);
4346
+ }
4347
+ catch (error) {
4348
+ throw new AtomicPlanError({
4349
+ statement,
4350
+ phase: "prepare",
4351
+ commitState: "not-committed",
4352
+ cause: error,
4353
+ });
4354
+ }
4355
+ });
4356
+ return database.batch(statements);
4357
+ },
4358
+ catch: (cause) => cause instanceof AtomicPlanError
4359
+ ? cause
4360
+ : new AtomicPlanError({
4361
+ phase: "execute",
4362
+ commitState: "unknown",
4363
+ cause,
4364
+ }),
4365
+ }).pipe(Effect.flatMap((results) => {
4366
+ const failed = results.findIndex((result) => result.success === false || result.error !== undefined);
4367
+ if (failed !== -1) {
4368
+ return Effect.fail(new AtomicPlanError({
4369
+ statement: failed,
4370
+ phase: "execute",
4371
+ commitState: "not-committed",
4372
+ cause: results[failed]?.error,
4373
+ }));
4374
+ }
4375
+ if (results.length !== renderedPlans.length) {
4376
+ return Effect.fail(new AtomicPlanError({
4377
+ phase: "execute",
4378
+ commitState: "unknown",
4379
+ cause: new Error(`D1 batch returned ${results.length} results for ${renderedPlans.length} statements`),
4380
+ }));
4381
+ }
4382
+ return Effect.try({
4383
+ try: () => results.map((result, statement) => {
4384
+ const entry = renderedPlans[statement];
4385
+ if (entry === undefined) {
4386
+ throw new AtomicPlanError({
4387
+ statement,
4388
+ phase: "decode",
4389
+ commitState: "committed",
4390
+ cause: new Error("Missing rendered atomic plan"),
4391
+ });
4392
+ }
4393
+ try {
4394
+ return EffectQbExecutor.decodeRows(entry.rendered, entry.plan, [...(result.results ?? [])], { driverMode: "raw" });
4395
+ }
4396
+ catch (error) {
4397
+ throw new AtomicPlanError({
4398
+ statement,
4399
+ phase: "decode",
4400
+ commitState: "committed",
4401
+ cause: error,
4402
+ });
4403
+ }
4404
+ }),
4405
+ catch: (cause) => cause instanceof AtomicPlanError
4406
+ ? cause
4407
+ : new AtomicPlanError({
4408
+ phase: "decode",
4409
+ commitState: "committed",
4410
+ cause,
4411
+ }),
4412
+ });
4413
+ }))));
4414
+ };
4415
+ return { dialect: "sqlite", execute };
4416
+ };
4417
+ export const D1EffectQbSqliteAuthStorageLive = (database, options = {}) => EffectQbSqliteAuthStorageLive(makeD1EffectQbSqliteExecutor(database), makeD1EffectQbSqliteAtomicPlanExecutor(database), options);
2825
4418
  //# sourceMappingURL=EffectQbSqliteStorage.js.map