@effect-auth/core 0.1.0-alpha.15 → 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 (627) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +332 -131
  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 +2 -14
  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/AuthConfig.d.ts.map +1 -1
  28. package/dist/AuthConfig.js.map +1 -1
  29. package/dist/AuthFlow.d.ts +326 -220
  30. package/dist/AuthFlow.d.ts.map +1 -1
  31. package/dist/AuthFlow.js +611 -263
  32. package/dist/AuthFlow.js.map +1 -1
  33. package/dist/AuthKernel.d.ts +1 -1
  34. package/dist/AuthRateLimit.d.ts +96 -0
  35. package/dist/AuthRateLimit.d.ts.map +1 -0
  36. package/dist/AuthRateLimit.js +212 -0
  37. package/dist/AuthRateLimit.js.map +1 -0
  38. package/dist/Challenge.d.ts +0 -3
  39. package/dist/Challenge.d.ts.map +1 -1
  40. package/dist/Challenge.js +1 -4
  41. package/dist/Challenge.js.map +1 -1
  42. package/dist/Client.d.ts +205 -10
  43. package/dist/Client.d.ts.map +1 -1
  44. package/dist/Client.js +397 -16
  45. package/dist/Client.js.map +1 -1
  46. package/dist/CloudflareTurnstile.d.ts +28 -0
  47. package/dist/CloudflareTurnstile.d.ts.map +1 -0
  48. package/dist/CloudflareTurnstile.js +205 -0
  49. package/dist/CloudflareTurnstile.js.map +1 -0
  50. package/dist/Crypto.d.ts +0 -3
  51. package/dist/Crypto.d.ts.map +1 -1
  52. package/dist/Crypto.js +5 -7
  53. package/dist/Crypto.js.map +1 -1
  54. package/dist/DelegatedAccessStorageMigrations.d.ts +7 -0
  55. package/dist/DelegatedAccessStorageMigrations.d.ts.map +1 -0
  56. package/dist/DelegatedAccessStorageMigrations.js +159 -0
  57. package/dist/DelegatedAccessStorageMigrations.js.map +1 -0
  58. package/dist/DisposableEmailDomains.d.ts +19 -0
  59. package/dist/DisposableEmailDomains.d.ts.map +1 -0
  60. package/dist/DisposableEmailDomains.js +85 -0
  61. package/dist/DisposableEmailDomains.js.map +1 -0
  62. package/dist/DomainVerification.d.ts +0 -9
  63. package/dist/DomainVerification.d.ts.map +1 -1
  64. package/dist/DomainVerification.js +36 -22
  65. package/dist/DomainVerification.js.map +1 -1
  66. package/dist/DrizzleBunSqliteStorage.d.ts +18 -0
  67. package/dist/DrizzleBunSqliteStorage.d.ts.map +1 -0
  68. package/dist/DrizzleBunSqliteStorage.js +11 -0
  69. package/dist/DrizzleBunSqliteStorage.js.map +1 -0
  70. package/dist/DrizzleD1SqliteStorage.d.ts +28 -0
  71. package/dist/DrizzleD1SqliteStorage.d.ts.map +1 -0
  72. package/dist/DrizzleD1SqliteStorage.js +10 -0
  73. package/dist/DrizzleD1SqliteStorage.js.map +1 -0
  74. package/dist/DrizzleDurableObjectSqliteStorage.d.ts +19 -0
  75. package/dist/DrizzleDurableObjectSqliteStorage.d.ts.map +1 -0
  76. package/dist/DrizzleDurableObjectSqliteStorage.js +14 -0
  77. package/dist/DrizzleDurableObjectSqliteStorage.js.map +1 -0
  78. package/dist/DrizzleEffectSqliteStorage.d.ts +31 -0
  79. package/dist/DrizzleEffectSqliteStorage.d.ts.map +1 -0
  80. package/dist/DrizzleEffectSqliteStorage.js +108 -0
  81. package/dist/DrizzleEffectSqliteStorage.js.map +1 -0
  82. package/dist/DrizzleNodeSqliteStorage.d.ts +18 -0
  83. package/dist/DrizzleNodeSqliteStorage.d.ts.map +1 -0
  84. package/dist/DrizzleNodeSqliteStorage.js +11 -0
  85. package/dist/DrizzleNodeSqliteStorage.js.map +1 -0
  86. package/dist/DrizzleWasmSqliteStorage.d.ts +23 -0
  87. package/dist/DrizzleWasmSqliteStorage.d.ts.map +1 -0
  88. package/dist/DrizzleWasmSqliteStorage.js +15 -0
  89. package/dist/DrizzleWasmSqliteStorage.js.map +1 -0
  90. package/dist/EffectQbPostgresDelegatedAccessStorage.d.ts +34 -0
  91. package/dist/EffectQbPostgresDelegatedAccessStorage.d.ts.map +1 -0
  92. package/dist/EffectQbPostgresDelegatedAccessStorage.js +499 -0
  93. package/dist/EffectQbPostgresDelegatedAccessStorage.js.map +1 -0
  94. package/dist/EffectQbPostgresStorage.d.ts +171 -0
  95. package/dist/EffectQbPostgresStorage.d.ts.map +1 -0
  96. package/dist/EffectQbPostgresStorage.js +4280 -0
  97. package/dist/EffectQbPostgresStorage.js.map +1 -0
  98. package/dist/EffectQbSqliteDelegatedAccessStorage.d.ts +112 -0
  99. package/dist/EffectQbSqliteDelegatedAccessStorage.d.ts.map +1 -0
  100. package/dist/EffectQbSqliteDelegatedAccessStorage.js +602 -0
  101. package/dist/EffectQbSqliteDelegatedAccessStorage.js.map +1 -0
  102. package/dist/EffectQbSqliteStorage.d.ts +184 -27
  103. package/dist/EffectQbSqliteStorage.d.ts.map +1 -1
  104. package/dist/EffectQbSqliteStorage.js +1729 -135
  105. package/dist/EffectQbSqliteStorage.js.map +1 -1
  106. package/dist/EmailAuth.d.ts +65 -0
  107. package/dist/EmailAuth.d.ts.map +1 -0
  108. package/dist/EmailAuth.js +171 -0
  109. package/dist/EmailAuth.js.map +1 -0
  110. package/dist/EmailIdentityAuth.d.ts +30 -0
  111. package/dist/EmailIdentityAuth.d.ts.map +1 -0
  112. package/dist/EmailIdentityAuth.js +142 -0
  113. package/dist/EmailIdentityAuth.js.map +1 -0
  114. package/dist/EmailOtp.d.ts +10 -11
  115. package/dist/EmailOtp.d.ts.map +1 -1
  116. package/dist/EmailOtp.js +44 -81
  117. package/dist/EmailOtp.js.map +1 -1
  118. package/dist/EmailRisk.d.ts +48 -0
  119. package/dist/EmailRisk.d.ts.map +1 -0
  120. package/dist/EmailRisk.js +33 -0
  121. package/dist/EmailRisk.js.map +1 -0
  122. package/dist/EmailVerification.d.ts +10 -16
  123. package/dist/EmailVerification.d.ts.map +1 -1
  124. package/dist/EmailVerification.js +85 -42
  125. package/dist/EmailVerification.js.map +1 -1
  126. package/dist/Guard.d.ts +11 -0
  127. package/dist/Guard.d.ts.map +1 -0
  128. package/dist/Guard.js +11 -0
  129. package/dist/Guard.js.map +1 -0
  130. package/dist/HibpPwnedPasswords.d.ts +56 -0
  131. package/dist/HibpPwnedPasswords.d.ts.map +1 -0
  132. package/dist/HibpPwnedPasswords.js +232 -0
  133. package/dist/HibpPwnedPasswords.js.map +1 -0
  134. package/dist/HttpApi/AdminPermissionDefinition.d.ts +4 -0
  135. package/dist/HttpApi/AdminPermissionDefinition.d.ts.map +1 -0
  136. package/dist/HttpApi/AdminPermissionDefinition.js +4 -0
  137. package/dist/HttpApi/AdminPermissionDefinition.js.map +1 -0
  138. package/dist/HttpApi/AdminSecurityTimeline.d.ts +4 -0
  139. package/dist/HttpApi/AdminSecurityTimeline.d.ts.map +1 -0
  140. package/dist/HttpApi/AdminSecurityTimeline.js +4 -0
  141. package/dist/HttpApi/AdminSecurityTimeline.js.map +1 -0
  142. package/dist/HttpApi/AdminSession.d.ts +4 -0
  143. package/dist/HttpApi/AdminSession.d.ts.map +1 -0
  144. package/dist/HttpApi/AdminSession.js +4 -0
  145. package/dist/HttpApi/AdminSession.js.map +1 -0
  146. package/dist/HttpApi/AdminTrustedDevice.d.ts +4 -0
  147. package/dist/HttpApi/AdminTrustedDevice.d.ts.map +1 -0
  148. package/dist/HttpApi/AdminTrustedDevice.js +4 -0
  149. package/dist/HttpApi/AdminTrustedDevice.js.map +1 -0
  150. package/dist/HttpApi/Api.d.ts +1172 -1172
  151. package/dist/HttpApi/Api.d.ts.map +1 -1
  152. package/dist/HttpApi/Api.js +2160 -896
  153. package/dist/HttpApi/Api.js.map +1 -1
  154. package/dist/HttpApi/ApiKey.d.ts +4 -0
  155. package/dist/HttpApi/ApiKey.d.ts.map +1 -0
  156. package/dist/HttpApi/ApiKey.js +4 -0
  157. package/dist/HttpApi/ApiKey.js.map +1 -0
  158. package/dist/HttpApi/Audit.d.ts +6 -0
  159. package/dist/HttpApi/Audit.d.ts.map +1 -0
  160. package/dist/HttpApi/Audit.js +10 -0
  161. package/dist/HttpApi/Audit.js.map +1 -0
  162. package/dist/HttpApi/ClientApi.d.ts +917 -0
  163. package/dist/HttpApi/ClientApi.d.ts.map +1 -0
  164. package/dist/HttpApi/ClientApi.js +101 -0
  165. package/dist/HttpApi/ClientApi.js.map +1 -0
  166. package/dist/HttpApi/EmailAuth.d.ts +6 -0
  167. package/dist/HttpApi/EmailAuth.d.ts.map +1 -0
  168. package/dist/HttpApi/EmailAuth.js +6 -0
  169. package/dist/HttpApi/EmailAuth.js.map +1 -0
  170. package/dist/HttpApi/EmailGuards.d.ts +115 -0
  171. package/dist/HttpApi/EmailGuards.d.ts.map +1 -0
  172. package/dist/HttpApi/EmailGuards.js +191 -0
  173. package/dist/HttpApi/EmailGuards.js.map +1 -0
  174. package/dist/HttpApi/EmailOtp.d.ts +6 -0
  175. package/dist/HttpApi/EmailOtp.d.ts.map +1 -0
  176. package/dist/HttpApi/EmailOtp.js +6 -0
  177. package/dist/HttpApi/EmailOtp.js.map +1 -0
  178. package/dist/HttpApi/EmailVerification.d.ts +6 -0
  179. package/dist/HttpApi/EmailVerification.d.ts.map +1 -0
  180. package/dist/HttpApi/EmailVerification.js +6 -0
  181. package/dist/HttpApi/EmailVerification.js.map +1 -0
  182. package/dist/HttpApi/Endpoints.d.ts +629 -301
  183. package/dist/HttpApi/Endpoints.d.ts.map +1 -1
  184. package/dist/HttpApi/Endpoints.js +178 -120
  185. package/dist/HttpApi/Endpoints.js.map +1 -1
  186. package/dist/HttpApi/Errors.d.ts +26 -7
  187. package/dist/HttpApi/Errors.d.ts.map +1 -1
  188. package/dist/HttpApi/Errors.js +26 -4
  189. package/dist/HttpApi/Errors.js.map +1 -1
  190. package/dist/HttpApi/Identity.d.ts +6 -0
  191. package/dist/HttpApi/Identity.d.ts.map +1 -0
  192. package/dist/HttpApi/Identity.js +6 -0
  193. package/dist/HttpApi/Identity.js.map +1 -0
  194. package/dist/HttpApi/IdentityGuards.d.ts +31 -0
  195. package/dist/HttpApi/IdentityGuards.d.ts.map +1 -0
  196. package/dist/HttpApi/IdentityGuards.js +33 -0
  197. package/dist/HttpApi/IdentityGuards.js.map +1 -0
  198. package/dist/HttpApi/Jwt.d.ts +4 -0
  199. package/dist/HttpApi/Jwt.d.ts.map +1 -0
  200. package/dist/HttpApi/Jwt.js +4 -0
  201. package/dist/HttpApi/Jwt.js.map +1 -0
  202. package/dist/HttpApi/JwtDiscovery.d.ts +4 -0
  203. package/dist/HttpApi/JwtDiscovery.d.ts.map +1 -0
  204. package/dist/HttpApi/JwtDiscovery.js +4 -0
  205. package/dist/HttpApi/JwtDiscovery.js.map +1 -0
  206. package/dist/HttpApi/LoginApproval.d.ts +5 -0
  207. package/dist/HttpApi/LoginApproval.d.ts.map +1 -0
  208. package/dist/HttpApi/LoginApproval.js +5 -0
  209. package/dist/HttpApi/LoginApproval.js.map +1 -0
  210. package/dist/HttpApi/LoginNotification.d.ts +5 -0
  211. package/dist/HttpApi/LoginNotification.d.ts.map +1 -0
  212. package/dist/HttpApi/LoginNotification.js +5 -0
  213. package/dist/HttpApi/LoginNotification.js.map +1 -0
  214. package/dist/HttpApi/MagicLink.d.ts +6 -0
  215. package/dist/HttpApi/MagicLink.d.ts.map +1 -0
  216. package/dist/HttpApi/MagicLink.js +6 -0
  217. package/dist/HttpApi/MagicLink.js.map +1 -0
  218. package/dist/HttpApi/Mfa.d.ts +5 -0
  219. package/dist/HttpApi/Mfa.d.ts.map +1 -0
  220. package/dist/HttpApi/Mfa.js +5 -0
  221. package/dist/HttpApi/Mfa.js.map +1 -0
  222. package/dist/HttpApi/Middleware.d.ts +14 -0
  223. package/dist/HttpApi/Middleware.d.ts.map +1 -0
  224. package/dist/HttpApi/Middleware.js +5 -0
  225. package/dist/HttpApi/Middleware.js.map +1 -0
  226. package/dist/HttpApi/OAuth.d.ts +4 -0
  227. package/dist/HttpApi/OAuth.d.ts.map +1 -0
  228. package/dist/HttpApi/OAuth.js +4 -0
  229. package/dist/HttpApi/OAuth.js.map +1 -0
  230. package/dist/HttpApi/OAuthProviderAuthorization.d.ts +4 -0
  231. package/dist/HttpApi/OAuthProviderAuthorization.d.ts.map +1 -0
  232. package/dist/HttpApi/OAuthProviderAuthorization.js +4 -0
  233. package/dist/HttpApi/OAuthProviderAuthorization.js.map +1 -0
  234. package/dist/HttpApi/OAuthToken.d.ts +4 -0
  235. package/dist/HttpApi/OAuthToken.d.ts.map +1 -0
  236. package/dist/HttpApi/OAuthToken.js +4 -0
  237. package/dist/HttpApi/OAuthToken.js.map +1 -0
  238. package/dist/HttpApi/OidcDiscovery.d.ts +4 -0
  239. package/dist/HttpApi/OidcDiscovery.d.ts.map +1 -0
  240. package/dist/HttpApi/OidcDiscovery.js +4 -0
  241. package/dist/HttpApi/OidcDiscovery.js.map +1 -0
  242. package/dist/HttpApi/Operations/AdminPermissionDefinition.d.ts +28 -0
  243. package/dist/HttpApi/Operations/AdminPermissionDefinition.d.ts.map +1 -0
  244. package/dist/HttpApi/Operations/AdminPermissionDefinition.js +5 -0
  245. package/dist/HttpApi/Operations/AdminPermissionDefinition.js.map +1 -0
  246. package/dist/HttpApi/Operations/AdminSession.d.ts +16 -0
  247. package/dist/HttpApi/Operations/AdminSession.d.ts.map +1 -0
  248. package/dist/HttpApi/Operations/AdminSession.js +5 -0
  249. package/dist/HttpApi/Operations/AdminSession.js.map +1 -0
  250. package/dist/HttpApi/Operations/ApiKey.d.ts +16 -0
  251. package/dist/HttpApi/Operations/ApiKey.d.ts.map +1 -0
  252. package/dist/HttpApi/Operations/ApiKey.js +5 -0
  253. package/dist/HttpApi/Operations/ApiKey.js.map +1 -0
  254. package/dist/HttpApi/Operations/Discovery.d.ts +19 -0
  255. package/dist/HttpApi/Operations/Discovery.d.ts.map +1 -0
  256. package/dist/HttpApi/Operations/Discovery.js +7 -0
  257. package/dist/HttpApi/Operations/Discovery.js.map +1 -0
  258. package/dist/HttpApi/Operations/EmailAuth.d.ts +12 -0
  259. package/dist/HttpApi/Operations/EmailAuth.d.ts.map +1 -0
  260. package/dist/HttpApi/Operations/EmailAuth.js +5 -0
  261. package/dist/HttpApi/Operations/EmailAuth.js.map +1 -0
  262. package/dist/HttpApi/Operations/EmailOtp.d.ts +14 -0
  263. package/dist/HttpApi/Operations/EmailOtp.d.ts.map +1 -0
  264. package/dist/HttpApi/Operations/EmailOtp.js +5 -0
  265. package/dist/HttpApi/Operations/EmailOtp.js.map +1 -0
  266. package/dist/HttpApi/Operations/EmailVerification.d.ts +14 -0
  267. package/dist/HttpApi/Operations/EmailVerification.d.ts.map +1 -0
  268. package/dist/HttpApi/Operations/EmailVerification.js +5 -0
  269. package/dist/HttpApi/Operations/EmailVerification.js.map +1 -0
  270. package/dist/HttpApi/Operations/HttpOperation.d.ts +4 -0
  271. package/dist/HttpApi/Operations/HttpOperation.d.ts.map +1 -0
  272. package/dist/HttpApi/Operations/HttpOperation.js +2 -0
  273. package/dist/HttpApi/Operations/HttpOperation.js.map +1 -0
  274. package/dist/HttpApi/Operations/Identity.d.ts +22 -0
  275. package/dist/HttpApi/Operations/Identity.d.ts.map +1 -0
  276. package/dist/HttpApi/Operations/Identity.js +5 -0
  277. package/dist/HttpApi/Operations/Identity.js.map +1 -0
  278. package/dist/HttpApi/Operations/Jwt.d.ts +14 -0
  279. package/dist/HttpApi/Operations/Jwt.d.ts.map +1 -0
  280. package/dist/HttpApi/Operations/Jwt.js +5 -0
  281. package/dist/HttpApi/Operations/Jwt.js.map +1 -0
  282. package/dist/HttpApi/Operations/LoginApproval.d.ts +16 -0
  283. package/dist/HttpApi/Operations/LoginApproval.d.ts.map +1 -0
  284. package/dist/HttpApi/Operations/LoginApproval.js +5 -0
  285. package/dist/HttpApi/Operations/LoginApproval.js.map +1 -0
  286. package/dist/HttpApi/Operations/MagicLink.d.ts +14 -0
  287. package/dist/HttpApi/Operations/MagicLink.d.ts.map +1 -0
  288. package/dist/HttpApi/Operations/MagicLink.js +5 -0
  289. package/dist/HttpApi/Operations/MagicLink.js.map +1 -0
  290. package/dist/HttpApi/Operations/Mfa.d.ts +24 -0
  291. package/dist/HttpApi/Operations/Mfa.d.ts.map +1 -0
  292. package/dist/HttpApi/Operations/Mfa.js +5 -0
  293. package/dist/HttpApi/Operations/Mfa.js.map +1 -0
  294. package/dist/HttpApi/Operations/OAuth.d.ts +20 -0
  295. package/dist/HttpApi/Operations/OAuth.d.ts.map +1 -0
  296. package/dist/HttpApi/Operations/OAuth.js +5 -0
  297. package/dist/HttpApi/Operations/OAuth.js.map +1 -0
  298. package/dist/HttpApi/Operations/OAuthProviderAuthorization.d.ts +12 -0
  299. package/dist/HttpApi/Operations/OAuthProviderAuthorization.d.ts.map +1 -0
  300. package/dist/HttpApi/Operations/OAuthProviderAuthorization.js +5 -0
  301. package/dist/HttpApi/Operations/OAuthProviderAuthorization.js.map +1 -0
  302. package/dist/HttpApi/Operations/OAuthToken.d.ts +16 -0
  303. package/dist/HttpApi/Operations/OAuthToken.d.ts.map +1 -0
  304. package/dist/HttpApi/Operations/OAuthToken.js +5 -0
  305. package/dist/HttpApi/Operations/OAuthToken.js.map +1 -0
  306. package/dist/HttpApi/Operations/Passkey.d.ts +22 -0
  307. package/dist/HttpApi/Operations/Passkey.d.ts.map +1 -0
  308. package/dist/HttpApi/Operations/Passkey.js +5 -0
  309. package/dist/HttpApi/Operations/Passkey.js.map +1 -0
  310. package/dist/HttpApi/Operations/Password.d.ts +22 -0
  311. package/dist/HttpApi/Operations/Password.d.ts.map +1 -0
  312. package/dist/HttpApi/Operations/Password.js +5 -0
  313. package/dist/HttpApi/Operations/Password.js.map +1 -0
  314. package/dist/HttpApi/Operations/RecoveryCodes.d.ts +20 -0
  315. package/dist/HttpApi/Operations/RecoveryCodes.d.ts.map +1 -0
  316. package/dist/HttpApi/Operations/RecoveryCodes.js +5 -0
  317. package/dist/HttpApi/Operations/RecoveryCodes.js.map +1 -0
  318. package/dist/HttpApi/Operations/RefreshToken.d.ts +12 -0
  319. package/dist/HttpApi/Operations/RefreshToken.d.ts.map +1 -0
  320. package/dist/HttpApi/Operations/RefreshToken.js +5 -0
  321. package/dist/HttpApi/Operations/RefreshToken.js.map +1 -0
  322. package/dist/HttpApi/Operations/Security.d.ts +12 -0
  323. package/dist/HttpApi/Operations/Security.d.ts.map +1 -0
  324. package/dist/HttpApi/Operations/Security.js +5 -0
  325. package/dist/HttpApi/Operations/Security.js.map +1 -0
  326. package/dist/HttpApi/Operations/SecurityTimeline.d.ts +19 -0
  327. package/dist/HttpApi/Operations/SecurityTimeline.d.ts.map +1 -0
  328. package/dist/HttpApi/Operations/SecurityTimeline.js +7 -0
  329. package/dist/HttpApi/Operations/SecurityTimeline.js.map +1 -0
  330. package/dist/HttpApi/Operations/Session.d.ts +22 -0
  331. package/dist/HttpApi/Operations/Session.d.ts.map +1 -0
  332. package/dist/HttpApi/Operations/Session.js +5 -0
  333. package/dist/HttpApi/Operations/Session.js.map +1 -0
  334. package/dist/HttpApi/Operations/StepUp.d.ts +22 -0
  335. package/dist/HttpApi/Operations/StepUp.d.ts.map +1 -0
  336. package/dist/HttpApi/Operations/StepUp.js +5 -0
  337. package/dist/HttpApi/Operations/StepUp.js.map +1 -0
  338. package/dist/HttpApi/Operations/Totp.d.ts +20 -0
  339. package/dist/HttpApi/Operations/Totp.d.ts.map +1 -0
  340. package/dist/HttpApi/Operations/Totp.js +5 -0
  341. package/dist/HttpApi/Operations/Totp.js.map +1 -0
  342. package/dist/HttpApi/Operations/TrustedDevice.d.ts +23 -0
  343. package/dist/HttpApi/Operations/TrustedDevice.d.ts.map +1 -0
  344. package/dist/HttpApi/Operations/TrustedDevice.js +7 -0
  345. package/dist/HttpApi/Operations/TrustedDevice.js.map +1 -0
  346. package/dist/HttpApi/Operations/index.d.ts +27 -0
  347. package/dist/HttpApi/Operations/index.d.ts.map +1 -0
  348. package/dist/HttpApi/Operations/index.js +27 -0
  349. package/dist/HttpApi/Operations/index.js.map +1 -0
  350. package/dist/HttpApi/Passkey.d.ts +5 -0
  351. package/dist/HttpApi/Passkey.d.ts.map +1 -0
  352. package/dist/HttpApi/Passkey.js +5 -0
  353. package/dist/HttpApi/Passkey.js.map +1 -0
  354. package/dist/HttpApi/Password.d.ts +220 -0
  355. package/dist/HttpApi/Password.d.ts.map +1 -0
  356. package/dist/HttpApi/Password.js +237 -0
  357. package/dist/HttpApi/Password.js.map +1 -0
  358. package/dist/HttpApi/PasswordContract.d.ts +307 -0
  359. package/dist/HttpApi/PasswordContract.d.ts.map +1 -0
  360. package/dist/HttpApi/PasswordContract.js +63 -0
  361. package/dist/HttpApi/PasswordContract.js.map +1 -0
  362. package/dist/HttpApi/PasswordGuards.d.ts +82 -0
  363. package/dist/HttpApi/PasswordGuards.d.ts.map +1 -0
  364. package/dist/HttpApi/PasswordGuards.js +179 -0
  365. package/dist/HttpApi/PasswordGuards.js.map +1 -0
  366. package/dist/HttpApi/RecoveryCodes.d.ts +5 -0
  367. package/dist/HttpApi/RecoveryCodes.d.ts.map +1 -0
  368. package/dist/HttpApi/RecoveryCodes.js +5 -0
  369. package/dist/HttpApi/RecoveryCodes.js.map +1 -0
  370. package/dist/HttpApi/RefreshToken.d.ts +4 -0
  371. package/dist/HttpApi/RefreshToken.d.ts.map +1 -0
  372. package/dist/HttpApi/RefreshToken.js +4 -0
  373. package/dist/HttpApi/RefreshToken.js.map +1 -0
  374. package/dist/HttpApi/RequestGuards.d.ts +42 -0
  375. package/dist/HttpApi/RequestGuards.d.ts.map +1 -0
  376. package/dist/HttpApi/RequestGuards.js +194 -0
  377. package/dist/HttpApi/RequestGuards.js.map +1 -0
  378. package/dist/HttpApi/Schemas.d.ts +361 -84
  379. package/dist/HttpApi/Schemas.d.ts.map +1 -1
  380. package/dist/HttpApi/Schemas.js +190 -27
  381. package/dist/HttpApi/Schemas.js.map +1 -1
  382. package/dist/HttpApi/Security.d.ts +12 -93
  383. package/dist/HttpApi/Security.d.ts.map +1 -1
  384. package/dist/HttpApi/Security.js +65 -356
  385. package/dist/HttpApi/Security.js.map +1 -1
  386. package/dist/HttpApi/SecurityTimeline.d.ts +4 -0
  387. package/dist/HttpApi/SecurityTimeline.d.ts.map +1 -0
  388. package/dist/HttpApi/SecurityTimeline.js +4 -0
  389. package/dist/HttpApi/SecurityTimeline.js.map +1 -0
  390. package/dist/HttpApi/Session.d.ts +5 -0
  391. package/dist/HttpApi/Session.d.ts.map +1 -0
  392. package/dist/HttpApi/Session.js +5 -0
  393. package/dist/HttpApi/Session.js.map +1 -0
  394. package/dist/HttpApi/SessionGuards.d.ts +15 -0
  395. package/dist/HttpApi/SessionGuards.d.ts.map +1 -0
  396. package/dist/HttpApi/SessionGuards.js +37 -0
  397. package/dist/HttpApi/SessionGuards.js.map +1 -0
  398. package/dist/HttpApi/StepUp.d.ts +5 -0
  399. package/dist/HttpApi/StepUp.d.ts.map +1 -0
  400. package/dist/HttpApi/StepUp.js +5 -0
  401. package/dist/HttpApi/StepUp.js.map +1 -0
  402. package/dist/HttpApi/Totp.d.ts +5 -0
  403. package/dist/HttpApi/Totp.d.ts.map +1 -0
  404. package/dist/HttpApi/Totp.js +5 -0
  405. package/dist/HttpApi/Totp.js.map +1 -0
  406. package/dist/HttpApi/Transport.d.ts +4 -6
  407. package/dist/HttpApi/Transport.d.ts.map +1 -1
  408. package/dist/HttpApi/Transport.js +62 -36
  409. package/dist/HttpApi/Transport.js.map +1 -1
  410. package/dist/HttpApi/TrustedDevice.d.ts +4 -0
  411. package/dist/HttpApi/TrustedDevice.d.ts.map +1 -0
  412. package/dist/HttpApi/TrustedDevice.js +4 -0
  413. package/dist/HttpApi/TrustedDevice.js.map +1 -0
  414. package/dist/HttpApi/index.d.ts +9 -3
  415. package/dist/HttpApi/index.d.ts.map +1 -1
  416. package/dist/HttpApi/index.js +8 -2
  417. package/dist/HttpApi/index.js.map +1 -1
  418. package/dist/Identity.d.ts +138 -0
  419. package/dist/Identity.d.ts.map +1 -0
  420. package/dist/Identity.js +105 -0
  421. package/dist/Identity.js.map +1 -0
  422. package/dist/IdentityManagement.d.ts +189 -0
  423. package/dist/IdentityManagement.d.ts.map +1 -0
  424. package/dist/IdentityManagement.js +317 -0
  425. package/dist/IdentityManagement.js.map +1 -0
  426. package/dist/IncidentAction.d.ts +1 -4
  427. package/dist/IncidentAction.d.ts.map +1 -1
  428. package/dist/IncidentAction.js +15 -9
  429. package/dist/IncidentAction.js.map +1 -1
  430. package/dist/Internal.d.ts +2 -0
  431. package/dist/Internal.d.ts.map +1 -1
  432. package/dist/Internal.js +30 -0
  433. package/dist/Internal.js.map +1 -1
  434. package/dist/Invitation.d.ts +362 -0
  435. package/dist/Invitation.d.ts.map +1 -0
  436. package/dist/Invitation.js +633 -0
  437. package/dist/Invitation.js.map +1 -0
  438. package/dist/IpQualityScore.d.ts +38 -0
  439. package/dist/IpQualityScore.d.ts.map +1 -0
  440. package/dist/IpQualityScore.js +215 -0
  441. package/dist/IpQualityScore.js.map +1 -0
  442. package/dist/Jwt.d.ts +9 -30
  443. package/dist/Jwt.d.ts.map +1 -1
  444. package/dist/Jwt.js +72 -60
  445. package/dist/Jwt.js.map +1 -1
  446. package/dist/LoginApproval.d.ts +16 -31
  447. package/dist/LoginApproval.d.ts.map +1 -1
  448. package/dist/LoginApproval.js +47 -41
  449. package/dist/LoginApproval.js.map +1 -1
  450. package/dist/LoginNotification.d.ts +8 -12
  451. package/dist/LoginNotification.d.ts.map +1 -1
  452. package/dist/LoginNotification.js +30 -17
  453. package/dist/LoginNotification.js.map +1 -1
  454. package/dist/LoginRisk.d.ts +11 -13
  455. package/dist/LoginRisk.d.ts.map +1 -1
  456. package/dist/LoginRisk.js +66 -26
  457. package/dist/LoginRisk.js.map +1 -1
  458. package/dist/LoginRiskEnrichment.d.ts +145 -0
  459. package/dist/LoginRiskEnrichment.d.ts.map +1 -0
  460. package/dist/LoginRiskEnrichment.js +220 -0
  461. package/dist/LoginRiskEnrichment.js.map +1 -0
  462. package/dist/MachineAuth.d.ts.map +1 -1
  463. package/dist/MachineAuth.js.map +1 -1
  464. package/dist/MagicLink.d.ts +9 -8
  465. package/dist/MagicLink.d.ts.map +1 -1
  466. package/dist/MagicLink.js +40 -73
  467. package/dist/MagicLink.js.map +1 -1
  468. package/dist/Mailer.d.ts +20 -13
  469. package/dist/Mailer.d.ts.map +1 -1
  470. package/dist/Mailer.js +10 -12
  471. package/dist/Mailer.js.map +1 -1
  472. package/dist/OAuth.d.ts +100 -103
  473. package/dist/OAuth.d.ts.map +1 -1
  474. package/dist/OAuth.js +404 -217
  475. package/dist/OAuth.js.map +1 -1
  476. package/dist/Passkey.d.ts +17 -19
  477. package/dist/Passkey.d.ts.map +1 -1
  478. package/dist/Passkey.js +86 -39
  479. package/dist/Passkey.js.map +1 -1
  480. package/dist/PasskeyBrowser.d.ts +39 -0
  481. package/dist/PasskeyBrowser.d.ts.map +1 -0
  482. package/dist/PasskeyBrowser.js +206 -0
  483. package/dist/PasskeyBrowser.js.map +1 -0
  484. package/dist/PasskeySimpleWebAuthn.d.ts.map +1 -1
  485. package/dist/PasskeySimpleWebAuthn.js +19 -7
  486. package/dist/PasskeySimpleWebAuthn.js.map +1 -1
  487. package/dist/Password.d.ts +71 -37
  488. package/dist/Password.d.ts.map +1 -1
  489. package/dist/Password.js +296 -82
  490. package/dist/Password.js.map +1 -1
  491. package/dist/PasswordRisk.d.ts +63 -0
  492. package/dist/PasswordRisk.d.ts.map +1 -0
  493. package/dist/PasswordRisk.js +37 -0
  494. package/dist/PasswordRisk.js.map +1 -0
  495. package/dist/Permission.d.ts +308 -0
  496. package/dist/Permission.d.ts.map +1 -0
  497. package/dist/Permission.js +827 -0
  498. package/dist/Permission.js.map +1 -0
  499. package/dist/Policy.d.ts +31 -18
  500. package/dist/Policy.d.ts.map +1 -1
  501. package/dist/Policy.js +58 -14
  502. package/dist/Policy.js.map +1 -1
  503. package/dist/PostgresDelegatedAccessStorageMigrations.d.ts +7 -0
  504. package/dist/PostgresDelegatedAccessStorageMigrations.d.ts.map +1 -0
  505. package/dist/PostgresDelegatedAccessStorageMigrations.js +119 -0
  506. package/dist/PostgresDelegatedAccessStorageMigrations.js.map +1 -0
  507. package/dist/PostgresStorageMigrations.d.ts +15 -0
  508. package/dist/PostgresStorageMigrations.d.ts.map +1 -0
  509. package/dist/PostgresStorageMigrations.js +620 -0
  510. package/dist/PostgresStorageMigrations.js.map +1 -0
  511. package/dist/Privacy.d.ts +0 -3
  512. package/dist/Privacy.d.ts.map +1 -1
  513. package/dist/Privacy.js +2 -5
  514. package/dist/Privacy.js.map +1 -1
  515. package/dist/RateLimiter.d.ts +0 -3
  516. package/dist/RateLimiter.d.ts.map +1 -1
  517. package/dist/RateLimiter.js +1 -4
  518. package/dist/RateLimiter.js.map +1 -1
  519. package/dist/RecoveryCode.d.ts +84 -12
  520. package/dist/RecoveryCode.d.ts.map +1 -1
  521. package/dist/RecoveryCode.js +273 -75
  522. package/dist/RecoveryCode.js.map +1 -1
  523. package/dist/RecoveryPolicy.d.ts +81 -0
  524. package/dist/RecoveryPolicy.d.ts.map +1 -0
  525. package/dist/RecoveryPolicy.js +119 -0
  526. package/dist/RecoveryPolicy.js.map +1 -0
  527. package/dist/RefreshToken.d.ts +1 -10
  528. package/dist/RefreshToken.d.ts.map +1 -1
  529. package/dist/RefreshToken.js +31 -22
  530. package/dist/RefreshToken.js.map +1 -1
  531. package/dist/Retention.d.ts.map +1 -1
  532. package/dist/Retention.js.map +1 -1
  533. package/dist/SecurityTimeline.d.ts +1 -4
  534. package/dist/SecurityTimeline.d.ts.map +1 -1
  535. package/dist/SecurityTimeline.js +110 -17
  536. package/dist/SecurityTimeline.js.map +1 -1
  537. package/dist/Sessions.d.ts +124 -29
  538. package/dist/Sessions.d.ts.map +1 -1
  539. package/dist/Sessions.js +626 -94
  540. package/dist/Sessions.js.map +1 -1
  541. package/dist/StepUp.d.ts +132 -11
  542. package/dist/StepUp.d.ts.map +1 -1
  543. package/dist/StepUp.js +387 -19
  544. package/dist/StepUp.js.map +1 -1
  545. package/dist/Storage.d.ts +89 -23
  546. package/dist/Storage.d.ts.map +1 -1
  547. package/dist/Storage.js +10 -12
  548. package/dist/Storage.js.map +1 -1
  549. package/dist/StorageMigrations.d.ts +6 -1
  550. package/dist/StorageMigrations.d.ts.map +1 -1
  551. package/dist/StorageMigrations.js +224 -0
  552. package/dist/StorageMigrations.js.map +1 -1
  553. package/dist/StorageSchemaGenerator.d.ts +486 -0
  554. package/dist/StorageSchemaGenerator.d.ts.map +1 -0
  555. package/dist/StorageSchemaGenerator.js +1022 -0
  556. package/dist/StorageSchemaGenerator.js.map +1 -0
  557. package/dist/StrongFactor.d.ts +64 -0
  558. package/dist/StrongFactor.d.ts.map +1 -0
  559. package/dist/StrongFactor.js +100 -0
  560. package/dist/StrongFactor.js.map +1 -0
  561. package/dist/Testing.d.ts +1 -1
  562. package/dist/Testing.js +2 -2
  563. package/dist/Testing.js.map +1 -1
  564. package/dist/Totp.d.ts +88 -13
  565. package/dist/Totp.d.ts.map +1 -1
  566. package/dist/Totp.js +194 -36
  567. package/dist/Totp.js.map +1 -1
  568. package/dist/TrustedDevice.d.ts +0 -9
  569. package/dist/TrustedDevice.d.ts.map +1 -1
  570. package/dist/TrustedDevice.js +15 -17
  571. package/dist/TrustedDevice.js.map +1 -1
  572. package/dist/WaitUntil.d.ts +0 -3
  573. package/dist/WaitUntil.d.ts.map +1 -1
  574. package/dist/WaitUntil.js +1 -4
  575. package/dist/WaitUntil.js.map +1 -1
  576. package/dist/Webhook.d.ts +42 -12
  577. package/dist/Webhook.d.ts.map +1 -1
  578. package/dist/Webhook.js +165 -26
  579. package/dist/Webhook.js.map +1 -1
  580. package/dist/alchemy/cloudflare/Email.d.ts +4 -4
  581. package/dist/alchemy/cloudflare/Email.d.ts.map +1 -1
  582. package/dist/alchemy/cloudflare/Email.js +10 -5
  583. package/dist/alchemy/cloudflare/Email.js.map +1 -1
  584. package/dist/alchemy/cloudflare/RateLimitDurableObject.d.ts +4 -4
  585. package/dist/alchemy/cloudflare/RateLimitDurableObject.d.ts.map +1 -1
  586. package/dist/alchemy/cloudflare/RateLimitDurableObject.js +5 -4
  587. package/dist/alchemy/cloudflare/RateLimitDurableObject.js.map +1 -1
  588. package/dist/cloudflare/Email.d.ts.map +1 -1
  589. package/dist/cloudflare/Email.js +1 -1
  590. package/dist/cloudflare/Email.js.map +1 -1
  591. package/dist/cloudflare/RateLimitDurableObject.d.ts +6 -5
  592. package/dist/cloudflare/RateLimitDurableObject.d.ts.map +1 -1
  593. package/dist/cloudflare/RateLimitDurableObject.js.map +1 -1
  594. package/dist/cloudflare/RequestMetadata.d.ts +49 -0
  595. package/dist/cloudflare/RequestMetadata.d.ts.map +1 -0
  596. package/dist/cloudflare/RequestMetadata.js +71 -0
  597. package/dist/cloudflare/RequestMetadata.js.map +1 -0
  598. package/dist/index.d.ts +0 -35
  599. package/dist/index.d.ts.map +1 -1
  600. package/dist/index.js +0 -35
  601. package/dist/index.js.map +1 -1
  602. package/dist/internal/AuthFlowFinalizer.d.ts +31 -0
  603. package/dist/internal/AuthFlowFinalizer.d.ts.map +1 -0
  604. package/dist/internal/AuthFlowFinalizer.js +182 -0
  605. package/dist/internal/AuthFlowFinalizer.js.map +1 -0
  606. package/dist/internal/RateLimitDurableObject.d.ts.map +1 -1
  607. package/dist/internal/RateLimitDurableObject.js +3 -0
  608. package/dist/internal/RateLimitDurableObject.js.map +1 -1
  609. package/migrations/0001_auth_storage.sql +1 -0
  610. package/migrations/0006_auth_totp_factor.sql +1 -0
  611. package/migrations/0024_auth_permission.sql +39 -0
  612. package/migrations/0025_auth_permission_definition.sql +19 -0
  613. package/migrations/0026_auth_user_identity.sql +59 -0
  614. package/migrations/0027_auth_audit_log_storage_order.sql +46 -0
  615. package/migrations/0028_auth_oauth_client_secret_key.sql +39 -0
  616. package/migrations/delegated-access/0001_auth_delegated_access.sql +152 -0
  617. package/migrations/postgres/0001_auth_core.sql +77 -0
  618. package/migrations/postgres/0002_auth_security_oauth.sql +65 -0
  619. package/migrations/postgres/0003_auth_credentials_tokens.sql +43 -0
  620. package/migrations/postgres/0004_auth_security_history.sql +103 -0
  621. package/migrations/postgres/0005_auth_webhooks.sql +29 -0
  622. package/migrations/postgres/0006_auth_domain_timeline.sql +50 -0
  623. package/migrations/postgres/0007_auth_oauth_clients.sql +85 -0
  624. package/migrations/postgres/0008_auth_oauth_tokens.sql +63 -0
  625. package/migrations/postgres/0009_auth_permissions.sql +66 -0
  626. package/migrations/postgres/delegated-access/0001_auth_delegated_access.sql +112 -0
  627. package/package.json +309 -2
@@ -1,33 +1,59 @@
1
1
  import { Data, Effect, Layer, Option, Redacted } from "effect";
2
- import { Column, Executor, Query, Scalar, Table } from "effect-qb/sqlite";
3
- 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";
4
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";
5
8
  import { DomainName, DomainVerificationId, DomainVerificationProofToken, DomainVerificationStore, DomainVerificationStoreError, } from "./DomainVerification.js";
6
- 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";
7
11
  import { currentUnixMillis } from "./Internal.js";
8
12
  import { JwtError, JwtRevocationStore } from "./Jwt.js";
9
13
  import { LoginApprovalReviewError, LoginApprovalReviewStore, } from "./LoginApproval.js";
10
14
  import { LoginRiskContext, LoginRiskHistory, LoginRiskHistoryError, LoginRiskSignal, maxLoginRiskLevel, } from "./LoginRisk.js";
11
- 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";
12
16
  import { PasskeyCredentialId, PasskeyCredentialStore, PasskeyCredentialStoreError, } from "./Passkey.js";
13
- 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";
14
19
  import { RefreshTokenFamilyId, RefreshTokenId, RefreshTokenSecretHash, RefreshTokenStore, RefreshTokenStoreError, } from "./RefreshToken.js";
15
20
  import { filterSecurityTimelineEvents, SecurityTimelineEventId, SecurityTimelineStore, SecurityTimelineStoreError, } from "./SecurityTimeline.js";
16
- import { CredentialStore, SessionStore, StorageError, UserStore, VerificationStore, } from "./Storage.js";
17
- 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";
18
23
  import { filterTrustedDevicesForUser, TrustedDeviceError, TrustedDeviceStore, } from "./TrustedDevice.js";
19
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
+ };
20
32
  export class D1EffectQbError extends Data.TaggedError("D1EffectQbError") {
21
33
  }
22
34
  const makeAuthUserTable = (name) => Table.make(name, {
23
35
  id: Column.text().pipe(Column.primaryKey),
24
- email: Column.text(),
25
- email_verified: Column.int(),
26
36
  created_at: Column.int(),
27
37
  updated_at: Column.int(),
28
38
  disabled_at: Column.int().pipe(Column.nullable),
29
39
  metadata: Column.text().pipe(Column.nullable),
30
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
+ });
31
57
  const makeAuthCredentialTable = (name) => Table.make(name, {
32
58
  id: Column.text().pipe(Column.primaryKey),
33
59
  user_id: Column.text(),
@@ -45,6 +71,7 @@ const makeAuthSessionTable = (name) => Table.make(name, {
45
71
  created_at: Column.int(),
46
72
  expires_at: Column.int(),
47
73
  auth_time: Column.int(),
74
+ authentication_events: Column.text(),
48
75
  aal: Column.text(),
49
76
  amr: Column.text(),
50
77
  mfa_verified_at: Column.int().pipe(Column.nullable),
@@ -64,6 +91,7 @@ const makeAuthVerificationTable = (name) => Table.make(name, {
64
91
  consumed_at: Column.int().pipe(Column.nullable),
65
92
  });
66
93
  const makeAuthAuditLogTable = (name) => Table.make(name, {
94
+ storage_id: Column.int().pipe(Column.primaryKey),
67
95
  id: Column.text().pipe(Column.nullable),
68
96
  type: Column.text(),
69
97
  user_id: Column.text().pipe(Column.nullable),
@@ -141,6 +169,7 @@ const makeAuthTotpFactorTable = (name) => Table.make(name, {
141
169
  created_at: Column.int(),
142
170
  confirmed_at: Column.int().pipe(Column.nullable),
143
171
  last_used_at: Column.int().pipe(Column.nullable),
172
+ last_accepted_counter: Column.int().pipe(Column.nullable),
144
173
  revoked_at: Column.int().pipe(Column.nullable),
145
174
  metadata: Column.text().pipe(Column.nullable),
146
175
  });
@@ -249,7 +278,7 @@ const makeAuthOAuthAuthorizationCodeTable = (name) => Table.make(name, {
249
278
  metadata: Column.text().pipe(Column.nullable),
250
279
  });
251
280
  const makeAuthOAuthClientSecretTable = (name) => Table.make(name, {
252
- prefix: Column.text().pipe(Column.primaryKey),
281
+ prefix: Column.text(),
253
282
  client_id: Column.text(),
254
283
  secret_hash: Column.text(),
255
284
  authentication_methods: Column.text(),
@@ -319,6 +348,52 @@ const makeAuthWebhookReplayTable = (name) => Table.make(name, {
319
348
  expires_at: Column.int(),
320
349
  created_at: Column.int(),
321
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
+ });
322
397
  const authSqliteTablesTypeId = Symbol.for("@effect-auth/core/EffectQbSqliteStorage/AuthSqliteTables");
323
398
  const jsonEncode = (value) => value === undefined ? null : JSON.stringify(value);
324
399
  const jsonDecode = (value) => {
@@ -345,14 +420,50 @@ const decodeBoolean = (value) => {
345
420
  };
346
421
  const mutationValues = (value) => value;
347
422
  const queryInsert = (table, values) => Query.insert(table, values);
423
+ const queryInsertMany = (table, values) => Query.from(Query.values(values))(Query.insert(table));
348
424
  const queryUpdate = (table, values) => Query.update(table, values);
349
425
  const queryEq = (left, right) => Query.eq(left, right);
426
+ const queryNeq = (left, right) => Query.neq(left, right);
350
427
  const queryGt = (left, right) => Query.gt(left, right);
351
428
  const queryLt = (left, right) => Query.lt(left, right);
352
429
  const queryLte = (left, right) => Query.lte(left, right);
353
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);
354
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
+ };
355
465
  const executePlan = (executor, plan) => executor.execute(plan);
466
+ const executeAtomicPlans = (atomic, plans) => atomic.execute(plans);
356
467
  const omitUndefined = (record) => Object.fromEntries(Object.entries(record).filter(([, value]) => value !== undefined));
357
468
  const sessionMetadataEnvelopeKey = "__effectAuthSession";
358
469
  const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
@@ -425,13 +536,31 @@ const encodeSessionMetadata = (row) => {
425
536
  };
426
537
  const decodeDefaultUserRow = (row) => omitUndefined({
427
538
  id: UserId(row.id),
428
- email: Email(row.email),
429
- emailVerified: decodeBoolean(row.emailVerified),
430
539
  createdAt: UnixMillis(Number(row.createdAt)),
431
540
  updatedAt: UnixMillis(Number(row.updatedAt)),
432
541
  disabledAt: optionalUnixMillis(row.disabledAt),
433
542
  metadata: jsonDecode(row.metadata),
434
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);
435
564
  const decodeUserRow = (tables, row) => tables.user.decode === undefined
436
565
  ? decodeDefaultUserRow(row)
437
566
  : tables.user.decode(row);
@@ -449,6 +578,34 @@ const decodePasswordCredentialRow = (tables, row) => tables.credential.decodePas
449
578
  : tables.credential.decodePassword(row);
450
579
  const decodeDefaultSessionRow = (row) => {
451
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
+ }
452
609
  return omitUndefined({
453
610
  id: SessionId(row.id),
454
611
  userId: UserId(row.userId),
@@ -456,6 +613,7 @@ const decodeDefaultSessionRow = (row) => {
456
613
  createdAt: UnixMillis(Number(row.createdAt)),
457
614
  expiresAt: UnixMillis(Number(row.expiresAt)),
458
615
  authTime: UnixMillis(Number(row.authTime)),
616
+ authenticationEvents,
459
617
  aal: row.aal,
460
618
  amr: decodeAmr(row.amr),
461
619
  mfaVerifiedAt: optionalUnixMillis(row.mfaVerifiedAt),
@@ -466,9 +624,13 @@ const decodeDefaultSessionRow = (row) => {
466
624
  rotatedAt: optionalUnixMillis(row.rotatedAt),
467
625
  });
468
626
  };
469
- const decodeSessionRow = (tables, row) => tables.session.decode === undefined
470
- ? decodeDefaultSessionRow(row)
471
- : 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
+ };
472
634
  const decodeDefaultVerificationRow = (row) => omitUndefined({
473
635
  id: ChallengeId(row.id),
474
636
  type: row.type,
@@ -482,7 +644,7 @@ const decodeDefaultVerificationRow = (row) => omitUndefined({
482
644
  const decodeVerificationRow = (tables, row) => tables.verification.decode === undefined
483
645
  ? decodeDefaultVerificationRow(row)
484
646
  : tables.verification.decode(row);
485
- const decodeDefaultAuditLogEvent = (row) => jsonDecode(row.event);
647
+ const decodeDefaultAuditLogEvent = (row) => decodeAuditEvent(jsonDecode(row.event));
486
648
  const decodeAuditLogEvent = (tables, row) => tables.auditLog.decode === undefined
487
649
  ? decodeDefaultAuditLogEvent(row)
488
650
  : tables.auditLog.decode(row);
@@ -522,7 +684,9 @@ const decodeDefaultLoginRiskHistoryRecord = (row) => omitUndefined({
522
684
  locationKey: row.locationKey ?? undefined,
523
685
  country: row.country ?? undefined,
524
686
  region: row.region ?? undefined,
525
- 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,
526
690
  longitude: row.longitudeMicro === null
527
691
  ? undefined
528
692
  : Number(row.longitudeMicro) / 1_000_000,
@@ -545,7 +709,9 @@ const decodeTrustedDeviceRow = (tables, row) => tables.trustedDevice.decode ===
545
709
  : tables.trustedDevice.decode(row);
546
710
  const decodePasskeyTransports = (value) => {
547
711
  const decoded = jsonDecode(value);
548
- return Array.isArray(decoded) ? decoded : undefined;
712
+ return Array.isArray(decoded)
713
+ ? decoded
714
+ : undefined;
549
715
  };
550
716
  const decodeDefaultPasskeyCredentialRow = (row) => omitUndefined({
551
717
  id: CredentialId(row.id),
@@ -575,6 +741,9 @@ const decodeDefaultTotpFactorRow = (row) => omitUndefined({
575
741
  createdAt: UnixMillis(Number(row.createdAt)),
576
742
  confirmedAt: optionalUnixMillis(row.confirmedAt),
577
743
  lastUsedAt: optionalUnixMillis(row.lastUsedAt),
744
+ lastAcceptedCounter: row.lastAcceptedCounter === null
745
+ ? undefined
746
+ : Number(row.lastAcceptedCounter),
578
747
  revokedAt: optionalUnixMillis(row.revokedAt),
579
748
  metadata: jsonDecode(row.metadata),
580
749
  });
@@ -711,7 +880,8 @@ const decodeOAuthConsentRow = (tables, row) => tables.oauthConsent.decode === un
711
880
  : tables.oauthConsent.decode(row);
712
881
  const oauthConsentCoversScopes = (record, scopes = []) => scopes.every((scope) => record.scopes.includes(scope));
713
882
  const isOAuthConsentActive = (record, input) => record.revokedAt === undefined &&
714
- (record.expiresAt === undefined || Number(record.expiresAt) > Number(input.now)) &&
883
+ (record.expiresAt === undefined ||
884
+ Number(record.expiresAt) > Number(input.now)) &&
715
885
  oauthConsentCoversScopes(record, input.scopes);
716
886
  const oauthClientRedirectUriValidationResult = (client, input) => {
717
887
  if (client === undefined) {
@@ -739,7 +909,9 @@ const decodeDefaultOAuthAuthorizationCodeRow = (row) => omitUndefined({
739
909
  subject: row.subject,
740
910
  redirectUri: row.redirectUri,
741
911
  scopes: decodeStringArray(row.scopes),
742
- codeChallenge: row.codeChallenge === null ? undefined : OAuthCodeChallenge(row.codeChallenge),
912
+ codeChallenge: row.codeChallenge === null
913
+ ? undefined
914
+ : OAuthCodeChallenge(row.codeChallenge),
743
915
  codeChallengeMethod: row.codeChallengeMethod === null
744
916
  ? undefined
745
917
  : row.codeChallengeMethod,
@@ -839,6 +1011,82 @@ const decodeDefaultWebhookOutboxRecord = (row) => omitUndefined({
839
1011
  const decodeWebhookOutboxRecord = (tables, row) => tables.webhookOutbox.decode === undefined
840
1012
  ? decodeDefaultWebhookOutboxRecord(row)
841
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);
842
1090
  const providerTokenVaultTokens = (record) => omitUndefined({
843
1091
  accessTokenCiphertext: record.accessTokenCiphertext,
844
1092
  refreshTokenCiphertext: record.refreshTokenCiphertext,
@@ -849,13 +1097,50 @@ const providerTokenVaultTokens = (record) => omitUndefined({
849
1097
  });
850
1098
  const userInsert = (row) => ({
851
1099
  id: row.id,
852
- email: row.email,
853
- email_verified: row.emailVerified ? 1 : 0,
854
1100
  created_at: Number(row.createdAt),
855
1101
  updated_at: Number(row.updatedAt),
856
1102
  disabled_at: row.disabledAt === undefined ? null : Number(row.disabledAt),
857
1103
  metadata: jsonEncode(row.metadata),
858
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
+ });
859
1144
  const passwordCredentialInsert = (row) => ({
860
1145
  id: row.id,
861
1146
  user_id: row.userId,
@@ -870,21 +1155,40 @@ const passwordCredentialUpdatePassword = (input) => ({
870
1155
  password_hash: input.passwordHash,
871
1156
  updated_at: Number(input.updatedAt),
872
1157
  });
873
- const sessionInsert = (row) => ({
874
- id: row.id,
875
- user_id: row.userId,
876
- secret_hash: row.secretHash,
877
- created_at: Number(row.createdAt),
878
- expires_at: Number(row.expiresAt),
879
- auth_time: Number(row.authTime),
880
- aal: row.aal,
881
- amr: JSON.stringify(row.amr),
882
- mfa_verified_at: row.mfaVerifiedAt === undefined ? null : Number(row.mfaVerifiedAt),
883
- metadata: encodeSessionMetadata(row),
884
- revoked_at: null,
885
- last_seen_at: null,
886
- rotated_at: null,
887
- });
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
+ };
888
1192
  const verificationInsert = (row) => ({
889
1193
  id: row.id,
890
1194
  type: row.type,
@@ -945,10 +1249,6 @@ const loginRiskHistoryInsert = (row) => ({
945
1249
  risk_level: row.riskLevel ?? null,
946
1250
  created_at: Number(row.createdAt),
947
1251
  });
948
- const userMarkEmailVerifiedUpdate = (input) => ({
949
- email_verified: 1,
950
- updated_at: Number(input.updatedAt),
951
- });
952
1252
  const sessionRefreshUpdate = (input) => omitUndefined({
953
1253
  expires_at: Number(input.expiresAt),
954
1254
  last_seen_at: input.lastSeenAt === undefined ? undefined : Number(input.lastSeenAt),
@@ -959,14 +1259,28 @@ const sessionRotateUpdate = (input) => omitUndefined({
959
1259
  expires_at: input.expiresAt === undefined ? undefined : Number(input.expiresAt),
960
1260
  });
961
1261
  const sessionUpdateClaimsUpdate = (row, input) => omitUndefined({
962
- aal: input.aal,
963
- amr: input.amr === undefined ? undefined : JSON.stringify(input.amr),
964
- mfa_verified_at: input.mfaVerifiedAt === undefined ? undefined : Number(input.mfaVerifiedAt),
965
1262
  metadata: encodeSessionMetadata({
966
1263
  ...row,
967
1264
  claims: input.claims ?? row.claims,
968
1265
  }),
969
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
+ });
970
1284
  const sessionRevokeUpdate = (input) => ({
971
1285
  revoked_at: Number(input.revokedAt),
972
1286
  });
@@ -1031,6 +1345,7 @@ const totpFactorInsert = (row) => ({
1031
1345
  created_at: Number(row.createdAt),
1032
1346
  confirmed_at: row.confirmedAt === undefined ? null : Number(row.confirmedAt),
1033
1347
  last_used_at: row.lastUsedAt === undefined ? null : Number(row.lastUsedAt),
1348
+ last_accepted_counter: row.lastAcceptedCounter ?? null,
1034
1349
  revoked_at: row.revokedAt === undefined ? null : Number(row.revokedAt),
1035
1350
  metadata: jsonEncode(row.metadata),
1036
1351
  });
@@ -1038,8 +1353,9 @@ const totpFactorConfirm = (input) => omitUndefined({
1038
1353
  confirmed_at: Number(input.confirmedAt),
1039
1354
  metadata: input.metadata === undefined ? undefined : jsonEncode(input.metadata),
1040
1355
  });
1041
- const totpFactorMarkUsed = (input) => omitUndefined({
1356
+ const totpFactorAcceptCounter = (input) => omitUndefined({
1042
1357
  last_used_at: Number(input.lastUsedAt),
1358
+ last_accepted_counter: input.counter,
1043
1359
  metadata: input.metadata === undefined ? undefined : jsonEncode(input.metadata),
1044
1360
  });
1045
1361
  const totpFactorRevoke = (input, row) => ({
@@ -1059,6 +1375,14 @@ const recoveryCodeMarkUsed = (input) => omitUndefined({
1059
1375
  used_at: Number(input.usedAt),
1060
1376
  metadata: input.metadata === undefined ? undefined : jsonEncode(input.metadata),
1061
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
+ });
1062
1386
  const recoveryCodeRevoke = (input, row) => ({
1063
1387
  revoked_at: Number(input.revokedAt),
1064
1388
  metadata: jsonEncode(metadataWithReason(row.metadata, "revokeReason", input.reason)),
@@ -1144,7 +1468,9 @@ const oauthProviderTokenVaultValues = (row) => ({
1144
1468
  refresh_token_ciphertext: row.refreshTokenCiphertext === undefined
1145
1469
  ? null
1146
1470
  : Redacted.value(row.refreshTokenCiphertext),
1147
- 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),
1148
1474
  token_type: row.tokenType,
1149
1475
  scopes: jsonEncode(row.scopes),
1150
1476
  expires_at: row.expiresAt === undefined ? null : Number(row.expiresAt),
@@ -1320,6 +1646,101 @@ const webhookReplayUpdate = (input) => ({
1320
1646
  expires_at: Number(input.expiresAt),
1321
1647
  created_at: Number(input.now),
1322
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
+ });
1323
1744
  const jwtRevocationUpdate = (record) => ({
1324
1745
  revoked_at: Number(record.revokedAt),
1325
1746
  expires_at: record.expiresAt === undefined ? null : Number(record.expiresAt),
@@ -1328,6 +1749,7 @@ const jwtRevocationUpdate = (record) => ({
1328
1749
  const authSqliteTableName = (name, defaultName) => name ?? defaultName;
1329
1750
  const makeInternalAuthSqliteTables = (names = {}) => {
1330
1751
  const user = makeAuthUserTable(names.user ?? "auth_user");
1752
+ const identity = makeAuthUserIdentityTable(names.identity ?? "auth_user_identity");
1331
1753
  const credential = makeAuthCredentialTable(names.credential ?? "auth_credential");
1332
1754
  const session = makeAuthSessionTable(names.session ?? "auth_session");
1333
1755
  const verification = makeAuthVerificationTable(names.verification ?? "auth_verification");
@@ -1352,20 +1774,46 @@ const makeInternalAuthSqliteTables = (names = {}) => {
1352
1774
  const securityTimeline = makeAuthSecurityTimelineTable(names.securityTimeline ?? "auth_security_timeline");
1353
1775
  const webhookOutbox = makeAuthWebhookOutboxTable(names.webhookOutbox ?? "auth_webhook_outbox");
1354
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");
1355
1782
  return {
1356
1783
  user: {
1357
1784
  table: user,
1358
1785
  select: {
1359
1786
  id: user.id,
1360
- email: user.email,
1361
- emailVerified: user.email_verified,
1362
1787
  createdAt: user.created_at,
1363
1788
  updatedAt: user.updated_at,
1364
1789
  disabledAt: user.disabled_at,
1365
1790
  metadata: user.metadata,
1366
1791
  },
1367
1792
  insert: userInsert,
1368
- 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,
1369
1817
  },
1370
1818
  credential: {
1371
1819
  table: credential,
@@ -1391,6 +1839,7 @@ const makeInternalAuthSqliteTables = (names = {}) => {
1391
1839
  createdAt: session.created_at,
1392
1840
  expiresAt: session.expires_at,
1393
1841
  authTime: session.auth_time,
1842
+ authenticationEvents: session.authentication_events,
1394
1843
  aal: session.aal,
1395
1844
  amr: session.amr,
1396
1845
  mfaVerifiedAt: session.mfa_verified_at,
@@ -1403,6 +1852,8 @@ const makeInternalAuthSqliteTables = (names = {}) => {
1403
1852
  refresh: sessionRefreshUpdate,
1404
1853
  rotate: sessionRotateUpdate,
1405
1854
  updateClaims: sessionUpdateClaimsUpdate,
1855
+ appendEvidence: sessionAppendEvidenceUpdate,
1856
+ assureAndRotate: sessionAssureAndRotateUpdate,
1406
1857
  revoke: sessionRevokeUpdate,
1407
1858
  },
1408
1859
  verification: {
@@ -1423,6 +1874,7 @@ const makeInternalAuthSqliteTables = (names = {}) => {
1423
1874
  auditLog: {
1424
1875
  table: auditLog,
1425
1876
  select: {
1877
+ storageOrder: auditLog.storage_id,
1426
1878
  id: auditLog.id,
1427
1879
  type: auditLog.type,
1428
1880
  userId: auditLog.user_id,
@@ -1525,12 +1977,13 @@ const makeInternalAuthSqliteTables = (names = {}) => {
1525
1977
  createdAt: totpFactor.created_at,
1526
1978
  confirmedAt: totpFactor.confirmed_at,
1527
1979
  lastUsedAt: totpFactor.last_used_at,
1980
+ lastAcceptedCounter: totpFactor.last_accepted_counter,
1528
1981
  revokedAt: totpFactor.revoked_at,
1529
1982
  metadata: totpFactor.metadata,
1530
1983
  },
1531
1984
  insert: totpFactorInsert,
1532
1985
  confirm: totpFactorConfirm,
1533
- markUsed: totpFactorMarkUsed,
1986
+ acceptCounter: totpFactorAcceptCounter,
1534
1987
  revoke: totpFactorRevoke,
1535
1988
  },
1536
1989
  recoveryCode: {
@@ -1546,6 +1999,7 @@ const makeInternalAuthSqliteTables = (names = {}) => {
1546
1999
  },
1547
2000
  insert: recoveryCodeInsert,
1548
2001
  markUsed: recoveryCodeMarkUsed,
2002
+ replaceActive: recoveryCodeReplaceActive,
1549
2003
  revoke: recoveryCodeRevoke,
1550
2004
  },
1551
2005
  apiKey: {
@@ -1798,6 +2252,84 @@ const makeInternalAuthSqliteTables = (names = {}) => {
1798
2252
  insert: webhookReplayInsert,
1799
2253
  update: webhookReplayUpdate,
1800
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
+ },
1801
2333
  };
1802
2334
  };
1803
2335
  const asAuthSqliteTables = (tables) => tables;
@@ -1805,6 +2337,7 @@ export const defineEffectQbAuthTables = (tables) => asAuthSqliteTables(tables);
1805
2337
  export const makeAuthSqliteTables = (names = {}) => asAuthSqliteTables(makeInternalAuthSqliteTables(names));
1806
2338
  export const authSqliteTables = makeAuthSqliteTables();
1807
2339
  const selectUserColumns = (tables) => tables.user.select;
2340
+ const selectIdentityColumns = (tables) => tables.identity.select;
1808
2341
  const selectCredentialColumns = (tables) => tables.credential.select;
1809
2342
  const selectSessionColumns = (tables) => tables.session.select;
1810
2343
  const selectVerificationColumns = (tables) => tables.verification.select;
@@ -1829,7 +2362,16 @@ const selectDomainVerificationColumns = (tables) => tables.domainVerification.se
1829
2362
  const selectSecurityTimelineColumns = (tables) => tables.securityTimeline.select;
1830
2363
  const selectWebhookOutboxColumns = (tables) => tables.webhookOutbox.select;
1831
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;
1832
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
+ });
1833
2375
  const auditLogStoreError = (operation, cause) => new AuditLogStoreError({
1834
2376
  operation,
1835
2377
  message: `Audit log ${operation} failed`,
@@ -1934,22 +2476,36 @@ const webhookReplayStoreError = (operation, cause) => new WebhookReplayStoreErro
1934
2476
  message: `Webhook replay ${operation} failed`,
1935
2477
  cause,
1936
2478
  });
2479
+ const permissionStoreError = (operation, cause) => new PermissionStoreError({
2480
+ operation,
2481
+ message: `Permission store ${operation} failed`,
2482
+ cause,
2483
+ });
1937
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
1938
2485
  ? Option.none()
1939
2486
  : Option.some(decodeUserRow(tables, rows[0]))));
1940
- 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
1941
2488
  ? Option.none()
1942
- : Option.some(decodeUserRow(tables, rows[0]))));
2489
+ : Option.some(decodeUserIdentityRow(tables, rows[0]))));
1943
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
1944
2491
  ? Option.none()
1945
2492
  : Option.some(decodePasswordCredentialRow(tables, rows[0]))));
1946
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
1947
2494
  ? Option.none()
1948
2495
  : Option.some(decodeJwtRevocationRow(tables, rows[0]))));
1949
- 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
1950
2497
  ? Option.none()
1951
- : Option.some(decodeSessionRow(tables, rows[0]))));
1952
- 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)))));
1953
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
1954
2510
  ? Option.none()
1955
2511
  : Option.some(decodeVerificationRow(tables, rows[0]))));
@@ -2021,7 +2577,9 @@ const findOAuthClientSecretByClientIdAndPrefix = (executor, tables, input, opera
2021
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
2022
2578
  ? Option.none()
2023
2579
  : Option.some(decodeOAuthProviderModeTokenRow(tables, rows[0]))));
2024
- 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));
2025
2583
  const deleteOAuthProviderModeRetiredTokens = (executor, tables, input) => Effect.gen(function* () {
2026
2584
  const expiredDeleted = input.beforeExpiresAt === undefined
2027
2585
  ? 0
@@ -2095,7 +2653,14 @@ const listAuditLogRecords = (executor, tables, input = {}) => {
2095
2653
  const query = input.types === undefined
2096
2654
  ? byOccurredAt
2097
2655
  : byOccurredAt.pipe(Query.where(queryIn(tables.auditLog.select.type, input.types[0], ...input.types.slice(1))));
2098
- 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
+ })));
2099
2664
  };
2100
2665
  const deleteAuditLogRecordsBefore = (executor, tables, input) => {
2101
2666
  const base = Query.delete(tables.auditLog.table).pipe(Query.where(queryLt(tables.auditLog.select.occurredAt, Number(input.beforeOccurredAt))));
@@ -2131,7 +2696,9 @@ const distanceKm = (first, second) => {
2131
2696
  Math.cos(firstLatitude) *
2132
2697
  Math.cos(secondLatitude) *
2133
2698
  Math.sin(longitudeDelta / 2) ** 2;
2134
- 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)));
2135
2702
  };
2136
2703
  const hasCoordinates = (value) => value.latitude !== undefined &&
2137
2704
  Number.isFinite(value.latitude) &&
@@ -2214,18 +2781,287 @@ const deleteLoginRiskHistoryRecordsBefore = (executor, tables, input) => {
2214
2781
  };
2215
2782
  export const makeEffectQbSqliteUserStore = (executor, options = {}) => {
2216
2783
  const tables = options.tables ?? authSqliteTables;
2217
- return UserStore.make({
2784
+ return UserStore.of({
2218
2785
  findById: (id) => findUserById(executor, tables, id),
2219
- findByEmail: (email) => findUserByEmail(executor, tables, email),
2220
2786
  insert: (row) => executePlan(executor, queryInsert(tables.user.table, mutationValues(tables.user.insert(row)))).pipe(Effect.mapError((cause) => storageError("user", "insert", cause)), Effect.asVoid),
2221
- 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
2222
2852
  ? Option.none()
2223
- : 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
+ },
2224
3060
  });
2225
3061
  };
2226
3062
  export const makeEffectQbSqliteCredentialStore = (executor, options = {}) => {
2227
3063
  const tables = options.tables ?? authSqliteTables;
2228
- return CredentialStore.make({
3064
+ return CredentialStore.of({
2229
3065
  findPasswordByUserId: (userId) => findPasswordCredentialByUserId(executor, tables, userId),
2230
3066
  insertPassword: (row) => executePlan(executor, queryInsert(tables.credential.table, mutationValues(tables.credential.insertPassword(row)))).pipe(Effect.mapError((cause) => storageError("credential", "insert", cause)), Effect.asVoid),
2231
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
@@ -2235,29 +3071,44 @@ export const makeEffectQbSqliteCredentialStore = (executor, options = {}) => {
2235
3071
  };
2236
3072
  export const makeEffectQbSqliteSessionStore = (executor, options = {}) => {
2237
3073
  const tables = options.tables ?? authSqliteTables;
2238
- return SessionStore.make({
3074
+ return SessionStore.of({
2239
3075
  findById: (id) => findSessionById(executor, tables, id),
2240
3076
  listByUser: (input) => listSessionsByUser(executor, tables, input),
2241
- insert: (row) => executePlan(executor, queryInsert(tables.session.table, mutationValues(tables.session.insert(row)))).pipe(Effect.mapError((cause) => storageError("session", "insert", cause)), Effect.asVoid),
2242
- refresh: (input) => {
2243
- 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)));
2244
- 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
2245
3081
  ? Option.none()
2246
- : Option.some(decodeSessionRow(tables, rows[0]))));
2247
- },
2248
- rotate: (input) => {
2249
- 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)));
2250
- 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
2251
3086
  ? Option.none()
2252
- : Option.some(decodeSessionRow(tables, rows[0]))));
2253
- },
2254
- 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) => {
2255
3105
  if (Option.isNone(rowOption)) {
2256
3106
  return Effect.succeed(Option.none());
2257
3107
  }
2258
- 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
2259
3110
  ? Option.none()
2260
- : Option.some(decodeSessionRow(tables, rows[0]))));
3111
+ : Option.some(decodeSessionRow(tables, rows[0])))));
2261
3112
  })),
2262
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),
2263
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),
@@ -2265,7 +3116,7 @@ export const makeEffectQbSqliteSessionStore = (executor, options = {}) => {
2265
3116
  };
2266
3117
  export const makeEffectQbSqliteVerificationStore = (executor, options = {}) => {
2267
3118
  const tables = options.tables ?? authSqliteTables;
2268
- return VerificationStore.make({
3119
+ return VerificationStore.of({
2269
3120
  findById: (id) => findVerificationById(executor, tables, id),
2270
3121
  insert: (row) => executePlan(executor, queryInsert(tables.verification.table, mutationValues(tables.verification.insert(row)))).pipe(Effect.mapError((cause) => storageError("verification", "insert", cause)), Effect.asVoid),
2271
3122
  consume: (input) => {
@@ -2293,10 +3144,13 @@ export const makeEffectQbSqliteVerificationStore = (executor, options = {}) => {
2293
3144
  };
2294
3145
  export const makeEffectQbSqliteAuditLogStore = (executor, options = {}) => {
2295
3146
  const tables = options.tables ?? authSqliteTables;
2296
- return AuditLogStore.make({
2297
- insert: (event) => Effect.gen(function* () {
3147
+ return AuditLogStore.of({
3148
+ insertMany: (events) => Effect.gen(function* () {
3149
+ if (events.length === 0) {
3150
+ return;
3151
+ }
2298
3152
  const createdAt = yield* currentUnixMillis;
2299
- 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);
2300
3154
  }),
2301
3155
  list: (input = {}) => listAuditLogRecords(executor, tables, input),
2302
3156
  deleteBefore: (input) => deleteAuditLogRecordsBefore(executor, tables, input),
@@ -2304,7 +3158,7 @@ export const makeEffectQbSqliteAuditLogStore = (executor, options = {}) => {
2304
3158
  };
2305
3159
  export const makeEffectQbSqliteLoginApprovalReviewStore = (executor, options = {}) => {
2306
3160
  const tables = options.tables ?? authSqliteTables;
2307
- return LoginApprovalReviewStore.make({
3161
+ return LoginApprovalReviewStore.of({
2308
3162
  insertPending: (input) => executePlan(executor, queryInsert(tables.loginApprovalReview.table, mutationValues(tables.loginApprovalReview.insertPending(input)))).pipe(Effect.mapError((cause) => loginApprovalReviewStoreError("insert", cause)), Effect.as(input)),
2309
3163
  findByApprovalChallengeId: (input) => findLoginApprovalReviewByApprovalChallengeId(executor, tables, input),
2310
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
@@ -2317,7 +3171,7 @@ export const makeEffectQbSqliteLoginApprovalReviewStore = (executor, options = {
2317
3171
  };
2318
3172
  export const makeEffectQbSqliteLoginRiskHistoryStore = (executor, options = {}) => {
2319
3173
  const tables = options.tables ?? authSqliteTables;
2320
- return LoginRiskHistory.make({
3174
+ return LoginRiskHistory.of({
2321
3175
  lookup: (input) => lookupLoginRiskHistory(executor, tables, input, options.loginRiskHistory),
2322
3176
  record: (input) => Effect.gen(function* () {
2323
3177
  const createdAt = input.createdAt ?? (yield* currentUnixMillis);
@@ -2328,15 +3182,21 @@ export const makeEffectQbSqliteLoginRiskHistoryStore = (executor, options = {})
2328
3182
  };
2329
3183
  export const makeEffectQbSqliteTrustedDeviceStore = (executor, options = {}) => {
2330
3184
  const tables = options.tables ?? authSqliteTables;
2331
- 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({
2332
3190
  find: (input) => findTrustedDevice(executor, tables, input),
2333
3191
  listForUser: (input) => listTrustedDevicesForUser(executor, tables, input),
2334
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),
2335
3193
  upsert: (input) => findTrustedDeviceIncludingExpired(executor, tables, input).pipe(Effect.flatMap((existing) => {
2336
3194
  if (Option.isNone(existing)) {
2337
- 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);
2338
3198
  }
2339
- 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);
2340
3200
  }), Effect.flatMap(() => findTrustedDeviceIncludingExpired(executor, tables, input)), Effect.flatMap((stored) => Option.isSome(stored)
2341
3201
  ? Effect.succeed(stored.value)
2342
3202
  : Effect.fail(trustedDeviceStoreError("trust", "missing row")))),
@@ -2344,7 +3204,7 @@ export const makeEffectQbSqliteTrustedDeviceStore = (executor, options = {}) =>
2344
3204
  };
2345
3205
  export const makeEffectQbSqlitePasskeyCredentialStore = (executor, options = {}) => {
2346
3206
  const tables = options.tables ?? authSqliteTables;
2347
- return PasskeyCredentialStore.make({
3207
+ return PasskeyCredentialStore.of({
2348
3208
  insert: (row) => executePlan(executor, queryInsert(tables.passkeyCredential.table, mutationValues(tables.passkeyCredential.insert(row)))).pipe(Effect.mapError((cause) => passkeyCredentialStoreError("insert", cause)), Effect.asVoid),
2349
3209
  findByCredentialId: (credentialId) => findPasskeyCredentialByCredentialId(executor, tables, credentialId),
2350
3210
  listByUser: (input) => {
@@ -2354,7 +3214,7 @@ export const makeEffectQbSqlitePasskeyCredentialStore = (executor, options = {})
2354
3214
  : base.pipe(Query.where(queryIsNull(tables.passkeyCredential.select.revokedAt)));
2355
3215
  return executePlan(executor, query).pipe(Effect.mapError((cause) => passkeyCredentialStoreError("list", cause)), Effect.map((rows) => rows.map((row) => decodePasskeyCredentialRow(tables, row))));
2356
3216
  },
2357
- 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
2358
3218
  ? Option.none()
2359
3219
  : Option.some(decodePasskeyCredentialRow(tables, rows[0])))),
2360
3220
  revoke: (input) => findPasskeyCredentialByCredentialId(executor, tables, input.credentialId, "revoke").pipe(Effect.flatMap((row) => {
@@ -2367,7 +3227,7 @@ export const makeEffectQbSqlitePasskeyCredentialStore = (executor, options = {})
2367
3227
  };
2368
3228
  export const makeEffectQbSqliteTotpFactorStore = (executor, options = {}) => {
2369
3229
  const tables = options.tables ?? authSqliteTables;
2370
- return TotpFactorStore.make({
3230
+ return TotpFactorStore.of({
2371
3231
  insert: (row) => executePlan(executor, queryInsert(tables.totpFactor.table, mutationValues(tables.totpFactor.insert(row)))).pipe(Effect.mapError((cause) => totpFactorStoreError("insert", cause)), Effect.asVoid),
2372
3232
  findById: (id) => findTotpFactorById(executor, tables, id),
2373
3233
  listByUser: (input) => {
@@ -2383,9 +3243,35 @@ export const makeEffectQbSqliteTotpFactorStore = (executor, options = {}) => {
2383
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
2384
3244
  ? Option.none()
2385
3245
  : Option.some(decodeTotpFactorRow(tables, rows[0])))),
2386
- 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
2387
- ? Option.none()
2388
- : 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
+ },
2389
3275
  revoke: (input) => findTotpFactorById(executor, tables, input.id, "revoke").pipe(Effect.flatMap((row) => {
2390
3276
  if (Option.isNone(row)) {
2391
3277
  return Effect.void;
@@ -2394,9 +3280,9 @@ export const makeEffectQbSqliteTotpFactorStore = (executor, options = {}) => {
2394
3280
  })),
2395
3281
  });
2396
3282
  };
2397
- export const makeEffectQbSqliteRecoveryCodeStore = (executor, options = {}) => {
3283
+ export const makeEffectQbSqliteRecoveryCodeStore = (executor, atomic, options = {}) => {
2398
3284
  const tables = options.tables ?? authSqliteTables;
2399
- return RecoveryCodeStore.make({
3285
+ return RecoveryCodeStore.of({
2400
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 }),
2401
3287
  findById: (id) => findRecoveryCodeById(executor, tables, id),
2402
3288
  listByUser: (input) => {
@@ -2412,6 +3298,13 @@ export const makeEffectQbSqliteRecoveryCodeStore = (executor, options = {}) => {
2412
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
2413
3299
  ? Effect.succeed(Option.none())
2414
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
+ },
2415
3308
  revoke: (input) => findRecoveryCodeById(executor, tables, input.id, "revoke").pipe(Effect.flatMap((row) => {
2416
3309
  if (Option.isNone(row)) {
2417
3310
  return Effect.void;
@@ -2420,9 +3313,219 @@ export const makeEffectQbSqliteRecoveryCodeStore = (executor, options = {}) => {
2420
3313
  })),
2421
3314
  });
2422
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
+ };
2423
3526
  export const makeEffectQbSqliteApiKeyStore = (executor, options = {}) => {
2424
3527
  const tables = options.tables ?? authSqliteTables;
2425
- return ApiKeyStore.make({
3528
+ return ApiKeyStore.of({
2426
3529
  insert: (row) => executePlan(executor, queryInsert(tables.apiKey.table, mutationValues(tables.apiKey.insert(row)))).pipe(Effect.mapError((cause) => apiKeyStoreError("insert", cause)), Effect.asVoid),
2427
3530
  findById: (id) => findApiKeyById(executor, tables, id),
2428
3531
  findByPrefix: (prefix) => findApiKeyByPrefix(executor, tables, prefix),
@@ -2444,9 +3547,9 @@ export const makeEffectQbSqliteApiKeyStore = (executor, options = {}) => {
2444
3547
  })),
2445
3548
  });
2446
3549
  };
2447
- export const makeEffectQbSqliteRefreshTokenStore = (executor, options = {}) => {
3550
+ export const makeEffectQbSqliteRefreshTokenStore = (executor, atomic, options = {}) => {
2448
3551
  const tables = options.tables ?? authSqliteTables;
2449
- return RefreshTokenStore.make({
3552
+ return RefreshTokenStore.of({
2450
3553
  insert: (row) => executePlan(executor, queryInsert(tables.refreshToken.table, mutationValues(tables.refreshToken.insert(row)))).pipe(Effect.mapError((cause) => refreshTokenStoreError("insert", cause)), Effect.asVoid),
2451
3554
  findById: (id) => findRefreshTokenById(executor, tables, id),
2452
3555
  findBySecretHash: (hash) => findRefreshTokenBySecretHash(executor, tables, hash),
@@ -2464,21 +3567,28 @@ export const makeEffectQbSqliteRefreshTokenStore = (executor, options = {}) => {
2464
3567
  : base.pipe(Query.where(queryIsNull(tables.refreshToken.select.revokedAt)));
2465
3568
  return executePlan(executor, query).pipe(Effect.mapError((cause) => refreshTokenStoreError("list", cause)), Effect.map((rows) => rows.map((row) => decodeRefreshTokenRow(tables, row))));
2466
3569
  },
2467
- 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) => {
2468
- const [updated] = rows;
2469
- if (updated === undefined) {
2470
- return Effect.succeed(Option.none());
2471
- }
2472
- return findRefreshTokenById(executor, tables, RefreshTokenId(updated.id), "rotate").pipe(Effect.flatMap((previous) => {
2473
- if (Option.isNone(previous)) {
2474
- return Effect.succeed(Option.none());
2475
- }
2476
- return executePlan(executor, queryInsert(tables.refreshToken.table, mutationValues(tables.refreshToken.insert(input.replacement)))).pipe(Effect.mapError((cause) => refreshTokenStoreError("rotate", cause)), Effect.as(Option.some({
2477
- previous: previous.value,
2478
- replacement: input.replacement,
2479
- })));
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),
2480
3577
  }));
2481
- })),
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
+ },
2482
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
2483
3593
  ? Option.none()
2484
3594
  : Option.some(decodeRefreshTokenRow(tables, rows[0])))),
@@ -2493,7 +3603,7 @@ export const makeEffectQbSqliteRefreshTokenStore = (executor, options = {}) => {
2493
3603
  };
2494
3604
  export const makeEffectQbSqliteJwtRevocationStore = (executor, options = {}) => {
2495
3605
  const tables = options.tables ?? authSqliteTables;
2496
- return JwtRevocationStore.make({
3606
+ return JwtRevocationStore.of({
2497
3607
  revoke: (record) => findJwtRevocation(executor, tables, record.jwtId, "revoke").pipe(Effect.flatMap((existing) => {
2498
3608
  if (Option.isNone(existing)) {
2499
3609
  return executePlan(executor, queryInsert(tables.jwtRevocation.table, mutationValues(tables.jwtRevocation.insert(record))));
@@ -2507,7 +3617,7 @@ export const makeEffectQbSqliteJwtRevocationStore = (executor, options = {}) =>
2507
3617
  };
2508
3618
  export const makeEffectQbSqliteOAuthAccountStore = (executor, options = {}) => {
2509
3619
  const tables = options.tables ?? authSqliteTables;
2510
- return OAuthAccountStore.make({
3620
+ return OAuthAccountStore.of({
2511
3621
  insert: (row) => executePlan(executor, queryInsert(tables.oauthAccount.table, mutationValues(tables.oauthAccount.insert(row)))).pipe(Effect.mapError((cause) => oauthAccountStoreError("insert", cause)), Effect.asVoid),
2512
3622
  findById: (id) => findOAuthAccountById(executor, tables, id),
2513
3623
  findByProviderAccount: (input) => findOAuthAccountByProviderAccount(executor, tables, input),
@@ -2531,9 +3641,90 @@ export const makeEffectQbSqliteOAuthAccountStore = (executor, options = {}) => {
2531
3641
  })),
2532
3642
  });
2533
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
+ };
2534
3725
  export const makeEffectQbSqliteOAuthClientStore = (executor, options = {}) => {
2535
3726
  const tables = options.tables ?? authSqliteTables;
2536
- return OAuthClientStore.make({
3727
+ return OAuthClientStore.of({
2537
3728
  upsert: (record) => executePlan(executor, queryInsert(tables.oauthClient.table, mutationValues(tables.oauthClient.insert(record))).pipe(Query.onConflict("id", {
2538
3729
  update: mutationValues(tables.oauthClient.update(record)),
2539
3730
  }))).pipe(Effect.mapError((cause) => oauthClientStoreError("upsert", cause)), Effect.asVoid),
@@ -2550,7 +3741,7 @@ export const makeEffectQbSqliteOAuthClientStore = (executor, options = {}) => {
2550
3741
  };
2551
3742
  export const makeEffectQbSqliteOAuthConsentStore = (executor, options = {}) => {
2552
3743
  const tables = options.tables ?? authSqliteTables;
2553
- return OAuthConsentStore.make({
3744
+ return OAuthConsentStore.of({
2554
3745
  upsert: (record) => executePlan(executor, queryInsert(tables.oauthConsent.table, mutationValues(tables.oauthConsent.insert(record))).pipe(Query.onConflict("id", {
2555
3746
  update: mutationValues(tables.oauthConsent.update(record)),
2556
3747
  }))).pipe(Effect.mapError((cause) => oauthConsentStoreError("upsert", cause)), Effect.asVoid),
@@ -2574,12 +3765,14 @@ export const makeEffectQbSqliteOAuthConsentStore = (executor, options = {}) => {
2574
3765
  };
2575
3766
  export const makeEffectQbSqliteOAuthAuthorizationCodeStore = (executor, options = {}) => {
2576
3767
  const tables = options.tables ?? authSqliteTables;
2577
- return OAuthAuthorizationCodeStore.make({
3768
+ return OAuthAuthorizationCodeStore.of({
2578
3769
  upsert: (record) => executePlan(executor, queryInsert(tables.oauthAuthorizationCode.table, mutationValues(tables.oauthAuthorizationCode.insert(record))).pipe(Query.onConflict("code_hash", {
2579
3770
  update: mutationValues(tables.oauthAuthorizationCode.update(record)),
2580
3771
  }))).pipe(Effect.mapError((cause) => oauthAuthorizationCodeStoreError("upsert", cause)), Effect.asVoid),
2581
3772
  findByHash: (input) => findOAuthAuthorizationCodeByHash(executor, tables, input),
2582
- 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) => {
2583
3776
  const [consumed] = rows;
2584
3777
  if (consumed !== undefined) {
2585
3778
  return findOAuthAuthorizationCodeByHash(executor, tables, { codeHash: consumed.codeHash }, "mark-consumed").pipe(Effect.map((record) => Option.isNone(record)
@@ -2600,8 +3793,8 @@ export const makeEffectQbSqliteOAuthAuthorizationCodeStore = (executor, options
2600
3793
  };
2601
3794
  export const makeEffectQbSqliteOAuthClientSecretStore = (executor, options = {}) => {
2602
3795
  const tables = options.tables ?? authSqliteTables;
2603
- return OAuthClientSecretStore.make({
2604
- 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"], {
2605
3798
  update: mutationValues(tables.oauthClientSecret.update(record)),
2606
3799
  }))).pipe(Effect.mapError((cause) => oauthClientSecretStoreError("upsert", cause)), Effect.asVoid),
2607
3800
  findByClientIdAndPrefix: (input) => findOAuthClientSecretByClientIdAndPrefix(executor, tables, input),
@@ -2620,7 +3813,7 @@ export const makeEffectQbSqliteOAuthClientSecretStore = (executor, options = {})
2620
3813
  };
2621
3814
  export const makeEffectQbSqliteOAuthProviderModeTokenStore = (executor, options = {}) => {
2622
3815
  const tables = options.tables ?? authSqliteTables;
2623
- return OAuthProviderModeTokenStore.make({
3816
+ return OAuthProviderModeTokenStore.of({
2624
3817
  upsert: (record) => executePlan(executor, queryInsert(tables.oauthProviderModeToken.table, mutationValues(tables.oauthProviderModeToken.insert(record))).pipe(Query.onConflict("token_hash", {
2625
3818
  update: mutationValues(tables.oauthProviderModeToken.update(record)),
2626
3819
  }))).pipe(Effect.mapError((cause) => oauthProviderModeTokenStoreError("upsert", cause)), Effect.asVoid),
@@ -2645,7 +3838,7 @@ export const makeEffectQbSqliteOAuthProviderModeTokenStore = (executor, options
2645
3838
  };
2646
3839
  export const makeEffectQbSqliteOAuthProviderTokenVault = (executor, options = {}) => {
2647
3840
  const tables = options.tables ?? authSqliteTables;
2648
- return OAuthProviderTokenVault.make({
3841
+ return OAuthProviderTokenVault.of({
2649
3842
  upsert: (record) => findOAuthProviderTokenVaultRecordByAccountId(executor, tables, { accountId: record.accountId }, "upsert", true).pipe(Effect.flatMap((existing) => Option.isNone(existing)
2650
3843
  ? executePlan(executor, queryInsert(tables.oauthProviderTokenVault.table, mutationValues(tables.oauthProviderTokenVault.insert(record))))
2651
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
@@ -2670,7 +3863,7 @@ export const makeEffectQbSqliteOAuthProviderTokenVault = (executor, options = {}
2670
3863
  };
2671
3864
  export const makeEffectQbSqliteDomainVerificationStore = (executor, options = {}) => {
2672
3865
  const tables = options.tables ?? authSqliteTables;
2673
- return DomainVerificationStore.make({
3866
+ return DomainVerificationStore.of({
2674
3867
  upsert: (record) => findDomainVerificationByOwnerAndDomain(executor, tables, { ownerId: record.ownerId, domain: record.domain }, "upsert").pipe(Effect.flatMap((existing) => Option.isNone(existing)
2675
3868
  ? executePlan(executor, queryInsert(tables.domainVerification.table, mutationValues(tables.domainVerification.insert(record))))
2676
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
@@ -2682,8 +3875,8 @@ export const makeEffectQbSqliteDomainVerificationStore = (executor, options = {}
2682
3875
  const base = Query.select(selectDomainVerificationColumns(tables)).pipe(Query.from(tables.domainVerification.table), Query.where(queryEq(tables.domainVerification.select.ownerId, input.ownerId)));
2683
3876
  const query = input.includeRevoked === true
2684
3877
  ? base
2685
- : base.pipe(Query.where(queryIsNull(tables.domainVerification.select.revokedAt)));
2686
- 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))));
2687
3880
  },
2688
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
2689
3882
  ? Option.none()
@@ -2698,7 +3891,7 @@ export const makeEffectQbSqliteDomainVerificationStore = (executor, options = {}
2698
3891
  };
2699
3892
  export const makeEffectQbSqliteSecurityTimelineStore = (executor, options = {}) => {
2700
3893
  const tables = options.tables ?? authSqliteTables;
2701
- return SecurityTimelineStore.make({
3894
+ return SecurityTimelineStore.of({
2702
3895
  insert: (event) => executePlan(executor, queryInsert(tables.securityTimeline.table, mutationValues(tables.securityTimeline.insert(event)))).pipe(Effect.mapError((cause) => securityTimelineStoreError("insert", cause)), Effect.asVoid),
2703
3896
  listByUser: (input) => listSecurityTimelineByUser(executor, tables, input),
2704
3897
  deleteBefore: (input) => deleteSecurityTimelineBefore(executor, tables, input),
@@ -2706,7 +3899,7 @@ export const makeEffectQbSqliteSecurityTimelineStore = (executor, options = {})
2706
3899
  };
2707
3900
  export const makeEffectQbSqliteWebhookOutboxStore = (executor, options = {}) => {
2708
3901
  const tables = options.tables ?? authSqliteTables;
2709
- return WebhookOutboxStore.make({
3902
+ return WebhookOutboxStore.of({
2710
3903
  enqueue: (input) => Effect.gen(function* () {
2711
3904
  const now = input.now ?? (yield* currentUnixMillis);
2712
3905
  const record = {
@@ -2725,7 +3918,8 @@ export const makeEffectQbSqliteWebhookOutboxStore = (executor, options = {}) =>
2725
3918
  claimDue: (input) => claimDueWebhookOutboxRecords(executor, tables, input),
2726
3919
  markDelivered: (input) => Effect.gen(function* () {
2727
3920
  const existing = yield* findWebhookOutboxRecordById(executor, tables, input.id, "markDelivered");
2728
- if (Option.isNone(existing) || existing.value.status === "dead_lettered") {
3921
+ if (Option.isNone(existing) ||
3922
+ existing.value.status === "dead_lettered") {
2729
3923
  return;
2730
3924
  }
2731
3925
  const deliveredAt = input.deliveredAt ?? (yield* currentUnixMillis);
@@ -2763,11 +3957,11 @@ export const makeEffectQbSqliteWebhookOutboxStore = (executor, options = {}) =>
2763
3957
  };
2764
3958
  export const makeEffectQbSqliteWebhookReplayStore = (executor, options = {}) => {
2765
3959
  const tables = options.tables ?? authSqliteTables;
2766
- return WebhookReplayStore.make({
3960
+ return WebhookReplayStore.of({
2767
3961
  claim: (input) => Effect.gen(function* () {
2768
3962
  const now = input.now ?? (yield* currentUnixMillis);
2769
3963
  const claim = { ...input, now };
2770
- const rows = yield* executePlan(executor, queryInsert(tables.webhookReplay.table, mutationValues(tables.webhookReplay.insert(claim))).pipe(Query.onConflict("id", {
3964
+ const rows = yield* executePlan(executor, queryInsert(tables.webhookReplay.table, mutationValues(tables.webhookReplay.insert(claim))).pipe(SqliteQuery.onConflict("id", {
2771
3965
  update: mutationValues(tables.webhookReplay.update(claim)),
2772
3966
  where: queryLt(tables.webhookReplay.select.expiresAt, Number(now)),
2773
3967
  }), Query.returning(selectWebhookReplayColumns(tables)))).pipe(Effect.mapError((cause) => webhookReplayStoreError("claim", cause)));
@@ -2776,8 +3970,297 @@ export const makeEffectQbSqliteWebhookReplayStore = (executor, options = {}) =>
2776
3970
  deleteExpired: (input = {}) => deleteExpiredWebhookReplayRecords(executor, tables, input),
2777
3971
  });
2778
3972
  };
2779
- 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 = {}) => ({
2780
4261
  userStore: makeEffectQbSqliteUserStore(executor, options),
4262
+ identityStore: makeEffectQbSqliteIdentityStore(executor, atomic, options),
4263
+ registrationStore: makeEffectQbSqliteRegistrationStore(executor, atomic, options),
2781
4264
  credentialStore: makeEffectQbSqliteCredentialStore(executor, options),
2782
4265
  sessionStore: makeEffectQbSqliteSessionStore(executor, options),
2783
4266
  verificationStore: makeEffectQbSqliteVerificationStore(executor, options),
@@ -2787,11 +4270,16 @@ export const makeEffectQbSqliteStorage = (executor, options = {}) => ({
2787
4270
  trustedDeviceStore: makeEffectQbSqliteTrustedDeviceStore(executor, options),
2788
4271
  passkeyCredentialStore: makeEffectQbSqlitePasskeyCredentialStore(executor, options),
2789
4272
  totpFactorStore: makeEffectQbSqliteTotpFactorStore(executor, options),
2790
- 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),
2791
4278
  apiKeyStore: makeEffectQbSqliteApiKeyStore(executor, options),
2792
- refreshTokenStore: makeEffectQbSqliteRefreshTokenStore(executor, options),
4279
+ refreshTokenStore: makeEffectQbSqliteRefreshTokenStore(executor, atomic, options),
2793
4280
  jwtRevocationStore: makeEffectQbSqliteJwtRevocationStore(executor, options),
2794
4281
  oauthAccountStore: makeEffectQbSqliteOAuthAccountStore(executor, options),
4282
+ oauthIdentityBridgeStore: makeEffectQbSqliteOAuthIdentityBridgeStore(executor, atomic, options),
2795
4283
  oauthClientStore: makeEffectQbSqliteOAuthClientStore(executor, options),
2796
4284
  oauthConsentStore: makeEffectQbSqliteOAuthConsentStore(executor, options),
2797
4285
  oauthAuthorizationCodeStore: makeEffectQbSqliteOAuthAuthorizationCodeStore(executor, options),
@@ -2802,8 +4290,9 @@ export const makeEffectQbSqliteStorage = (executor, options = {}) => ({
2802
4290
  securityTimelineStore: makeEffectQbSqliteSecurityTimelineStore(executor, options),
2803
4291
  webhookOutboxStore: makeEffectQbSqliteWebhookOutboxStore(executor, options),
2804
4292
  webhookReplayStore: makeEffectQbSqliteWebhookReplayStore(executor, options),
4293
+ permissionStore: makeEffectQbSqlitePermissionStore(executor, options),
2805
4294
  });
2806
- 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)));
2807
4296
  export const makeD1EffectQbSqliteExecutor = (database) => Executor.make({
2808
4297
  driver: Executor.driver((query) => Effect.tryPromise({
2809
4298
  try: () => database
@@ -2820,5 +4309,110 @@ export const makeD1EffectQbSqliteExecutor = (database) => Executor.make({
2820
4309
  return Effect.succeed([...(result.results ?? [])]);
2821
4310
  }))),
2822
4311
  });
2823
- 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);
2824
4418
  //# sourceMappingURL=EffectQbSqliteStorage.js.map