@yielded/auth-persistence 0.1.0-beta.7

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 (526) hide show
  1. package/README.md +21 -0
  2. package/dist/Drizzle.d.mts +28 -0
  3. package/dist/Drizzle.mjs +28 -0
  4. package/dist/DrizzleD1.d.mts +963 -0
  5. package/dist/DrizzleD1.mjs +29 -0
  6. package/dist/DrizzleD1.mjs.map +1 -0
  7. package/dist/DrizzleLibsql.d.mts +2284 -0
  8. package/dist/DrizzleLibsql.mjs +92 -0
  9. package/dist/DrizzleLibsql.mjs.map +1 -0
  10. package/dist/DrizzleMysql2.d.mts +1820 -0
  11. package/dist/DrizzleMysql2.mjs +55 -0
  12. package/dist/DrizzleMysql2.mjs.map +1 -0
  13. package/dist/DrizzlePglite.d.mts +1826 -0
  14. package/dist/DrizzlePglite.mjs +53 -0
  15. package/dist/DrizzlePglite.mjs.map +1 -0
  16. package/dist/DrizzlePostgres.d.mts +1826 -0
  17. package/dist/DrizzlePostgres.mjs +53 -0
  18. package/dist/DrizzlePostgres.mjs.map +1 -0
  19. package/dist/DrizzleSqliteBun.d.mts +2279 -0
  20. package/dist/DrizzleSqliteBun.mjs +70 -0
  21. package/dist/DrizzleSqliteBun.mjs.map +1 -0
  22. package/dist/DrizzleSqliteDo.d.mts +2329 -0
  23. package/dist/DrizzleSqliteDo.mjs +105 -0
  24. package/dist/DrizzleSqliteDo.mjs.map +1 -0
  25. package/dist/DrizzleSqliteNode.d.mts +2279 -0
  26. package/dist/DrizzleSqliteNode.mjs +70 -0
  27. package/dist/DrizzleSqliteNode.mjs.map +1 -0
  28. package/dist/DrizzleSqliteWasm.d.mts +2282 -0
  29. package/dist/DrizzleSqliteWasm.mjs +70 -0
  30. package/dist/DrizzleSqliteWasm.mjs.map +1 -0
  31. package/dist/drizzle/D1BatchStatements.d.mts +11 -0
  32. package/dist/drizzle/D1BatchStatements.mjs +8 -0
  33. package/dist/drizzle/D1BatchStatements.mjs.map +1 -0
  34. package/dist/drizzle/PasswordPreparedPostconditions.mjs +12 -0
  35. package/dist/drizzle/PasswordPreparedPostconditions.mjs.map +1 -0
  36. package/dist/drizzle/SuppliedService.d.mts +10 -0
  37. package/dist/drizzle/d1-emails.d.mts +62 -0
  38. package/dist/drizzle/d1-emails.mjs +580 -0
  39. package/dist/drizzle/d1-emails.mjs.map +1 -0
  40. package/dist/drizzle/d1-generated-statement.d.mts +2 -0
  41. package/dist/drizzle/d1-generated-statement.mjs +79 -0
  42. package/dist/drizzle/d1-generated-statement.mjs.map +1 -0
  43. package/dist/drizzle/d1-identity.d.mts +31 -0
  44. package/dist/drizzle/d1-identity.mjs +119 -0
  45. package/dist/drizzle/d1-identity.mjs.map +1 -0
  46. package/dist/drizzle/d1-oauth.d.mts +301 -0
  47. package/dist/drizzle/d1-oauth.mjs +76 -0
  48. package/dist/drizzle/d1-oauth.mjs.map +1 -0
  49. package/dist/drizzle/d1-passkey.d.mts +51 -0
  50. package/dist/drizzle/d1-passkey.mjs +95 -0
  51. package/dist/drizzle/d1-passkey.mjs.map +1 -0
  52. package/dist/drizzle/d1-password-prepared.d.mts +25 -0
  53. package/dist/drizzle/d1-password-prepared.mjs +461 -0
  54. package/dist/drizzle/d1-password-prepared.mjs.map +1 -0
  55. package/dist/drizzle/d1-passwords.d.mts +87 -0
  56. package/dist/drizzle/d1-passwords.mjs +1004 -0
  57. package/dist/drizzle/d1-passwords.mjs.map +1 -0
  58. package/dist/drizzle/d1-phone.d.mts +26 -0
  59. package/dist/drizzle/d1-phone.mjs +30 -0
  60. package/dist/drizzle/d1-phone.mjs.map +1 -0
  61. package/dist/drizzle/d1-planning.d.mts +12 -0
  62. package/dist/drizzle/d1-planning.mjs +43 -0
  63. package/dist/drizzle/d1-planning.mjs.map +1 -0
  64. package/dist/drizzle/d1-proofs.d.mts +80 -0
  65. package/dist/drizzle/d1-proofs.mjs +830 -0
  66. package/dist/drizzle/d1-proofs.mjs.map +1 -0
  67. package/dist/drizzle/d1-sessions.d.mts +67 -0
  68. package/dist/drizzle/d1-sessions.mjs +966 -0
  69. package/dist/drizzle/d1-sessions.mjs.map +1 -0
  70. package/dist/drizzle/d1-totp.d.mts +26 -0
  71. package/dist/drizzle/d1-totp.mjs +27 -0
  72. package/dist/drizzle/d1-totp.mjs.map +1 -0
  73. package/dist/drizzle/email-model.d.mts +354 -0
  74. package/dist/drizzle/email-model.mjs +21 -0
  75. package/dist/drizzle/email-model.mjs.map +1 -0
  76. package/dist/drizzle/email-registration.d.mts +30 -0
  77. package/dist/drizzle/email-registration.mjs +238 -0
  78. package/dist/drizzle/email-registration.mjs.map +1 -0
  79. package/dist/drizzle/email-sql.d.mts +4 -0
  80. package/dist/drizzle/email-sql.mjs +9 -0
  81. package/dist/drizzle/email-sql.mjs.map +1 -0
  82. package/dist/drizzle/email-target.d.mts +20 -0
  83. package/dist/drizzle/email-target.mjs +72 -0
  84. package/dist/drizzle/email-target.mjs.map +1 -0
  85. package/dist/drizzle/model.d.mts +250 -0
  86. package/dist/drizzle/model.mjs +58 -0
  87. package/dist/drizzle/model.mjs.map +1 -0
  88. package/dist/drizzle/mysql-emails.d.mts +71 -0
  89. package/dist/drizzle/mysql-emails.mjs +38 -0
  90. package/dist/drizzle/mysql-emails.mjs.map +1 -0
  91. package/dist/drizzle/mysql-identity.d.mts +22 -0
  92. package/dist/drizzle/mysql-identity.mjs +94 -0
  93. package/dist/drizzle/mysql-identity.mjs.map +1 -0
  94. package/dist/drizzle/mysql-password-prepared.d.mts +30 -0
  95. package/dist/drizzle/mysql-password-prepared.mjs +16 -0
  96. package/dist/drizzle/mysql-password-prepared.mjs.map +1 -0
  97. package/dist/drizzle/mysql-passwords.d.mts +86 -0
  98. package/dist/drizzle/mysql-passwords.mjs +38 -0
  99. package/dist/drizzle/mysql-passwords.mjs.map +1 -0
  100. package/dist/drizzle/mysql-proofs.d.mts +75 -0
  101. package/dist/drizzle/mysql-proofs.mjs +23 -0
  102. package/dist/drizzle/mysql-proofs.mjs.map +1 -0
  103. package/dist/drizzle/mysql-sessions.d.mts +237 -0
  104. package/dist/drizzle/mysql-sessions.mjs +53 -0
  105. package/dist/drizzle/mysql-sessions.mjs.map +1 -0
  106. package/dist/drizzle/mysql.d.mts +70 -0
  107. package/dist/drizzle/mysql.mjs +218 -0
  108. package/dist/drizzle/mysql.mjs.map +1 -0
  109. package/dist/drizzle/oauth-accounts.mjs +358 -0
  110. package/dist/drizzle/oauth-accounts.mjs.map +1 -0
  111. package/dist/drizzle/oauth-connected-access.mjs +210 -0
  112. package/dist/drizzle/oauth-connected-access.mjs.map +1 -0
  113. package/dist/drizzle/oauth-connected-collection.mjs +202 -0
  114. package/dist/drizzle/oauth-connected-collection.mjs.map +1 -0
  115. package/dist/drizzle/oauth-connected-custody.mjs +146 -0
  116. package/dist/drizzle/oauth-connected-custody.mjs.map +1 -0
  117. package/dist/drizzle/oauth-connected-drivers.mjs +76 -0
  118. package/dist/drizzle/oauth-connected-drivers.mjs.map +1 -0
  119. package/dist/drizzle/oauth-connected-flow.mjs +247 -0
  120. package/dist/drizzle/oauth-connected-flow.mjs.map +1 -0
  121. package/dist/drizzle/oauth-connected-input.mjs +103 -0
  122. package/dist/drizzle/oauth-connected-input.mjs.map +1 -0
  123. package/dist/drizzle/oauth-connected-maintenance.mjs +214 -0
  124. package/dist/drizzle/oauth-connected-maintenance.mjs.map +1 -0
  125. package/dist/drizzle/oauth-connected-management.mjs +228 -0
  126. package/dist/drizzle/oauth-connected-management.mjs.map +1 -0
  127. package/dist/drizzle/oauth-connected-model.d.mts +278 -0
  128. package/dist/drizzle/oauth-connected-model.mjs +22 -0
  129. package/dist/drizzle/oauth-connected-model.mjs.map +1 -0
  130. package/dist/drizzle/oauth-connected-reference.d.mts +17 -0
  131. package/dist/drizzle/oauth-connected-reference.mjs +53 -0
  132. package/dist/drizzle/oauth-connected-reference.mjs.map +1 -0
  133. package/dist/drizzle/oauth-connected-settlement.mjs +117 -0
  134. package/dist/drizzle/oauth-connected-settlement.mjs.map +1 -0
  135. package/dist/drizzle/oauth-connected-state.d.mts +14 -0
  136. package/dist/drizzle/oauth-connected-state.mjs +422 -0
  137. package/dist/drizzle/oauth-connected-state.mjs.map +1 -0
  138. package/dist/drizzle/oauth-connected-target.d.mts +753 -0
  139. package/dist/drizzle/oauth-connected-target.mjs +91 -0
  140. package/dist/drizzle/oauth-connected-target.mjs.map +1 -0
  141. package/dist/drizzle/oauth-drivers.mjs +51 -0
  142. package/dist/drizzle/oauth-drivers.mjs.map +1 -0
  143. package/dist/drizzle/oauth-flow.d.mts +5 -0
  144. package/dist/drizzle/oauth-flow.mjs +253 -0
  145. package/dist/drizzle/oauth-flow.mjs.map +1 -0
  146. package/dist/drizzle/oauth-input.mjs +127 -0
  147. package/dist/drizzle/oauth-input.mjs.map +1 -0
  148. package/dist/drizzle/oauth-model.d.mts +463 -0
  149. package/dist/drizzle/oauth-model.mjs +49 -0
  150. package/dist/drizzle/oauth-model.mjs.map +1 -0
  151. package/dist/drizzle/oauth-mysql.mjs +8 -0
  152. package/dist/drizzle/oauth-mysql.mjs.map +1 -0
  153. package/dist/drizzle/oauth-owner.d.mts +4 -0
  154. package/dist/drizzle/oauth-owner.mjs +10 -0
  155. package/dist/drizzle/oauth-owner.mjs.map +1 -0
  156. package/dist/drizzle/oauth-registration.d.mts +6 -0
  157. package/dist/drizzle/oauth-registration.mjs +374 -0
  158. package/dist/drizzle/oauth-registration.mjs.map +1 -0
  159. package/dist/drizzle/oauth-state.mjs +107 -0
  160. package/dist/drizzle/oauth-state.mjs.map +1 -0
  161. package/dist/drizzle/oauth-target.d.mts +111 -0
  162. package/dist/drizzle/oauth-target.mjs +246 -0
  163. package/dist/drizzle/oauth-target.mjs.map +1 -0
  164. package/dist/drizzle/passkey-drivers.mjs +34 -0
  165. package/dist/drizzle/passkey-drivers.mjs.map +1 -0
  166. package/dist/drizzle/passkey-kernel.mjs +8 -0
  167. package/dist/drizzle/passkey-kernel.mjs.map +1 -0
  168. package/dist/drizzle/passkey-model.d.mts +272 -0
  169. package/dist/drizzle/passkey-model.mjs +34 -0
  170. package/dist/drizzle/passkey-model.mjs.map +1 -0
  171. package/dist/drizzle/passkey-registration-ceremony-model.d.mts +50 -0
  172. package/dist/drizzle/passkey-registration-ceremony-model.mjs +18 -0
  173. package/dist/drizzle/passkey-registration-ceremony-model.mjs.map +1 -0
  174. package/dist/drizzle/passkey-state.mjs +7 -0
  175. package/dist/drizzle/passkey-state.mjs.map +1 -0
  176. package/dist/drizzle/passkey-target.d.mts +9 -0
  177. package/dist/drizzle/passkey-target.mjs +7 -0
  178. package/dist/drizzle/passkey-target.mjs.map +1 -0
  179. package/dist/drizzle/passkey-write-drivers.mjs +32 -0
  180. package/dist/drizzle/passkey-write-drivers.mjs.map +1 -0
  181. package/dist/drizzle/passkey-write-model.d.mts +190 -0
  182. package/dist/drizzle/passkey-write-model.mjs +13 -0
  183. package/dist/drizzle/passkey-write-model.mjs.map +1 -0
  184. package/dist/drizzle/passkey-write-target.d.mts +11 -0
  185. package/dist/drizzle/passkey-write-target.mjs +7 -0
  186. package/dist/drizzle/passkey-write-target.mjs.map +1 -0
  187. package/dist/drizzle/password-model.d.mts +329 -0
  188. package/dist/drizzle/password-model.mjs +23 -0
  189. package/dist/drizzle/password-model.mjs.map +1 -0
  190. package/dist/drizzle/password-prepared-model.d.mts +94 -0
  191. package/dist/drizzle/password-prepared-model.mjs +11 -0
  192. package/dist/drizzle/password-prepared-model.mjs.map +1 -0
  193. package/dist/drizzle/password-prepared-sql.mjs +390 -0
  194. package/dist/drizzle/password-prepared-sql.mjs.map +1 -0
  195. package/dist/drizzle/password-prepared-state.mjs +104 -0
  196. package/dist/drizzle/password-prepared-state.mjs.map +1 -0
  197. package/dist/drizzle/password-prepared-target.d.mts +12 -0
  198. package/dist/drizzle/password-prepared-target.mjs +56 -0
  199. package/dist/drizzle/password-prepared-target.mjs.map +1 -0
  200. package/dist/drizzle/password-registration.d.mts +19 -0
  201. package/dist/drizzle/password-registration.mjs +135 -0
  202. package/dist/drizzle/password-registration.mjs.map +1 -0
  203. package/dist/drizzle/password-sql.d.mts +5 -0
  204. package/dist/drizzle/password-sql.mjs +9 -0
  205. package/dist/drizzle/password-sql.mjs.map +1 -0
  206. package/dist/drizzle/password-target.d.mts +17 -0
  207. package/dist/drizzle/password-target.mjs +61 -0
  208. package/dist/drizzle/password-target.mjs.map +1 -0
  209. package/dist/drizzle/pg-emails.d.mts +72 -0
  210. package/dist/drizzle/pg-emails.mjs +38 -0
  211. package/dist/drizzle/pg-emails.mjs.map +1 -0
  212. package/dist/drizzle/pg-password-prepared.d.mts +31 -0
  213. package/dist/drizzle/pg-password-prepared.mjs +16 -0
  214. package/dist/drizzle/pg-password-prepared.mjs.map +1 -0
  215. package/dist/drizzle/pg-passwords.d.mts +87 -0
  216. package/dist/drizzle/pg-passwords.mjs +38 -0
  217. package/dist/drizzle/pg-passwords.mjs.map +1 -0
  218. package/dist/drizzle/pg-proofs.d.mts +76 -0
  219. package/dist/drizzle/pg-proofs.mjs +22 -0
  220. package/dist/drizzle/pg-proofs.mjs.map +1 -0
  221. package/dist/drizzle/pg-sessions.d.mts +238 -0
  222. package/dist/drizzle/pg-sessions.mjs +53 -0
  223. package/dist/drizzle/pg-sessions.mjs.map +1 -0
  224. package/dist/drizzle/pg.d.mts +81 -0
  225. package/dist/drizzle/pg.mjs +290 -0
  226. package/dist/drizzle/pg.mjs.map +1 -0
  227. package/dist/drizzle/phone-model.d.mts +115 -0
  228. package/dist/drizzle/phone-model.mjs +39 -0
  229. package/dist/drizzle/phone-model.mjs.map +1 -0
  230. package/dist/drizzle/phone-state.mjs +16 -0
  231. package/dist/drizzle/phone-state.mjs.map +1 -0
  232. package/dist/drizzle/phone-target.d.mts +17 -0
  233. package/dist/drizzle/phone-target.mjs +170 -0
  234. package/dist/drizzle/phone-target.mjs.map +1 -0
  235. package/dist/drizzle/proof-model.d.mts +286 -0
  236. package/dist/drizzle/proof-model.mjs +17 -0
  237. package/dist/drizzle/proof-model.mjs.map +1 -0
  238. package/dist/drizzle/proof-sql.d.mts +4 -0
  239. package/dist/drizzle/proof-sql.mjs +8 -0
  240. package/dist/drizzle/proof-sql.mjs.map +1 -0
  241. package/dist/drizzle/proof-target.d.mts +12 -0
  242. package/dist/drizzle/proof-target.mjs +36 -0
  243. package/dist/drizzle/proof-target.mjs.map +1 -0
  244. package/dist/drizzle/query-operations.mjs +26 -0
  245. package/dist/drizzle/query-operations.mjs.map +1 -0
  246. package/dist/drizzle/session-model.d.mts +200 -0
  247. package/dist/drizzle/session-model.mjs +20 -0
  248. package/dist/drizzle/session-model.mjs.map +1 -0
  249. package/dist/drizzle/session-sql.mjs +8 -0
  250. package/dist/drizzle/session-sql.mjs.map +1 -0
  251. package/dist/drizzle/session-target.d.mts +28 -0
  252. package/dist/drizzle/session-target.mjs +47 -0
  253. package/dist/drizzle/session-target.mjs.map +1 -0
  254. package/dist/drizzle/sqlite-emails.mjs +45 -0
  255. package/dist/drizzle/sqlite-emails.mjs.map +1 -0
  256. package/dist/drizzle/sqlite-identity.mjs +91 -0
  257. package/dist/drizzle/sqlite-identity.mjs.map +1 -0
  258. package/dist/drizzle/sqlite-password-prepared.mjs +21 -0
  259. package/dist/drizzle/sqlite-password-prepared.mjs.map +1 -0
  260. package/dist/drizzle/sqlite-passwords.mjs +49 -0
  261. package/dist/drizzle/sqlite-passwords.mjs.map +1 -0
  262. package/dist/drizzle/sqlite-proofs.mjs +29 -0
  263. package/dist/drizzle/sqlite-proofs.mjs.map +1 -0
  264. package/dist/drizzle/sqlite-sessions.mjs +74 -0
  265. package/dist/drizzle/sqlite-sessions.mjs.map +1 -0
  266. package/dist/drizzle/sqlite.d.mts +78 -0
  267. package/dist/drizzle/sqlite.mjs +217 -0
  268. package/dist/drizzle/sqlite.mjs.map +1 -0
  269. package/dist/drizzle/standalone-guard.mjs +19 -0
  270. package/dist/drizzle/standalone-guard.mjs.map +1 -0
  271. package/dist/drizzle/step-up-model.d.mts +65 -0
  272. package/dist/drizzle/step-up-model.mjs +9 -0
  273. package/dist/drizzle/step-up-model.mjs.map +1 -0
  274. package/dist/drizzle/step-up-state.mjs +32 -0
  275. package/dist/drizzle/step-up-state.mjs.map +1 -0
  276. package/dist/drizzle/totp-model.d.mts +84 -0
  277. package/dist/drizzle/totp-model.mjs +13 -0
  278. package/dist/drizzle/totp-model.mjs.map +1 -0
  279. package/dist/drizzle/totp-state.d.mts +38 -0
  280. package/dist/drizzle/totp-state.mjs +287 -0
  281. package/dist/drizzle/totp-state.mjs.map +1 -0
  282. package/dist/drizzle/totp-target.d.mts +17 -0
  283. package/dist/drizzle/totp-target.mjs +81 -0
  284. package/dist/drizzle/totp-target.mjs.map +1 -0
  285. package/dist/drizzle/transaction-execution.d.mts +4 -0
  286. package/dist/drizzle/transaction-execution.mjs +11 -0
  287. package/dist/drizzle/transaction-execution.mjs.map +1 -0
  288. package/dist/drizzle/transaction-mysql.mjs +46 -0
  289. package/dist/drizzle/transaction-mysql.mjs.map +1 -0
  290. package/dist/drizzle/transaction-owner.d.mts +2 -0
  291. package/dist/drizzle/transaction-owner.mjs +8 -0
  292. package/dist/drizzle/transaction-owner.mjs.map +1 -0
  293. package/dist/index.d.mts +5 -0
  294. package/dist/index.mjs +5 -0
  295. package/dist/internal/configuration.d.mts +174 -0
  296. package/dist/internal/configuration.mjs +7 -0
  297. package/dist/internal/configuration.mjs.map +1 -0
  298. package/dist/internal/drizzle-migrations.mjs +19 -0
  299. package/dist/internal/drizzle-migrations.mjs.map +1 -0
  300. package/dist/internal/drizzle-postgres.mjs +39 -0
  301. package/dist/internal/drizzle-postgres.mjs.map +1 -0
  302. package/dist/internal/drizzle-sqlite.mjs +37 -0
  303. package/dist/internal/drizzle-sqlite.mjs.map +1 -0
  304. package/dist/internal/email-kernel.d.mts +13 -0
  305. package/dist/internal/email-kernel.mjs +487 -0
  306. package/dist/internal/email-kernel.mjs.map +1 -0
  307. package/dist/internal/mapping-error.d.mts +11 -0
  308. package/dist/internal/mapping-error.mjs +30 -0
  309. package/dist/internal/mapping-error.mjs.map +1 -0
  310. package/dist/internal/passkey/admission.d.mts +7 -0
  311. package/dist/internal/passkey/admission.mjs +216 -0
  312. package/dist/internal/passkey/admission.mjs.map +1 -0
  313. package/dist/internal/passkey/credentials.d.mts +6 -0
  314. package/dist/internal/passkey/credentials.mjs +249 -0
  315. package/dist/internal/passkey/credentials.mjs.map +1 -0
  316. package/dist/internal/passkey/enrollment.mjs +138 -0
  317. package/dist/internal/passkey/enrollment.mjs.map +1 -0
  318. package/dist/internal/passkey/flow.d.mts +10 -0
  319. package/dist/internal/passkey/flow.mjs +452 -0
  320. package/dist/internal/passkey/flow.mjs.map +1 -0
  321. package/dist/internal/passkey/management.mjs +231 -0
  322. package/dist/internal/passkey/management.mjs.map +1 -0
  323. package/dist/internal/passkey/registration-ceremony.d.mts +9 -0
  324. package/dist/internal/passkey/registration-ceremony.mjs +129 -0
  325. package/dist/internal/passkey/registration-ceremony.mjs.map +1 -0
  326. package/dist/internal/passkey/registration-custody.d.mts +2 -0
  327. package/dist/internal/passkey/registration-custody.mjs +52 -0
  328. package/dist/internal/passkey/registration-custody.mjs.map +1 -0
  329. package/dist/internal/passkey/registration-write.mjs +211 -0
  330. package/dist/internal/passkey/registration-write.mjs.map +1 -0
  331. package/dist/internal/passkey/state.d.mts +5 -0
  332. package/dist/internal/passkey/state.mjs +234 -0
  333. package/dist/internal/passkey/state.mjs.map +1 -0
  334. package/dist/internal/passkey/target.d.mts +19 -0
  335. package/dist/internal/passkey/target.mjs +190 -0
  336. package/dist/internal/passkey/target.mjs.map +1 -0
  337. package/dist/internal/passkey/write-state.mjs +345 -0
  338. package/dist/internal/passkey/write-state.mjs.map +1 -0
  339. package/dist/internal/passkey/write-target.mjs +192 -0
  340. package/dist/internal/passkey/write-target.mjs.map +1 -0
  341. package/dist/internal/passkey-kernel.mjs +49 -0
  342. package/dist/internal/passkey-kernel.mjs.map +1 -0
  343. package/dist/internal/passkeys.mjs +367 -0
  344. package/dist/internal/passkeys.mjs.map +1 -0
  345. package/dist/internal/password-kernel.d.mts +13 -0
  346. package/dist/internal/password-kernel.mjs +704 -0
  347. package/dist/internal/password-kernel.mjs.map +1 -0
  348. package/dist/internal/persistence.mjs +289 -0
  349. package/dist/internal/persistence.mjs.map +1 -0
  350. package/dist/internal/phone-kernel.mjs +434 -0
  351. package/dist/internal/phone-kernel.mjs.map +1 -0
  352. package/dist/internal/proof-kernel.d.mts +9 -0
  353. package/dist/internal/proof-kernel.mjs +619 -0
  354. package/dist/internal/proof-kernel.mjs.map +1 -0
  355. package/dist/internal/query-operations.d.mts +3 -0
  356. package/dist/internal/registration.mjs +65 -0
  357. package/dist/internal/registration.mjs.map +1 -0
  358. package/dist/internal/session-kernel.mjs +737 -0
  359. package/dist/internal/session-kernel.mjs.map +1 -0
  360. package/dist/internal/sql-persistence.d.mts +11 -0
  361. package/dist/internal/sql-persistence.mjs +37 -0
  362. package/dist/internal/sql-persistence.mjs.map +1 -0
  363. package/dist/internal/sql-query.mjs +207 -0
  364. package/dist/internal/sql-query.mjs.map +1 -0
  365. package/dist/internal/sql-table.d.mts +37 -0
  366. package/dist/internal/sql-table.mjs +71 -0
  367. package/dist/internal/sql-table.mjs.map +1 -0
  368. package/dist/internal/storage-mapping.mjs +687 -0
  369. package/dist/internal/storage-mapping.mjs.map +1 -0
  370. package/dist/internal/storage-tables.d.mts +914 -0
  371. package/dist/internal/storage-tables.mjs +387 -0
  372. package/dist/internal/storage-tables.mjs.map +1 -0
  373. package/dist/internal/storage-validation.mjs +58 -0
  374. package/dist/internal/storage-validation.mjs.map +1 -0
  375. package/dist/internal/transaction-execution-kernel.d.mts +18 -0
  376. package/dist/internal/transaction-execution-kernel.mjs +118 -0
  377. package/dist/internal/transaction-execution-kernel.mjs.map +1 -0
  378. package/dist/internal/transaction-kernel.d.mts +7 -0
  379. package/dist/internal/transaction-kernel.mjs +260 -0
  380. package/dist/internal/transaction-kernel.mjs.map +1 -0
  381. package/package.json +1 -0
  382. package/src/Drizzle.ts +378 -0
  383. package/src/DrizzleD1.ts +136 -0
  384. package/src/DrizzleLibsql.ts +301 -0
  385. package/src/DrizzleMysql2.ts +184 -0
  386. package/src/DrizzlePglite.ts +174 -0
  387. package/src/DrizzlePostgres.ts +174 -0
  388. package/src/DrizzleSqliteBun.ts +258 -0
  389. package/src/DrizzleSqliteDo.ts +473 -0
  390. package/src/DrizzleSqliteNode.ts +259 -0
  391. package/src/DrizzleSqliteWasm.ts +263 -0
  392. package/src/drizzle/D1BatchStatements.ts +10 -0
  393. package/src/drizzle/PasswordPreparedPostconditions.ts +30 -0
  394. package/src/drizzle/SuppliedService.ts +10 -0
  395. package/src/drizzle/d1-emails.ts +1829 -0
  396. package/src/drizzle/d1-generated-statement.ts +129 -0
  397. package/src/drizzle/d1-identity.ts +375 -0
  398. package/src/drizzle/d1-oauth.ts +340 -0
  399. package/src/drizzle/d1-passkey.ts +373 -0
  400. package/src/drizzle/d1-password-prepared.ts +1270 -0
  401. package/src/drizzle/d1-passwords.ts +2656 -0
  402. package/src/drizzle/d1-phone.ts +96 -0
  403. package/src/drizzle/d1-planning.ts +104 -0
  404. package/src/drizzle/d1-proofs.ts +2305 -0
  405. package/src/drizzle/d1-sessions.ts +2857 -0
  406. package/src/drizzle/d1-totp.ts +78 -0
  407. package/src/drizzle/email-model.ts +516 -0
  408. package/src/drizzle/email-registration.ts +698 -0
  409. package/src/drizzle/email-sql.ts +24 -0
  410. package/src/drizzle/email-target.ts +243 -0
  411. package/src/drizzle/model.ts +463 -0
  412. package/src/drizzle/mysql-emails.ts +321 -0
  413. package/src/drizzle/mysql-identity.ts +324 -0
  414. package/src/drizzle/mysql-password-prepared.ts +204 -0
  415. package/src/drizzle/mysql-passwords.ts +339 -0
  416. package/src/drizzle/mysql-proofs.ts +181 -0
  417. package/src/drizzle/mysql-sessions.ts +741 -0
  418. package/src/drizzle/mysql.ts +687 -0
  419. package/src/drizzle/oauth-accounts.ts +804 -0
  420. package/src/drizzle/oauth-connected-access.ts +354 -0
  421. package/src/drizzle/oauth-connected-collection.ts +324 -0
  422. package/src/drizzle/oauth-connected-custody.ts +277 -0
  423. package/src/drizzle/oauth-connected-drivers.ts +465 -0
  424. package/src/drizzle/oauth-connected-flow.ts +441 -0
  425. package/src/drizzle/oauth-connected-input.ts +127 -0
  426. package/src/drizzle/oauth-connected-maintenance.ts +345 -0
  427. package/src/drizzle/oauth-connected-management.ts +365 -0
  428. package/src/drizzle/oauth-connected-model.ts +347 -0
  429. package/src/drizzle/oauth-connected-reference.ts +112 -0
  430. package/src/drizzle/oauth-connected-settlement.ts +226 -0
  431. package/src/drizzle/oauth-connected-state.ts +845 -0
  432. package/src/drizzle/oauth-connected-target.ts +264 -0
  433. package/src/drizzle/oauth-drivers.ts +569 -0
  434. package/src/drizzle/oauth-flow.ts +462 -0
  435. package/src/drizzle/oauth-input.ts +201 -0
  436. package/src/drizzle/oauth-model.ts +617 -0
  437. package/src/drizzle/oauth-mysql.ts +10 -0
  438. package/src/drizzle/oauth-owner.ts +29 -0
  439. package/src/drizzle/oauth-registration.ts +640 -0
  440. package/src/drizzle/oauth-state.ts +215 -0
  441. package/src/drizzle/oauth-target.ts +750 -0
  442. package/src/drizzle/passkey-drivers.ts +385 -0
  443. package/src/drizzle/passkey-kernel.ts +3 -0
  444. package/src/drizzle/passkey-model.ts +353 -0
  445. package/src/drizzle/passkey-registration-ceremony-model.ts +69 -0
  446. package/src/drizzle/passkey-state.ts +3 -0
  447. package/src/drizzle/passkey-target.ts +23 -0
  448. package/src/drizzle/passkey-write-drivers.ts +395 -0
  449. package/src/drizzle/passkey-write-model.ts +271 -0
  450. package/src/drizzle/passkey-write-target.ts +9 -0
  451. package/src/drizzle/password-model.ts +491 -0
  452. package/src/drizzle/password-prepared-model.ts +259 -0
  453. package/src/drizzle/password-prepared-sql.ts +1191 -0
  454. package/src/drizzle/password-prepared-state.ts +231 -0
  455. package/src/drizzle/password-prepared-target.ts +160 -0
  456. package/src/drizzle/password-registration.ts +374 -0
  457. package/src/drizzle/password-sql.ts +12 -0
  458. package/src/drizzle/password-target.ts +224 -0
  459. package/src/drizzle/pg-emails.ts +321 -0
  460. package/src/drizzle/pg-password-prepared.ts +205 -0
  461. package/src/drizzle/pg-passwords.ts +338 -0
  462. package/src/drizzle/pg-proofs.ts +191 -0
  463. package/src/drizzle/pg-sessions.ts +750 -0
  464. package/src/drizzle/pg.ts +984 -0
  465. package/src/drizzle/phone-model.ts +190 -0
  466. package/src/drizzle/phone-state.ts +21 -0
  467. package/src/drizzle/phone-target.ts +472 -0
  468. package/src/drizzle/proof-model.ts +398 -0
  469. package/src/drizzle/proof-sql.ts +9 -0
  470. package/src/drizzle/proof-target.ts +122 -0
  471. package/src/drizzle/query-operations.ts +37 -0
  472. package/src/drizzle/session-model.ts +370 -0
  473. package/src/drizzle/session-sql.ts +13 -0
  474. package/src/drizzle/session-target.ts +338 -0
  475. package/src/drizzle/sqlite-emails.ts +366 -0
  476. package/src/drizzle/sqlite-identity.ts +344 -0
  477. package/src/drizzle/sqlite-password-prepared.ts +260 -0
  478. package/src/drizzle/sqlite-passwords.ts +497 -0
  479. package/src/drizzle/sqlite-proofs.ts +202 -0
  480. package/src/drizzle/sqlite-sessions.ts +971 -0
  481. package/src/drizzle/sqlite.ts +705 -0
  482. package/src/drizzle/standalone-guard.ts +38 -0
  483. package/src/drizzle/step-up-model.ts +128 -0
  484. package/src/drizzle/step-up-state.ts +139 -0
  485. package/src/drizzle/totp-model.ts +92 -0
  486. package/src/drizzle/totp-state.ts +525 -0
  487. package/src/drizzle/totp-target.ts +308 -0
  488. package/src/drizzle/transaction-execution.ts +12 -0
  489. package/src/drizzle/transaction-mysql.ts +104 -0
  490. package/src/drizzle/transaction-owner.ts +10 -0
  491. package/src/index.ts +4 -0
  492. package/src/internal/configuration.ts +282 -0
  493. package/src/internal/drizzle-migrations.ts +30 -0
  494. package/src/internal/drizzle-postgres.ts +81 -0
  495. package/src/internal/drizzle-sqlite.ts +79 -0
  496. package/src/internal/email-kernel.ts +1532 -0
  497. package/src/internal/mapping-error.ts +36 -0
  498. package/src/internal/passkey/admission.ts +362 -0
  499. package/src/internal/passkey/credentials.ts +511 -0
  500. package/src/internal/passkey/enrollment.ts +326 -0
  501. package/src/internal/passkey/flow.ts +1062 -0
  502. package/src/internal/passkey/management.ts +422 -0
  503. package/src/internal/passkey/registration-ceremony.ts +283 -0
  504. package/src/internal/passkey/registration-custody.ts +91 -0
  505. package/src/internal/passkey/registration-write.ts +426 -0
  506. package/src/internal/passkey/state.ts +354 -0
  507. package/src/internal/passkey/target.ts +586 -0
  508. package/src/internal/passkey/write-state.ts +681 -0
  509. package/src/internal/passkey/write-target.ts +452 -0
  510. package/src/internal/passkey-kernel.ts +102 -0
  511. package/src/internal/passkeys.ts +597 -0
  512. package/src/internal/password-kernel.ts +2122 -0
  513. package/src/internal/persistence.ts +590 -0
  514. package/src/internal/phone-kernel.ts +843 -0
  515. package/src/internal/proof-kernel.ts +1949 -0
  516. package/src/internal/query-operations.ts +26 -0
  517. package/src/internal/registration.ts +160 -0
  518. package/src/internal/session-kernel.ts +2210 -0
  519. package/src/internal/sql-persistence.ts +45 -0
  520. package/src/internal/sql-query.ts +298 -0
  521. package/src/internal/sql-table.ts +89 -0
  522. package/src/internal/storage-mapping.ts +706 -0
  523. package/src/internal/storage-tables.ts +408 -0
  524. package/src/internal/storage-validation.ts +117 -0
  525. package/src/internal/transaction-execution-kernel.ts +317 -0
  526. package/src/internal/transaction-kernel.ts +582 -0
@@ -0,0 +1,2210 @@
1
+ import {
2
+ coordinateCommit,
3
+ CurrentCommitJournal,
4
+ hasCommitScope,
5
+ LifecycleHooks,
6
+ } from "@yielded/auth/Hooks";
7
+ import { reportPersistenceFailure } from "@yielded/auth/Persistence";
8
+ import type { SubjectId, TokenDigest } from "@yielded/auth/Schema";
9
+ import {
10
+ assessAuthentication,
11
+ snapshotSessionAuthenticationProvenance,
12
+ PendingAuthenticationInvalid,
13
+ SessionConflict,
14
+ SessionInvalid,
15
+ SessionStepUpInvalid,
16
+ SessionUnavailable,
17
+ StaleAuthentication,
18
+ type AuthenticationEvidence,
19
+ type AuthenticationRevision,
20
+ type PendingConsumption,
21
+ type SecurityRevision,
22
+ type SessionInspection,
23
+ SessionMetadata,
24
+ pendingAuthenticationContext,
25
+ snapshotPendingAuthenticationContext,
26
+ type PendingAuthentication,
27
+ type PendingAuthenticationRecord,
28
+ type PendingAuthenticationState,
29
+ type AuthenticationAuthority,
30
+ type PrepareSessionCommit,
31
+ type SessionRepository,
32
+ type SignedSessionValidity,
33
+ type StatefulSessionPersistence,
34
+ type StatefulSessionRecord,
35
+ type SessionStepUpIntent,
36
+ type SessionStepUpPersistence,
37
+ } from "@yielded/auth/Sessions";
38
+ /* oxlint-disable no-explicit-any -- existing storage kernels erase foreign table shapes; domain errors remain typed. */
39
+ import type { EffectDrizzleQueryError } from "drizzle-orm/effect-core";
40
+ import { Cause, Context, DateTime, Effect, Option, Schema } from "effect";
41
+ import type * as SqlError from "effect/unstable/sql/SqlError";
42
+
43
+ import type {
44
+ AuthenticationAuthorityMapping,
45
+ PendingAuthenticationTables,
46
+ PendingAuthenticationMapping,
47
+ SessionAuthorityTables,
48
+ SessionFlowTables,
49
+ SessionSubjectTables,
50
+ SignedSessionValidityMapping,
51
+ StatefulSessionMapping,
52
+ } from "../drizzle/session-model";
53
+ import type { SessionStepUpMapping } from "../drizzle/step-up-model";
54
+ import {
55
+ decodeStepUpIntent,
56
+ encodeStepUpIntent,
57
+ sameStepUpRevision,
58
+ stepUpIntentLive,
59
+ validateStepUpPlan,
60
+ stepUpRotationMatches,
61
+ } from "../drizzle/step-up-state";
62
+ import { PersistenceMappingError, isMappedConstraintConflict } from "./mapping-error";
63
+ import type { QueryOperations } from "./query-operations";
64
+
65
+ type CommitMode = "interactive" | "synchronous";
66
+
67
+ type AnySubjectMapping = SessionSubjectTables<any, any>;
68
+
69
+ type AnyAuthorityMapping = SessionAuthorityTables<any, any, any>;
70
+
71
+ type AnyFlowMapping = SessionFlowTables<any, any>;
72
+
73
+ type AnyPendingMapping = AnyAuthorityMapping &
74
+ AnyFlowMapping &
75
+ PendingAuthenticationTables<any, any, any, any>;
76
+
77
+ type AnyStepUpMapping = SessionStepUpMapping<any, any, any, any, any, any, any, any>;
78
+
79
+ interface SessionSqlQuery<A = ReadonlyArray<any>> extends Effect.Effect<
80
+ A,
81
+ EffectDrizzleQueryError | SqlError.SqlError
82
+ > {
83
+ readonly from: (...args: ReadonlyArray<any>) => SessionSqlQuery<A>;
84
+ readonly where: (...args: ReadonlyArray<any>) => SessionSqlQuery<A>;
85
+ readonly limit: (...args: ReadonlyArray<any>) => SessionSqlQuery<A>;
86
+ readonly orderBy: (...args: ReadonlyArray<any>) => SessionSqlQuery<A>;
87
+ readonly for: (...args: ReadonlyArray<any>) => SessionSqlQuery<A>;
88
+ readonly set: (...args: ReadonlyArray<any>) => SessionSqlQuery<A>;
89
+ readonly values: (...args: ReadonlyArray<any>) => SessionSqlQuery<A>;
90
+ }
91
+
92
+ export interface SessionSqlDatabase {
93
+ readonly select: (...args: ReadonlyArray<any>) => SessionSqlQuery;
94
+ readonly insert: (...args: ReadonlyArray<any>) => SessionSqlQuery;
95
+ readonly update: (...args: ReadonlyArray<any>) => SessionSqlQuery;
96
+ readonly delete: (...args: ReadonlyArray<any>) => SessionSqlQuery;
97
+ readonly transaction: <A, E, R>(
98
+ body: (transaction: SessionSqlDatabase) => Effect.Effect<A, E, R>,
99
+ ) => Effect.Effect<A, E | SqlError.SqlError, R>;
100
+ }
101
+
102
+ type Database = SessionSqlDatabase;
103
+
104
+ /** Native SQL authority for the current transaction or savepoint. */
105
+ export class CurrentSessionSql extends Context.Service<CurrentSessionSql, SessionSqlDatabase>()(
106
+ "effect-auth/drizzle/CurrentSessionSql",
107
+ ) {}
108
+
109
+ type Guard = Effect.Effect<void, SessionUnavailable>;
110
+
111
+ export interface SessionSqlOptions {
112
+ readonly mode: CommitMode;
113
+ readonly locking: boolean;
114
+ /** Target-specific ambient transaction preflight. It runs before IDs or writes. */
115
+ readonly standaloneGuard: Guard;
116
+ }
117
+
118
+ type SessionDomainError =
119
+ | SessionStepUpInvalid
120
+ | PendingAuthenticationInvalid
121
+ | SessionConflict
122
+ | SessionInvalid
123
+ | SessionUnavailable
124
+ | StaleAuthentication;
125
+
126
+ type AdapterFailure =
127
+ | EffectDrizzleQueryError
128
+ | PersistenceMappingError
129
+ | Schema.SchemaError
130
+ | SqlError.SqlError;
131
+
132
+ type NormalizedSessionFailure<E> = E extends SessionDomainError ? E : SessionUnavailable;
133
+
134
+ type PendingProjection = "record" | "context";
135
+
136
+ type SqlStepUpRejectAbsence = SessionStepUpInvalid | StaleAuthentication;
137
+
138
+ type SqlStepUpReadFailure = AdapterFailure | SqlStepUpRejectAbsence | SessionUnavailable;
139
+
140
+ export const makeSessionKernel = (operations: QueryOperations) => {
141
+ const { and, eq, gt, lte, inArray, or, sql, column, updateValues } = operations;
142
+ const unavailable = () => SessionUnavailable.make({});
143
+
144
+ const stale = () => StaleAuthentication.make({});
145
+
146
+ const invalidPending = () => PendingAuthenticationInvalid.make({});
147
+
148
+ const invalidSession = () => SessionInvalid.make({});
149
+
150
+ const mapFailureCause = <A, E, R, E2>(
151
+ effect: Effect.Effect<A, E, R>,
152
+ map: (error: E) => E2,
153
+ ): Effect.Effect<A, E2, R> =>
154
+ Effect.catchCause(effect, (cause) => Effect.failCause(Cause.map(cause, map)));
155
+
156
+ const isExpectedMutationError = (error: unknown): error is SessionDomainError => {
157
+ const tag =
158
+ typeof error === "object" && error !== null ? Reflect.get(error, "_tag") : undefined;
159
+
160
+ return (
161
+ tag === "SessionStepUpInvalid" ||
162
+ tag === "StaleAuthentication" ||
163
+ tag === "PendingAuthenticationInvalid" ||
164
+ tag === "SessionConflict" ||
165
+ tag === "SessionInvalid" ||
166
+ tag === "SessionUnavailable"
167
+ );
168
+ };
169
+
170
+ const normalizeMutation =
171
+ (mapping: any) =>
172
+ <A, E, R>(effect: Effect.Effect<A, E, R>) =>
173
+ effect.pipe(
174
+ Effect.catchCause((cause) =>
175
+ Effect.failCause(
176
+ Cause.map(cause, (error) =>
177
+ isMappedConstraintConflict(mapping.isConstraintConflict, error)
178
+ ? SessionConflict.make({})
179
+ : error,
180
+ ),
181
+ ),
182
+ ),
183
+ );
184
+
185
+ const safeTransaction = <A, E, R>(
186
+ effect: Effect.Effect<A, E, R>,
187
+ ): Effect.Effect<A, NormalizedSessionFailure<E> | SessionUnavailable, R> =>
188
+ reportPersistenceFailure(effect, isExpectedMutationError).pipe(
189
+ Effect.catchCause((cause) =>
190
+ Effect.failCause(
191
+ Cause.map(
192
+ cause,
193
+ (error): NormalizedSessionFailure<E> =>
194
+ (isExpectedMutationError(error)
195
+ ? error
196
+ : unavailable()) as NormalizedSessionFailure<E>,
197
+ ),
198
+ ),
199
+ ),
200
+ );
201
+
202
+ const selectRows = (
203
+ query: SessionSqlQuery,
204
+ locking: boolean,
205
+ ): Effect.Effect<ReadonlyArray<any>, AdapterFailure> =>
206
+ locking && typeof query.for === "function" ? query.for("update") : query;
207
+
208
+ const inTransaction = <A, E, R>(
209
+ database: Database,
210
+ effect: Effect.Effect<A, E, R | CurrentSessionSql>,
211
+ ): Effect.Effect<A, E | SqlError.SqlError, Exclude<R, CurrentSessionSql>> =>
212
+ Effect.suspend(() =>
213
+ database.transaction((current) =>
214
+ effect.pipe(Effect.provideService(CurrentSessionSql, current)),
215
+ ),
216
+ );
217
+
218
+ const owned = <A, E, R>(
219
+ database: Database,
220
+ options: SessionSqlOptions,
221
+ isConstraintConflict: (cause: unknown) => boolean,
222
+ body: Effect.Effect<A, E, R | CurrentSessionSql | CurrentCommitJournal>,
223
+ ): Effect.Effect<
224
+ A,
225
+ NormalizedSessionFailure<E> | SessionUnavailable,
226
+ Exclude<Exclude<R, CurrentSessionSql>, CurrentCommitJournal> | LifecycleHooks
227
+ > => {
228
+ const run = coordinateCommit(
229
+ () =>
230
+ database.transaction((current) =>
231
+ body.pipe(Effect.provideService(CurrentSessionSql, current)),
232
+ ),
233
+ { mode: options.mode },
234
+ ).pipe(
235
+ (effect) =>
236
+ reportPersistenceFailure(
237
+ effect,
238
+ (error) =>
239
+ isExpectedMutationError(error) ||
240
+ isMappedConstraintConflict(isConstraintConflict, error),
241
+ ),
242
+ Effect.map((result) => result.value),
243
+ Effect.catchCause((cause) =>
244
+ Effect.failCause(
245
+ Cause.map(cause, (error): NormalizedSessionFailure<E> | SessionUnavailable =>
246
+ isExpectedMutationError(error) ? (error as NormalizedSessionFailure<E>) : unavailable(),
247
+ ),
248
+ ),
249
+ ),
250
+ );
251
+
252
+ return Effect.gen(function* () {
253
+ const current = yield* Effect.serviceOption(CurrentSessionSql);
254
+ const transactionBound = Option.isSome(current) && current.value === database;
255
+
256
+ if (!transactionBound) {
257
+ // A root service must never commit one database underneath another owner.
258
+ if (yield* hasCommitScope) return yield* unavailable();
259
+ yield* options.standaloneGuard;
260
+ }
261
+
262
+ // A tx-bound service still owns a savepoint and child journal. If the caller
263
+ // catches its failure, both partial writes and prepared values are discarded.
264
+ return yield* run;
265
+ });
266
+ };
267
+
268
+ const uniqueStrings = (values: ReadonlyArray<string>): ReadonlyArray<string> =>
269
+ [...new Set(values)].sort();
270
+
271
+ const preservesRevision = (
272
+ completed: AuthenticationEvidence,
273
+ original: AuthenticationEvidence,
274
+ ): boolean => {
275
+ const current = new Map(
276
+ completed.revision.credentials.map((item) => [item.credentialId, item.revision]),
277
+ );
278
+
279
+ return (
280
+ completed.flowId === original.flowId &&
281
+ completed.bindingDigest === original.bindingDigest &&
282
+ completed.revision.subjectId === original.revision.subjectId &&
283
+ completed.revision.securityRevision === original.revision.securityRevision &&
284
+ original.revision.credentials.every(
285
+ (item) => current.get(item.credentialId) === item.revision,
286
+ ) &&
287
+ original.proofs.every((expected) =>
288
+ completed.proofs.some(
289
+ (proof) =>
290
+ proof.method === expected.method &&
291
+ proof.credentialId === expected.credentialId &&
292
+ proof.userVerified === expected.userVerified &&
293
+ proof.phishingResistant === expected.phishingResistant &&
294
+ DateTime.toEpochMillis(proof.verifiedAt) ===
295
+ DateTime.toEpochMillis(expected.verifiedAt) &&
296
+ proof.factors.length === expected.factors.length &&
297
+ proof.factors.every((factor, index) => factor === expected.factors[index]),
298
+ ),
299
+ )
300
+ );
301
+ };
302
+
303
+ const authorityColumns = (mapping: any) => ({
304
+ subjectId: column(mapping.subject.table, mapping.subject.id),
305
+ subjectStatus: column(mapping.subject.table, mapping.subject.status),
306
+ subjectRevision: column(mapping.subject.table, mapping.subject.securityRevision),
307
+ credentialSubjectId: column(mapping.credential.table, mapping.credential.subjectId),
308
+ credentialId: column(mapping.credential.table, mapping.credential.credentialId),
309
+ credentialRevision: column(mapping.credential.table, mapping.credential.revision),
310
+ ...(mapping.credential.status === undefined
311
+ ? {}
312
+ : { credentialStatus: column(mapping.credential.table, mapping.credential.status) }),
313
+ });
314
+
315
+ const subjectColumns = (mapping: any) => ({
316
+ subjectId: column(mapping.subject.table, mapping.subject.id),
317
+ subjectStatus: column(mapping.subject.table, mapping.subject.status),
318
+ subjectRevision: column(mapping.subject.table, mapping.subject.securityRevision),
319
+ });
320
+
321
+ const readSubject = Effect.fn("DrizzleSession.readSubject")(function* (
322
+ mapping: AnySubjectMapping,
323
+ nativeSubjectId: unknown,
324
+ locking: boolean,
325
+ ) {
326
+ const database = yield* CurrentSessionSql;
327
+ const columns = subjectColumns(mapping);
328
+
329
+ return yield* selectRows(
330
+ database
331
+ .select()
332
+ .from(mapping.subject.table)
333
+ .where(eq(columns.subjectId, nativeSubjectId))
334
+ .limit(1),
335
+ locking,
336
+ );
337
+ });
338
+
339
+ const captureDetailsIn = Effect.fn("DrizzleSession.captureDetails")(function* (
340
+ mapping: AnyAuthorityMapping,
341
+ subjectId: SubjectId,
342
+ credentialIds: ReadonlyArray<string>,
343
+ locking: boolean,
344
+ ) {
345
+ const database = yield* CurrentSessionSql;
346
+ const requested = uniqueStrings(credentialIds);
347
+
348
+ if (requested.length !== credentialIds.length) return yield* stale();
349
+ const nativeSubjectId = yield* mapping.subjectId.toNative(subjectId);
350
+ const subjectRows = yield* readSubject(mapping, nativeSubjectId, locking);
351
+ const subject = subjectRows[0];
352
+ const columns = authorityColumns(mapping);
353
+
354
+ if (subject === undefined || !mapping.subject.isActiveStatus(subject[mapping.subject.status]))
355
+ return yield* stale();
356
+
357
+ const query = database
358
+ .select()
359
+ .from(mapping.credential.table)
360
+ .where(
361
+ and(
362
+ eq(columns.credentialSubjectId, nativeSubjectId),
363
+ requested.length === 0 ? sql<boolean>`false` : inArray(columns.credentialId, requested),
364
+ ),
365
+ )
366
+ .orderBy(columns.credentialId);
367
+
368
+ const credentialRows = requested.length === 0 ? [] : yield* selectRows(query, locking);
369
+
370
+ if (credentialRows.length !== requested.length) return yield* stale();
371
+
372
+ const credentials = credentialRows
373
+ .map((row: any) => ({
374
+ credentialId: row[mapping.credential.credentialId] as string,
375
+ revision: row[mapping.credential.revision] as SecurityRevision,
376
+ active:
377
+ mapping.credential.status === undefined ||
378
+ mapping.credential.isActiveStatus?.(row[mapping.credential.status]) === true,
379
+ }))
380
+ .sort((left: any, right: any) => left.credentialId.localeCompare(right.credentialId));
381
+
382
+ if (credentials.some((item: any) => !item.active)) return yield* stale();
383
+
384
+ return {
385
+ subject,
386
+ revision: {
387
+ subjectId,
388
+ securityRevision: subject[mapping.subject.securityRevision] as SecurityRevision,
389
+ credentials: credentials.map(({ credentialId, revision }: any) => ({
390
+ credentialId,
391
+ revision,
392
+ })),
393
+ } satisfies AuthenticationRevision,
394
+ };
395
+ });
396
+
397
+ const captureIn = Effect.fn("DrizzleSession.capture")(function* (
398
+ mapping: AnyAuthorityMapping,
399
+ subjectId: SubjectId,
400
+ credentialIds: ReadonlyArray<string>,
401
+ locking: boolean,
402
+ ) {
403
+ return (yield* captureDetailsIn(mapping, subjectId, credentialIds, locking)).revision;
404
+ });
405
+
406
+ const validateEvidenceIn = Effect.fn("DrizzleSession.validateEvidence")(function* (
407
+ mapping: AnyAuthorityMapping,
408
+ evidence: AuthenticationEvidence,
409
+ locking: boolean,
410
+ ) {
411
+ const captured = yield* captureDetailsIn(
412
+ mapping,
413
+ evidence.revision.subjectId,
414
+ evidence.revision.credentials.map((item) => item.credentialId),
415
+ locking,
416
+ );
417
+
418
+ if (
419
+ captured.revision.securityRevision !== evidence.revision.securityRevision ||
420
+ captured.revision.credentials.length !== evidence.revision.credentials.length ||
421
+ captured.revision.credentials.some(
422
+ (item: { credentialId: string; revision: SecurityRevision }, index: number) => {
423
+ const expected = [...evidence.revision.credentials].sort((a, b) =>
424
+ a.credentialId.localeCompare(b.credentialId),
425
+ )[index];
426
+
427
+ return (
428
+ expected === undefined ||
429
+ expected.credentialId !== item.credentialId ||
430
+ expected.revision !== item.revision
431
+ );
432
+ },
433
+ )
434
+ )
435
+ return yield* stale();
436
+ const nativeSubjectId = yield* mapping.subjectId.toNative(evidence.revision.subjectId);
437
+ const requirement = yield* mapping.subject.decodeRequirement(captured.subject);
438
+
439
+ return { nativeSubjectId, requirement };
440
+ });
441
+
442
+ const flowColumns = (mapping: any) => ({
443
+ flowId: column(mapping.flow.table, mapping.flow.flowId),
444
+ subjectId: column(mapping.flow.table, mapping.flow.subjectId),
445
+ state: column(mapping.flow.table, mapping.flow.state),
446
+ pendingDigest: column(mapping.flow.table, mapping.flow.pendingDigest),
447
+ dedupUntil: column(mapping.flow.table, mapping.flow.dedupUntil),
448
+ });
449
+
450
+ const readFlow = Effect.fn("DrizzleSession.readFlow")(function* (
451
+ mapping: AnyFlowMapping,
452
+ flowId: string,
453
+ locking: boolean,
454
+ ) {
455
+ const database = yield* CurrentSessionSql;
456
+ const columns = flowColumns(mapping);
457
+
458
+ return yield* selectRows(
459
+ database.select().from(mapping.flow.table).where(eq(columns.flowId, flowId)).limit(1),
460
+ locking,
461
+ );
462
+ });
463
+
464
+ const ensureDirectFlowAvailable = Effect.fn("DrizzleSession.ensureFlowAvailable")(function* (
465
+ mapping: AnyAuthorityMapping &
466
+ AnyFlowMapping & {
467
+ readonly pending?: PendingAuthenticationTables<any, any, any, any>["pending"];
468
+ },
469
+ evidence: AuthenticationEvidence,
470
+ nativeSubjectId: unknown,
471
+ dedupUntil: DateTime.Utc,
472
+ ) {
473
+ const database = yield* CurrentSessionSql;
474
+ const rows = yield* readFlow(mapping, evidence.flowId, true);
475
+ const existing = rows[0];
476
+
477
+ if (existing !== undefined) {
478
+ const now = yield* freshNow;
479
+
480
+ if (
481
+ DateTime.toEpochMillis(now) <
482
+ DateTime.toEpochMillis(yield* mapping.flow.decodeInstant(existing[mapping.flow.dedupUntil]))
483
+ )
484
+ return yield* SessionConflict.make({});
485
+ if (mapping.pending !== undefined)
486
+ yield* database
487
+ .delete(mapping.pending.table)
488
+ .where(eq(column(mapping.pending.table, mapping.pending.flowId), evidence.flowId));
489
+ yield* database
490
+ .delete(mapping.flow.table)
491
+ .where(eq(flowColumns(mapping).flowId, evidence.flowId));
492
+ }
493
+
494
+ return mapping.flow.encodeEstablishedInsert({
495
+ evidence,
496
+ subjectId: nativeSubjectId,
497
+ dedupUntil,
498
+ });
499
+ });
500
+
501
+ const pendingColumns = (mapping: any) => ({
502
+ digest: column(mapping.pending.table, mapping.pending.digest),
503
+ version: column(mapping.pending.table, mapping.pending.version),
504
+ flowId: column(mapping.pending.table, mapping.pending.flowId),
505
+ subjectId: column(mapping.pending.table, mapping.pending.subjectId),
506
+ bindingDigest: column(mapping.pending.table, mapping.pending.bindingDigest),
507
+ expiresAt: column(mapping.pending.table, mapping.pending.expiresAt),
508
+ attemptLimit: column(mapping.pending.table, mapping.pending.attemptLimit),
509
+ failedAttempts: column(mapping.pending.table, mapping.pending.failedAttempts),
510
+ consumed: column(mapping.pending.table, mapping.pending.consumed),
511
+ });
512
+
513
+ const readPending = Effect.fn("DrizzleSession.readPending")(function* (
514
+ mapping: AnyPendingMapping,
515
+ digest: TokenDigest,
516
+ locking: boolean,
517
+ ) {
518
+ const database = yield* CurrentSessionSql;
519
+
520
+ return yield* selectRows(
521
+ database
522
+ .select()
523
+ .from(mapping.pending.table)
524
+ .where(eq(pendingColumns(mapping).digest, digest))
525
+ .limit(1),
526
+ locking,
527
+ );
528
+ });
529
+
530
+ const validatePending = Effect.fn("DrizzleSession.validatePending")(function* <
531
+ Record extends PendingAuthenticationState = PendingAuthenticationRecord<unknown>,
532
+ >(
533
+ mapping: AnyPendingMapping,
534
+ input: PendingConsumption,
535
+ projection: PendingProjection = "record",
536
+ ) {
537
+ const rows = yield* readPending(mapping, input.digest, true);
538
+ const row = rows[0];
539
+
540
+ if (row === undefined) return yield* invalidPending();
541
+
542
+ const decode = (
543
+ projection === "context" ? mapping.pending.decodeContext : mapping.pending.decode
544
+ ) as (row: any) => Effect.Effect<Record, PersistenceMappingError>;
545
+
546
+ const record = yield* decode(row);
547
+ const failedAttempts = Number(row[mapping.pending.failedAttempts]);
548
+
549
+ if (
550
+ record.version !== input.version ||
551
+ record.evidence.flowId !== input.flowId ||
552
+ record.evidence.bindingDigest !== input.bindingDigest ||
553
+ row[mapping.pending.consumed] !== false ||
554
+ failedAttempts >= record.attemptLimit
555
+ )
556
+ return yield* invalidPending();
557
+ const flows = yield* readFlow(mapping, input.flowId, true);
558
+ const flow = flows[0];
559
+
560
+ if (
561
+ flow === undefined ||
562
+ flow[mapping.flow.state] !== mapping.flow.pendingStateValue ||
563
+ flow[mapping.flow.pendingDigest] !== input.digest
564
+ )
565
+ return yield* invalidPending();
566
+ if (DateTime.toEpochMillis(yield* freshNow) >= DateTime.toEpochMillis(record.expiresAt))
567
+ return yield* invalidPending();
568
+
569
+ return record;
570
+ });
571
+
572
+ const consumePending = Effect.fn("DrizzleSession.consumePending")(function* (
573
+ mapping: AnyPendingMapping,
574
+ input: PendingConsumption,
575
+ until: DateTime.Utc,
576
+ ) {
577
+ const database = yield* CurrentSessionSql;
578
+
579
+ yield* Effect.all([
580
+ database
581
+ .update(mapping.pending.table)
582
+ .set(updateValues<any>([[mapping.pending.consumed, true]]))
583
+ .where(
584
+ and(
585
+ eq(pendingColumns(mapping).digest, input.digest),
586
+ eq(pendingColumns(mapping).version, input.version),
587
+ eq(pendingColumns(mapping).consumed, false),
588
+ ),
589
+ ),
590
+
591
+ database
592
+ .update(mapping.flow.table)
593
+ .set(
594
+ updateValues<any>([
595
+ [mapping.flow.state, mapping.flow.establishedStateValue],
596
+ [mapping.flow.pendingDigest, null],
597
+ [mapping.flow.dedupUntil, mapping.flow.encodeInstant(until)],
598
+ ]),
599
+ )
600
+ .where(eq(flowColumns(mapping).flowId, input.flowId)),
601
+ ]);
602
+ });
603
+
604
+ const freshNow = DateTime.now;
605
+
606
+ const allocate = <A>(
607
+ mode: CommitMode,
608
+ asynchronous: Effect.Effect<A, PersistenceMappingError> | undefined,
609
+ synchronous: (() => A) | undefined,
610
+ ): Effect.Effect<A, PersistenceMappingError | SessionUnavailable> => {
611
+ if (mode === "synchronous")
612
+ return synchronous === undefined
613
+ ? Effect.fail(unavailable())
614
+ : Effect.try({
615
+ try: synchronous,
616
+ catch: (cause) => PersistenceMappingError.make({ operation: "allocate", cause }),
617
+ });
618
+ if (asynchronous !== undefined) return asynchronous;
619
+
620
+ return synchronous === undefined
621
+ ? Effect.fail(unavailable())
622
+ : Effect.try({
623
+ try: synchronous,
624
+ catch: (cause) => PersistenceMappingError.make({ operation: "allocate", cause }),
625
+ });
626
+ };
627
+
628
+ const makeSqlAuthenticationAuthority = <Claims>(
629
+ database: Database,
630
+ mapping: AuthenticationAuthorityMapping<Claims, any, any, any, any, any>,
631
+ options: SessionSqlOptions,
632
+ ) =>
633
+ Effect.map(LifecycleHooks, (hooks) => {
634
+ const service = {
635
+ capture: (subjectId: SubjectId, credentialIds: ReadonlyArray<string>) =>
636
+ safeTransaction(
637
+ inTransaction(database, captureIn(mapping, subjectId, credentialIds, true)),
638
+ ),
639
+ requirements: (evidence: AuthenticationEvidence) =>
640
+ safeTransaction(
641
+ inTransaction(
642
+ database,
643
+ validateEvidenceIn(mapping, evidence, true).pipe(
644
+ Effect.map((result) => result.requirement),
645
+ ),
646
+ ),
647
+ ),
648
+ approve: <A>(
649
+ input: Parameters<AuthenticationAuthority["Service"]["approve"]>[0],
650
+ prepare: PrepareSessionCommit<void, A>,
651
+ ) =>
652
+ owned(
653
+ database,
654
+ options,
655
+ mapping.isConstraintConflict,
656
+ Effect.gen(function* () {
657
+ const journal = yield* CurrentCommitJournal;
658
+ const { requirement } = yield* validateEvidenceIn(mapping, input.evidence, true);
659
+
660
+ if (input.pending === undefined) {
661
+ const assessed = yield* assessAuthentication(input.evidence, requirement).pipe(
662
+ Effect.mapError(unavailable),
663
+ );
664
+
665
+ const now = yield* freshNow;
666
+
667
+ if (
668
+ !assessed.satisfied ||
669
+ DateTime.toEpochMillis(now) >= DateTime.toEpochMillis(input.expiresAt) ||
670
+ DateTime.toEpochMillis(input.expiresAt) >
671
+ DateTime.toEpochMillis(input.absoluteExpiresAt)
672
+ )
673
+ return yield* stale();
674
+
675
+ return prepare(undefined, journal);
676
+ }
677
+ if (mapping.pending === undefined) return yield* invalidPending();
678
+ const pendingMapping = { ...mapping, ...mapping.pending };
679
+ const pending = yield* validatePending(pendingMapping, input.pending);
680
+
681
+ if (!preservesRevision(input.evidence, pending.evidence))
682
+ return yield* invalidPending();
683
+
684
+ const assessed = yield* assessAuthentication(input.evidence, requirement).pipe(
685
+ Effect.mapError(unavailable),
686
+ );
687
+
688
+ const now = yield* freshNow;
689
+
690
+ if (
691
+ !assessed.satisfied ||
692
+ DateTime.toEpochMillis(now) >= DateTime.toEpochMillis(pending.expiresAt) ||
693
+ DateTime.toEpochMillis(now) >= DateTime.toEpochMillis(input.expiresAt) ||
694
+ DateTime.toEpochMillis(input.expiresAt) >
695
+ DateTime.toEpochMillis(input.absoluteExpiresAt)
696
+ )
697
+ return yield* stale();
698
+ const receipt = prepare(undefined, journal);
699
+
700
+ yield* consumePending(pendingMapping, input.pending, input.absoluteExpiresAt);
701
+
702
+ return receipt;
703
+ }),
704
+ ),
705
+ };
706
+
707
+ return {
708
+ capture: (subjectId, credentialIds) =>
709
+ service
710
+ .capture(subjectId, credentialIds)
711
+ .pipe(Effect.provideService(LifecycleHooks, hooks)),
712
+ requirements: (evidence) =>
713
+ service.requirements(evidence).pipe(Effect.provideService(LifecycleHooks, hooks)),
714
+ approve: (input, prepare) =>
715
+ service.approve(input, prepare).pipe(Effect.provideService(LifecycleHooks, hooks)),
716
+ } satisfies AuthenticationAuthority["Service"];
717
+ });
718
+
719
+ /** The same locked security checks serve full consumption reads and the
720
+ * claims-free additional-factor context. No lock spans proof verification. */
721
+ const readAuthenticatedPending = Effect.fn("DrizzleSession.readAuthenticatedPending")(function* <
722
+ Record extends PendingAuthenticationState,
723
+ >(
724
+ mapping: AnyPendingMapping,
725
+ input: { readonly digest: TokenDigest; readonly bindingDigest?: TokenDigest },
726
+ projection: PendingProjection,
727
+ ) {
728
+ const initial = (yield* readPending(mapping, input.digest, false))[0];
729
+
730
+ if (initial === undefined) return yield* invalidPending();
731
+ const initialNativeSubjectId = initial[mapping.pending.subjectId];
732
+ const subject = (yield* readSubject(mapping, initialNativeSubjectId, true))[0];
733
+
734
+ if (subject === undefined) return yield* invalidPending();
735
+ const current = (yield* readPending(mapping, input.digest, true))[0];
736
+
737
+ if (
738
+ current === undefined ||
739
+ !mapping.subjectId.equals(current[mapping.pending.subjectId], initialNativeSubjectId)
740
+ )
741
+ return yield* invalidPending();
742
+
743
+ const decode = (
744
+ projection === "context" ? mapping.pending.decodeContext : mapping.pending.decode
745
+ ) as (row: any) => Effect.Effect<Record, PersistenceMappingError>;
746
+
747
+ const record = yield* decode(current);
748
+ const nativeSubjectId = yield* mapping.subjectId.toNative(record.evidence.revision.subjectId);
749
+ const failedAttempts = Number(current[mapping.pending.failedAttempts]);
750
+
751
+ if (
752
+ !mapping.subjectId.equals(nativeSubjectId, initialNativeSubjectId) ||
753
+ (input.bindingDigest !== undefined &&
754
+ record.evidence.bindingDigest !== input.bindingDigest) ||
755
+ current[mapping.pending.version] !== record.version ||
756
+ current[mapping.pending.flowId] !== record.evidence.flowId ||
757
+ current[mapping.pending.bindingDigest] !== record.evidence.bindingDigest ||
758
+ current[mapping.pending.consumed] !== false ||
759
+ failedAttempts >= record.attemptLimit
760
+ )
761
+ return yield* invalidPending();
762
+ const flow = (yield* readFlow(mapping, record.evidence.flowId, true))[0];
763
+
764
+ if (
765
+ flow === undefined ||
766
+ flow[mapping.flow.state] !== mapping.flow.pendingStateValue ||
767
+ flow[mapping.flow.pendingDigest] !== input.digest
768
+ )
769
+ return yield* invalidPending();
770
+
771
+ const { requirement } = yield* mapFailureCause(
772
+ validateEvidenceIn(mapping, record.evidence, true),
773
+ (error) => (error._tag === "StaleAuthentication" ? invalidPending() : error),
774
+ );
775
+
776
+ yield* assessAuthentication(record.evidence, requirement).pipe(
777
+ Effect.mapError(() => invalidPending()),
778
+ );
779
+ if (DateTime.toEpochMillis(yield* freshNow) >= DateTime.toEpochMillis(record.expiresAt))
780
+ return yield* invalidPending();
781
+
782
+ return record;
783
+ });
784
+
785
+ const makeSqlPendingAuthentication = <Claims>(
786
+ database: Database,
787
+ mapping: PendingAuthenticationMapping<Claims, any, any, any, any, any>,
788
+ options: SessionSqlOptions,
789
+ ) =>
790
+ Effect.map(LifecycleHooks, (hooks) => {
791
+ const service = {
792
+ create: <A>(
793
+ input: Omit<PendingAuthenticationRecord<Claims>, "version">,
794
+ _inputNow: DateTime.Utc,
795
+ prepare: PrepareSessionCommit<PendingAuthenticationRecord<Claims>, A>,
796
+ ) =>
797
+ owned(
798
+ database,
799
+ options,
800
+ mapping.isConstraintConflict,
801
+ Effect.gen(function* () {
802
+ const transaction = yield* CurrentSessionSql;
803
+ const journal = yield* CurrentCommitJournal;
804
+
805
+ const { nativeSubjectId, requirement } = yield* validateEvidenceIn(
806
+ mapping,
807
+ input.evidence,
808
+ true,
809
+ );
810
+
811
+ const existing = (yield* readFlow(mapping, input.evidence.flowId, true))[0];
812
+
813
+ if (existing !== undefined) {
814
+ const dedupUntil = yield* mapping.flow.decodeInstant(
815
+ existing[mapping.flow.dedupUntil],
816
+ );
817
+
818
+ if (DateTime.toEpochMillis(yield* freshNow) < DateTime.toEpochMillis(dedupUntil))
819
+ return yield* SessionConflict.make({});
820
+ yield* transaction
821
+ .delete(mapping.pending.table)
822
+ .where(eq(pendingColumns(mapping).flowId, input.evidence.flowId));
823
+ yield* transaction
824
+ .delete(mapping.flow.table)
825
+ .where(eq(flowColumns(mapping).flowId, input.evidence.flowId));
826
+ }
827
+
828
+ const version = yield* allocate(
829
+ options.mode,
830
+ mapping.pending.allocateVersion,
831
+ mapping.pending.allocateVersionSync,
832
+ );
833
+
834
+ yield* assessAuthentication(input.evidence, requirement);
835
+ const now = yield* freshNow;
836
+
837
+ if (DateTime.toEpochMillis(now) >= DateTime.toEpochMillis(input.expiresAt))
838
+ return yield* stale();
839
+ const record = { ...input, version };
840
+ const receipt = prepare(record, journal);
841
+
842
+ yield* transaction.insert(mapping.flow.table).values(
843
+ mapping.flow.encodePendingInsert({
844
+ evidence: input.evidence,
845
+ subjectId: nativeSubjectId,
846
+ pendingDigest: input.digest,
847
+ dedupUntil: input.expiresAt,
848
+ }),
849
+ );
850
+ yield* transaction.insert(mapping.pending.table).values(
851
+ mapping.pending.encodeInsert(record, {
852
+ subjectId: nativeSubjectId,
853
+ failedAttempts: 0,
854
+ consumed: false,
855
+ }),
856
+ );
857
+
858
+ return receipt;
859
+ }).pipe(normalizeMutation(mapping)),
860
+ ),
861
+ context: (input: { readonly digest: TokenDigest; readonly now: DateTime.Utc }) =>
862
+ safeTransaction(
863
+ inTransaction(
864
+ database,
865
+ readAuthenticatedPending<PendingAuthenticationState>(mapping, input, "context"),
866
+ ),
867
+ ).pipe(Effect.flatMap(pendingAuthenticationContext)),
868
+ read: (input: {
869
+ readonly digest: TokenDigest;
870
+ readonly bindingDigest: TokenDigest;
871
+ readonly now: DateTime.Utc;
872
+ }) =>
873
+ safeTransaction(
874
+ inTransaction(
875
+ database,
876
+ readAuthenticatedPending<PendingAuthenticationRecord<Claims>>(
877
+ mapping,
878
+ input,
879
+ "record",
880
+ ),
881
+ ),
882
+ ).pipe(Effect.map((record) => record as PendingAuthenticationRecord<Claims>)),
883
+ reject: <A>(
884
+ input: Parameters<PendingAuthentication<Claims>["reject"]>[0],
885
+ prepare: PrepareSessionCommit<{ readonly _tag: "Rejected" }, A>,
886
+ ) =>
887
+ owned(
888
+ database,
889
+ options,
890
+ mapping.isConstraintConflict,
891
+ Effect.gen(function* () {
892
+ const transaction = yield* CurrentSessionSql;
893
+ const journal = yield* CurrentCommitJournal;
894
+ const initial = (yield* readPending(mapping, input.digest, false))[0];
895
+
896
+ if (initial === undefined) return prepare({ _tag: "Rejected" }, journal);
897
+ const record = yield* mapping.pending.decodeContext(initial);
898
+
899
+ const nativeSubjectId = yield* mapping.subjectId.toNative(
900
+ record.evidence.revision.subjectId,
901
+ );
902
+
903
+ const subject = (yield* readSubject(mapping, nativeSubjectId, true))[0];
904
+ const current = (yield* readPending(mapping, input.digest, true))[0];
905
+ const now = yield* freshNow;
906
+ const receipt = prepare({ _tag: "Rejected" }, journal);
907
+
908
+ if (
909
+ subject !== undefined &&
910
+ mapping.subject.isActiveStatus(subject[mapping.subject.status]) &&
911
+ subject[mapping.subject.securityRevision] ===
912
+ record.evidence.revision.securityRevision &&
913
+ current !== undefined &&
914
+ current[mapping.pending.consumed] === false &&
915
+ record.evidence.bindingDigest === input.bindingDigest &&
916
+ DateTime.toEpochMillis(now) < DateTime.toEpochMillis(record.expiresAt)
917
+ ) {
918
+ yield* transaction
919
+ .update(mapping.pending.table)
920
+ .set(
921
+ updateValues<any>([
922
+ [
923
+ mapping.pending.failedAttempts,
924
+ sql`case when ${pendingColumns(mapping).failedAttempts} < ${pendingColumns(mapping).attemptLimit} then ${pendingColumns(mapping).failedAttempts} + 1 else ${pendingColumns(mapping).failedAttempts} end`,
925
+ ],
926
+ ]),
927
+ )
928
+ .where(eq(pendingColumns(mapping).digest, input.digest));
929
+ }
930
+
931
+ return receipt;
932
+ }),
933
+ ),
934
+ };
935
+
936
+ return {
937
+ create: (input, now, prepare) =>
938
+ service.create(input, now, prepare).pipe(Effect.provideService(LifecycleHooks, hooks)),
939
+ context: (input) =>
940
+ service.context(input).pipe(Effect.provideService(LifecycleHooks, hooks)),
941
+ read: (input) => service.read(input).pipe(Effect.provideService(LifecycleHooks, hooks)),
942
+ reject: (input, prepare) =>
943
+ service.reject(input, prepare).pipe(Effect.provideService(LifecycleHooks, hooks)),
944
+ } satisfies PendingAuthentication<Claims>;
945
+ });
946
+
947
+ const sessionColumns = (mapping: any) => ({
948
+ sessionId: column(mapping.session.table, mapping.session.sessionId),
949
+ subjectId: column(mapping.session.table, mapping.session.subjectId),
950
+ digest: column(mapping.session.table, mapping.session.digest),
951
+ version: column(mapping.session.table, mapping.session.version),
952
+ securityRevision: column(mapping.session.table, mapping.session.securityRevision),
953
+ issuedAt: column(mapping.session.table, mapping.session.issuedAt),
954
+ expiresAt: column(mapping.session.table, mapping.session.expiresAt),
955
+ absoluteExpiresAt: column(mapping.session.table, mapping.session.absoluteExpiresAt),
956
+ });
957
+
958
+ const makeSqlStatefulSessions = <Claims>(
959
+ database: Database,
960
+ mapping: StatefulSessionMapping<Claims, any, any, any, any, any, any, any>,
961
+ options: SessionSqlOptions,
962
+ ) =>
963
+ Effect.map(LifecycleHooks, (hooks) => {
964
+ const c = sessionColumns(mapping);
965
+
966
+ const persistence = {
967
+ establish: <A>(
968
+ input: Parameters<StatefulSessionPersistence<Claims>["establish"]>[0],
969
+ prepare: PrepareSessionCommit<StatefulSessionRecord<Claims>, A>,
970
+ ) =>
971
+ owned(
972
+ database,
973
+ options,
974
+ mapping.isConstraintConflict,
975
+ Effect.gen(function* () {
976
+ const transaction = yield* CurrentSessionSql;
977
+ const journal = yield* CurrentCommitJournal;
978
+
979
+ const { nativeSubjectId, requirement } = yield* validateEvidenceIn(
980
+ mapping,
981
+ input.evidence,
982
+ true,
983
+ );
984
+
985
+ let flowInsert: unknown | undefined;
986
+ let pendingExpiresAt: DateTime.Utc | undefined;
987
+
988
+ if (input.pending === undefined)
989
+ flowInsert = yield* ensureDirectFlowAvailable(
990
+ mapping,
991
+ input.evidence,
992
+ nativeSubjectId,
993
+ input.session.absoluteExpiresAt,
994
+ );
995
+ else {
996
+ if (mapping.pending === undefined) return yield* invalidPending();
997
+ const pending = yield* validatePending(mapping, input.pending);
998
+
999
+ if (!preservesRevision(input.evidence, pending.evidence))
1000
+ return yield* invalidPending();
1001
+ pendingExpiresAt = pending.expiresAt;
1002
+ }
1003
+
1004
+ const nativeSessionId = yield* allocate(
1005
+ options.mode,
1006
+ mapping.session.allocateId,
1007
+ mapping.session.allocateIdSync,
1008
+ );
1009
+
1010
+ const sessionId = yield* mapping.sessionId.toSession(nativeSessionId);
1011
+
1012
+ const version = yield* allocate(
1013
+ options.mode,
1014
+ mapping.session.allocateVersion,
1015
+ mapping.session.allocateVersionSync,
1016
+ );
1017
+
1018
+ const assessed = yield* assessAuthentication(input.evidence, requirement);
1019
+ const now = yield* freshNow;
1020
+
1021
+ if (
1022
+ !assessed.satisfied ||
1023
+ (pendingExpiresAt !== undefined &&
1024
+ DateTime.toEpochMillis(now) >= DateTime.toEpochMillis(pendingExpiresAt)) ||
1025
+ DateTime.toEpochMillis(now) >= DateTime.toEpochMillis(input.session.expiresAt) ||
1026
+ DateTime.toEpochMillis(input.session.expiresAt) >
1027
+ DateTime.toEpochMillis(input.session.absoluteExpiresAt)
1028
+ )
1029
+ return yield* stale();
1030
+
1031
+ const record = {
1032
+ ...input.session,
1033
+ sessionId,
1034
+ version,
1035
+ subjectId: input.evidence.revision.subjectId,
1036
+ securityRevision: input.evidence.revision.securityRevision,
1037
+ assurance: assessed.assurance,
1038
+ provenance: yield* snapshotSessionAuthenticationProvenance({
1039
+ evidence: input.evidence,
1040
+ }),
1041
+ issuedAt: now,
1042
+ };
1043
+
1044
+ const receipt = prepare(record, journal);
1045
+
1046
+ if (flowInsert !== undefined)
1047
+ yield* transaction.insert(mapping.flow.table).values(flowInsert);
1048
+ else
1049
+ yield* consumePending(
1050
+ { ...mapping, pending: mapping.pending! },
1051
+ input.pending!,
1052
+ input.session.absoluteExpiresAt,
1053
+ );
1054
+ yield* transaction.insert(mapping.session.table).values(
1055
+ mapping.session.encodeInsert(record, {
1056
+ subjectId: nativeSubjectId,
1057
+ sessionId: nativeSessionId,
1058
+ }),
1059
+ );
1060
+
1061
+ return receipt;
1062
+ }).pipe(normalizeMutation(mapping)),
1063
+ ),
1064
+ verify: (input: any) =>
1065
+ safeTransaction(
1066
+ inTransaction(
1067
+ database,
1068
+ Effect.gen(function* () {
1069
+ const read = yield* CurrentSessionSql;
1070
+
1071
+ const rows = yield* read
1072
+ .select()
1073
+ .from(mapping.session.table)
1074
+ .where(eq(c.digest, input.digest))
1075
+ .limit(1);
1076
+
1077
+ const row = rows[0];
1078
+
1079
+ if (row === undefined) return yield* invalidSession();
1080
+ const record = yield* mapping.session.decode(row);
1081
+ const nativeSubjectId = yield* mapping.subjectId.toNative(record.subjectId);
1082
+ const subjects = yield* readSubject(mapping, nativeSubjectId, false);
1083
+ const subject = subjects[0];
1084
+ const now = yield* freshNow;
1085
+
1086
+ if (
1087
+ subject === undefined ||
1088
+ !mapping.subject.isActiveStatus(subject[mapping.subject.status]) ||
1089
+ subject[mapping.subject.securityRevision] !== record.securityRevision ||
1090
+ DateTime.toEpochMillis(now) >= DateTime.toEpochMillis(record.expiresAt) ||
1091
+ DateTime.toEpochMillis(now) >= DateTime.toEpochMillis(record.absoluteExpiresAt)
1092
+ )
1093
+ return yield* invalidSession();
1094
+
1095
+ return record;
1096
+ }),
1097
+ ),
1098
+ ),
1099
+ rotate: <A>(
1100
+ input: Parameters<StatefulSessionPersistence<Claims>["rotate"]>[0],
1101
+ prepare: PrepareSessionCommit<StatefulSessionRecord<Claims>, A>,
1102
+ ) =>
1103
+ owned(
1104
+ database,
1105
+ options,
1106
+ mapping.isConstraintConflict,
1107
+ Effect.gen(function* () {
1108
+ const transaction = yield* CurrentSessionSql;
1109
+ const journal = yield* CurrentCommitJournal;
1110
+ const nativeSessionId = yield* mapping.sessionId.toNative(input.sessionId);
1111
+
1112
+ const initial = (yield* transaction
1113
+ .select()
1114
+ .from(mapping.session.table)
1115
+ .where(eq(c.sessionId, nativeSessionId))
1116
+ .limit(1))[0];
1117
+
1118
+ if (initial === undefined) return yield* SessionConflict.make({});
1119
+ const initialRecord = yield* mapping.session.decode(initial);
1120
+ const nativeSubjectId = yield* mapping.subjectId.toNative(initialRecord.subjectId);
1121
+ const subject = (yield* readSubject(mapping, nativeSubjectId, true))[0];
1122
+
1123
+ const row = (yield* selectRows(
1124
+ transaction
1125
+ .select()
1126
+ .from(mapping.session.table)
1127
+ .where(eq(c.sessionId, nativeSessionId))
1128
+ .limit(1),
1129
+ options.locking,
1130
+ ))[0];
1131
+
1132
+ if (row === undefined) return yield* SessionConflict.make({});
1133
+ const record = yield* mapping.session.decode(row);
1134
+
1135
+ const version = yield* allocate(
1136
+ options.mode,
1137
+ mapping.session.allocateVersion,
1138
+ mapping.session.allocateVersionSync,
1139
+ );
1140
+
1141
+ const now = yield* freshNow;
1142
+
1143
+ if (
1144
+ subject === undefined ||
1145
+ !mapping.subject.isActiveStatus(subject[mapping.subject.status]) ||
1146
+ subject[mapping.subject.securityRevision] !== input.expectedSecurityRevision ||
1147
+ record.securityRevision !== input.expectedSecurityRevision ||
1148
+ record.digest !== input.expectedDigest ||
1149
+ record.version !== input.expectedVersion ||
1150
+ DateTime.toEpochMillis(now) >= DateTime.toEpochMillis(record.expiresAt) ||
1151
+ DateTime.toEpochMillis(now) >= DateTime.toEpochMillis(record.absoluteExpiresAt) ||
1152
+ DateTime.toEpochMillis(input.nextExpiresAt) >
1153
+ DateTime.toEpochMillis(record.absoluteExpiresAt) ||
1154
+ DateTime.toEpochMillis(input.nextExpiresAt) <= DateTime.toEpochMillis(now)
1155
+ )
1156
+ return yield* SessionConflict.make({});
1157
+
1158
+ const next = {
1159
+ ...record,
1160
+ digest: input.nextDigest,
1161
+ credentialVersion: input.nextCredentialVersion,
1162
+ version,
1163
+ issuedAt: now,
1164
+ expiresAt: input.nextExpiresAt,
1165
+ };
1166
+
1167
+ const receipt = prepare(next, journal);
1168
+
1169
+ yield* transaction
1170
+ .update(mapping.session.table)
1171
+ .set(mapping.session.encodeRotation(next))
1172
+ .where(
1173
+ and(
1174
+ eq(c.sessionId, nativeSessionId),
1175
+ eq(c.digest, input.expectedDigest),
1176
+ eq(c.version, input.expectedVersion),
1177
+ ),
1178
+ );
1179
+
1180
+ return receipt;
1181
+ }),
1182
+ ),
1183
+ revokeDigest: <A>(digest: TokenDigest, prepare: PrepareSessionCommit<boolean, A>) =>
1184
+ owned(
1185
+ database,
1186
+ options,
1187
+ mapping.isConstraintConflict,
1188
+ Effect.gen(function* () {
1189
+ const transaction = yield* CurrentSessionSql;
1190
+ const journal = yield* CurrentCommitJournal;
1191
+
1192
+ const initial = (yield* transaction
1193
+ .select()
1194
+ .from(mapping.session.table)
1195
+ .where(eq(c.digest, digest))
1196
+ .limit(1))[0];
1197
+
1198
+ if (initial === undefined) return prepare(false, journal);
1199
+ const record = yield* mapping.session.decode(initial);
1200
+ const nativeSubjectId = yield* mapping.subjectId.toNative(record.subjectId);
1201
+
1202
+ yield* readSubject(mapping, nativeSubjectId, true);
1203
+
1204
+ const row = (yield* selectRows(
1205
+ transaction
1206
+ .select()
1207
+ .from(mapping.session.table)
1208
+ .where(eq(c.digest, digest))
1209
+ .limit(1),
1210
+ options.locking,
1211
+ ))[0];
1212
+
1213
+ const receipt = prepare(row !== undefined, journal);
1214
+
1215
+ if (row !== undefined)
1216
+ yield* transaction.delete(mapping.session.table).where(eq(c.digest, digest));
1217
+
1218
+ return receipt;
1219
+ }),
1220
+ ),
1221
+ revoke: <A>(
1222
+ input: Parameters<StatefulSessionPersistence<Claims>["revoke"]>[0],
1223
+ prepare: PrepareSessionCommit<void, A>,
1224
+ ) =>
1225
+ owned(
1226
+ database,
1227
+ options,
1228
+ mapping.isConstraintConflict,
1229
+ Effect.gen(function* () {
1230
+ const transaction = yield* CurrentSessionSql;
1231
+ const journal = yield* CurrentCommitJournal;
1232
+ const nativeSubjectId = yield* mapping.subjectId.toNative(input.subjectId);
1233
+ const subject = (yield* readSubject(mapping, nativeSubjectId, true))[0];
1234
+
1235
+ if (
1236
+ subject === undefined ||
1237
+ !mapping.subject.isActiveStatus(subject[mapping.subject.status]) ||
1238
+ subject[mapping.subject.securityRevision] !== input.expectedSecurityRevision
1239
+ )
1240
+ return yield* stale();
1241
+ const nativeSessionId = yield* mapping.sessionId.toNative(input.sessionId);
1242
+ const receipt = prepare(undefined, journal);
1243
+
1244
+ yield* transaction
1245
+ .delete(mapping.session.table)
1246
+ .where(and(eq(c.subjectId, nativeSubjectId), eq(c.sessionId, nativeSessionId)));
1247
+
1248
+ return receipt;
1249
+ }),
1250
+ ),
1251
+ revokeAll: <A>(
1252
+ input: Parameters<StatefulSessionPersistence<Claims>["revokeAll"]>[0],
1253
+ prepare: PrepareSessionCommit<void, A>,
1254
+ ) =>
1255
+ owned(
1256
+ database,
1257
+ options,
1258
+ mapping.isConstraintConflict,
1259
+ Effect.gen(function* () {
1260
+ const transaction = yield* CurrentSessionSql;
1261
+ const journal = yield* CurrentCommitJournal;
1262
+ const nativeSubjectId = yield* mapping.subjectId.toNative(input.subjectId);
1263
+ const subject = (yield* readSubject(mapping, nativeSubjectId, true))[0];
1264
+
1265
+ if (
1266
+ subject === undefined ||
1267
+ !mapping.subject.isActiveStatus(subject[mapping.subject.status]) ||
1268
+ subject[mapping.subject.securityRevision] !== input.expectedSecurityRevision
1269
+ )
1270
+ return yield* stale();
1271
+ const nextRevisionSync = mapping.subject.nextSecurityRevisionSync;
1272
+
1273
+ const next = yield* allocate(
1274
+ options.mode,
1275
+ mapping.subject.nextSecurityRevision?.(input.expectedSecurityRevision),
1276
+ nextRevisionSync === undefined
1277
+ ? undefined
1278
+ : () => nextRevisionSync(input.expectedSecurityRevision),
1279
+ );
1280
+
1281
+ const receipt = prepare(undefined, journal);
1282
+
1283
+ yield* transaction
1284
+ .update(mapping.subject.table)
1285
+ .set(updateValues<any>([[mapping.subject.securityRevision, next]]))
1286
+ .where(
1287
+ and(
1288
+ eq(authorityColumns(mapping).subjectId, nativeSubjectId),
1289
+ eq(authorityColumns(mapping).subjectRevision, input.expectedSecurityRevision),
1290
+ ),
1291
+ );
1292
+ yield* transaction
1293
+ .delete(mapping.session.table)
1294
+ .where(eq(c.subjectId, nativeSubjectId));
1295
+
1296
+ return receipt;
1297
+ }),
1298
+ ),
1299
+ };
1300
+
1301
+ const repository = {
1302
+ list: (input: any) =>
1303
+ safeTransaction(
1304
+ inTransaction(
1305
+ database,
1306
+ Effect.gen(function* () {
1307
+ const read = yield* CurrentSessionSql;
1308
+ const nativeSubjectId = yield* mapping.subjectId.toNative(input.subjectId);
1309
+ const subject = (yield* readSubject(mapping, nativeSubjectId, false))[0];
1310
+
1311
+ if (
1312
+ subject === undefined ||
1313
+ !mapping.subject.isActiveStatus(subject[mapping.subject.status])
1314
+ )
1315
+ return { sessions: [] };
1316
+
1317
+ const cursor =
1318
+ input.cursor === undefined
1319
+ ? undefined
1320
+ : yield* mapping.sessionId.toNative(input.cursor as any);
1321
+
1322
+ const now = yield* freshNow;
1323
+
1324
+ const rows = yield* read
1325
+ .select()
1326
+ .from(mapping.session.table)
1327
+ .where(
1328
+ and(
1329
+ eq(c.subjectId, nativeSubjectId),
1330
+ eq(c.securityRevision, subject[mapping.subject.securityRevision]),
1331
+ gt(c.expiresAt, mapping.session.encodeInstant(now)),
1332
+ gt(c.absoluteExpiresAt, mapping.session.encodeInstant(now)),
1333
+ cursor === undefined ? undefined : gt(c.sessionId, cursor),
1334
+ ),
1335
+ )
1336
+ .orderBy(c.sessionId)
1337
+ .limit(input.limit + 1);
1338
+
1339
+ const decoded = yield* Effect.forEach(rows, (row: any) =>
1340
+ mapping.session.decode(row),
1341
+ );
1342
+
1343
+ const codec = Schema.toCodecJson(Schema.toType(SessionMetadata));
1344
+
1345
+ const sessions = yield* Effect.forEach(
1346
+ decoded.slice(0, input.limit),
1347
+ (record: any) =>
1348
+ Schema.encodeEffect(codec)(record).pipe(
1349
+ Effect.flatMap(Schema.decodeEffect(codec)),
1350
+ ),
1351
+ );
1352
+
1353
+ const extra = decoded[input.limit];
1354
+
1355
+ return {
1356
+ sessions,
1357
+ ...(extra === undefined ? {} : { nextCursor: sessions.at(-1)?.sessionId }),
1358
+ };
1359
+ }),
1360
+ ),
1361
+ ),
1362
+ };
1363
+
1364
+ return {
1365
+ statefulSessionPersistence: {
1366
+ establish: (input, prepare) =>
1367
+ persistence
1368
+ .establish(input, prepare)
1369
+ .pipe(Effect.provideService(LifecycleHooks, hooks)),
1370
+ verify: (input) =>
1371
+ persistence.verify(input).pipe(Effect.provideService(LifecycleHooks, hooks)),
1372
+ rotate: (input, prepare) =>
1373
+ persistence.rotate(input, prepare).pipe(Effect.provideService(LifecycleHooks, hooks)),
1374
+ revokeDigest: (digest, prepare) =>
1375
+ persistence
1376
+ .revokeDigest(digest, prepare)
1377
+ .pipe(Effect.provideService(LifecycleHooks, hooks)),
1378
+ revoke: (input, prepare) =>
1379
+ persistence.revoke(input, prepare).pipe(Effect.provideService(LifecycleHooks, hooks)),
1380
+ revokeAll: (input, prepare) =>
1381
+ persistence
1382
+ .revokeAll(input, prepare)
1383
+ .pipe(Effect.provideService(LifecycleHooks, hooks)),
1384
+ } satisfies StatefulSessionPersistence<Claims>,
1385
+ sessionRepository: {
1386
+ list: (input) =>
1387
+ repository.list(input).pipe(Effect.provideService(LifecycleHooks, hooks)),
1388
+ } satisfies SessionRepository,
1389
+ };
1390
+ });
1391
+
1392
+ const makeSqlSignedValidity = (
1393
+ database: Database,
1394
+ mapping: SignedSessionValidityMapping<any, any, any, any>,
1395
+ options: SessionSqlOptions,
1396
+ ) =>
1397
+ Effect.map(LifecycleHooks, (hooks) => {
1398
+ const subject = subjectColumns(mapping);
1399
+
1400
+ const tombstone = {
1401
+ subjectId: column(mapping.tombstone.table, mapping.tombstone.subjectId),
1402
+ sessionId: column(mapping.tombstone.table, mapping.tombstone.sessionId),
1403
+ absoluteExpiresAt: column(mapping.tombstone.table, mapping.tombstone.absoluteExpiresAt),
1404
+ };
1405
+
1406
+ const service = {
1407
+ verify: (session: SessionMetadata, _now: DateTime.Utc) =>
1408
+ safeTransaction(
1409
+ inTransaction(
1410
+ database,
1411
+ Effect.gen(function* () {
1412
+ const read = yield* CurrentSessionSql;
1413
+ const nativeSubjectId = yield* mapping.subjectId.toNative(session.subjectId);
1414
+ const nativeSessionId = yield* mapping.sessionId.toNative(session.sessionId);
1415
+ const subjects = yield* readSubject(mapping, nativeSubjectId, false);
1416
+ const row = subjects[0];
1417
+
1418
+ if (
1419
+ row === undefined ||
1420
+ !mapping.subject.isActiveStatus(row[mapping.subject.status]) ||
1421
+ row[mapping.subject.securityRevision] !== session.securityRevision
1422
+ )
1423
+ return yield* invalidSession();
1424
+ const authoritativeNow = yield* freshNow;
1425
+
1426
+ if (
1427
+ DateTime.toEpochMillis(authoritativeNow) >=
1428
+ DateTime.toEpochMillis(session.expiresAt) ||
1429
+ DateTime.toEpochMillis(authoritativeNow) >=
1430
+ DateTime.toEpochMillis(session.absoluteExpiresAt)
1431
+ )
1432
+ return yield* invalidSession();
1433
+
1434
+ const revoked = yield* read
1435
+ .select({ sessionId: tombstone.sessionId })
1436
+ .from(mapping.tombstone.table)
1437
+ .where(
1438
+ and(
1439
+ eq(tombstone.subjectId, nativeSubjectId),
1440
+ eq(tombstone.sessionId, nativeSessionId),
1441
+ gt(
1442
+ tombstone.absoluteExpiresAt,
1443
+ mapping.tombstone.encodeInstant(authoritativeNow),
1444
+ ),
1445
+ ),
1446
+ )
1447
+ .limit(1);
1448
+
1449
+ if (revoked.length > 0) return yield* invalidSession();
1450
+ }),
1451
+ ),
1452
+ ),
1453
+ revoke: <A>(
1454
+ input: Parameters<SignedSessionValidity["revoke"]>[0],
1455
+ prepare: PrepareSessionCommit<void, A>,
1456
+ ) =>
1457
+ owned(
1458
+ database,
1459
+ options,
1460
+ mapping.isConstraintConflict,
1461
+ Effect.gen(function* () {
1462
+ const transaction = yield* CurrentSessionSql;
1463
+ const journal = yield* CurrentCommitJournal;
1464
+ const nativeSubjectId = yield* mapping.subjectId.toNative(input.subjectId);
1465
+ const row = (yield* readSubject(mapping, nativeSubjectId, true))[0];
1466
+
1467
+ if (
1468
+ row === undefined ||
1469
+ !mapping.subject.isActiveStatus(row[mapping.subject.status]) ||
1470
+ row[mapping.subject.securityRevision] !== input.expectedSecurityRevision
1471
+ )
1472
+ return yield* stale();
1473
+ const nativeSessionId = yield* mapping.sessionId.toNative(input.sessionId);
1474
+
1475
+ const existing = yield* transaction
1476
+ .select()
1477
+ .from(mapping.tombstone.table)
1478
+ .where(
1479
+ and(
1480
+ eq(tombstone.subjectId, nativeSubjectId),
1481
+ eq(tombstone.sessionId, nativeSessionId),
1482
+ ),
1483
+ )
1484
+ .limit(1);
1485
+
1486
+ const receipt = prepare(undefined, journal);
1487
+
1488
+ if (existing[0] === undefined)
1489
+ yield* transaction.insert(mapping.tombstone.table).values(
1490
+ mapping.tombstone.encodeInsert({
1491
+ subjectId: nativeSubjectId,
1492
+ sessionId: nativeSessionId,
1493
+ absoluteExpiresAt: input.absoluteExpiresAt,
1494
+ }),
1495
+ );
1496
+ else {
1497
+ const existingExpiry = yield* mapping.tombstone.decodeInstant(
1498
+ existing[0][mapping.tombstone.absoluteExpiresAt],
1499
+ );
1500
+
1501
+ if (
1502
+ DateTime.toEpochMillis(input.absoluteExpiresAt) >
1503
+ DateTime.toEpochMillis(existingExpiry)
1504
+ )
1505
+ yield* transaction
1506
+ .update(mapping.tombstone.table)
1507
+ .set(
1508
+ updateValues<any>([
1509
+ [
1510
+ mapping.tombstone.absoluteExpiresAt,
1511
+ mapping.tombstone.encodeInstant(input.absoluteExpiresAt),
1512
+ ],
1513
+ ]),
1514
+ )
1515
+ .where(
1516
+ and(
1517
+ eq(tombstone.subjectId, nativeSubjectId),
1518
+ eq(tombstone.sessionId, nativeSessionId),
1519
+ ),
1520
+ );
1521
+ }
1522
+
1523
+ return receipt;
1524
+ }),
1525
+ ),
1526
+ revokeAll: <A>(
1527
+ input: Parameters<SignedSessionValidity["revokeAll"]>[0],
1528
+ prepare: PrepareSessionCommit<void, A>,
1529
+ ) =>
1530
+ owned(
1531
+ database,
1532
+ options,
1533
+ mapping.isConstraintConflict,
1534
+ Effect.gen(function* () {
1535
+ const transaction = yield* CurrentSessionSql;
1536
+ const journal = yield* CurrentCommitJournal;
1537
+ const nativeSubjectId = yield* mapping.subjectId.toNative(input.subjectId);
1538
+ const row = (yield* readSubject(mapping, nativeSubjectId, true))[0];
1539
+
1540
+ if (
1541
+ row === undefined ||
1542
+ !mapping.subject.isActiveStatus(row[mapping.subject.status]) ||
1543
+ row[mapping.subject.securityRevision] !== input.expectedSecurityRevision
1544
+ )
1545
+ return yield* stale();
1546
+ const nextRevisionSync = mapping.subject.nextSecurityRevisionSync;
1547
+
1548
+ const next = yield* allocate(
1549
+ options.mode,
1550
+ mapping.subject.nextSecurityRevision?.(input.expectedSecurityRevision),
1551
+ nextRevisionSync === undefined
1552
+ ? undefined
1553
+ : () => nextRevisionSync(input.expectedSecurityRevision),
1554
+ );
1555
+
1556
+ const receipt = prepare(undefined, journal);
1557
+
1558
+ yield* transaction
1559
+ .update(mapping.subject.table)
1560
+ .set(updateValues<any>([[mapping.subject.securityRevision, next]]))
1561
+ .where(
1562
+ and(
1563
+ eq(subject.subjectId, nativeSubjectId),
1564
+ eq(subject.subjectRevision, input.expectedSecurityRevision),
1565
+ ),
1566
+ );
1567
+
1568
+ return receipt;
1569
+ }),
1570
+ ),
1571
+ };
1572
+
1573
+ return {
1574
+ verify: (session, now) =>
1575
+ service.verify(session, now).pipe(Effect.provideService(LifecycleHooks, hooks)),
1576
+ revoke: (input, prepare) =>
1577
+ service.revoke(input, prepare).pipe(Effect.provideService(LifecycleHooks, hooks)),
1578
+ revokeAll: (input, prepare) =>
1579
+ service.revokeAll(input, prepare).pipe(Effect.provideService(LifecycleHooks, hooks)),
1580
+ } satisfies SignedSessionValidity;
1581
+ });
1582
+
1583
+ const stepUpColumns = (mapping: any) =>
1584
+ Object.fromEntries(
1585
+ [
1586
+ "digest",
1587
+ "version",
1588
+ "flowId",
1589
+ "subjectId",
1590
+ "bindingDigest",
1591
+ "snapshot",
1592
+ "expiresAt",
1593
+ "attemptLimit",
1594
+ "failedAttempts",
1595
+ "consumed",
1596
+ ].map((key) => [key, column(mapping.intent.table, mapping.intent[key])]),
1597
+ );
1598
+
1599
+ const stepUpLocator = Effect.fn("DrizzleStepUp.locator")(function* (
1600
+ mapping: AnyStepUpMapping,
1601
+ digest: TokenDigest,
1602
+ locking: boolean,
1603
+ expiresAt?: DateTime.Utc,
1604
+ ) {
1605
+ const database = yield* CurrentSessionSql;
1606
+
1607
+ return yield* selectRows(
1608
+ database
1609
+ .select()
1610
+ .from(mapping.intent.table)
1611
+ .where(
1612
+ and(
1613
+ eq(stepUpColumns(mapping).digest, digest),
1614
+ expiresAt === undefined
1615
+ ? undefined
1616
+ : eq(stepUpColumns(mapping).expiresAt, mapping.intent.encodeInstant(expiresAt)),
1617
+ ),
1618
+ )
1619
+ .limit(1),
1620
+ locking,
1621
+ ).pipe(Effect.map((rows) => rows[0]));
1622
+ });
1623
+
1624
+ const stepUpAuthority = Effect.fn("DrizzleStepUp.authority")(function* (
1625
+ mapping: AnyStepUpMapping,
1626
+ revision: AuthenticationRevision,
1627
+ locking: boolean,
1628
+ ) {
1629
+ const current = yield* captureDetailsIn(
1630
+ mapping,
1631
+ revision.subjectId,
1632
+ revision.credentials.map((c) => c.credentialId),
1633
+ locking,
1634
+ );
1635
+
1636
+ if (!sameStepUpRevision(current.revision, revision)) return yield* stale();
1637
+ const requirement = yield* mapping.subject.decodeRequirement(current.subject);
1638
+
1639
+ return {
1640
+ nativeSubjectId: yield* mapping.subjectId.toNative(revision.subjectId),
1641
+ requirement,
1642
+ };
1643
+ });
1644
+
1645
+ const stepUpSource = Effect.fn("DrizzleStepUp.source")(function* (
1646
+ mapping: AnyStepUpMapping,
1647
+ intent: SessionStepUpIntent,
1648
+ nativeSubjectId: unknown,
1649
+ now: DateTime.Utc,
1650
+ locking: boolean,
1651
+ ) {
1652
+ const database = yield* CurrentSessionSql;
1653
+
1654
+ if (!stepUpIntentLive(intent, mapping.source.kind, now))
1655
+ return yield* SessionStepUpInvalid.make({});
1656
+ const source = mapping.source;
1657
+
1658
+ if (source.kind === "StatelessSigned") return { nativeSessionId: undefined, row: undefined };
1659
+ const nativeSessionId = yield* source.sessionId.toNative(intent.sourceSessionId);
1660
+
1661
+ if (source.kind === "StateAssistedSigned") {
1662
+ const t = source.tombstone;
1663
+
1664
+ const found = yield* selectRows(
1665
+ database
1666
+ .select()
1667
+ .from(t.table)
1668
+ .where(
1669
+ and(
1670
+ eq(column(t.table, t.subjectId), nativeSubjectId),
1671
+ eq(column(t.table, t.sessionId), nativeSessionId),
1672
+ gt(column(t.table, t.absoluteExpiresAt), t.encodeInstant(now)),
1673
+ ),
1674
+ )
1675
+ .limit(1),
1676
+ locking,
1677
+ );
1678
+
1679
+ if (found.length > 0) return yield* SessionStepUpInvalid.make({});
1680
+
1681
+ return { nativeSessionId, row: undefined };
1682
+ }
1683
+
1684
+ const s = source.session,
1685
+ c = sessionColumns(source);
1686
+
1687
+ const row = (yield* selectRows(
1688
+ database
1689
+ .select()
1690
+ .from(s.table)
1691
+ .where(
1692
+ and(
1693
+ eq(c.sessionId, nativeSessionId),
1694
+ eq(c.subjectId, nativeSubjectId),
1695
+ eq(c.securityRevision, intent.revision.securityRevision),
1696
+ eq(column(s.table, s.credentialVersion), intent.sourceCredentialVersion),
1697
+ eq(column(s.table, s.authenticatedAt), s.encodeInstant(intent.sourceAuthenticatedAt)),
1698
+ eq(c.expiresAt, s.encodeInstant(intent.sourceExpiresAt)),
1699
+ eq(c.absoluteExpiresAt, s.encodeInstant(intent.sourceAbsoluteExpiresAt)),
1700
+ gt(c.expiresAt, s.encodeInstant(now)),
1701
+ gt(c.absoluteExpiresAt, s.encodeInstant(now)),
1702
+ ),
1703
+ )
1704
+ .limit(1),
1705
+ locking,
1706
+ ))[0];
1707
+
1708
+ if (row === undefined) return yield* SessionStepUpInvalid.make({});
1709
+
1710
+ return { nativeSessionId, row };
1711
+ });
1712
+
1713
+ const readSqlStepUp = Effect.fn("DrizzleStepUp.read")(function* (
1714
+ mapping: AnyStepUpMapping,
1715
+ digest: TokenDigest,
1716
+ locking: boolean,
1717
+ ) {
1718
+ const first = yield* stepUpLocator(mapping, digest, false);
1719
+
1720
+ if (first === undefined) return yield* SessionStepUpInvalid.make({});
1721
+ const intent = yield* decodeStepUpIntent(first[mapping.intent.snapshot]);
1722
+ const authority = yield* stepUpAuthority(mapping, intent.revision, locking);
1723
+
1724
+ const source = yield* stepUpSource(
1725
+ mapping,
1726
+ intent,
1727
+ authority.nativeSubjectId,
1728
+ yield* freshNow,
1729
+ locking,
1730
+ );
1731
+
1732
+ const row = yield* stepUpLocator(mapping, digest, locking, intent.expiresAt);
1733
+ const m = mapping.intent;
1734
+
1735
+ if (
1736
+ row === undefined ||
1737
+ row[m.snapshot] !== first[m.snapshot] ||
1738
+ row[m.version] !== intent.version ||
1739
+ row[m.digest] !== intent.digest ||
1740
+ digest !== intent.digest ||
1741
+ row[m.flowId] !== intent.flowId ||
1742
+ row[m.bindingDigest] !== intent.bindingDigest ||
1743
+ !mapping.subjectId.equals(row[m.subjectId], authority.nativeSubjectId) ||
1744
+ Boolean(row[m.consumed]) ||
1745
+ !Schema.is(Schema.Natural)(row[m.failedAttempts]) ||
1746
+ row[m.failedAttempts] >= row[m.attemptLimit] ||
1747
+ row[m.attemptLimit] !== intent.attemptLimit ||
1748
+ !stepUpIntentLive(intent, mapping.source.kind, yield* freshNow)
1749
+ )
1750
+ return yield* SessionStepUpInvalid.make({});
1751
+
1752
+ return { row, intent, source, ...authority };
1753
+ });
1754
+
1755
+ const isSqlStepUpRejectAbsence = (error: unknown): error is SqlStepUpRejectAbsence => {
1756
+ const tag =
1757
+ typeof error === "object" && error !== null ? Reflect.get(error, "_tag") : undefined;
1758
+
1759
+ return tag === "SessionStepUpInvalid" || tag === "StaleAuthentication";
1760
+ };
1761
+
1762
+ const recoverSqlStepUpRejectAbsence = <A, B, R>(
1763
+ effect: Effect.Effect<A, SqlStepUpReadFailure, R>,
1764
+ rejected: B,
1765
+ ): Effect.Effect<A | B, AdapterFailure | SessionUnavailable, R> =>
1766
+ effect.pipe(
1767
+ Effect.catchCause((cause) => {
1768
+ if (
1769
+ cause.reasons.length > 0 &&
1770
+ cause.reasons.every(
1771
+ (reason) => Cause.isFailReason(reason) && isSqlStepUpRejectAbsence(reason.error),
1772
+ )
1773
+ )
1774
+ return Effect.succeed(rejected);
1775
+
1776
+ return Effect.failCause(
1777
+ Cause.map(cause, (error) => (isSqlStepUpRejectAbsence(error) ? unavailable() : error)),
1778
+ );
1779
+ }),
1780
+ );
1781
+
1782
+ /** Shared implementation; public driver entrypoints restore concrete tables/IDs. */
1783
+ const makeSqlSessionStepUp = <Claims>(
1784
+ database: Database,
1785
+ mapping: SessionStepUpMapping<Claims, any, any, any, any, any, any, any>,
1786
+ options: SessionSqlOptions,
1787
+ ) =>
1788
+ Effect.map(LifecycleHooks, (hooks) => {
1789
+ const m = mapping.intent,
1790
+ c = stepUpColumns(mapping);
1791
+
1792
+ const read = (digest: TokenDigest) =>
1793
+ mapFailureCause(
1794
+ safeTransaction(inTransaction(database, readSqlStepUp(mapping, digest, options.locking))),
1795
+ (error) => (error._tag === "StaleAuthentication" ? SessionStepUpInvalid.make({}) : error),
1796
+ );
1797
+
1798
+ const service = {
1799
+ create: <A>(
1800
+ input: Omit<SessionStepUpIntent, "version">,
1801
+ _now: DateTime.Utc,
1802
+ prepare: PrepareSessionCommit<SessionStepUpIntent, A>,
1803
+ ) =>
1804
+ owned(
1805
+ database,
1806
+ options,
1807
+ mapping.isConstraintConflict,
1808
+ Effect.gen(function* () {
1809
+ const tx = yield* CurrentSessionSql;
1810
+ const journal = yield* CurrentCommitJournal;
1811
+ const authority = yield* stepUpAuthority(mapping, input.revision, options.locking);
1812
+
1813
+ const version = yield* allocate(
1814
+ options.mode,
1815
+ m.allocateVersion,
1816
+ m.allocateVersionSync,
1817
+ );
1818
+
1819
+ const record: SessionStepUpIntent = { ...input, version };
1820
+
1821
+ yield* stepUpSource(
1822
+ mapping,
1823
+ record,
1824
+ authority.nativeSubjectId,
1825
+ yield* freshNow,
1826
+ options.locking,
1827
+ );
1828
+
1829
+ const existing = yield* selectRows(
1830
+ tx
1831
+ .select()
1832
+ .from(m.table)
1833
+ .where(or(eq(c.digest, input.digest), eq(c.flowId, input.flowId)))
1834
+ .limit(1),
1835
+ options.locking,
1836
+ );
1837
+
1838
+ if (existing.length > 0) return yield* SessionConflict.make({});
1839
+ const snapshot = yield* encodeStepUpIntent(record);
1840
+
1841
+ const values = {
1842
+ ...m.encodeInsert(record, { subjectId: authority.nativeSubjectId }),
1843
+ ...updateValues<any>([
1844
+ [m.digest, record.digest],
1845
+ [m.version, version],
1846
+ [m.flowId, record.flowId],
1847
+ [m.subjectId, authority.nativeSubjectId],
1848
+ [m.bindingDigest, record.bindingDigest],
1849
+ [m.snapshot, snapshot],
1850
+ [m.expiresAt, m.encodeInstant(record.expiresAt)],
1851
+ [m.attemptLimit, record.attemptLimit],
1852
+ [m.failedAttempts, 0],
1853
+ [m.consumed, false],
1854
+ ]),
1855
+ };
1856
+
1857
+ const receipt = prepare(record, journal);
1858
+
1859
+ yield* tx.insert(m.table).values(values).pipe(normalizeMutation(mapping));
1860
+
1861
+ const inserted = (yield* tx
1862
+ .select()
1863
+ .from(m.table)
1864
+ .where(
1865
+ and(
1866
+ eq(c.digest, record.digest),
1867
+ eq(c.version, version),
1868
+ eq(c.flowId, record.flowId),
1869
+ eq(c.subjectId, authority.nativeSubjectId),
1870
+ eq(c.bindingDigest, record.bindingDigest),
1871
+ eq(c.snapshot, snapshot),
1872
+ eq(c.expiresAt, m.encodeInstant(record.expiresAt)),
1873
+ eq(c.attemptLimit, record.attemptLimit),
1874
+ eq(c.failedAttempts, 0),
1875
+ eq(c.consumed, false),
1876
+ ),
1877
+ )
1878
+ .limit(1))[0];
1879
+
1880
+ if (inserted === undefined) return yield* unavailable();
1881
+
1882
+ return receipt;
1883
+ }),
1884
+ ).pipe((effect) =>
1885
+ mapFailureCause(effect, (error) =>
1886
+ error._tag === "SessionStepUpInvalid" ? stale() : error,
1887
+ ),
1888
+ ),
1889
+ context: (input: any) =>
1890
+ mapFailureCause(
1891
+ read(input.digest).pipe(
1892
+ Effect.flatMap(({ intent }) =>
1893
+ snapshotPendingAuthenticationContext({
1894
+ flowId: intent.flowId,
1895
+ bindingDigest: intent.bindingDigest,
1896
+ revision: intent.revision,
1897
+ expiresAtMillis: DateTime.toEpochMillis(intent.expiresAt),
1898
+ }),
1899
+ ),
1900
+ ),
1901
+ (error) => (error._tag === "PendingAuthenticationInvalid" ? unavailable() : error),
1902
+ ),
1903
+ read: (input: any) =>
1904
+ read(input.digest).pipe(
1905
+ Effect.flatMap((value) =>
1906
+ value.intent.bindingDigest === input.bindingDigest
1907
+ ? Effect.succeed(value.intent)
1908
+ : Effect.fail(SessionStepUpInvalid.make({})),
1909
+ ),
1910
+ ),
1911
+ reject: <A>(
1912
+ input: Parameters<SessionStepUpPersistence<Claims>["reject"]>[0],
1913
+ prepare: PrepareSessionCommit<{ readonly _tag: "Rejected" }, A>,
1914
+ ) =>
1915
+ owned(
1916
+ database,
1917
+ options,
1918
+ mapping.isConstraintConflict,
1919
+ Effect.gen(function* () {
1920
+ const tx = yield* CurrentSessionSql;
1921
+ const journal = yield* CurrentCommitJournal;
1922
+
1923
+ const selected = yield* recoverSqlStepUpRejectAbsence(
1924
+ readSqlStepUp(mapping, input.digest, options.locking).pipe(
1925
+ Effect.map((value) => ({ _tag: "Found" as const, value })),
1926
+ ),
1927
+ { _tag: "Rejected" as const },
1928
+ );
1929
+
1930
+ if (selected._tag === "Rejected") return prepare({ _tag: "Rejected" }, journal);
1931
+ const { row, intent } = selected.value;
1932
+ const receipt = prepare({ _tag: "Rejected" }, journal);
1933
+
1934
+ yield* tx
1935
+ .update(m.table)
1936
+ .set(updateValues<any>([[m.failedAttempts, sql`${c.failedAttempts} + 1`]]))
1937
+ .where(
1938
+ and(
1939
+ eq(c.digest, input.digest),
1940
+ eq(c.snapshot, row[m.snapshot]),
1941
+ eq(c.version, intent.version),
1942
+ eq(c.consumed, false),
1943
+ sql`${c.failedAttempts} < ${c.attemptLimit}`,
1944
+ ),
1945
+ );
1946
+ const written = yield* stepUpLocator(mapping, input.digest, false);
1947
+
1948
+ if (
1949
+ written === undefined ||
1950
+ written[m.version] !== intent.version ||
1951
+ written[m.snapshot] !== row[m.snapshot] ||
1952
+ written[m.failedAttempts] !== row[m.failedAttempts] + 1
1953
+ )
1954
+ return yield* unavailable();
1955
+
1956
+ return receipt;
1957
+ }),
1958
+ ),
1959
+ complete: <A>(
1960
+ plan: Parameters<SessionStepUpPersistence<Claims>["complete"]>[0],
1961
+ prepare: PrepareSessionCommit<SessionInspection<Claims>, A>,
1962
+ ) =>
1963
+ owned(
1964
+ database,
1965
+ options,
1966
+ mapping.isConstraintConflict,
1967
+ Effect.gen(function* () {
1968
+ const tx = yield* CurrentSessionSql;
1969
+ const journal = yield* CurrentCommitJournal;
1970
+
1971
+ yield* validateStepUpPlan(plan);
1972
+
1973
+ // Lock the entire combined vector before source/intent, including added factors.
1974
+ const current = yield* stepUpAuthority(
1975
+ mapping,
1976
+ plan.evidence.revision,
1977
+ options.locking,
1978
+ );
1979
+
1980
+ const stored = yield* readSqlStepUp(mapping, plan.intent.digest, options.locking);
1981
+
1982
+ if (
1983
+ (yield* encodeStepUpIntent(stored.intent)) !==
1984
+ (yield* encodeStepUpIntent(plan.intent))
1985
+ )
1986
+ return yield* SessionStepUpInvalid.make({});
1987
+
1988
+ const base = yield* assessAuthentication(plan.evidence, current.requirement).pipe(
1989
+ Effect.mapError(() => stale()),
1990
+ );
1991
+
1992
+ const profile = yield* assessAuthentication(
1993
+ plan.evidence,
1994
+ stored.intent.requirement,
1995
+ ).pipe(Effect.mapError(() => stale()));
1996
+
1997
+ if (!base.satisfied || !profile.satisfied) return yield* stale();
1998
+
1999
+ const replacement = plan.replacement,
2000
+ now = yield* freshNow;
2001
+
2002
+ if (
2003
+ DateTime.toEpochMillis(replacement.inspection.session.expiresAt) >
2004
+ DateTime.toEpochMillis(stored.intent.sourceAbsoluteExpiresAt) ||
2005
+ DateTime.toEpochMillis(now) <
2006
+ DateTime.toEpochMillis(replacement.inspection.session.issuedAt) ||
2007
+ DateTime.toEpochMillis(now) >=
2008
+ Math.min(
2009
+ DateTime.toEpochMillis(replacement.inspection.session.expiresAt),
2010
+ DateTime.toEpochMillis(stored.intent.expiresAt),
2011
+ )
2012
+ )
2013
+ return yield* SessionStepUpInvalid.make({});
2014
+ let rotation: any;
2015
+
2016
+ if (replacement._tag === "Stateful") {
2017
+ if (mapping.source.kind !== "Stateful") return yield* SessionStepUpInvalid.make({});
2018
+
2019
+ const s = mapping.source.session,
2020
+ row = stored.source.row;
2021
+
2022
+ if (
2023
+ row[s.digest] !== replacement.expectedDigest ||
2024
+ row[s.version] !== replacement.expectedRowVersion
2025
+ )
2026
+ return yield* SessionConflict.make({});
2027
+
2028
+ const version = yield* allocate(
2029
+ options.mode,
2030
+ s.allocateVersion,
2031
+ s.allocateVersionSync,
2032
+ );
2033
+
2034
+ const next: StatefulSessionRecord<Claims> = {
2035
+ ...replacement.inspection.session,
2036
+ provenance: replacement.inspection.provenance,
2037
+ credentialVersion: replacement.inspection.credentialVersion,
2038
+ digest: replacement.nextDigest,
2039
+ version,
2040
+ };
2041
+
2042
+ rotation = {
2043
+ ...s.encodeRotation(next),
2044
+ ...updateValues<any>([
2045
+ [s.version, version],
2046
+ [s.digest, next.digest],
2047
+ [s.credentialVersion, next.credentialVersion],
2048
+ [s.authenticatedAt, s.encodeInstant(next.assurance.authenticatedAt)],
2049
+ [s.issuedAt, s.encodeInstant(next.issuedAt)],
2050
+ [s.expiresAt, s.encodeInstant(next.expiresAt)],
2051
+ [s.absoluteExpiresAt, s.encodeInstant(next.absoluteExpiresAt)],
2052
+ ]),
2053
+ };
2054
+ }
2055
+
2056
+ const completionVersion = yield* allocate(
2057
+ options.mode,
2058
+ m.allocateVersion,
2059
+ m.allocateVersionSync,
2060
+ );
2061
+
2062
+ if (completionVersion === stored.intent.version) return yield* unavailable();
2063
+
2064
+ const completionSnapshot = yield* encodeStepUpIntent({
2065
+ ...stored.intent,
2066
+ version: completionVersion,
2067
+ });
2068
+
2069
+ const receipt = prepare(replacement.inspection, journal);
2070
+
2071
+ if (replacement._tag === "Stateful" && mapping.source.kind === "Stateful") {
2072
+ const s = mapping.source.session,
2073
+ sc = sessionColumns(mapping.source);
2074
+
2075
+ yield* tx
2076
+ .update(s.table)
2077
+ .set(rotation)
2078
+ .where(
2079
+ and(
2080
+ eq(sc.sessionId, stored.source.nativeSessionId),
2081
+ eq(sc.digest, replacement.expectedDigest),
2082
+ eq(sc.version, replacement.expectedRowVersion),
2083
+ ),
2084
+ );
2085
+
2086
+ const written = (yield* tx
2087
+ .select()
2088
+ .from(s.table)
2089
+ .where(eq(sc.sessionId, stored.source.nativeSessionId))
2090
+ .limit(1))[0];
2091
+
2092
+ if (written === undefined || written[s.version] !== rotation[s.version])
2093
+ return yield* unavailable();
2094
+ const decoded = yield* s.decode(written);
2095
+
2096
+ if (
2097
+ decoded.version !== rotation[s.version] ||
2098
+ !stepUpRotationMatches(plan, decoded)
2099
+ )
2100
+ return yield* unavailable();
2101
+ } else if (
2102
+ replacement._tag === "StateAssistedSigned" &&
2103
+ mapping.source.kind === "StateAssistedSigned"
2104
+ ) {
2105
+ const t = mapping.source.tombstone;
2106
+
2107
+ const owner = column(t.table, t.subjectId),
2108
+ id = column(t.table, t.sessionId),
2109
+ expiry = column(t.table, t.absoluteExpiresAt);
2110
+
2111
+ yield* tx
2112
+ .delete(t.table)
2113
+ .where(
2114
+ and(
2115
+ eq(owner, stored.nativeSubjectId),
2116
+ eq(id, stored.source.nativeSessionId),
2117
+ lte(expiry, t.encodeInstant(yield* freshNow)),
2118
+ ),
2119
+ );
2120
+
2121
+ const values = t.encodeInsert({
2122
+ subjectId: stored.nativeSubjectId,
2123
+ sessionId: stored.source.nativeSessionId,
2124
+ absoluteExpiresAt: replacement.tombstoneUntil,
2125
+ });
2126
+
2127
+ yield* tx.insert(t.table).values(values).pipe(normalizeMutation(mapping));
2128
+
2129
+ const inserted = (yield* tx
2130
+ .select()
2131
+ .from(t.table)
2132
+ .where(
2133
+ and(
2134
+ eq(owner, stored.nativeSubjectId),
2135
+ eq(id, stored.source.nativeSessionId),
2136
+ eq(expiry, t.encodeInstant(replacement.tombstoneUntil)),
2137
+ ),
2138
+ )
2139
+ .limit(1))[0];
2140
+
2141
+ if (inserted === undefined) return yield* unavailable();
2142
+ }
2143
+ yield* tx
2144
+ .update(m.table)
2145
+ .set(
2146
+ updateValues<any>([
2147
+ [m.consumed, true],
2148
+ [m.version, completionVersion],
2149
+ [m.snapshot, completionSnapshot],
2150
+ ]),
2151
+ )
2152
+ .where(
2153
+ and(
2154
+ eq(c.digest, stored.intent.digest),
2155
+ eq(c.version, stored.intent.version),
2156
+ eq(c.snapshot, stored.row[m.snapshot]),
2157
+ eq(c.consumed, false),
2158
+ eq(c.failedAttempts, stored.row[m.failedAttempts]),
2159
+ ),
2160
+ );
2161
+ const completed = yield* stepUpLocator(mapping, stored.intent.digest, false);
2162
+
2163
+ if (
2164
+ completed === undefined ||
2165
+ !completed[m.consumed] ||
2166
+ completed[m.version] !== completionVersion ||
2167
+ completed[m.snapshot] !== completionSnapshot
2168
+ )
2169
+ return yield* unavailable();
2170
+ const commitNow = yield* freshNow;
2171
+
2172
+ if (
2173
+ !stepUpIntentLive(stored.intent, mapping.source.kind, commitNow) ||
2174
+ DateTime.toEpochMillis(commitNow) >=
2175
+ DateTime.toEpochMillis(replacement.inspection.session.expiresAt) ||
2176
+ !(yield* assessAuthentication(plan.evidence, current.requirement).pipe(
2177
+ Effect.mapError(() => stale()),
2178
+ )).satisfied ||
2179
+ !(yield* assessAuthentication(plan.evidence, stored.intent.requirement).pipe(
2180
+ Effect.mapError(() => stale()),
2181
+ )).satisfied
2182
+ )
2183
+ return yield* stale();
2184
+
2185
+ return receipt;
2186
+ }),
2187
+ ),
2188
+ };
2189
+
2190
+ return {
2191
+ create: (input, now, prepare) =>
2192
+ service.create(input, now, prepare).pipe(Effect.provideService(LifecycleHooks, hooks)),
2193
+ context: (input) =>
2194
+ service.context(input).pipe(Effect.provideService(LifecycleHooks, hooks)),
2195
+ read: (input) => service.read(input).pipe(Effect.provideService(LifecycleHooks, hooks)),
2196
+ reject: (input, prepare) =>
2197
+ service.reject(input, prepare).pipe(Effect.provideService(LifecycleHooks, hooks)),
2198
+ complete: (plan, prepare) =>
2199
+ service.complete(plan, prepare).pipe(Effect.provideService(LifecycleHooks, hooks)),
2200
+ } satisfies SessionStepUpPersistence<Claims>;
2201
+ });
2202
+
2203
+ return {
2204
+ makeSqlAuthenticationAuthority,
2205
+ makeSqlPendingAuthentication,
2206
+ makeSqlStatefulSessions,
2207
+ makeSqlSignedValidity,
2208
+ makeSqlSessionStepUp,
2209
+ };
2210
+ };