@yielded/auth-persistence-drizzle 0.1.0-beta.25 → 0.1.0-beta.27

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 (383) hide show
  1. package/README.md +4 -2
  2. package/dist/D1.d.mts +495 -1151
  3. package/dist/D1.mjs +41 -19
  4. package/dist/D1.mjs.map +1 -1
  5. package/dist/Libsql.d.mts +385 -1996
  6. package/dist/Libsql.mjs +43 -47
  7. package/dist/Libsql.mjs.map +1 -1
  8. package/dist/Mysql2.d.mts +397 -1743
  9. package/dist/Mysql2.mjs +35 -41
  10. package/dist/Mysql2.mjs.map +1 -1
  11. package/dist/Pglite.d.mts +397 -1743
  12. package/dist/Pglite.mjs +42 -31
  13. package/dist/Pglite.mjs.map +1 -1
  14. package/dist/Postgres.d.mts +397 -1743
  15. package/dist/Postgres.mjs +42 -31
  16. package/dist/Postgres.mjs.map +1 -1
  17. package/dist/SqliteBun.d.mts +385 -1992
  18. package/dist/SqliteBun.mjs +43 -43
  19. package/dist/SqliteBun.mjs.map +1 -1
  20. package/dist/SqliteDo.d.mts +385 -1991
  21. package/dist/SqliteDo.mjs +42 -41
  22. package/dist/SqliteDo.mjs.map +1 -1
  23. package/dist/SqliteNode.d.mts +385 -1992
  24. package/dist/SqliteNode.mjs +43 -43
  25. package/dist/SqliteNode.mjs.map +1 -1
  26. package/dist/SqliteWasm.d.mts +385 -1992
  27. package/dist/SqliteWasm.mjs +43 -43
  28. package/dist/SqliteWasm.mjs.map +1 -1
  29. package/dist/drizzle/d1-identity.mjs +2 -2
  30. package/dist/drizzle/d1-identity.mjs.map +1 -1
  31. package/dist/drizzle/driver-types.d.mts +15 -0
  32. package/dist/drizzle/email-drivers.mjs +32 -0
  33. package/dist/drizzle/email-drivers.mjs.map +1 -0
  34. package/dist/drizzle/email-model.d.mts +9 -13
  35. package/dist/drizzle/email-target.d.mts +10 -11
  36. package/dist/drizzle/email-target.mjs +33 -70
  37. package/dist/drizzle/email-target.mjs.map +1 -1
  38. package/dist/drizzle/model.d.mts +1 -10
  39. package/dist/drizzle/model.mjs +1 -2
  40. package/dist/drizzle/model.mjs.map +1 -1
  41. package/dist/drizzle/mysql-identity.mjs +3 -6
  42. package/dist/drizzle/mysql-identity.mjs.map +1 -1
  43. package/dist/drizzle/native-clock.d.mts +9 -0
  44. package/dist/drizzle/native-clock.mjs +18 -0
  45. package/dist/drizzle/native-clock.mjs.map +1 -0
  46. package/dist/drizzle/native-database.d.mts +46 -0
  47. package/dist/drizzle/native-database.mjs +6 -5
  48. package/dist/drizzle/native-database.mjs.map +1 -1
  49. package/dist/drizzle/native-sql-table.mjs +13 -5
  50. package/dist/drizzle/native-sql-table.mjs.map +1 -1
  51. package/dist/drizzle/native-target.d.mts +6 -0
  52. package/dist/drizzle/native-target.mjs +44 -0
  53. package/dist/drizzle/native-target.mjs.map +1 -0
  54. package/dist/drizzle/oauth-connected-drivers.mjs +4 -56
  55. package/dist/drizzle/oauth-connected-drivers.mjs.map +1 -1
  56. package/dist/drizzle/oauth-connected-model.d.mts +5 -22
  57. package/dist/drizzle/oauth-connected-model.mjs +1 -7
  58. package/dist/drizzle/oauth-connected-reference.d.mts +5 -7
  59. package/dist/drizzle/oauth-connected-reference.mjs +11 -3
  60. package/dist/drizzle/oauth-connected-reference.mjs.map +1 -1
  61. package/dist/drizzle/oauth-drivers.mjs +1 -2
  62. package/dist/drizzle/oauth-drivers.mjs.map +1 -1
  63. package/dist/drizzle/oauth-model.d.mts +11 -37
  64. package/dist/drizzle/oauth-model.mjs +3 -4
  65. package/dist/drizzle/oauth-model.mjs.map +1 -1
  66. package/dist/drizzle/oauth-proxy.d.mts +1 -1
  67. package/dist/drizzle/oauth-proxy.mjs +1 -1
  68. package/dist/drizzle/oauth-target.d.mts +9 -6
  69. package/dist/drizzle/oauth-target.mjs +47 -15
  70. package/dist/drizzle/oauth-target.mjs.map +1 -1
  71. package/dist/drizzle/passkey/target.d.mts +14 -0
  72. package/dist/drizzle/passkey/target.mjs +52 -0
  73. package/dist/drizzle/passkey/target.mjs.map +1 -0
  74. package/dist/drizzle/passkey/write-target.d.mts +11 -0
  75. package/dist/drizzle/passkey/write-target.mjs +28 -0
  76. package/dist/drizzle/passkey/write-target.mjs.map +1 -0
  77. package/dist/drizzle/passkey-drivers.mjs +2 -4
  78. package/dist/drizzle/passkey-drivers.mjs.map +1 -1
  79. package/dist/drizzle/passkey-model.d.mts +5 -26
  80. package/dist/drizzle/passkey-model.mjs +2 -8
  81. package/dist/drizzle/passkey-registration-ceremony-model.d.mts +3 -6
  82. package/dist/drizzle/passkey-write-drivers.mjs +2 -3
  83. package/dist/drizzle/passkey-write-drivers.mjs.map +1 -1
  84. package/dist/drizzle/passkey-write-model.d.mts +7 -9
  85. package/dist/drizzle/passkey-write-model.mjs +1 -3
  86. package/dist/drizzle/passkey-write-model.mjs.map +1 -1
  87. package/dist/drizzle/password-drivers.mjs +31 -0
  88. package/dist/drizzle/password-drivers.mjs.map +1 -0
  89. package/dist/drizzle/password-model.d.mts +7 -10
  90. package/dist/drizzle/password-target.d.mts +7 -8
  91. package/dist/drizzle/password-target.mjs +23 -68
  92. package/dist/drizzle/password-target.mjs.map +1 -1
  93. package/dist/drizzle/pg-identity.mjs +3 -6
  94. package/dist/drizzle/pg-identity.mjs.map +1 -1
  95. package/dist/drizzle/phone-drivers.mjs +24 -0
  96. package/dist/drizzle/phone-drivers.mjs.map +1 -0
  97. package/dist/drizzle/phone-model.d.mts +13 -104
  98. package/dist/drizzle/phone-model.mjs +5 -33
  99. package/dist/drizzle/phone-model.mjs.map +1 -1
  100. package/dist/drizzle/phone-target.mjs +22 -171
  101. package/dist/drizzle/phone-target.mjs.map +1 -1
  102. package/dist/drizzle/proof-drivers.mjs +24 -0
  103. package/dist/drizzle/proof-drivers.mjs.map +1 -0
  104. package/dist/drizzle/proof-model.d.mts +7 -15
  105. package/dist/drizzle/proof-target.d.mts +5 -5
  106. package/dist/drizzle/proof-target.mjs +14 -28
  107. package/dist/drizzle/proof-target.mjs.map +1 -1
  108. package/dist/drizzle/session-drivers.mjs +97 -0
  109. package/dist/drizzle/session-drivers.mjs.map +1 -0
  110. package/dist/drizzle/session-model.d.mts +11 -22
  111. package/dist/drizzle/sqlite-do-database.d.mts +3 -3
  112. package/dist/drizzle/sqlite-do-database.mjs +6 -1
  113. package/dist/drizzle/sqlite-do-database.mjs.map +1 -1
  114. package/dist/drizzle/sqlite-identity.mjs +3 -5
  115. package/dist/drizzle/sqlite-identity.mjs.map +1 -1
  116. package/dist/drizzle/step-up-model.d.mts +2 -5
  117. package/dist/drizzle/storage-validation.mjs +2 -1
  118. package/dist/drizzle/storage-validation.mjs.map +1 -1
  119. package/dist/drizzle/totp-model.d.mts +7 -74
  120. package/dist/drizzle/totp-model.mjs +1 -5
  121. package/dist/drizzle/totp-model.mjs.map +1 -1
  122. package/dist/drizzle/totp-target.mjs +39 -75
  123. package/dist/drizzle/totp-target.mjs.map +1 -1
  124. package/dist/drizzle/transaction-handle.mjs +13 -0
  125. package/dist/drizzle/transaction-handle.mjs.map +1 -0
  126. package/dist/drizzle/transaction-mysql.mjs +43 -12
  127. package/dist/drizzle/transaction-mysql.mjs.map +1 -1
  128. package/dist/index.d.mts +17 -22
  129. package/dist/index.mjs +10 -16
  130. package/dist/internal/drizzle-postgres.mjs +4 -6
  131. package/dist/internal/drizzle-postgres.mjs.map +1 -1
  132. package/dist/internal/drizzle-sqlite.mjs +4 -6
  133. package/dist/internal/drizzle-sqlite.mjs.map +1 -1
  134. package/package.json +1 -1
  135. package/src/D1.ts +77 -74
  136. package/src/Libsql.ts +65 -105
  137. package/src/Mysql2.ts +52 -87
  138. package/src/Pglite.ts +74 -81
  139. package/src/Postgres.ts +74 -81
  140. package/src/SqliteBun.ts +65 -113
  141. package/src/SqliteDo.ts +58 -106
  142. package/src/SqliteNode.ts +65 -113
  143. package/src/SqliteWasm.ts +65 -113
  144. package/src/drizzle/d1-identity.ts +2 -2
  145. package/src/drizzle/driver-types.ts +26 -0
  146. package/src/drizzle/email-drivers.ts +320 -0
  147. package/src/drizzle/email-model.ts +24 -78
  148. package/src/drizzle/email-target.ts +118 -211
  149. package/src/drizzle/model.ts +0 -2
  150. package/src/drizzle/mysql-identity.ts +4 -24
  151. package/src/drizzle/native-clock.ts +21 -0
  152. package/src/drizzle/native-database.ts +59 -18
  153. package/src/drizzle/native-sql-table.ts +104 -87
  154. package/src/drizzle/native-target.ts +120 -0
  155. package/src/drizzle/oauth-connected-drivers.ts +24 -233
  156. package/src/drizzle/oauth-connected-model.ts +3 -93
  157. package/src/drizzle/oauth-connected-reference.ts +16 -25
  158. package/src/drizzle/oauth-drivers.ts +58 -99
  159. package/src/drizzle/oauth-model.ts +27 -132
  160. package/src/drizzle/oauth-target.ts +250 -33
  161. package/src/drizzle/passkey/target.ts +205 -0
  162. package/src/drizzle/passkey/write-target.ts +91 -0
  163. package/src/drizzle/passkey-drivers.ts +35 -133
  164. package/src/drizzle/passkey-model.ts +3 -81
  165. package/src/drizzle/passkey-registration-ceremony-model.ts +5 -25
  166. package/src/drizzle/passkey-write-drivers.ts +32 -102
  167. package/src/drizzle/passkey-write-model.ts +10 -44
  168. package/src/drizzle/password-drivers.ts +286 -0
  169. package/src/drizzle/password-model.ts +10 -44
  170. package/src/drizzle/password-target.ts +85 -239
  171. package/src/drizzle/pg-identity.ts +4 -19
  172. package/src/drizzle/phone-drivers.ts +142 -0
  173. package/src/drizzle/phone-model.ts +22 -162
  174. package/src/drizzle/phone-target.ts +51 -493
  175. package/src/drizzle/proof-drivers.ts +123 -0
  176. package/src/drizzle/proof-model.ts +12 -123
  177. package/src/drizzle/proof-target.ts +38 -102
  178. package/src/drizzle/session-drivers.ts +696 -0
  179. package/src/drizzle/session-model.ts +54 -118
  180. package/src/drizzle/sqlite-do-database.ts +8 -0
  181. package/src/drizzle/sqlite-identity.ts +4 -22
  182. package/src/drizzle/step-up-model.ts +13 -37
  183. package/src/drizzle/storage-validation.ts +2 -1
  184. package/src/drizzle/totp-model.ts +20 -77
  185. package/src/drizzle/totp-target.ts +121 -261
  186. package/src/drizzle/transaction-handle.ts +41 -0
  187. package/src/drizzle/transaction-mysql.ts +91 -26
  188. package/src/index.ts +7 -122
  189. package/src/internal/drizzle-postgres.ts +4 -7
  190. package/src/internal/drizzle-sqlite.ts +4 -7
  191. package/dist/drizzle/crypto.mjs +0 -11
  192. package/dist/drizzle/crypto.mjs.map +0 -1
  193. package/dist/drizzle/d1-emails.d.mts +0 -62
  194. package/dist/drizzle/d1-emails.mjs +0 -626
  195. package/dist/drizzle/d1-emails.mjs.map +0 -1
  196. package/dist/drizzle/d1-generated-statement.mjs +0 -79
  197. package/dist/drizzle/d1-generated-statement.mjs.map +0 -1
  198. package/dist/drizzle/d1-oauth.d.mts +0 -331
  199. package/dist/drizzle/d1-oauth.mjs +0 -79
  200. package/dist/drizzle/d1-oauth.mjs.map +0 -1
  201. package/dist/drizzle/d1-passkey.d.mts +0 -37
  202. package/dist/drizzle/d1-passkey.mjs +0 -98
  203. package/dist/drizzle/d1-passkey.mjs.map +0 -1
  204. package/dist/drizzle/d1-password-prepared.d.mts +0 -25
  205. package/dist/drizzle/d1-password-prepared.mjs +0 -473
  206. package/dist/drizzle/d1-password-prepared.mjs.map +0 -1
  207. package/dist/drizzle/d1-passwords.d.mts +0 -70
  208. package/dist/drizzle/d1-passwords.mjs +0 -808
  209. package/dist/drizzle/d1-passwords.mjs.map +0 -1
  210. package/dist/drizzle/d1-phone.d.mts +0 -23
  211. package/dist/drizzle/d1-phone.mjs +0 -33
  212. package/dist/drizzle/d1-phone.mjs.map +0 -1
  213. package/dist/drizzle/d1-planning.d.mts +0 -11
  214. package/dist/drizzle/d1-planning.mjs +0 -43
  215. package/dist/drizzle/d1-planning.mjs.map +0 -1
  216. package/dist/drizzle/d1-proofs.d.mts +0 -80
  217. package/dist/drizzle/d1-proofs.mjs +0 -842
  218. package/dist/drizzle/d1-proofs.mjs.map +0 -1
  219. package/dist/drizzle/d1-sessions.d.mts +0 -68
  220. package/dist/drizzle/d1-sessions.mjs +0 -987
  221. package/dist/drizzle/d1-sessions.mjs.map +0 -1
  222. package/dist/drizzle/d1-totp.d.mts +0 -23
  223. package/dist/drizzle/d1-totp.mjs +0 -30
  224. package/dist/drizzle/d1-totp.mjs.map +0 -1
  225. package/dist/drizzle/email-registration.d.mts +0 -33
  226. package/dist/drizzle/email-registration.mjs +0 -281
  227. package/dist/drizzle/email-registration.mjs.map +0 -1
  228. package/dist/drizzle/email-sql.d.mts +0 -1
  229. package/dist/drizzle/email-sql.mjs +0 -9
  230. package/dist/drizzle/email-sql.mjs.map +0 -1
  231. package/dist/drizzle/mysql-emails.d.mts +0 -71
  232. package/dist/drizzle/mysql-emails.mjs +0 -43
  233. package/dist/drizzle/mysql-emails.mjs.map +0 -1
  234. package/dist/drizzle/mysql-password-prepared.d.mts +0 -30
  235. package/dist/drizzle/mysql-password-prepared.mjs +0 -21
  236. package/dist/drizzle/mysql-password-prepared.mjs.map +0 -1
  237. package/dist/drizzle/mysql-passwords.d.mts +0 -71
  238. package/dist/drizzle/mysql-passwords.mjs +0 -43
  239. package/dist/drizzle/mysql-passwords.mjs.map +0 -1
  240. package/dist/drizzle/mysql-proofs.d.mts +0 -75
  241. package/dist/drizzle/mysql-proofs.mjs +0 -28
  242. package/dist/drizzle/mysql-proofs.mjs.map +0 -1
  243. package/dist/drizzle/mysql-sessions.d.mts +0 -92
  244. package/dist/drizzle/mysql-sessions.mjs +0 -58
  245. package/dist/drizzle/mysql-sessions.mjs.map +0 -1
  246. package/dist/drizzle/oauth-connected-model.mjs.map +0 -1
  247. package/dist/drizzle/oauth-connected-target.d.mts +0 -6
  248. package/dist/drizzle/oauth-connected-target.mjs +0 -12
  249. package/dist/drizzle/oauth-connected-target.mjs.map +0 -1
  250. package/dist/drizzle/oauth-kernel.mjs +0 -11
  251. package/dist/drizzle/oauth-kernel.mjs.map +0 -1
  252. package/dist/drizzle/oauth-mysql.mjs +0 -8
  253. package/dist/drizzle/oauth-mysql.mjs.map +0 -1
  254. package/dist/drizzle/passkey-kernel.mjs +0 -8
  255. package/dist/drizzle/passkey-kernel.mjs.map +0 -1
  256. package/dist/drizzle/passkey-model.mjs.map +0 -1
  257. package/dist/drizzle/passkey-registration-ceremony-model.mjs +0 -2
  258. package/dist/drizzle/passkey-state.mjs +0 -7
  259. package/dist/drizzle/passkey-state.mjs.map +0 -1
  260. package/dist/drizzle/passkey-target.d.mts +0 -2
  261. package/dist/drizzle/passkey-target.mjs +0 -7
  262. package/dist/drizzle/passkey-target.mjs.map +0 -1
  263. package/dist/drizzle/passkey-write-target.d.mts +0 -5
  264. package/dist/drizzle/passkey-write-target.mjs +0 -7
  265. package/dist/drizzle/passkey-write-target.mjs.map +0 -1
  266. package/dist/drizzle/password-prepared-model.d.mts +0 -92
  267. package/dist/drizzle/password-prepared-model.mjs +0 -11
  268. package/dist/drizzle/password-prepared-model.mjs.map +0 -1
  269. package/dist/drizzle/password-prepared-sql.mjs +0 -395
  270. package/dist/drizzle/password-prepared-sql.mjs.map +0 -1
  271. package/dist/drizzle/password-prepared-state.mjs +0 -104
  272. package/dist/drizzle/password-prepared-state.mjs.map +0 -1
  273. package/dist/drizzle/password-prepared-target.d.mts +0 -13
  274. package/dist/drizzle/password-prepared-target.mjs +0 -62
  275. package/dist/drizzle/password-prepared-target.mjs.map +0 -1
  276. package/dist/drizzle/password-registration.d.mts +0 -7
  277. package/dist/drizzle/password-registration.mjs +0 -137
  278. package/dist/drizzle/password-registration.mjs.map +0 -1
  279. package/dist/drizzle/password-sql.d.mts +0 -1
  280. package/dist/drizzle/password-sql.mjs +0 -10
  281. package/dist/drizzle/password-sql.mjs.map +0 -1
  282. package/dist/drizzle/pg-emails.d.mts +0 -72
  283. package/dist/drizzle/pg-emails.mjs +0 -43
  284. package/dist/drizzle/pg-emails.mjs.map +0 -1
  285. package/dist/drizzle/pg-password-prepared.d.mts +0 -31
  286. package/dist/drizzle/pg-password-prepared.mjs +0 -21
  287. package/dist/drizzle/pg-password-prepared.mjs.map +0 -1
  288. package/dist/drizzle/pg-passwords.d.mts +0 -72
  289. package/dist/drizzle/pg-passwords.mjs +0 -43
  290. package/dist/drizzle/pg-passwords.mjs.map +0 -1
  291. package/dist/drizzle/pg-proofs.d.mts +0 -76
  292. package/dist/drizzle/pg-proofs.mjs +0 -27
  293. package/dist/drizzle/pg-proofs.mjs.map +0 -1
  294. package/dist/drizzle/pg-sessions.d.mts +0 -93
  295. package/dist/drizzle/pg-sessions.mjs +0 -58
  296. package/dist/drizzle/pg-sessions.mjs.map +0 -1
  297. package/dist/drizzle/phone-state.mjs +0 -14
  298. package/dist/drizzle/phone-state.mjs.map +0 -1
  299. package/dist/drizzle/phone-target.d.mts +0 -15
  300. package/dist/drizzle/proof-sql.d.mts +0 -1
  301. package/dist/drizzle/proof-sql.mjs +0 -8
  302. package/dist/drizzle/proof-sql.mjs.map +0 -1
  303. package/dist/drizzle/query-operations.mjs +0 -31
  304. package/dist/drizzle/query-operations.mjs.map +0 -1
  305. package/dist/drizzle/session-sql.d.mts +0 -1
  306. package/dist/drizzle/session-sql.mjs +0 -9
  307. package/dist/drizzle/session-sql.mjs.map +0 -1
  308. package/dist/drizzle/session-target.d.mts +0 -30
  309. package/dist/drizzle/session-target.mjs +0 -49
  310. package/dist/drizzle/session-target.mjs.map +0 -1
  311. package/dist/drizzle/sqlite-emails.mjs +0 -50
  312. package/dist/drizzle/sqlite-emails.mjs.map +0 -1
  313. package/dist/drizzle/sqlite-password-prepared.mjs +0 -26
  314. package/dist/drizzle/sqlite-password-prepared.mjs.map +0 -1
  315. package/dist/drizzle/sqlite-passwords.mjs +0 -54
  316. package/dist/drizzle/sqlite-passwords.mjs.map +0 -1
  317. package/dist/drizzle/sqlite-proofs.mjs +0 -34
  318. package/dist/drizzle/sqlite-proofs.mjs.map +0 -1
  319. package/dist/drizzle/sqlite-sessions.mjs +0 -79
  320. package/dist/drizzle/sqlite-sessions.mjs.map +0 -1
  321. package/dist/drizzle/standalone-guard.d.mts +0 -2
  322. package/dist/drizzle/standalone-guard.mjs +0 -21
  323. package/dist/drizzle/standalone-guard.mjs.map +0 -1
  324. package/dist/drizzle/totp-state.d.mts +0 -37
  325. package/dist/drizzle/totp-state.mjs +0 -287
  326. package/dist/drizzle/totp-state.mjs.map +0 -1
  327. package/dist/drizzle/totp-target.d.mts +0 -15
  328. package/dist/drizzle/transaction-execution.d.mts +0 -2
  329. package/dist/drizzle/transaction-execution.mjs +0 -11
  330. package/dist/drizzle/transaction-execution.mjs.map +0 -1
  331. package/dist/drizzle/transaction-owner.d.mts +0 -1
  332. package/dist/drizzle/transaction-owner.mjs +0 -8
  333. package/dist/drizzle/transaction-owner.mjs.map +0 -1
  334. package/src/drizzle/crypto.ts +0 -14
  335. package/src/drizzle/d1-emails.ts +0 -1945
  336. package/src/drizzle/d1-generated-statement.ts +0 -129
  337. package/src/drizzle/d1-oauth.ts +0 -360
  338. package/src/drizzle/d1-passkey.ts +0 -383
  339. package/src/drizzle/d1-password-prepared.ts +0 -1266
  340. package/src/drizzle/d1-passwords.ts +0 -2061
  341. package/src/drizzle/d1-phone.ts +0 -103
  342. package/src/drizzle/d1-planning.ts +0 -104
  343. package/src/drizzle/d1-proofs.ts +0 -2354
  344. package/src/drizzle/d1-sessions.ts +0 -2940
  345. package/src/drizzle/d1-totp.ts +0 -88
  346. package/src/drizzle/email-registration.ts +0 -850
  347. package/src/drizzle/email-sql.ts +0 -25
  348. package/src/drizzle/mysql-emails.ts +0 -343
  349. package/src/drizzle/mysql-password-prepared.ts +0 -198
  350. package/src/drizzle/mysql-passwords.ts +0 -343
  351. package/src/drizzle/mysql-proofs.ts +0 -196
  352. package/src/drizzle/mysql-sessions.ts +0 -780
  353. package/src/drizzle/oauth-connected-target.ts +0 -21
  354. package/src/drizzle/oauth-kernel.ts +0 -10
  355. package/src/drizzle/oauth-mysql.ts +0 -10
  356. package/src/drizzle/passkey-kernel.ts +0 -4
  357. package/src/drizzle/passkey-state.ts +0 -3
  358. package/src/drizzle/passkey-target.ts +0 -23
  359. package/src/drizzle/passkey-write-target.ts +0 -9
  360. package/src/drizzle/password-prepared-model.ts +0 -241
  361. package/src/drizzle/password-prepared-sql.ts +0 -1202
  362. package/src/drizzle/password-prepared-state.ts +0 -231
  363. package/src/drizzle/password-prepared-target.ts +0 -169
  364. package/src/drizzle/password-registration.ts +0 -369
  365. package/src/drizzle/password-sql.ts +0 -16
  366. package/src/drizzle/pg-emails.ts +0 -343
  367. package/src/drizzle/pg-password-prepared.ts +0 -199
  368. package/src/drizzle/pg-passwords.ts +0 -342
  369. package/src/drizzle/pg-proofs.ts +0 -206
  370. package/src/drizzle/pg-sessions.ts +0 -789
  371. package/src/drizzle/phone-state.ts +0 -21
  372. package/src/drizzle/proof-sql.ts +0 -10
  373. package/src/drizzle/query-operations.ts +0 -49
  374. package/src/drizzle/session-sql.ts +0 -15
  375. package/src/drizzle/session-target.ts +0 -337
  376. package/src/drizzle/sqlite-emails.ts +0 -393
  377. package/src/drizzle/sqlite-password-prepared.ts +0 -230
  378. package/src/drizzle/sqlite-passwords.ts +0 -497
  379. package/src/drizzle/sqlite-proofs.ts +0 -229
  380. package/src/drizzle/sqlite-sessions.ts +0 -1013
  381. package/src/drizzle/totp-state.ts +0 -525
  382. package/src/drizzle/transaction-execution.ts +0 -13
  383. package/src/drizzle/transaction-owner.ts +0 -11
@@ -1,2940 +0,0 @@
1
- import {
2
- NativeDatabase,
3
- decodeStepUpIntent,
4
- encodeStepUpIntent,
5
- stepUpIntentLive,
6
- validateStepUpPlan,
7
- } from "@yielded/auth-persistence/Adapter";
8
- import {
9
- coordinateCommit,
10
- CurrentCommitJournal,
11
- hasCommitScope,
12
- type PreparedCommit,
13
- LifecycleHooks,
14
- type HookConfigurationError,
15
- } from "@yielded/auth/Hooks";
16
- import { reportPersistenceFailure } from "@yielded/auth/Persistence";
17
- import type { SubjectId, TokenDigest } from "@yielded/auth/Schema";
18
- import {
19
- assessAuthentication,
20
- AuthenticationCapture,
21
- snapshotSessionAuthenticationProvenance,
22
- AuthenticationAuthority,
23
- PendingAuthenticationInvalid,
24
- SessionConflict,
25
- type SessionConfigurationError,
26
- SessionInvalid,
27
- SessionStepUpInvalid,
28
- SessionUnavailable,
29
- StaleAuthentication,
30
- SessionMetadata,
31
- type AuthenticationEvidence,
32
- type AuthenticationRequirement,
33
- type AuthenticationRevision,
34
- type PendingConsumption,
35
- type SecurityRevision,
36
- pendingAuthenticationContext,
37
- snapshotPendingAuthenticationContext,
38
- type PendingAuthentication,
39
- type PendingAuthenticationRecord,
40
- type PendingAuthenticationState,
41
- type PrepareSessionCommit,
42
- type SessionRepository,
43
- type SignedSessionValidity,
44
- type StatefulSessionPersistence,
45
- type SessionStepUpIntent,
46
- type SessionStepUpCompletionPlan,
47
- type SessionStepUpPersistence,
48
- } from "@yielded/auth/Sessions";
49
- /* oxlint-disable no-explicit-any -- D1 batch planning bridges dynamic consumer Drizzle tables to Effect SQL statements. */
50
- import { and, eq, getTableColumns, gt, inArray, not, or, sql, type SQL } from "drizzle-orm";
51
- import type { EffectDrizzleQueryError } from "drizzle-orm/effect-core";
52
- import type { AnySQLiteTable } from "drizzle-orm/sqlite-core";
53
- import { Cause, Context, Data, DateTime, Effect, Schema } from "effect";
54
- import type * as SqlError from "effect/sql/SqlError";
55
- import type { Statement } from "effect/sql/Statement";
56
-
57
- import { Database as DatabaseService } from "./d1-database";
58
- import { compactD1GeneratedStatement } from "./d1-generated-statement";
59
- import {
60
- CurrentD1PlanningDatabase,
61
- makeD1Owner,
62
- type D1Owner,
63
- type D1PlanningDatabase,
64
- } from "./d1-planning";
65
- import { D1BatchStatements } from "./D1BatchStatements";
66
- import { column, PersistenceMappingError, isMappedConstraintConflict, updateValues } from "./model";
67
- import { nativeDatabase } from "./native-database";
68
- import type {
69
- D1AuthenticationAuthorityMapping,
70
- D1PendingAuthenticationMapping,
71
- D1SignedSessionValidityMapping,
72
- D1StatefulSessionMapping,
73
- } from "./session-model";
74
- import type { D1SessionStepUpMapping } from "./step-up-model";
75
- import { validateDrizzleStorage } from "./storage-validation";
76
- import type { SuppliedService } from "./SuppliedService";
77
-
78
- type Database = D1PlanningDatabase;
79
- type AnyD1StepUpMapping = D1SessionStepUpMapping<any, any, any, any, any, any, any, any>;
80
- type D1DomainError =
81
- | SessionStepUpInvalid
82
- | PendingAuthenticationInvalid
83
- | SessionConflict
84
- | SessionInvalid
85
- | SessionUnavailable
86
- | StaleAuthentication;
87
- type D1PlanError =
88
- | D1DomainError
89
- | SessionConfigurationError
90
- | PersistenceMappingError
91
- | EffectDrizzleQueryError
92
- | Schema.SchemaError;
93
- type D1AdapterFailure = PersistenceMappingError | EffectDrizzleQueryError | Schema.SchemaError;
94
- type NormalizedD1Error<E> = Extract<E, D1DomainError> | SessionUnavailable;
95
- type PreparedValue<Method extends (...args: any[]) => any> =
96
- Parameters<Method>[1] extends PrepareSessionCommit<infer Value, any> ? Value : never;
97
- interface Planned<A> {
98
- readonly receipt: A;
99
- readonly statements: ReadonlyArray<Statement<any>>;
100
- readonly isConstraintConflict?: (cause: unknown) => boolean;
101
- readonly recoverGuard?: Effect.Effect<never, D1PlanError, CurrentD1PlanningDatabase>;
102
- readonly journalGuard?: PreparedCommit<void>;
103
- }
104
- interface D1SessionBatch {
105
- readonly owner: D1Owner<SessionUnavailable>;
106
- readonly append: (planned: Planned<any>) => Effect.Effect<void, SessionUnavailable>;
107
- }
108
- class CurrentD1SessionBatch extends Context.Service<CurrentD1SessionBatch, D1SessionBatch>()(
109
- "effect-auth/drizzle/CurrentD1SessionBatch",
110
- ) {}
111
- class D1BatchFailure extends Data.TaggedError("D1BatchFailure")<{
112
- readonly cause: Cause.Cause<SqlError.SqlError>;
113
- readonly mutation?: Planned<any>;
114
- }> {}
115
-
116
- const unavailable = () => SessionUnavailable.make({});
117
- const stale = () => StaleAuthentication.make({});
118
- const invalidPending = () => PendingAuthenticationInvalid.make({});
119
- const invalidSession = () => SessionInvalid.make({});
120
-
121
- const mapFailureCause = <A, E, R, E2>(
122
- effect: Effect.Effect<A, E, R>,
123
- map: (error: E) => E2,
124
- ): Effect.Effect<A, E2, R> =>
125
- Effect.catchCause(effect, (cause) => Effect.failCause(Cause.map(cause, map)));
126
-
127
- const isD1DomainError = (error: unknown): error is D1DomainError => {
128
- const tag = typeof error === "object" && error !== null ? Reflect.get(error, "_tag") : undefined;
129
-
130
- return (
131
- tag === "SessionStepUpInvalid" ||
132
- tag === "PendingAuthenticationInvalid" ||
133
- tag === "SessionConflict" ||
134
- tag === "SessionInvalid" ||
135
- tag === "SessionUnavailable" ||
136
- tag === "StaleAuthentication"
137
- );
138
- };
139
-
140
- const normalizeD1Failure = <A, E, R>(
141
- effect: Effect.Effect<A, E, R>,
142
- isExpected: (error: E) => boolean,
143
- ): Effect.Effect<A, NormalizedD1Error<E>, R> =>
144
- reportPersistenceFailure(effect, isExpected).pipe(
145
- Effect.catchCause((cause) =>
146
- Effect.failCause(
147
- Cause.map(cause, (error): NormalizedD1Error<E> =>
148
- isD1DomainError(error) ? (error as Extract<E, D1DomainError>) : unavailable(),
149
- ),
150
- ),
151
- ),
152
- );
153
-
154
- const terminalD1 = <A, E, R>(effect: Effect.Effect<A, E, R>) =>
155
- normalizeD1Failure(effect, isD1DomainError);
156
-
157
- const containsFailure = (failure: unknown, predicate: (value: unknown) => boolean): boolean => {
158
- const seen = new Set<unknown>();
159
- const pending: Array<unknown> = [failure];
160
-
161
- for (let inspected = 0; inspected < 24 && pending.length > 0; inspected++) {
162
- const current = pending.shift();
163
-
164
- if (current === undefined || current === null || seen.has(current)) continue;
165
- seen.add(current);
166
- if (predicate(current)) return true;
167
- if (Cause.isCause(current)) {
168
- for (const reason of current.reasons) {
169
- if (Cause.isFailReason(reason)) pending.push(reason.error);
170
- else if (Cause.isDieReason(reason)) pending.push(reason.defect);
171
- }
172
- continue;
173
- }
174
- if (typeof current !== "object") continue;
175
-
176
- const wrapped = current as {
177
- readonly cause?: unknown;
178
- readonly reason?: unknown;
179
- readonly message?: unknown;
180
- };
181
-
182
- if (wrapped.cause !== undefined) pending.push(wrapped.cause);
183
- if (wrapped.reason !== undefined) pending.push(wrapped.reason);
184
- if (wrapped.message !== undefined) pending.push(wrapped.message);
185
- }
186
-
187
- return false;
188
- };
189
-
190
- const isGuardFailure = (failure: unknown): boolean =>
191
- containsFailure(
192
- failure,
193
- (value) => typeof value === "string" && value.includes("effect-auth-session-guard"),
194
- );
195
-
196
- const recoverBatchFailure = (
197
- failure: D1BatchFailure,
198
- ): Effect.Effect<never, D1PlanError | SqlError.SqlError, CurrentD1PlanningDatabase> => {
199
- const { cause, mutation } = failure;
200
- const onlyFailures = cause.reasons.length > 0 && cause.reasons.every(Cause.isFailReason);
201
-
202
- if (mutation === undefined) return Effect.failCause(cause);
203
- if (
204
- onlyFailures &&
205
- mutation.isConstraintConflict !== undefined &&
206
- cause.reasons.every(
207
- (reason) =>
208
- Cause.isFailReason(reason) &&
209
- isMappedConstraintConflict(mutation.isConstraintConflict!, reason.error),
210
- )
211
- )
212
- return Effect.fail(SessionConflict.make({}));
213
- if (
214
- onlyFailures &&
215
- mutation.recoverGuard !== undefined &&
216
- cause.reasons.every((reason) => Cause.isFailReason(reason) && isGuardFailure(reason.error))
217
- )
218
- return mutation.recoverGuard;
219
-
220
- return Effect.failCause(cause);
221
- };
222
-
223
- const splitD1BatchFailure = <E>(cause: Cause.Cause<E | D1BatchFailure>) => {
224
- const marker = cause.reasons.find(
225
- (reason) => Cause.isFailReason(reason) && reason.error instanceof D1BatchFailure,
226
- );
227
-
228
- if (marker === undefined || !Cause.isFailReason(marker)) return undefined;
229
- const remaining = Cause.fromReasons(cause.reasons.filter((reason) => reason !== marker));
230
-
231
- return {
232
- failure: marker.error as D1BatchFailure,
233
- // The private marker is removed by identity; every retained reason belongs to E.
234
- remaining: remaining as Cause.Cause<E>,
235
- };
236
- };
237
-
238
- const recoverD1BatchCause = <E>(
239
- cause: Cause.Cause<E | D1BatchFailure>,
240
- ): Effect.Effect<never, E | D1PlanError | SqlError.SqlError, CurrentD1PlanningDatabase> => {
241
- const split = splitD1BatchFailure(cause);
242
-
243
- if (split === undefined) {
244
- // No private marker remains, so this is the original E Cause.
245
- return Effect.failCause(cause as Cause.Cause<E>);
246
- }
247
-
248
- return recoverBatchFailure(split.failure).pipe(
249
- Effect.catchCause((recovered) => Effect.failCause(Cause.combine(recovered, split.remaining))),
250
- );
251
- };
252
-
253
- const recoverCoordinatedD1BatchCause = <E>(
254
- cause: Cause.Cause<E | D1BatchFailure>,
255
- ): Effect.Effect<never, E | D1DomainError, CurrentD1PlanningDatabase> => {
256
- const split = splitD1BatchFailure(cause);
257
-
258
- if (split === undefined) {
259
- // No private marker remains, so this is the original E Cause.
260
- return Effect.failCause(cause as Cause.Cause<E>);
261
- }
262
-
263
- return terminalD1(recoverBatchFailure(split.failure)).pipe(
264
- Effect.catchCause((recovered) => Effect.failCause(Cause.combine(recovered, split.remaining))),
265
- );
266
- };
267
-
268
- const statement = (query: { readonly toSQL: () => { sql: string; params: unknown[] } }) =>
269
- Effect.map(CurrentD1PlanningDatabase, (database) => {
270
- const rendered = query.toSQL();
271
-
272
- return compactD1GeneratedStatement(
273
- database.$client,
274
- database.$client.unsafe(rendered.sql, rendered.params),
275
- unavailable,
276
- );
277
- });
278
-
279
- const assertion = (
280
- condition: SQL,
281
- ): Effect.Effect<Statement<any>, never, CurrentD1PlanningDatabase> =>
282
- Effect.gen(function* () {
283
- const database = yield* CurrentD1PlanningDatabase;
284
-
285
- return yield* statement(
286
- database
287
- .select({
288
- ok: sql`case when ${condition} then 1 else json_extract('[]', '$[effect-auth-session-guard]') end`.as(
289
- "ok",
290
- ),
291
- })
292
- .from(sql`(select 1)`),
293
- );
294
- });
295
-
296
- const conditionMatches = (condition: SQL) =>
297
- Effect.flatMap(CurrentD1PlanningDatabase, (database) =>
298
- database
299
- .select({ ok: sql<number>`1` })
300
- .from(sql`(select 1)`)
301
- .where(condition)
302
- .limit(1)
303
- .pipe(Effect.map((rows) => rows.length > 0)),
304
- );
305
-
306
- function runPlanned<A, E>(
307
- plan: Effect.Effect<Planned<A>, E, CurrentD1PlanningDatabase | CurrentCommitJournal>,
308
- ): Effect.Effect<
309
- A,
310
- NormalizedD1Error<E | HookConfigurationError | SqlError.SqlError>,
311
- CurrentD1PlanningDatabase | LifecycleHooks
312
- >;
313
- function runPlanned<A, E>(
314
- plan: Effect.Effect<Planned<A>, E, CurrentD1PlanningDatabase | CurrentCommitJournal>,
315
- isConstraintConflict: (cause: unknown) => boolean,
316
- ): Effect.Effect<
317
- A,
318
- NormalizedD1Error<E | SessionConflict | HookConfigurationError | SqlError.SqlError>,
319
- CurrentD1PlanningDatabase | LifecycleHooks
320
- >;
321
- function runPlanned<A, E, GuardE extends D1PlanError>(
322
- plan: Effect.Effect<Planned<A>, E, CurrentD1PlanningDatabase | CurrentCommitJournal>,
323
- isConstraintConflict: undefined,
324
- recoverGuard: Effect.Effect<never, GuardE, CurrentD1PlanningDatabase>,
325
- ): Effect.Effect<
326
- A,
327
- NormalizedD1Error<E | GuardE | HookConfigurationError | SqlError.SqlError>,
328
- CurrentD1PlanningDatabase | LifecycleHooks
329
- >;
330
- function runPlanned<A, E, GuardE extends D1PlanError>(
331
- plan: Effect.Effect<Planned<A>, E, CurrentD1PlanningDatabase | CurrentCommitJournal>,
332
- isConstraintConflict: (cause: unknown) => boolean,
333
- recoverGuard: Effect.Effect<never, GuardE, CurrentD1PlanningDatabase>,
334
- ): Effect.Effect<
335
- A,
336
- NormalizedD1Error<E | GuardE | SessionConflict | HookConfigurationError | SqlError.SqlError>,
337
- CurrentD1PlanningDatabase | LifecycleHooks
338
- >;
339
- function runPlanned<A, E, GuardE extends D1PlanError = never>(
340
- plan: Effect.Effect<Planned<A>, E, CurrentD1PlanningDatabase | CurrentCommitJournal>,
341
- isConstraintConflict?: (cause: unknown) => boolean,
342
- recoverGuard?: Effect.Effect<never, GuardE, CurrentD1PlanningDatabase>,
343
- ) {
344
- const planned = plan.pipe(
345
- Effect.map((value) => ({
346
- ...value,
347
- ...(isConstraintConflict === undefined ? {} : { isConstraintConflict }),
348
- ...(recoverGuard === undefined ? {} : { recoverGuard }),
349
- })),
350
- );
351
-
352
- const execution = Effect.gen(function* () {
353
- const batch = yield* Effect.serviceOption(CurrentD1SessionBatch);
354
-
355
- if (batch._tag === "Some") {
356
- const owned = batch.value;
357
-
358
- const result = yield* coordinateCommit(
359
- () =>
360
- Effect.gen(function* () {
361
- yield* owned.owner.check;
362
- const journal = yield* CurrentCommitJournal;
363
- const value = yield* planned;
364
-
365
- yield* owned.owner.check;
366
- const guarded = { ...value, journalGuard: journal.prepare(undefined) };
367
-
368
- yield* owned.append(guarded);
369
-
370
- return guarded.receipt;
371
- }),
372
- { mode: "batch" },
373
- );
374
-
375
- return result.value;
376
- }
377
-
378
- const database = yield* CurrentD1PlanningDatabase;
379
-
380
- const result = yield* coordinateCommit(
381
- () =>
382
- Effect.gen(function* () {
383
- const prepared = yield* planned;
384
-
385
- yield* database.$client
386
- .batch(prepared.statements)
387
- .pipe(
388
- Effect.catchCause((cause) =>
389
- Effect.fail(new D1BatchFailure({ cause, mutation: prepared })),
390
- ),
391
- );
392
-
393
- return prepared.receipt;
394
- }),
395
- { mode: "batch" },
396
- );
397
-
398
- return result.value;
399
- });
400
-
401
- return normalizeD1Failure(
402
- execution.pipe(Effect.catchCause(recoverD1BatchCause)),
403
- isD1DomainError,
404
- );
405
- }
406
-
407
- const subjectColumns = (mapping: any) => ({
408
- id: column(mapping.subject.table, mapping.subject.id),
409
- status: column(mapping.subject.table, mapping.subject.status),
410
- revision: column(mapping.subject.table, mapping.subject.securityRevision),
411
- });
412
-
413
- const credentialColumns = (mapping: any) => ({
414
- subjectId: column(mapping.credential.table, mapping.credential.subjectId),
415
- id: column(mapping.credential.table, mapping.credential.credentialId),
416
- revision: column(mapping.credential.table, mapping.credential.revision),
417
- ...(mapping.credential.status === undefined
418
- ? {}
419
- : { status: column(mapping.credential.table, mapping.credential.status) }),
420
- });
421
-
422
- const flowColumns = (mapping: any) => ({
423
- flowId: column(mapping.flow.table, mapping.flow.flowId),
424
- subjectId: column(mapping.flow.table, mapping.flow.subjectId),
425
- state: column(mapping.flow.table, mapping.flow.state),
426
- pendingDigest: column(mapping.flow.table, mapping.flow.pendingDigest),
427
- dedupUntil: column(mapping.flow.table, mapping.flow.dedupUntil),
428
- });
429
-
430
- const hasActiveFlow = Effect.fn("D1Session.hasActiveFlow")(function* (
431
- mapping: any,
432
- flowId: string,
433
- ) {
434
- const database = yield* CurrentD1PlanningDatabase;
435
- const f = flowColumns(mapping);
436
-
437
- return yield* database
438
- .select({ flowId: f.flowId })
439
- .from(mapping.flow.table)
440
- .where(and(eq(f.flowId, flowId), gt(f.dedupUntil, mapping.d1.engineNow)))
441
- .limit(1)
442
- .pipe(Effect.map((rows) => rows.length > 0));
443
- });
444
-
445
- const recoverFlowOrStale = (
446
- mapping: any,
447
- flowId: string,
448
- ): Effect.Effect<
449
- never,
450
- SessionConflict | StaleAuthentication | D1AdapterFailure,
451
- CurrentD1PlanningDatabase
452
- > =>
453
- hasActiveFlow(mapping, flowId).pipe(
454
- Effect.flatMap((active) =>
455
- Effect.fail(active ? SessionConflict.make({}) : StaleAuthentication.make({})),
456
- ),
457
- );
458
-
459
- const pendingColumns = (mapping: any) => ({
460
- digest: column(mapping.pending.table, mapping.pending.digest),
461
- version: column(mapping.pending.table, mapping.pending.version),
462
- flowId: column(mapping.pending.table, mapping.pending.flowId),
463
- subjectId: column(mapping.pending.table, mapping.pending.subjectId),
464
- bindingDigest: column(mapping.pending.table, mapping.pending.bindingDigest),
465
- expiresAt: column(mapping.pending.table, mapping.pending.expiresAt),
466
- attemptLimit: column(mapping.pending.table, mapping.pending.attemptLimit),
467
- failedAttempts: column(mapping.pending.table, mapping.pending.failedAttempts),
468
- consumed: column(mapping.pending.table, mapping.pending.consumed),
469
- });
470
-
471
- const sessionColumns = (mapping: any) => ({
472
- sessionId: column(mapping.session.table, mapping.session.sessionId),
473
- subjectId: column(mapping.session.table, mapping.session.subjectId),
474
- digest: column(mapping.session.table, mapping.session.digest),
475
- version: column(mapping.session.table, mapping.session.version),
476
- securityRevision: column(mapping.session.table, mapping.session.securityRevision),
477
- issuedAt: column(mapping.session.table, mapping.session.issuedAt),
478
- expiresAt: column(mapping.session.table, mapping.session.expiresAt),
479
- absoluteExpiresAt: column(mapping.session.table, mapping.session.absoluteExpiresAt),
480
- });
481
-
482
- const captureDetails = Effect.fn("DrizzleD1Session.captureDetails")(function* (
483
- mapping: any,
484
- subjectId: SubjectId,
485
- credentialIds: ReadonlyArray<string>,
486
- ): Effect.fn.Return<
487
- { readonly subject: any; readonly revision: AuthenticationRevision },
488
- StaleAuthentication | D1AdapterFailure,
489
- CurrentD1PlanningDatabase
490
- > {
491
- const database = yield* CurrentD1PlanningDatabase;
492
- const requested = [...new Set(credentialIds)].sort();
493
-
494
- if (requested.length !== credentialIds.length) return yield* stale();
495
- const nativeSubjectId = yield* mapping.subjectId.toNative(subjectId);
496
- const sc = subjectColumns(mapping);
497
- const cc = credentialColumns(mapping);
498
-
499
- const rows: any[] = yield* requested.length === 0
500
- ? database
501
- .select({ subject: getTableColumns(mapping.subject.table) })
502
- .from(mapping.subject.table)
503
- .where(eq(sc.id, nativeSubjectId))
504
- .limit(1)
505
- .pipe(
506
- Effect.map((selected: any[]) =>
507
- selected.map((row: any) => ({ subject: row.subject, credential: undefined })),
508
- ),
509
- )
510
- : database
511
- .select({
512
- subject: getTableColumns(mapping.subject.table),
513
- credential: getTableColumns(mapping.credential.table),
514
- })
515
- .from(mapping.subject.table)
516
- .innerJoin(
517
- mapping.credential.table,
518
- and(eq(cc.subjectId, sc.id), inArray(cc.id, requested)),
519
- )
520
- .where(eq(sc.id, nativeSubjectId));
521
-
522
- const subject = rows[0]?.subject;
523
-
524
- if (subject === undefined || !mapping.subject.isActiveStatus(subject[mapping.subject.status]))
525
- return yield* stale();
526
-
527
- const credentials = rows.flatMap((row: any) =>
528
- row.credential === undefined || row.credential === null ? [] : [row.credential],
529
- );
530
-
531
- if (
532
- credentials.length !== requested.length ||
533
- credentials.some(
534
- (row: any) =>
535
- mapping.credential.status !== undefined &&
536
- mapping.credential.isActiveStatus?.(row[mapping.credential.status]) !== true,
537
- )
538
- )
539
- return yield* stale();
540
-
541
- return {
542
- subject,
543
- revision: {
544
- subjectId,
545
- securityRevision: subject[mapping.subject.securityRevision],
546
- credentials: credentials
547
- .map((row: any) => ({
548
- credentialId: row[mapping.credential.credentialId],
549
- revision: row[mapping.credential.revision],
550
- }))
551
- .sort((a: any, b: any) => a.credentialId.localeCompare(b.credentialId)),
552
- } satisfies AuthenticationRevision,
553
- };
554
- });
555
-
556
- const capture = Effect.fn("DrizzleD1Session.capture")(function* (
557
- mapping: any,
558
- subjectId: SubjectId,
559
- credentialIds: ReadonlyArray<string>,
560
- ): Effect.fn.Return<
561
- AuthenticationCapture,
562
- StaleAuthentication | D1AdapterFailure,
563
- CurrentD1PlanningDatabase
564
- > {
565
- const details = yield* captureDetails(mapping, subjectId, credentialIds);
566
-
567
- const requirement = yield* mapping.subject.decodeRequirement(details.subject);
568
-
569
- return yield* Schema.decodeEffect(AuthenticationCapture)({
570
- revision: details.revision,
571
- requirement,
572
- });
573
- });
574
-
575
- const requirement = Effect.fn("DrizzleD1Session.requirement")(function* (
576
- mapping: any,
577
- evidence: AuthenticationEvidence,
578
- ): Effect.fn.Return<
579
- AuthenticationRequirement,
580
- StaleAuthentication | D1AdapterFailure,
581
- CurrentD1PlanningDatabase
582
- > {
583
- const current = yield* captureDetails(
584
- mapping,
585
- evidence.revision.subjectId,
586
- evidence.revision.credentials.map((item) => item.credentialId),
587
- );
588
-
589
- const expected = new Map(
590
- evidence.revision.credentials.map((item) => [item.credentialId, item.revision]),
591
- );
592
-
593
- if (
594
- current.revision.securityRevision !== evidence.revision.securityRevision ||
595
- current.revision.credentials.some((item) => expected.get(item.credentialId) !== item.revision)
596
- )
597
- return yield* stale();
598
-
599
- return yield* mapping.subject.decodeRequirement(current.subject);
600
- });
601
-
602
- const authorityCondition = (
603
- mapping: any,
604
- nativeSubjectId: unknown,
605
- evidence: Pick<AuthenticationEvidence, "revision">,
606
- ): SQL => {
607
- const sc = subjectColumns(mapping);
608
- const cc = credentialColumns(mapping);
609
- const subject = sql`exists(select 1 from ${mapping.subject.table} where ${sc.id} = ${sql.param(nativeSubjectId, sc.id)} and ${sc.status} = ${sql.param(mapping.d1.activeSubjectStatusValue, sc.status)} and ${sc.revision} = ${sql.param(evidence.revision.securityRevision, sc.revision)})`;
610
-
611
- const credentials = evidence.revision.credentials.map(
612
- (item) =>
613
- sql`exists(select 1 from ${mapping.credential.table} where ${cc.subjectId} = ${sql.param(nativeSubjectId, cc.subjectId)} and ${cc.id} = ${sql.param(item.credentialId, cc.id)} and ${cc.revision} = ${sql.param(item.revision, cc.revision)}${
614
- mapping.credential.status === undefined
615
- ? sql``
616
- : sql` and ${cc.status} = ${sql.param(mapping.d1.activeCredentialStatusValue, cc.status)}`
617
- })`,
618
- );
619
-
620
- return and(subject, ...credentials)!;
621
- };
622
-
623
- const subjectRevisionCondition = (
624
- mapping: any,
625
- nativeSubjectId: unknown,
626
- revision: SecurityRevision,
627
- ) => {
628
- const sc = subjectColumns(mapping);
629
-
630
- return sql`exists(select 1 from ${mapping.subject.table} where ${sc.id} = ${sql.param(nativeSubjectId, sc.id)} and ${sc.status} = ${sql.param(mapping.d1.activeSubjectStatusValue, sc.status)} and ${sc.revision} = ${sql.param(revision, sc.revision)})`;
631
- };
632
-
633
- /** Engine-time counterpart of assessAuthentication. Complete transitions must
634
- * satisfy an entire alternative; incomplete pending flows need one eligible proof. */
635
- const freshnessCondition = (
636
- mapping: any,
637
- evidence: AuthenticationEvidence,
638
- requirement: AuthenticationRequirement,
639
- complete: boolean,
640
- ): SQL => {
641
- const fresh = evidence.proofs.map((proof) => {
642
- const verified = DateTime.toEpochMillis(proof.verifiedAt);
643
-
644
- return and(
645
- sql`${mapping.d1.engineNowMillis} >= ${verified}`,
646
- sql`${mapping.d1.engineNowMillis} - ${verified} < ${requirement.maximumAgeMillis}`,
647
- )!;
648
- });
649
-
650
- const any = (conditions: ReadonlyArray<SQL>): SQL =>
651
- conditions.length === 0 ? sql`false` : or(...conditions)!;
652
-
653
- const notFuture = evidence.proofs.map(
654
- (proof) => sql`${mapping.d1.engineNowMillis} >= ${DateTime.toEpochMillis(proof.verifiedAt)}`,
655
- );
656
-
657
- if (!complete) return and(...notFuture, any(fresh))!;
658
- const byCredential = new Map<string, SQL[]>();
659
-
660
- evidence.proofs.forEach((proof, index) => {
661
- const previous = byCredential.get(proof.credentialId) ?? [];
662
-
663
- previous.push(fresh[index]);
664
- byCredential.set(proof.credentialId, previous);
665
- });
666
-
667
- const credentialCount = sql.join(
668
- [...byCredential.values()].map((proofs) => sql`case when ${any(proofs)} then 1 else 0 end`),
669
- sql` + `,
670
- );
671
-
672
- const alternatives = requirement.alternatives.map((alternative) =>
673
- and(
674
- ...alternative.factors.map((factor) =>
675
- any(fresh.filter((_, index) => evidence.proofs[index].factors.includes(factor))),
676
- ),
677
- sql`(${credentialCount}) >= ${alternative.minimumCredentials}`,
678
- any(
679
- fresh.filter(
680
- (_, index) =>
681
- (!alternative.userVerified || evidence.proofs[index].userVerified) &&
682
- (!alternative.phishingResistant || evidence.proofs[index].phishingResistant),
683
- ),
684
- ),
685
- )!,
686
- );
687
-
688
- return and(...notFuture, any(alternatives))!;
689
- };
690
-
691
- const allocate = <A>(
692
- asynchronous: Effect.Effect<A, PersistenceMappingError> | undefined,
693
- synchronous: (() => A) | undefined,
694
- ): Effect.Effect<A, D1AdapterFailure | SessionUnavailable> =>
695
- asynchronous !== undefined
696
- ? asynchronous
697
- : synchronous === undefined
698
- ? Effect.fail(unavailable())
699
- : Effect.try({
700
- try: synchronous,
701
- catch: (cause) => PersistenceMappingError.make({ operation: "allocate", cause }),
702
- });
703
-
704
- const sameOriginalRevision = (
705
- completed: AuthenticationEvidence,
706
- original: AuthenticationEvidence,
707
- ) => {
708
- const current = new Map(
709
- completed.revision.credentials.map((item) => [item.credentialId, item.revision]),
710
- );
711
-
712
- return (
713
- completed.flowId === original.flowId &&
714
- completed.bindingDigest === original.bindingDigest &&
715
- completed.revision.subjectId === original.revision.subjectId &&
716
- completed.revision.securityRevision === original.revision.securityRevision &&
717
- original.revision.credentials.every(
718
- (item) => current.get(item.credentialId) === item.revision,
719
- ) &&
720
- original.proofs.every((expected) =>
721
- completed.proofs.some(
722
- (proof) =>
723
- proof.method === expected.method &&
724
- proof.credentialId === expected.credentialId &&
725
- proof.userVerified === expected.userVerified &&
726
- proof.phishingResistant === expected.phishingResistant &&
727
- DateTime.toEpochMillis(proof.verifiedAt) ===
728
- DateTime.toEpochMillis(expected.verifiedAt) &&
729
- proof.factors.length === expected.factors.length &&
730
- proof.factors.every((factor, index) => factor === expected.factors[index]),
731
- ),
732
- )
733
- );
734
- };
735
-
736
- type PendingProjection = "record" | "context";
737
-
738
- const readPendingWith = Effect.fn("D1Session.readPendingWith")(function* <
739
- Record extends PendingAuthenticationState,
740
- >(
741
- mapping: any,
742
- digest: TokenDigest,
743
- projection: PendingProjection,
744
- ): Effect.fn.Return<Record | undefined, D1AdapterFailure, CurrentD1PlanningDatabase> {
745
- const database = yield* CurrentD1PlanningDatabase;
746
-
747
- const row = (yield* database
748
- .select()
749
- .from(mapping.pending.table)
750
- .where(eq(pendingColumns(mapping).digest, digest))
751
- .limit(1))[0];
752
-
753
- if (row === undefined) return undefined;
754
-
755
- const decode: (row: any) => Effect.Effect<Record, PersistenceMappingError> =
756
- projection === "context" ? mapping.pending.decodeContext : mapping.pending.decode;
757
-
758
- return yield* decode(row);
759
- });
760
-
761
- const readPending = <Claims>(mapping: any, digest: TokenDigest) =>
762
- readPendingWith<PendingAuthenticationRecord<Claims>>(mapping, digest, "record");
763
-
764
- const pendingCondition = (
765
- mapping: any,
766
- input: PendingConsumption,
767
- nativeSubjectId: unknown,
768
- ): SQL => {
769
- const p = pendingColumns(mapping);
770
- const f = flowColumns(mapping);
771
-
772
- return and(
773
- sql`exists(select 1 from ${mapping.pending.table} where ${p.digest} = ${sql.param(input.digest, p.digest)} and ${p.version} = ${sql.param(input.version, p.version)} and ${p.flowId} = ${sql.param(input.flowId, p.flowId)} and ${p.subjectId} = ${sql.param(nativeSubjectId, p.subjectId)} and ${p.bindingDigest} = ${sql.param(input.bindingDigest, p.bindingDigest)} and ${p.consumed} = ${sql.param(false, p.consumed)} and ${p.failedAttempts} < ${p.attemptLimit} and ${p.expiresAt} > ${mapping.d1.engineNow})`,
774
- sql`exists(select 1 from ${mapping.flow.table} where ${f.flowId} = ${sql.param(input.flowId, f.flowId)} and ${f.subjectId} = ${sql.param(nativeSubjectId, f.subjectId)} and ${f.state} = ${sql.param(mapping.flow.pendingStateValue, f.state)} and ${f.pendingDigest} = ${sql.param(input.digest, f.pendingDigest)} and ${f.dedupUntil} > ${mapping.d1.engineNow})`,
775
- )!;
776
- };
777
-
778
- const recoverPendingOrStale = (
779
- mapping: any,
780
- input: PendingConsumption,
781
- subjectId: SubjectId,
782
- ): Effect.Effect<
783
- never,
784
- PendingAuthenticationInvalid | StaleAuthentication | D1AdapterFailure,
785
- CurrentD1PlanningDatabase
786
- > =>
787
- mapping.subjectId.toNative(subjectId).pipe(
788
- Effect.flatMap((nativeSubjectId: unknown) =>
789
- conditionMatches(pendingCondition(mapping, input, nativeSubjectId)),
790
- ),
791
- Effect.flatMap((pendingIsCurrent) =>
792
- Effect.fail(
793
- pendingIsCurrent ? StaleAuthentication.make({}) : PendingAuthenticationInvalid.make({}),
794
- ),
795
- ),
796
- );
797
-
798
- const consumePendingStatements = Effect.fn("D1Session.consumePendingStatements")(function* (
799
- mapping: any,
800
- input: PendingConsumption,
801
- absoluteExpiresAt: DateTime.Utc,
802
- ): Effect.fn.Return<ReadonlyArray<Statement<any>>, never, CurrentD1PlanningDatabase> {
803
- const database = yield* CurrentD1PlanningDatabase;
804
-
805
- return [
806
- yield* statement(
807
- database
808
- .update(mapping.pending.table)
809
- .set(updateValues<any>([[mapping.pending.consumed, true]]))
810
- .where(
811
- and(
812
- eq(pendingColumns(mapping).digest, input.digest),
813
- eq(pendingColumns(mapping).version, input.version),
814
- ),
815
- ),
816
- ),
817
- yield* statement(
818
- database
819
- .update(mapping.flow.table)
820
- .set(
821
- updateValues<any>([
822
- [mapping.flow.state, mapping.flow.establishedStateValue],
823
- [mapping.flow.pendingDigest, null],
824
- [mapping.flow.dedupUntil, mapping.flow.encodeInstant(absoluteExpiresAt)],
825
- ]),
826
- )
827
- .where(eq(flowColumns(mapping).flowId, input.flowId)),
828
- ),
829
- ];
830
- });
831
-
832
- export const makeD1AuthenticationAuthority = <Claims>(
833
- mapping: D1AuthenticationAuthorityMapping<Claims, any, any, any, any, any>,
834
- ) => ({
835
- capture: (
836
- subjectId: Parameters<AuthenticationAuthority["Service"]["capture"]>[0],
837
- credentialIds: Parameters<AuthenticationAuthority["Service"]["capture"]>[1],
838
- ) => capture(mapping, subjectId, credentialIds).pipe(terminalD1),
839
- requirements: (evidence: AuthenticationEvidence) =>
840
- requirement(mapping, evidence).pipe(terminalD1),
841
- approve: <A>(
842
- input: Parameters<AuthenticationAuthority["Service"]["approve"]>[0],
843
- prepare: PrepareSessionCommit<void, A>,
844
- ) =>
845
- runPlanned(
846
- Effect.gen(function* () {
847
- const journal = yield* CurrentCommitJournal;
848
- const policy = yield* requirement(mapping, input.evidence);
849
- const assessed = yield* assessAuthentication(input.evidence, policy);
850
-
851
- if (!assessed.satisfied) return yield* stale();
852
-
853
- const nativeSubjectId = yield* mapping.subjectId.toNative(
854
- input.evidence.revision.subjectId,
855
- );
856
-
857
- let condition = and(
858
- authorityCondition(mapping, nativeSubjectId, input.evidence),
859
- freshnessCondition(mapping, input.evidence, policy, true),
860
- sql`${mapping.d1.engineNowMillis} < ${DateTime.toEpochMillis(input.expiresAt)}`,
861
- sql`${DateTime.toEpochMillis(input.expiresAt)} <= ${DateTime.toEpochMillis(input.absoluteExpiresAt)}`,
862
- )!;
863
-
864
- const statements: Statement<any>[] = [];
865
-
866
- if (input.pending !== undefined) {
867
- if (mapping.pending === undefined) return yield* invalidPending();
868
- const pendingMapping = { ...mapping, ...mapping.pending };
869
- const stored = yield* readPending(pendingMapping, input.pending.digest);
870
-
871
- if (stored === undefined || !sameOriginalRevision(input.evidence, stored.evidence))
872
- return yield* invalidPending();
873
- condition = and(
874
- condition,
875
- pendingCondition(pendingMapping, input.pending, nativeSubjectId),
876
- )!;
877
- statements.push(
878
- ...(yield* consumePendingStatements(
879
- pendingMapping,
880
- input.pending,
881
- input.absoluteExpiresAt,
882
- )),
883
- );
884
- }
885
- const receipt = prepare(undefined, journal);
886
-
887
- return { receipt, statements: [yield* assertion(condition), ...statements] };
888
- }),
889
- undefined,
890
- Effect.suspend(() =>
891
- input.pending === undefined || mapping.pending === undefined
892
- ? Effect.fail(stale())
893
- : recoverPendingOrStale(
894
- { ...mapping, ...mapping.pending },
895
- input.pending,
896
- input.evidence.revision.subjectId,
897
- ),
898
- ),
899
- ),
900
- });
901
-
902
- /** D1's last authoritative statement checks the original record version and
903
- * security state together. A later race is still rejected by final consumption. */
904
- const readAuthenticatedPending = Effect.fn("D1Session.readAuthenticatedPending")(function* <
905
- Record extends PendingAuthenticationState,
906
- >(
907
- mapping: any,
908
- input: { readonly digest: TokenDigest; readonly bindingDigest?: TokenDigest },
909
- projection: PendingProjection,
910
- ): Effect.fn.Return<
911
- Record,
912
- PendingAuthenticationInvalid | D1AdapterFailure,
913
- CurrentD1PlanningDatabase
914
- > {
915
- const record = yield* readPendingWith<Record>(mapping, input.digest, projection);
916
-
917
- if (
918
- record === undefined ||
919
- (input.bindingDigest !== undefined && record.evidence.bindingDigest !== input.bindingDigest)
920
- )
921
- return yield* invalidPending();
922
-
923
- const policy = yield* mapFailureCause(requirement(mapping, record.evidence), (error) =>
924
- error._tag === "StaleAuthentication" ? invalidPending() : error,
925
- );
926
-
927
- yield* assessAuthentication(record.evidence, policy).pipe(
928
- Effect.mapError(() => invalidPending()),
929
- );
930
- const nativeSubjectId = yield* mapping.subjectId.toNative(record.evidence.revision.subjectId);
931
-
932
- const current = yield* conditionMatches(
933
- and(
934
- authorityCondition(mapping, nativeSubjectId, record.evidence),
935
- freshnessCondition(mapping, record.evidence, policy, false),
936
- pendingCondition(
937
- mapping,
938
- {
939
- digest: input.digest,
940
- bindingDigest: record.evidence.bindingDigest,
941
- flowId: record.evidence.flowId,
942
- version: record.version,
943
- },
944
- nativeSubjectId,
945
- ),
946
- )!,
947
- );
948
-
949
- if (!current) return yield* invalidPending();
950
-
951
- return record;
952
- });
953
-
954
- export const makeD1PendingAuthentication = <Claims>(
955
- mapping: D1PendingAuthenticationMapping<Claims, any, any, any, any, any>,
956
- ) => ({
957
- create: <A>(
958
- input: Parameters<PendingAuthentication<Claims>["create"]>[0],
959
- _now: DateTime.Utc,
960
- prepare: PrepareSessionCommit<PendingAuthenticationRecord<Claims>, A>,
961
- ) =>
962
- runPlanned(
963
- Effect.gen(function* () {
964
- const database = yield* CurrentD1PlanningDatabase;
965
- const journal = yield* CurrentCommitJournal;
966
- const policy = yield* requirement(mapping, input.evidence);
967
-
968
- yield* assessAuthentication(input.evidence, policy);
969
-
970
- const nativeSubjectId = yield* mapping.subjectId.toNative(
971
- input.evidence.revision.subjectId,
972
- );
973
-
974
- if (yield* hasActiveFlow(mapping, input.evidence.flowId))
975
- return yield* SessionConflict.make({});
976
-
977
- const version = yield* allocate(
978
- mapping.pending.allocateVersion,
979
- mapping.pending.allocateVersionSync,
980
- );
981
-
982
- const record = { ...input, version };
983
- const f = flowColumns(mapping);
984
-
985
- const condition = and(
986
- authorityCondition(mapping, nativeSubjectId, input.evidence),
987
- freshnessCondition(mapping, input.evidence, policy, false),
988
- sql`${mapping.d1.engineNowMillis} < ${DateTime.toEpochMillis(input.expiresAt)}`,
989
- not(
990
- sql`exists(select 1 from ${mapping.flow.table} where ${f.flowId} = ${sql.param(input.evidence.flowId, f.flowId)} and ${f.dedupUntil} > ${mapping.d1.engineNow})`,
991
- ),
992
- )!;
993
-
994
- const cleanupPending = database
995
- .delete(mapping.pending.table)
996
- .where(
997
- sql`${pendingColumns(mapping).flowId} in (select ${f.flowId} from ${mapping.flow.table} where ${f.flowId} = ${sql.param(input.evidence.flowId, f.flowId)} and ${f.dedupUntil} <= ${mapping.d1.engineNow})`,
998
- );
999
-
1000
- const cleanupFlow = database
1001
- .delete(mapping.flow.table)
1002
- .where(
1003
- and(
1004
- eq(f.flowId, input.evidence.flowId),
1005
- sql`${f.dedupUntil} <= ${mapping.d1.engineNow}`,
1006
- ),
1007
- );
1008
-
1009
- const insertFlow = database.insert(mapping.flow.table).values(
1010
- mapping.flow.encodePendingInsert({
1011
- evidence: input.evidence,
1012
- subjectId: nativeSubjectId,
1013
- pendingDigest: input.digest,
1014
- dedupUntil: input.expiresAt,
1015
- }),
1016
- );
1017
-
1018
- const insertPending = database.insert(mapping.pending.table).values(
1019
- mapping.pending.encodeInsert(record, {
1020
- subjectId: nativeSubjectId,
1021
- failedAttempts: 0,
1022
- consumed: false,
1023
- }),
1024
- );
1025
-
1026
- const receipt = prepare(record, journal);
1027
-
1028
- return {
1029
- receipt,
1030
- statements: [
1031
- yield* assertion(condition),
1032
- yield* statement(cleanupPending),
1033
- yield* statement(cleanupFlow),
1034
- yield* statement(insertFlow),
1035
- yield* statement(insertPending),
1036
- ],
1037
- };
1038
- }),
1039
- mapping.isConstraintConflict,
1040
- recoverFlowOrStale(mapping, input.evidence.flowId),
1041
- ),
1042
- context: (input: Parameters<PendingAuthentication<Claims>["context"]>[0]) =>
1043
- readAuthenticatedPending<PendingAuthenticationState>(mapping, input, "context").pipe(
1044
- Effect.flatMap(pendingAuthenticationContext),
1045
- terminalD1,
1046
- ),
1047
- read: (input: Parameters<PendingAuthentication<Claims>["read"]>[0]) =>
1048
- readAuthenticatedPending<PendingAuthenticationRecord<Claims>>(mapping, input, "record").pipe(
1049
- terminalD1,
1050
- ),
1051
- reject: <A>(
1052
- input: Parameters<PendingAuthentication<Claims>["reject"]>[0],
1053
- prepare: PrepareSessionCommit<{ readonly _tag: "Rejected" }, A>,
1054
- ) =>
1055
- runPlanned(
1056
- Effect.gen(function* () {
1057
- const database = yield* CurrentD1PlanningDatabase;
1058
- const journal = yield* CurrentCommitJournal;
1059
-
1060
- const record = yield* readPendingWith<PendingAuthenticationState>(
1061
- mapping,
1062
- input.digest,
1063
- "context",
1064
- );
1065
-
1066
- if (record === undefined)
1067
- return {
1068
- receipt: prepare({ _tag: "Rejected" }, journal),
1069
- statements: [yield* assertion(sql`true`)],
1070
- };
1071
-
1072
- const nativeSubjectId = yield* mapping.subjectId.toNative(
1073
- record.evidence.revision.subjectId,
1074
- );
1075
-
1076
- const p = pendingColumns(mapping);
1077
-
1078
- const condition = and(
1079
- authorityCondition(mapping, nativeSubjectId, record.evidence),
1080
- eq(p.digest, input.digest),
1081
- eq(p.bindingDigest, input.bindingDigest),
1082
- eq(p.consumed, false),
1083
- gt(p.expiresAt, mapping.d1.engineNow),
1084
- )!;
1085
-
1086
- const update = database
1087
- .update(mapping.pending.table)
1088
- .set(
1089
- updateValues<any>([
1090
- [
1091
- mapping.pending.failedAttempts,
1092
- sql`case when ${p.failedAttempts} < ${p.attemptLimit} then ${p.failedAttempts} + 1 else ${p.failedAttempts} end`,
1093
- ],
1094
- ]),
1095
- )
1096
- .where(condition);
1097
-
1098
- const receipt = prepare({ _tag: "Rejected" }, journal);
1099
-
1100
- return { receipt, statements: [yield* statement(update)] };
1101
- }),
1102
- ),
1103
- });
1104
-
1105
- export const makeD1StatefulSessions = <Claims>(
1106
- mapping: D1StatefulSessionMapping<Claims, any, any, any, any, any, any, any>,
1107
- ) => {
1108
- const s = sessionColumns(mapping);
1109
-
1110
- const persistence = {
1111
- establish: <A>(
1112
- input: Parameters<StatefulSessionPersistence<Claims>["establish"]>[0],
1113
- prepare: PrepareSessionCommit<
1114
- PreparedValue<StatefulSessionPersistence<Claims>["establish"]>,
1115
- A
1116
- >,
1117
- ) =>
1118
- runPlanned(
1119
- Effect.gen(function* () {
1120
- const database = yield* CurrentD1PlanningDatabase;
1121
- const journal = yield* CurrentCommitJournal;
1122
- const policy = yield* requirement(mapping, input.evidence);
1123
- const assessed = yield* assessAuthentication(input.evidence, policy);
1124
-
1125
- if (!assessed.satisfied) return yield* stale();
1126
-
1127
- const nativeSubjectId = yield* mapping.subjectId.toNative(
1128
- input.evidence.revision.subjectId,
1129
- );
1130
-
1131
- if (
1132
- input.pending === undefined &&
1133
- (input.fresh !== true || input.handoffSourceSessionId !== undefined) &&
1134
- (yield* hasActiveFlow(mapping, input.evidence.flowId))
1135
- )
1136
- return yield* SessionConflict.make({});
1137
-
1138
- const nativeSessionId = yield* allocate(
1139
- mapping.session.allocateId,
1140
- mapping.session.allocateIdSync,
1141
- );
1142
-
1143
- const sessionId = yield* mapping.sessionId.toSession(nativeSessionId);
1144
-
1145
- if (sessionId === input.handoffSourceSessionId) return yield* SessionConflict.make({});
1146
-
1147
- const version = yield* allocate(
1148
- mapping.session.allocateVersion,
1149
- mapping.session.allocateVersionSync,
1150
- );
1151
-
1152
- const record = {
1153
- ...input.session,
1154
- sessionId,
1155
- version,
1156
- subjectId: input.evidence.revision.subjectId,
1157
- securityRevision: input.evidence.revision.securityRevision,
1158
- assurance:
1159
- input.handoffSourceSessionId === undefined
1160
- ? assessed.assurance
1161
- : input.session.assurance,
1162
- provenance: yield* snapshotSessionAuthenticationProvenance({
1163
- evidence: input.evidence,
1164
- }),
1165
- // Ordered D1 batches cannot expose the engine timestamp to the
1166
- // prepare callback. Persist the core's trusted planned time and
1167
- // guard it against the engine clock in the final batch.
1168
- issuedAt: DateTime.makeUnsafe(DateTime.toEpochMillis(input.session.issuedAt)),
1169
- };
1170
-
1171
- const commitCondition = and(
1172
- authorityCondition(mapping, nativeSubjectId, input.evidence),
1173
- freshnessCondition(mapping, input.evidence, policy, true),
1174
- sql`${DateTime.toEpochMillis(record.issuedAt)} <= ${mapping.d1.engineNowMillis}`,
1175
- sql`${mapping.d1.engineNowMillis} < ${DateTime.toEpochMillis(record.expiresAt)}`,
1176
- sql`${DateTime.toEpochMillis(record.expiresAt)} <= ${DateTime.toEpochMillis(record.absoluteExpiresAt)}`,
1177
- )!;
1178
-
1179
- let condition = commitCondition;
1180
- const statements: Statement<any>[] = [];
1181
-
1182
- if (
1183
- input.pending === undefined &&
1184
- (input.fresh !== true || input.handoffSourceSessionId !== undefined)
1185
- ) {
1186
- const f = flowColumns(mapping);
1187
-
1188
- condition = and(
1189
- condition,
1190
- not(
1191
- sql`exists(select 1 from ${mapping.flow.table} where ${f.flowId} = ${sql.param(input.evidence.flowId, f.flowId)} and ${f.dedupUntil} > ${mapping.d1.engineNow})`,
1192
- ),
1193
- )!;
1194
- if (mapping.pending !== undefined)
1195
- statements.push(
1196
- yield* statement(
1197
- database
1198
- .delete(mapping.pending.table)
1199
- .where(
1200
- eq(
1201
- column(mapping.pending.table, mapping.pending.flowId),
1202
- input.evidence.flowId,
1203
- ),
1204
- ),
1205
- ),
1206
- );
1207
- statements.push(
1208
- yield* statement(
1209
- database.delete(mapping.flow.table).where(eq(f.flowId, input.evidence.flowId)),
1210
- ),
1211
- yield* statement(
1212
- database.insert(mapping.flow.table).values(
1213
- mapping.flow.encodeEstablishedInsert({
1214
- evidence: input.evidence,
1215
- subjectId: nativeSubjectId,
1216
- dedupUntil: record.absoluteExpiresAt,
1217
- }),
1218
- ),
1219
- ),
1220
- );
1221
- } else if (input.pending !== undefined) {
1222
- if (mapping.pending === undefined) return yield* invalidPending();
1223
- const stored = yield* readPending(mapping, input.pending.digest);
1224
-
1225
- if (stored === undefined || !sameOriginalRevision(input.evidence, stored.evidence))
1226
- return yield* invalidPending();
1227
- condition = and(condition, pendingCondition(mapping, input.pending, nativeSubjectId))!;
1228
- statements.push(
1229
- ...(yield* consumePendingStatements(
1230
- mapping,
1231
- input.pending,
1232
- record.absoluteExpiresAt,
1233
- )),
1234
- );
1235
- }
1236
- statements.push(
1237
- yield* statement(
1238
- database.insert(mapping.session.table).values(
1239
- mapping.session.encodeInsert(record, {
1240
- subjectId: nativeSubjectId,
1241
- sessionId: nativeSessionId,
1242
- }),
1243
- ),
1244
- ),
1245
- // Recheck after insertion as well as admission; the capped source
1246
- // expiry and proof freshness must still hold at the final batch step.
1247
- yield* assertion(commitCondition),
1248
- );
1249
- const receipt = prepare(record, journal);
1250
-
1251
- return { receipt, statements: [yield* assertion(condition), ...statements] };
1252
- }),
1253
- mapping.isConstraintConflict,
1254
- Effect.suspend<
1255
- never,
1256
- SessionConflict | PendingAuthenticationInvalid | StaleAuthentication | D1AdapterFailure,
1257
- CurrentD1PlanningDatabase
1258
- >(() =>
1259
- input.pending === undefined
1260
- ? input.fresh === true && input.handoffSourceSessionId === undefined
1261
- ? Effect.fail(stale())
1262
- : recoverFlowOrStale(mapping, input.evidence.flowId)
1263
- : recoverPendingOrStale(mapping, input.pending, input.evidence.revision.subjectId),
1264
- ),
1265
- ),
1266
- verify: (input: Parameters<StatefulSessionPersistence<Claims>["verify"]>[0]) =>
1267
- Effect.gen(function* () {
1268
- const database = yield* CurrentD1PlanningDatabase;
1269
-
1270
- const rows = yield* database
1271
- .select({ ...getTableColumns(mapping.session.table) })
1272
- .from(mapping.session.table)
1273
- .innerJoin(
1274
- mapping.subject.table,
1275
- and(
1276
- eq(s.subjectId, subjectColumns(mapping).id),
1277
- eq(s.securityRevision, subjectColumns(mapping).revision),
1278
- eq(subjectColumns(mapping).status, mapping.d1.activeSubjectStatusValue),
1279
- ),
1280
- )
1281
- .where(
1282
- and(
1283
- eq(s.digest, input.digest),
1284
- gt(s.expiresAt, mapping.d1.engineNow),
1285
- gt(s.absoluteExpiresAt, mapping.d1.engineNow),
1286
- ),
1287
- )
1288
- .limit(1);
1289
-
1290
- if (rows[0] === undefined) return yield* invalidSession();
1291
-
1292
- return yield* mapping.session.decode(rows[0]);
1293
- }).pipe(terminalD1),
1294
- rotate: <A>(
1295
- input: Parameters<StatefulSessionPersistence<Claims>["rotate"]>[0],
1296
- prepare: PrepareSessionCommit<PreparedValue<StatefulSessionPersistence<Claims>["rotate"]>, A>,
1297
- ) =>
1298
- runPlanned(
1299
- Effect.gen(function* () {
1300
- const database = yield* CurrentD1PlanningDatabase;
1301
- const journal = yield* CurrentCommitJournal;
1302
- const nativeSessionId = yield* mapping.sessionId.toNative(input.sessionId);
1303
-
1304
- const row = (yield* database
1305
- .select()
1306
- .from(mapping.session.table)
1307
- .where(eq(s.sessionId, nativeSessionId))
1308
- .limit(1))[0];
1309
-
1310
- if (row === undefined) return yield* SessionConflict.make({});
1311
- const current = yield* mapping.session.decode(row);
1312
- const nativeSubjectId = yield* mapping.subjectId.toNative(current.subjectId);
1313
-
1314
- const version = yield* allocate(
1315
- mapping.session.allocateVersion,
1316
- mapping.session.allocateVersionSync,
1317
- );
1318
-
1319
- const next = {
1320
- ...current,
1321
- digest: input.nextDigest,
1322
- credentialVersion: input.nextCredentialVersion,
1323
- version,
1324
- issuedAt: DateTime.makeUnsafe(DateTime.toEpochMillis(input.now)),
1325
- expiresAt: input.nextExpiresAt,
1326
- };
1327
-
1328
- const sessionCondition = and(
1329
- eq(s.sessionId, nativeSessionId),
1330
- eq(s.digest, input.expectedDigest),
1331
- eq(s.version, input.expectedVersion),
1332
- eq(s.securityRevision, input.expectedSecurityRevision),
1333
- gt(s.expiresAt, mapping.d1.engineNow),
1334
- gt(s.absoluteExpiresAt, mapping.d1.engineNow),
1335
- )!;
1336
-
1337
- const timeCondition = and(
1338
- sql`${DateTime.toEpochMillis(next.issuedAt)} <= ${mapping.d1.engineNowMillis}`,
1339
- sql`${mapping.d1.engineNowMillis} < ${DateTime.toEpochMillis(input.nextExpiresAt)}`,
1340
- sql`${DateTime.toEpochMillis(input.nextExpiresAt)} <= ${DateTime.toEpochMillis(current.absoluteExpiresAt)}`,
1341
- )!;
1342
-
1343
- const authority = subjectRevisionCondition(
1344
- mapping,
1345
- nativeSubjectId,
1346
- input.expectedSecurityRevision,
1347
- );
1348
-
1349
- const assertionCondition = and(
1350
- authority,
1351
- sql`exists(select 1 from ${mapping.session.table} where ${sessionCondition})`,
1352
- timeCondition,
1353
- )!;
1354
-
1355
- const update = database
1356
- .update(mapping.session.table)
1357
- .set(mapping.session.encodeRotation(next))
1358
- .where(and(authority, sessionCondition, timeCondition));
1359
-
1360
- const receipt = prepare(next, journal);
1361
-
1362
- return {
1363
- receipt,
1364
- statements: [yield* assertion(assertionCondition), yield* statement(update)],
1365
- };
1366
- }),
1367
- undefined,
1368
- Effect.fail(SessionConflict.make({})),
1369
- ),
1370
- revokeDigest: <A>(
1371
- digest: Parameters<StatefulSessionPersistence<Claims>["revokeDigest"]>[0],
1372
- prepare: PrepareSessionCommit<boolean, A>,
1373
- ) =>
1374
- runPlanned(
1375
- Effect.gen(function* () {
1376
- const database = yield* CurrentD1PlanningDatabase;
1377
- const journal = yield* CurrentCommitJournal;
1378
-
1379
- const row = (yield* database
1380
- .select()
1381
- .from(mapping.session.table)
1382
- .where(eq(s.digest, digest))
1383
- .limit(1))[0];
1384
-
1385
- const statements =
1386
- row === undefined
1387
- ? [yield* assertion(sql`true`)]
1388
- : [
1389
- yield* statement(
1390
- database.delete(mapping.session.table).where(eq(s.digest, digest)),
1391
- ),
1392
- ];
1393
-
1394
- return { receipt: prepare(row !== undefined, journal), statements };
1395
- }),
1396
- ),
1397
- revoke: <A>(
1398
- input: Parameters<StatefulSessionPersistence<Claims>["revoke"]>[0],
1399
- prepare: PrepareSessionCommit<void, A>,
1400
- ) =>
1401
- runPlanned(
1402
- Effect.gen(function* () {
1403
- const database = yield* CurrentD1PlanningDatabase;
1404
- const journal = yield* CurrentCommitJournal;
1405
- const nativeSubjectId = yield* mapping.subjectId.toNative(input.subjectId);
1406
- const nativeSessionId = yield* mapping.sessionId.toNative(input.sessionId);
1407
-
1408
- const condition = subjectRevisionCondition(
1409
- mapping,
1410
- nativeSubjectId,
1411
- input.expectedSecurityRevision,
1412
- );
1413
-
1414
- const remove = database
1415
- .delete(mapping.session.table)
1416
- .where(and(eq(s.subjectId, nativeSubjectId), eq(s.sessionId, nativeSessionId)));
1417
-
1418
- const receipt = prepare(undefined, journal);
1419
-
1420
- return {
1421
- receipt,
1422
- statements: [yield* assertion(condition), yield* statement(remove)],
1423
- };
1424
- }),
1425
- undefined,
1426
- Effect.fail(stale()),
1427
- ),
1428
- revokeAll: <A>(
1429
- input: Parameters<StatefulSessionPersistence<Claims>["revokeAll"]>[0],
1430
- prepare: PrepareSessionCommit<void, A>,
1431
- ) =>
1432
- runPlanned(
1433
- Effect.gen(function* () {
1434
- const database = yield* CurrentD1PlanningDatabase;
1435
- const journal = yield* CurrentCommitJournal;
1436
- const nativeSubjectId = yield* mapping.subjectId.toNative(input.subjectId);
1437
-
1438
- const next = yield* allocate(
1439
- mapping.subject.nextSecurityRevision?.(input.expectedSecurityRevision),
1440
- mapping.subject.nextSecurityRevisionSync === undefined
1441
- ? undefined
1442
- : () => mapping.subject.nextSecurityRevisionSync!(input.expectedSecurityRevision),
1443
- );
1444
-
1445
- const sc = subjectColumns(mapping);
1446
-
1447
- const condition = and(
1448
- eq(sc.id, nativeSubjectId),
1449
- eq(sc.status, mapping.d1.activeSubjectStatusValue),
1450
- eq(sc.revision, input.expectedSecurityRevision),
1451
- )!;
1452
-
1453
- const updateSubject = database
1454
- .update(mapping.subject.table)
1455
- .set(updateValues<any>([[mapping.subject.securityRevision, next]]))
1456
- .where(condition);
1457
-
1458
- const removeSessions = database
1459
- .delete(mapping.session.table)
1460
- .where(eq(s.subjectId, nativeSubjectId));
1461
-
1462
- const receipt = prepare(undefined, journal);
1463
-
1464
- return {
1465
- receipt,
1466
- statements: [
1467
- yield* assertion(
1468
- sql`exists(select 1 from ${mapping.subject.table} where ${condition})`,
1469
- ),
1470
- yield* statement(updateSubject),
1471
- yield* statement(removeSessions),
1472
- ],
1473
- };
1474
- }),
1475
- undefined,
1476
- Effect.fail(stale()),
1477
- ),
1478
- };
1479
-
1480
- const repository = {
1481
- list: (input: Parameters<SessionRepository["list"]>[0]) =>
1482
- Effect.gen(function* () {
1483
- const database = yield* CurrentD1PlanningDatabase;
1484
- const nativeSubjectId = yield* mapping.subjectId.toNative(input.subjectId);
1485
-
1486
- const cursor =
1487
- input.cursor === undefined
1488
- ? undefined
1489
- : yield* mapping.sessionId.toNative(input.cursor as any);
1490
-
1491
- const rows = yield* database
1492
- .select({ ...getTableColumns(mapping.session.table) })
1493
- .from(mapping.session.table)
1494
- .innerJoin(
1495
- mapping.subject.table,
1496
- and(
1497
- eq(s.subjectId, subjectColumns(mapping).id),
1498
- eq(s.securityRevision, subjectColumns(mapping).revision),
1499
- eq(subjectColumns(mapping).status, mapping.d1.activeSubjectStatusValue),
1500
- ),
1501
- )
1502
- .where(
1503
- and(
1504
- eq(s.subjectId, nativeSubjectId),
1505
- gt(s.expiresAt, mapping.d1.engineNow),
1506
- gt(s.absoluteExpiresAt, mapping.d1.engineNow),
1507
- cursor === undefined ? undefined : gt(s.sessionId, cursor),
1508
- ),
1509
- )
1510
- .orderBy(s.sessionId)
1511
- .limit(input.limit + 1);
1512
-
1513
- const decoded = yield* Effect.forEach(rows, (row) => mapping.session.decode(row));
1514
- const codec = Schema.toCodecJson(Schema.toType(SessionMetadata));
1515
-
1516
- const sessions = yield* Effect.forEach(decoded.slice(0, input.limit), (record) =>
1517
- Schema.encodeEffect(codec)(record).pipe(Effect.flatMap(Schema.decodeEffect(codec))),
1518
- );
1519
-
1520
- const nextCursor = decoded.length <= input.limit ? undefined : sessions.at(-1)?.sessionId;
1521
-
1522
- return {
1523
- sessions,
1524
- ...(nextCursor === undefined ? {} : { nextCursor }),
1525
- };
1526
- }).pipe(terminalD1),
1527
- };
1528
-
1529
- return { persistence, repository };
1530
- };
1531
-
1532
- export const makeD1SignedValidity = (
1533
- mapping: D1SignedSessionValidityMapping<any, any, any, any>,
1534
- ) => {
1535
- const sc = subjectColumns(mapping);
1536
-
1537
- const t = {
1538
- subjectId: column(mapping.tombstone.table, mapping.tombstone.subjectId),
1539
- sessionId: column(mapping.tombstone.table, mapping.tombstone.sessionId),
1540
- absoluteExpiresAt: column(mapping.tombstone.table, mapping.tombstone.absoluteExpiresAt),
1541
- };
1542
-
1543
- return {
1544
- verify: (session: Parameters<SignedSessionValidity["verify"]>[0], _now: DateTime.Utc) =>
1545
- Effect.gen(function* () {
1546
- const database = yield* CurrentD1PlanningDatabase;
1547
- const nativeSubjectId = yield* mapping.subjectId.toNative(session.subjectId);
1548
- const nativeSessionId = yield* mapping.sessionId.toNative(session.sessionId);
1549
-
1550
- const rows = yield* database
1551
- .select({ subjectId: sc.id })
1552
- .from(mapping.subject.table)
1553
- .where(
1554
- and(
1555
- eq(sc.id, nativeSubjectId),
1556
- eq(sc.status, mapping.d1.activeSubjectStatusValue),
1557
- eq(sc.revision, session.securityRevision),
1558
- sql`${mapping.d1.engineNowMillis} < ${DateTime.toEpochMillis(session.expiresAt)}`,
1559
- sql`${mapping.d1.engineNowMillis} < ${DateTime.toEpochMillis(session.absoluteExpiresAt)}`,
1560
- not(
1561
- sql`exists(select 1 from ${mapping.tombstone.table} where ${t.subjectId} = ${sql.param(nativeSubjectId, t.subjectId)} and ${t.sessionId} = ${sql.param(nativeSessionId, t.sessionId)} and ${t.absoluteExpiresAt} > ${mapping.d1.engineNow})`,
1562
- ),
1563
- ),
1564
- )
1565
- .limit(1);
1566
-
1567
- if (rows.length === 0) return yield* invalidSession();
1568
- }).pipe(terminalD1),
1569
- revoke: <A>(
1570
- input: Parameters<SignedSessionValidity["revoke"]>[0],
1571
- prepare: PrepareSessionCommit<void, A>,
1572
- ) =>
1573
- runPlanned(
1574
- Effect.gen(function* () {
1575
- const database = yield* CurrentD1PlanningDatabase;
1576
- const journal = yield* CurrentCommitJournal;
1577
- const nativeSubjectId = yield* mapping.subjectId.toNative(input.subjectId);
1578
- const nativeSessionId = yield* mapping.sessionId.toNative(input.sessionId);
1579
- const condition = sql`exists(select 1 from ${mapping.subject.table} where ${sc.id} = ${sql.param(nativeSubjectId, sc.id)} and ${sc.status} = ${sql.param(mapping.d1.activeSubjectStatusValue, sc.status)} and ${sc.revision} = ${sql.param(input.expectedSecurityRevision, sc.revision)})`;
1580
- const encodedExpiry = mapping.tombstone.encodeInstant(input.absoluteExpiresAt);
1581
- const desiredExpiry = sql.param(encodedExpiry, t.absoluteExpiresAt);
1582
-
1583
- const write = database
1584
- .insert(mapping.tombstone.table)
1585
- .values(
1586
- mapping.tombstone.encodeInsert({
1587
- subjectId: nativeSubjectId,
1588
- sessionId: nativeSessionId,
1589
- absoluteExpiresAt: input.absoluteExpiresAt,
1590
- }),
1591
- )
1592
- .onConflictDoUpdate({
1593
- target: [t.subjectId, t.sessionId],
1594
- set: updateValues<any>([
1595
- [
1596
- mapping.tombstone.absoluteExpiresAt,
1597
- sql`case when ${t.absoluteExpiresAt} < ${desiredExpiry} then ${desiredExpiry} else ${t.absoluteExpiresAt} end`,
1598
- ],
1599
- ]),
1600
- });
1601
-
1602
- const receipt = prepare(undefined, journal);
1603
-
1604
- return {
1605
- receipt,
1606
- statements: [yield* assertion(condition), yield* statement(write)],
1607
- };
1608
- }),
1609
- undefined,
1610
- Effect.fail(stale()),
1611
- ),
1612
- revokeAll: <A>(
1613
- input: Parameters<SignedSessionValidity["revokeAll"]>[0],
1614
- prepare: PrepareSessionCommit<void, A>,
1615
- ) =>
1616
- runPlanned(
1617
- Effect.gen(function* () {
1618
- const database = yield* CurrentD1PlanningDatabase;
1619
- const journal = yield* CurrentCommitJournal;
1620
- const nativeSubjectId = yield* mapping.subjectId.toNative(input.subjectId);
1621
-
1622
- const next = yield* allocate(
1623
- mapping.subject.nextSecurityRevision?.(input.expectedSecurityRevision),
1624
- mapping.subject.nextSecurityRevisionSync === undefined
1625
- ? undefined
1626
- : () => mapping.subject.nextSecurityRevisionSync!(input.expectedSecurityRevision),
1627
- );
1628
-
1629
- const condition = and(
1630
- eq(sc.id, nativeSubjectId),
1631
- eq(sc.status, mapping.d1.activeSubjectStatusValue),
1632
- eq(sc.revision, input.expectedSecurityRevision),
1633
- )!;
1634
-
1635
- const updateSubject = database
1636
- .update(mapping.subject.table)
1637
- .set(updateValues<any>([[mapping.subject.securityRevision, next]]))
1638
- .where(condition);
1639
-
1640
- const receipt = prepare(undefined, journal);
1641
-
1642
- return {
1643
- receipt,
1644
- statements: [
1645
- yield* assertion(
1646
- sql`exists(select 1 from ${mapping.subject.table} where ${condition})`,
1647
- ),
1648
- yield* statement(updateSubject),
1649
- ],
1650
- };
1651
- }),
1652
- undefined,
1653
- Effect.fail(stale()),
1654
- ),
1655
- };
1656
- };
1657
-
1658
- export const makeD1SessionServiceEffects = {
1659
- authority: <Claims>(mapping: any) =>
1660
- Effect.gen(function* () {
1661
- const database = yield* CurrentD1PlanningDatabase;
1662
-
1663
- yield* validateDrizzleStorage(mapping).pipe(Effect.mapError(unavailable));
1664
- const hooks = yield* LifecycleHooks;
1665
- const raw = makeD1AuthenticationAuthority<Claims>(mapping);
1666
-
1667
- const run = <A, E, R>(effect: Effect.Effect<A, E, R>) =>
1668
- Effect.gen(function* () {
1669
- if (
1670
- (yield* Effect.serviceOption(CurrentD1SessionBatch))._tag === "Some" ||
1671
- (yield* hasCommitScope)
1672
- )
1673
- return yield* unavailable();
1674
-
1675
- return yield* effect.pipe(
1676
- Effect.provideService(CurrentD1PlanningDatabase, database),
1677
- Effect.provideService(LifecycleHooks, hooks),
1678
- );
1679
- });
1680
-
1681
- const authenticationAuthority: AuthenticationAuthority["Service"] = {
1682
- capture: (subjectId, credentialIds) => run(raw.capture(subjectId, credentialIds)),
1683
- requirements: (evidence) => run(raw.requirements(evidence)),
1684
- approve: (input, prepare) => run(raw.approve(input, prepare)),
1685
- };
1686
-
1687
- return { authenticationAuthority };
1688
- }),
1689
- pending: <Claims>(mapping: any) =>
1690
- Effect.gen(function* () {
1691
- const database = yield* CurrentD1PlanningDatabase;
1692
-
1693
- yield* validateDrizzleStorage(mapping).pipe(Effect.mapError(unavailable));
1694
- const hooks = yield* LifecycleHooks;
1695
- const raw = makeD1PendingAuthentication<Claims>(mapping);
1696
-
1697
- const run = <A, E, R>(effect: Effect.Effect<A, E, R>) =>
1698
- Effect.gen(function* () {
1699
- if (
1700
- (yield* Effect.serviceOption(CurrentD1SessionBatch))._tag === "Some" ||
1701
- (yield* hasCommitScope)
1702
- )
1703
- return yield* unavailable();
1704
-
1705
- return yield* effect.pipe(
1706
- Effect.provideService(CurrentD1PlanningDatabase, database),
1707
- Effect.provideService(LifecycleHooks, hooks),
1708
- );
1709
- });
1710
-
1711
- const pendingAuthentication: PendingAuthentication<Claims> = {
1712
- create: (input, now, prepare) => run(raw.create(input, now, prepare)),
1713
- context: (input) => run(raw.context(input)),
1714
- read: (input) => run(raw.read(input)),
1715
- reject: (input, prepare) => run(raw.reject(input, prepare)),
1716
- };
1717
-
1718
- return { pendingAuthentication };
1719
- }),
1720
- stateful: <Claims>(mapping: any) =>
1721
- Effect.gen(function* () {
1722
- const database = yield* CurrentD1PlanningDatabase;
1723
-
1724
- yield* validateDrizzleStorage(mapping).pipe(Effect.mapError(unavailable));
1725
- const hooks = yield* LifecycleHooks;
1726
- const raw = makeD1StatefulSessions<Claims>(mapping);
1727
-
1728
- const run = <A, E, R>(effect: Effect.Effect<A, E, R>) =>
1729
- Effect.gen(function* () {
1730
- if (
1731
- (yield* Effect.serviceOption(CurrentD1SessionBatch))._tag === "Some" ||
1732
- (yield* hasCommitScope)
1733
- )
1734
- return yield* unavailable();
1735
-
1736
- return yield* effect.pipe(
1737
- Effect.provideService(CurrentD1PlanningDatabase, database),
1738
- Effect.provideService(LifecycleHooks, hooks),
1739
- );
1740
- });
1741
-
1742
- const statefulSessionPersistence: StatefulSessionPersistence<Claims> = {
1743
- establish: (input, prepare) => run(raw.persistence.establish(input, prepare)),
1744
- verify: (input) => run(raw.persistence.verify(input)),
1745
- rotate: (input, prepare) => run(raw.persistence.rotate(input, prepare)),
1746
- revokeDigest: (digest, prepare) => run(raw.persistence.revokeDigest(digest, prepare)),
1747
- revoke: (input, prepare) => run(raw.persistence.revoke(input, prepare)),
1748
- revokeAll: (input, prepare) => run(raw.persistence.revokeAll(input, prepare)),
1749
- };
1750
-
1751
- const sessionRepository: SessionRepository = {
1752
- list: (input) => run(raw.repository.list(input)),
1753
- };
1754
-
1755
- return { statefulSessionPersistence, sessionRepository };
1756
- }),
1757
- validity: (mapping: any) =>
1758
- Effect.gen(function* () {
1759
- const database = yield* CurrentD1PlanningDatabase;
1760
-
1761
- yield* validateDrizzleStorage(mapping).pipe(Effect.mapError(unavailable));
1762
- const hooks = yield* LifecycleHooks;
1763
- const raw = makeD1SignedValidity(mapping);
1764
-
1765
- const run = <A, E, R>(effect: Effect.Effect<A, E, R>) =>
1766
- Effect.gen(function* () {
1767
- if (
1768
- (yield* Effect.serviceOption(CurrentD1SessionBatch))._tag === "Some" ||
1769
- (yield* hasCommitScope)
1770
- )
1771
- return yield* unavailable();
1772
-
1773
- return yield* effect.pipe(
1774
- Effect.provideService(CurrentD1PlanningDatabase, database),
1775
- Effect.provideService(LifecycleHooks, hooks),
1776
- );
1777
- });
1778
-
1779
- const signedSessionValidity: SignedSessionValidity = {
1780
- verify: (session, now) => run(raw.verify(session, now)),
1781
- revoke: (input, prepare) => run(raw.revoke(input, prepare)),
1782
- revokeAll: (input, prepare) => run(raw.revokeAll(input, prepare)),
1783
- };
1784
-
1785
- return { signedSessionValidity };
1786
- }),
1787
- };
1788
-
1789
- type D1CoordinatorError<E> = E | D1DomainError | HookConfigurationError;
1790
-
1791
- /**
1792
- * Owns one atomic D1 persistence transition together with any number of
1793
- * application statements appended by `owner`. Keeping one persistence
1794
- * transition makes a failed batch's domain guard recoverable without exposing
1795
- * a prepared value. Compose additional transitions in later root batches.
1796
- * Interruption or owner failure before `$client.batch` discards the journal;
1797
- * the adapter performs no hidden retry and a retry must re-plan the whole owner.
1798
- * Caller failures remain in the error channel. D1 batch/transport failures that
1799
- * cannot be tied to this adapter's reserved guard become `SessionUnavailable`.
1800
- */
1801
- export const coordinateD1SessionBatch = <Services, A, E, R>(
1802
- database: Database,
1803
- mapping: unknown,
1804
- make: (batch: D1SessionBatch) => Effect.Effect<Services, never, LifecycleHooks>,
1805
- owner: (services: Services, batch: D1SessionBatch) => Effect.Effect<A, E, R>,
1806
- ): Effect.Effect<A, D1CoordinatorError<E>, R | LifecycleHooks | NativeDatabase> =>
1807
- Effect.gen(function* (): Effect.fn.Return<
1808
- A,
1809
- D1CoordinatorError<E>,
1810
- R | LifecycleHooks | NativeDatabase
1811
- > {
1812
- if (yield* hasCommitScope) return yield* unavailable();
1813
-
1814
- yield* validateDrizzleStorage(mapping).pipe(Effect.mapError(unavailable));
1815
-
1816
- const result = yield* coordinateCommit(
1817
- () =>
1818
- Effect.gen(function* () {
1819
- const statements: Statement<any>[] = [];
1820
- let mutation: Planned<any> | undefined;
1821
-
1822
- const nativeCollector = D1BatchStatements.of({
1823
- append: (statement) => Effect.sync(() => statements.push(statement)),
1824
- });
1825
-
1826
- const d1Owner = yield* makeD1Owner(unavailable()).pipe(
1827
- Effect.provideService(D1BatchStatements, nativeCollector),
1828
- );
1829
-
1830
- let batch!: D1SessionBatch;
1831
-
1832
- const appendMutation = (input: Planned<any>) =>
1833
- d1Owner.run(
1834
- Effect.suspend(() => {
1835
- // Guard recovery is unambiguous only for one persistence transition.
1836
- // The owner may append any number of application statements around it.
1837
- if (mutation !== undefined) return Effect.fail(unavailable());
1838
- mutation = input;
1839
- statements.push(...input.statements);
1840
-
1841
- return Effect.void;
1842
- }),
1843
- );
1844
-
1845
- batch = { owner: d1Owner, append: appendMutation };
1846
- const services = yield* make(batch);
1847
-
1848
- const value = yield* d1Owner.close(
1849
- Effect.provideService(owner(services, batch), D1BatchStatements, d1Owner.collector),
1850
- );
1851
-
1852
- if (mutation?.journalGuard !== undefined) {
1853
- const status = yield* Effect.result(mutation.journalGuard.read);
1854
-
1855
- if (status._tag === "Success" || status.failure._tag !== "CommitPending")
1856
- return yield* unavailable();
1857
- }
1858
-
1859
- yield* database.$client
1860
- .batch(statements)
1861
- .pipe(
1862
- Effect.catchCause((cause) =>
1863
- Effect.fail(
1864
- new D1BatchFailure({ cause, ...(mutation === undefined ? {} : { mutation }) }),
1865
- ),
1866
- ),
1867
- );
1868
-
1869
- return value;
1870
- }),
1871
- { mode: "batch" },
1872
- ).pipe(
1873
- Effect.catchCause(recoverCoordinatedD1BatchCause),
1874
- Effect.provideService(CurrentD1PlanningDatabase, database),
1875
- );
1876
-
1877
- return result.value;
1878
- });
1879
-
1880
- export const makeD1AuthenticationAuthorityServices = <
1881
- Claims,
1882
- Subject extends AnySQLiteTable,
1883
- Credential extends AnySQLiteTable,
1884
- Flow extends AnySQLiteTable,
1885
- Pending extends AnySQLiteTable,
1886
- NativeSubjectId,
1887
- >(
1888
- mapping: D1AuthenticationAuthorityMapping<
1889
- Claims,
1890
- Subject,
1891
- Credential,
1892
- Flow,
1893
- Pending,
1894
- NativeSubjectId
1895
- >,
1896
- ) =>
1897
- makeD1SessionServiceEffects
1898
- .authority<Claims>(mapping as any)
1899
- .pipe(
1900
- Effect.provideServiceEffect(CurrentD1PlanningDatabase, DatabaseService),
1901
- Effect.provideServiceEffect(NativeDatabase, nativeDatabase(DatabaseService)),
1902
- );
1903
-
1904
- export const makeD1PendingAuthenticationServices = <
1905
- Claims,
1906
- Subject extends AnySQLiteTable,
1907
- Credential extends AnySQLiteTable,
1908
- Pending extends AnySQLiteTable,
1909
- Flow extends AnySQLiteTable,
1910
- NativeSubjectId,
1911
- >(
1912
- mapping: D1PendingAuthenticationMapping<
1913
- Claims,
1914
- Subject,
1915
- Credential,
1916
- Pending,
1917
- Flow,
1918
- NativeSubjectId
1919
- >,
1920
- ) =>
1921
- makeD1SessionServiceEffects
1922
- .pending<Claims>(mapping as any)
1923
- .pipe(
1924
- Effect.provideServiceEffect(CurrentD1PlanningDatabase, DatabaseService),
1925
- Effect.provideServiceEffect(NativeDatabase, nativeDatabase(DatabaseService)),
1926
- );
1927
-
1928
- export const makeD1StatefulSessionServices = <
1929
- Claims,
1930
- Subject extends AnySQLiteTable,
1931
- Credential extends AnySQLiteTable,
1932
- Session extends AnySQLiteTable,
1933
- Flow extends AnySQLiteTable,
1934
- Pending extends AnySQLiteTable,
1935
- NativeSubjectId,
1936
- NativeSessionId,
1937
- >(
1938
- mapping: D1StatefulSessionMapping<
1939
- Claims,
1940
- Subject,
1941
- Credential,
1942
- Session,
1943
- Flow,
1944
- Pending,
1945
- NativeSubjectId,
1946
- NativeSessionId
1947
- >,
1948
- ) =>
1949
- makeD1SessionServiceEffects
1950
- .stateful<Claims>(mapping as any)
1951
- .pipe(
1952
- Effect.provideServiceEffect(CurrentD1PlanningDatabase, DatabaseService),
1953
- Effect.provideServiceEffect(NativeDatabase, nativeDatabase(DatabaseService)),
1954
- );
1955
-
1956
- export const makeD1SignedSessionValidityServices = <
1957
- Subject extends AnySQLiteTable,
1958
- Tombstone extends AnySQLiteTable,
1959
- NativeSubjectId,
1960
- NativeSessionId,
1961
- >(
1962
- mapping: D1SignedSessionValidityMapping<Subject, Tombstone, NativeSubjectId, NativeSessionId>,
1963
- ) =>
1964
- makeD1SessionServiceEffects
1965
- .validity(mapping as any)
1966
- .pipe(
1967
- Effect.provideServiceEffect(CurrentD1PlanningDatabase, DatabaseService),
1968
- Effect.provideServiceEffect(NativeDatabase, nativeDatabase(DatabaseService)),
1969
- );
1970
-
1971
- export function coordinateD1AuthenticationAuthority<
1972
- Claims,
1973
- Subject extends AnySQLiteTable,
1974
- Credential extends AnySQLiteTable,
1975
- Flow extends AnySQLiteTable,
1976
- Pending extends AnySQLiteTable,
1977
- NativeSubjectId,
1978
- A,
1979
- E,
1980
- R,
1981
- DatabaseError,
1982
- DatabaseRequirements,
1983
- >(
1984
- acquire: Effect.Effect<Database, DatabaseError, DatabaseRequirements>,
1985
- options: {
1986
- readonly mapping: D1AuthenticationAuthorityMapping<
1987
- Claims,
1988
- Subject,
1989
- Credential,
1990
- Flow,
1991
- Pending,
1992
- NativeSubjectId
1993
- >;
1994
- },
1995
- body: Effect.Effect<A, E, R>,
1996
- ): Effect.Effect<
1997
- A,
1998
- D1CoordinatorError<E> | DatabaseError,
1999
- Exclude<R, AuthenticationAuthority | D1BatchStatements> | LifecycleHooks | DatabaseRequirements
2000
- > {
2001
- return Effect.flatMap(nativeDatabase(acquire), (database) =>
2002
- coordinateD1SessionBatch(
2003
- database,
2004
- options.mapping,
2005
- (batch) =>
2006
- Effect.gen(function* () {
2007
- const hooks = yield* LifecycleHooks;
2008
- const raw = makeD1AuthenticationAuthority<Claims>(options.mapping as any);
2009
-
2010
- const run = <Out, Failure, Requirements>(
2011
- effect: Effect.Effect<Out, Failure, Requirements>,
2012
- ) =>
2013
- batch.owner.run(
2014
- effect.pipe(
2015
- Effect.provideService(CurrentD1PlanningDatabase, database),
2016
- Effect.provideService(LifecycleHooks, hooks),
2017
- Effect.provideService(CurrentD1SessionBatch, batch),
2018
- ),
2019
- );
2020
-
2021
- const authenticationAuthority: AuthenticationAuthority["Service"] = {
2022
- capture: (subjectId, credentialIds) => run(raw.capture(subjectId, credentialIds)),
2023
- requirements: (evidence) => run(raw.requirements(evidence)),
2024
- approve: (input, prepare) => run(raw.approve(input, prepare)),
2025
- };
2026
-
2027
- return { authenticationAuthority };
2028
- }),
2029
- (
2030
- ownedServices: { readonly authenticationAuthority: AuthenticationAuthority["Service"] },
2031
- batch,
2032
- ) => {
2033
- const provided = Context.make(
2034
- AuthenticationAuthority,
2035
- ownedServices.authenticationAuthority,
2036
- ).pipe(Context.add(D1BatchStatements, batch.owner.collector));
2037
-
2038
- return Effect.provideContext(body, provided);
2039
- },
2040
- ).pipe(Effect.provideService(NativeDatabase, database)),
2041
- );
2042
- }
2043
-
2044
- export function coordinateD1PendingAuthentication<
2045
- TargetId,
2046
- Claims,
2047
- Subject extends AnySQLiteTable,
2048
- Credential extends AnySQLiteTable,
2049
- Pending extends AnySQLiteTable,
2050
- Flow extends AnySQLiteTable,
2051
- NativeSubjectId,
2052
- A,
2053
- E,
2054
- R,
2055
- DatabaseError,
2056
- DatabaseRequirements,
2057
- >(
2058
- acquire: Effect.Effect<Database, DatabaseError, DatabaseRequirements>,
2059
- options: {
2060
- readonly mapping: D1PendingAuthenticationMapping<
2061
- NoInfer<Claims>,
2062
- Subject,
2063
- Credential,
2064
- Pending,
2065
- Flow,
2066
- NativeSubjectId
2067
- >;
2068
- readonly target: SuppliedService<TargetId, PendingAuthentication<Claims>>;
2069
- },
2070
- body: Effect.Effect<A, E, R>,
2071
- ): Effect.Effect<
2072
- A,
2073
- D1CoordinatorError<E> | DatabaseError,
2074
- Exclude<R, TargetId | D1BatchStatements> | LifecycleHooks | DatabaseRequirements
2075
- > {
2076
- return Effect.flatMap(nativeDatabase(acquire), (database) =>
2077
- coordinateD1SessionBatch(
2078
- database,
2079
- options.mapping,
2080
- (batch) =>
2081
- Effect.gen(function* () {
2082
- const hooks = yield* LifecycleHooks;
2083
- const raw = makeD1PendingAuthentication<Claims>(options.mapping as any);
2084
-
2085
- const run = <Out, Failure, Requirements>(
2086
- effect: Effect.Effect<Out, Failure, Requirements>,
2087
- ) =>
2088
- batch.owner.run(
2089
- effect.pipe(
2090
- Effect.provideService(CurrentD1PlanningDatabase, database),
2091
- Effect.provideService(LifecycleHooks, hooks),
2092
- Effect.provideService(CurrentD1SessionBatch, batch),
2093
- ),
2094
- );
2095
-
2096
- const pendingAuthentication: PendingAuthentication<Claims> = {
2097
- create: (input, now, prepare) => run(raw.create(input, now, prepare)),
2098
- context: (input) => run(raw.context(input)),
2099
- read: (input) => run(raw.read(input)),
2100
- reject: (input, prepare) => run(raw.reject(input, prepare)),
2101
- };
2102
-
2103
- return { pendingAuthentication };
2104
- }),
2105
- (ownedServices: { readonly pendingAuthentication: PendingAuthentication<Claims> }, batch) => {
2106
- const provided = Context.make(options.target, ownedServices.pendingAuthentication).pipe(
2107
- Context.add(D1BatchStatements, batch.owner.collector),
2108
- );
2109
-
2110
- return Effect.provideContext(body, provided);
2111
- },
2112
- ).pipe(Effect.provideService(NativeDatabase, database)),
2113
- );
2114
- }
2115
-
2116
- export function coordinateD1StatefulSessions<
2117
- PersistenceId,
2118
- RepositoryId,
2119
- Claims,
2120
- Subject extends AnySQLiteTable,
2121
- Credential extends AnySQLiteTable,
2122
- Session extends AnySQLiteTable,
2123
- Flow extends AnySQLiteTable,
2124
- Pending extends AnySQLiteTable,
2125
- NativeSubjectId,
2126
- NativeSessionId,
2127
- A,
2128
- E,
2129
- R,
2130
- DatabaseError,
2131
- DatabaseRequirements,
2132
- >(
2133
- acquire: Effect.Effect<Database, DatabaseError, DatabaseRequirements>,
2134
- options: {
2135
- readonly mapping: D1StatefulSessionMapping<
2136
- NoInfer<Claims>,
2137
- Subject,
2138
- Credential,
2139
- Session,
2140
- Flow,
2141
- Pending,
2142
- NativeSubjectId,
2143
- NativeSessionId
2144
- >;
2145
- readonly persistence: SuppliedService<PersistenceId, StatefulSessionPersistence<Claims>>;
2146
- readonly repository: SuppliedService<RepositoryId, SessionRepository>;
2147
- },
2148
- body: Effect.Effect<A, E, R>,
2149
- ): Effect.Effect<
2150
- A,
2151
- D1CoordinatorError<E> | DatabaseError,
2152
- | Exclude<R, PersistenceId | RepositoryId | D1BatchStatements>
2153
- | LifecycleHooks
2154
- | DatabaseRequirements
2155
- > {
2156
- return Effect.flatMap(nativeDatabase(acquire), (database) =>
2157
- coordinateD1SessionBatch(
2158
- database,
2159
- options.mapping,
2160
- (batch) =>
2161
- Effect.gen(function* () {
2162
- const hooks = yield* LifecycleHooks;
2163
- const raw = makeD1StatefulSessions<Claims>(options.mapping as any);
2164
-
2165
- const run = <Out, Failure, Requirements>(
2166
- effect: Effect.Effect<Out, Failure, Requirements>,
2167
- ) =>
2168
- batch.owner.run(
2169
- effect.pipe(
2170
- Effect.provideService(CurrentD1PlanningDatabase, database),
2171
- Effect.provideService(LifecycleHooks, hooks),
2172
- Effect.provideService(CurrentD1SessionBatch, batch),
2173
- ),
2174
- );
2175
-
2176
- const statefulSessionPersistence: StatefulSessionPersistence<Claims> = {
2177
- establish: (input, prepare) => run(raw.persistence.establish(input, prepare)),
2178
- verify: (input) => run(raw.persistence.verify(input)),
2179
- rotate: (input, prepare) => run(raw.persistence.rotate(input, prepare)),
2180
- revokeDigest: (digest, prepare) => run(raw.persistence.revokeDigest(digest, prepare)),
2181
- revoke: (input, prepare) => run(raw.persistence.revoke(input, prepare)),
2182
- revokeAll: (input, prepare) => run(raw.persistence.revokeAll(input, prepare)),
2183
- };
2184
-
2185
- const sessionRepository: SessionRepository = {
2186
- list: (input) => run(raw.repository.list(input)),
2187
- };
2188
-
2189
- return { statefulSessionPersistence, sessionRepository };
2190
- }),
2191
- (
2192
- ownedServices: {
2193
- readonly statefulSessionPersistence: StatefulSessionPersistence<Claims>;
2194
- readonly sessionRepository: SessionRepository;
2195
- },
2196
- batch,
2197
- ) => {
2198
- const provided = Context.make(
2199
- options.persistence,
2200
- ownedServices.statefulSessionPersistence,
2201
- ).pipe(
2202
- Context.add(options.repository, ownedServices.sessionRepository),
2203
- Context.add(D1BatchStatements, batch.owner.collector),
2204
- );
2205
-
2206
- return Effect.provideContext(body, provided);
2207
- },
2208
- ).pipe(Effect.provideService(NativeDatabase, database)),
2209
- );
2210
- }
2211
-
2212
- export function coordinateD1SignedSessionValidity<
2213
- TargetId,
2214
- Subject extends AnySQLiteTable,
2215
- Tombstone extends AnySQLiteTable,
2216
- NativeSubjectId,
2217
- NativeSessionId,
2218
- A,
2219
- E,
2220
- R,
2221
- DatabaseError,
2222
- DatabaseRequirements,
2223
- >(
2224
- acquire: Effect.Effect<Database, DatabaseError, DatabaseRequirements>,
2225
- options: {
2226
- readonly mapping: D1SignedSessionValidityMapping<
2227
- Subject,
2228
- Tombstone,
2229
- NativeSubjectId,
2230
- NativeSessionId
2231
- >;
2232
- readonly target: SuppliedService<TargetId, SignedSessionValidity>;
2233
- },
2234
- body: Effect.Effect<A, E, R>,
2235
- ): Effect.Effect<
2236
- A,
2237
- D1CoordinatorError<E> | DatabaseError,
2238
- Exclude<R, TargetId | D1BatchStatements> | LifecycleHooks | DatabaseRequirements
2239
- > {
2240
- return Effect.flatMap(nativeDatabase(acquire), (database) =>
2241
- coordinateD1SessionBatch(
2242
- database,
2243
- options.mapping,
2244
- (batch) =>
2245
- Effect.gen(function* () {
2246
- const hooks = yield* LifecycleHooks;
2247
- const raw = makeD1SignedValidity(options.mapping as any);
2248
-
2249
- const run = <Out, Failure, Requirements>(
2250
- effect: Effect.Effect<Out, Failure, Requirements>,
2251
- ) =>
2252
- batch.owner.run(
2253
- effect.pipe(
2254
- Effect.provideService(CurrentD1PlanningDatabase, database),
2255
- Effect.provideService(LifecycleHooks, hooks),
2256
- Effect.provideService(CurrentD1SessionBatch, batch),
2257
- ),
2258
- );
2259
-
2260
- const signedSessionValidity: SignedSessionValidity = {
2261
- verify: (session, now) => run(raw.verify(session, now)),
2262
- revoke: (input, prepare) => run(raw.revoke(input, prepare)),
2263
- revokeAll: (input, prepare) => run(raw.revokeAll(input, prepare)),
2264
- };
2265
-
2266
- return { signedSessionValidity };
2267
- }),
2268
- (ownedServices: { readonly signedSessionValidity: SignedSessionValidity }, batch) => {
2269
- const provided = Context.make(options.target, ownedServices.signedSessionValidity).pipe(
2270
- Context.add(D1BatchStatements, batch.owner.collector),
2271
- );
2272
-
2273
- return Effect.provideContext(body, provided);
2274
- },
2275
- ).pipe(Effect.provideService(NativeDatabase, database)),
2276
- );
2277
- }
2278
-
2279
- const stepUpColumns = (mapping: any) =>
2280
- Object.fromEntries(
2281
- [
2282
- "digest",
2283
- "version",
2284
- "flowId",
2285
- "subjectId",
2286
- "bindingDigest",
2287
- "snapshot",
2288
- "expiresAt",
2289
- "attemptLimit",
2290
- "failedAttempts",
2291
- "consumed",
2292
- ].map((key) => [key, column(mapping.intent.table, mapping.intent[key])]),
2293
- );
2294
-
2295
- const stepUpSourceCondition = (
2296
- mapping: any,
2297
- intent: SessionStepUpIntent,
2298
- nativeSubjectId: unknown,
2299
- nativeSessionId: unknown,
2300
- plan?: SessionStepUpCompletionPlan<unknown>,
2301
- ): SQL => {
2302
- const source = mapping.source;
2303
-
2304
- if (source.kind !== intent.sourceKind) return sql`false`;
2305
-
2306
- const time = and(
2307
- sql`${mapping.d1.engineNowMillis} >= ${DateTime.toEpochMillis(intent.sourceAuthenticatedAt)}`,
2308
- sql`${mapping.d1.engineNowMillis} < ${DateTime.toEpochMillis(intent.sourceExpiresAt)}`,
2309
- sql`${mapping.d1.engineNowMillis} < ${DateTime.toEpochMillis(intent.sourceAbsoluteExpiresAt)}`,
2310
- )!;
2311
-
2312
- if (source.kind === "StatelessSigned") return time;
2313
- if (source.kind === "StateAssistedSigned") {
2314
- const t = source.tombstone,
2315
- owner = column(t.table, t.subjectId),
2316
- id = column(t.table, t.sessionId),
2317
- expiry = column(t.table, t.absoluteExpiresAt);
2318
-
2319
- return and(
2320
- time,
2321
- not(
2322
- sql`exists(select 1 from ${t.table} where ${owner}=${sql.param(nativeSubjectId, owner)} and ${id}=${sql.param(nativeSessionId, id)} and ${expiry}>${mapping.d1.engineNow})`,
2323
- ),
2324
- )!;
2325
- }
2326
-
2327
- const s = source.session,
2328
- c = sessionColumns(source);
2329
-
2330
- const condition = and(
2331
- eq(c.sessionId, nativeSessionId),
2332
- eq(c.subjectId, nativeSubjectId),
2333
- eq(c.securityRevision, intent.revision.securityRevision),
2334
- eq(column(s.table, s.credentialVersion), intent.sourceCredentialVersion),
2335
- eq(column(s.table, s.authenticatedAt), s.encodeInstant(intent.sourceAuthenticatedAt)),
2336
- eq(c.expiresAt, s.encodeInstant(intent.sourceExpiresAt)),
2337
- eq(c.absoluteExpiresAt, s.encodeInstant(intent.sourceAbsoluteExpiresAt)),
2338
- gt(c.expiresAt, mapping.d1.engineNow),
2339
- gt(c.absoluteExpiresAt, mapping.d1.engineNow),
2340
- plan?.replacement._tag === "Stateful"
2341
- ? eq(c.digest, plan.replacement.expectedDigest)
2342
- : undefined,
2343
- plan?.replacement._tag === "Stateful"
2344
- ? eq(c.version, plan.replacement.expectedRowVersion)
2345
- : undefined,
2346
- )!;
2347
-
2348
- return and(time, sql`exists(select 1 from ${s.table} where ${condition})`)!;
2349
- };
2350
-
2351
- const stepUpRowCondition = (
2352
- mapping: any,
2353
- row: any,
2354
- intent: SessionStepUpIntent,
2355
- nativeSubjectId: unknown,
2356
- ): SQL => {
2357
- const c = stepUpColumns(mapping),
2358
- m = mapping.intent;
2359
-
2360
- return and(
2361
- eq(c.digest, intent.digest),
2362
- eq(c.version, intent.version),
2363
- eq(c.flowId, intent.flowId),
2364
- eq(c.subjectId, nativeSubjectId),
2365
- eq(c.bindingDigest, intent.bindingDigest),
2366
- eq(c.snapshot, row[m.snapshot]),
2367
- eq(c.expiresAt, m.encodeInstant(intent.expiresAt)),
2368
- eq(c.attemptLimit, intent.attemptLimit),
2369
- eq(c.consumed, false),
2370
- eq(c.failedAttempts, row[m.failedAttempts]),
2371
- sql`${c.failedAttempts} >= 0`,
2372
- sql`${c.failedAttempts} < ${c.attemptLimit}`,
2373
- gt(c.expiresAt, mapping.d1.engineNow),
2374
- )!;
2375
- };
2376
-
2377
- const readD1StepUp = Effect.fn("DrizzleD1StepUp.read")(function* (
2378
- mapping: AnyD1StepUpMapping,
2379
- digest: TokenDigest,
2380
- ) {
2381
- const database = yield* CurrentD1PlanningDatabase;
2382
-
2383
- const m = mapping.intent,
2384
- c = stepUpColumns(mapping);
2385
-
2386
- const row = (yield* database.select().from(m.table).where(eq(c.digest, digest)).limit(1))[0];
2387
-
2388
- if (row === undefined) return yield* SessionStepUpInvalid.make({});
2389
- const intent = yield* decodeStepUpIntent(row[m.snapshot]);
2390
-
2391
- if (
2392
- intent.digest !== digest ||
2393
- !stepUpIntentLive(intent, mapping.source.kind, yield* DateTime.now) ||
2394
- !Schema.is(Schema.Natural)(row[m.failedAttempts])
2395
- )
2396
- return yield* SessionStepUpInvalid.make({});
2397
- const nativeSubjectId = yield* mapping.subjectId.toNative(intent.revision.subjectId);
2398
-
2399
- const nativeSessionId =
2400
- mapping.source.kind === "StatelessSigned"
2401
- ? undefined
2402
- : yield* mapping.source.sessionId.toNative(intent.sourceSessionId);
2403
-
2404
- const condition = and(
2405
- authorityCondition(mapping, nativeSubjectId, intent),
2406
- stepUpSourceCondition(mapping, intent, nativeSubjectId, nativeSessionId),
2407
- sql`exists(select 1 from ${m.table} where ${stepUpRowCondition(mapping, row, intent, nativeSubjectId)})`,
2408
- sql`${mapping.d1.engineNowMillis} < ${DateTime.toEpochMillis(intent.expiresAt)}`,
2409
- )!;
2410
-
2411
- if (!(yield* conditionMatches(condition))) return yield* SessionStepUpInvalid.make({});
2412
-
2413
- return { row, intent, nativeSubjectId, nativeSessionId };
2414
- });
2415
-
2416
- const isD1StepUpRejectAbsence = (error: unknown): error is SessionStepUpInvalid =>
2417
- typeof error === "object" &&
2418
- error !== null &&
2419
- Reflect.get(error, "_tag") === "SessionStepUpInvalid";
2420
-
2421
- type D1StepUpReadFailure = D1AdapterFailure | SessionStepUpInvalid | SessionUnavailable;
2422
-
2423
- const recoverD1StepUpRejectAbsence = <A, B, R>(
2424
- effect: Effect.Effect<A, D1StepUpReadFailure, R>,
2425
- rejected: B,
2426
- ): Effect.Effect<A | B, D1AdapterFailure | SessionUnavailable, R> =>
2427
- effect.pipe(
2428
- Effect.catchCause((cause) => {
2429
- if (
2430
- cause.reasons.length > 0 &&
2431
- cause.reasons.every(
2432
- (reason) => Cause.isFailReason(reason) && isD1StepUpRejectAbsence(reason.error),
2433
- )
2434
- )
2435
- return Effect.succeed(rejected);
2436
-
2437
- return Effect.failCause(
2438
- Cause.map(cause, (error) =>
2439
- isD1StepUpRejectAbsence(error) ? SessionUnavailable.make({}) : error,
2440
- ),
2441
- );
2442
- }),
2443
- );
2444
-
2445
- export const makeD1SessionStepUp = <Claims>(
2446
- mapping: D1SessionStepUpMapping<Claims, any, any, any, any, any, any, any>,
2447
- ) => {
2448
- const m = mapping.intent,
2449
- c = stepUpColumns(mapping);
2450
-
2451
- const recover = Effect.fail(SessionStepUpInvalid.make({}));
2452
-
2453
- return {
2454
- create: <A>(
2455
- input: Parameters<SessionStepUpPersistence<Claims>["create"]>[0],
2456
- _now: DateTime.Utc,
2457
- prepare: PrepareSessionCommit<SessionStepUpIntent, A>,
2458
- ) =>
2459
- runPlanned(
2460
- Effect.gen(function* () {
2461
- const database = yield* CurrentD1PlanningDatabase;
2462
- const journal = yield* CurrentCommitJournal;
2463
-
2464
- const version = yield* allocate(m.allocateVersion, m.allocateVersionSync),
2465
- record: SessionStepUpIntent = { ...input, version };
2466
-
2467
- if (!stepUpIntentLive(record, mapping.source.kind, yield* DateTime.now))
2468
- return yield* stale();
2469
- const nativeSubjectId = yield* mapping.subjectId.toNative(input.revision.subjectId);
2470
-
2471
- const nativeSessionId =
2472
- mapping.source.kind === "StatelessSigned"
2473
- ? undefined
2474
- : yield* mapping.source.sessionId.toNative(input.sourceSessionId);
2475
-
2476
- const existing = yield* database
2477
- .select()
2478
- .from(m.table)
2479
- .where(or(eq(c.digest, input.digest), eq(c.flowId, input.flowId)))
2480
- .limit(1);
2481
-
2482
- if (existing.length > 0) return yield* SessionConflict.make({});
2483
-
2484
- const condition = and(
2485
- authorityCondition(mapping, nativeSubjectId, record),
2486
- stepUpSourceCondition(mapping, record, nativeSubjectId, nativeSessionId),
2487
- sql`${mapping.d1.engineNowMillis} < ${DateTime.toEpochMillis(input.expiresAt)}`,
2488
- )!;
2489
-
2490
- if (!(yield* conditionMatches(condition))) return yield* stale();
2491
- const snapshot = yield* encodeStepUpIntent(record);
2492
-
2493
- const values = {
2494
- ...m.encodeInsert(record, { subjectId: nativeSubjectId }),
2495
- ...updateValues<any>([
2496
- [m.digest, record.digest],
2497
- [m.version, version],
2498
- [m.flowId, record.flowId],
2499
- [m.subjectId, nativeSubjectId],
2500
- [m.bindingDigest, record.bindingDigest],
2501
- [m.snapshot, snapshot],
2502
- [m.expiresAt, m.encodeInstant(record.expiresAt)],
2503
- [m.attemptLimit, record.attemptLimit],
2504
- [m.failedAttempts, 0],
2505
- [m.consumed, false],
2506
- ]),
2507
- };
2508
-
2509
- const receipt = prepare(record, journal);
2510
-
2511
- const exact = and(
2512
- eq(c.digest, record.digest),
2513
- eq(c.version, version),
2514
- eq(c.flowId, record.flowId),
2515
- eq(c.subjectId, nativeSubjectId),
2516
- eq(c.bindingDigest, record.bindingDigest),
2517
- eq(c.snapshot, snapshot),
2518
- eq(c.expiresAt, m.encodeInstant(record.expiresAt)),
2519
- eq(c.attemptLimit, record.attemptLimit),
2520
- eq(c.failedAttempts, 0),
2521
- eq(c.consumed, false),
2522
- )!;
2523
-
2524
- return {
2525
- receipt,
2526
- statements: [
2527
- yield* assertion(condition),
2528
- yield* statement(database.insert(m.table).values(values)),
2529
- yield* assertion(sql`changes() = 1`),
2530
- yield* assertion(
2531
- and(condition, sql`exists(select 1 from ${m.table} where ${exact})`)!,
2532
- ),
2533
- ],
2534
- };
2535
- }),
2536
- mapping.isConstraintConflict,
2537
- Effect.fail(stale()),
2538
- ),
2539
- context: (input: Parameters<SessionStepUpPersistence<Claims>["context"]>[0]) =>
2540
- mapFailureCause(
2541
- readD1StepUp(mapping, input.digest).pipe(
2542
- Effect.flatMap(({ intent }) =>
2543
- snapshotPendingAuthenticationContext({
2544
- flowId: intent.flowId,
2545
- bindingDigest: intent.bindingDigest,
2546
- revision: intent.revision,
2547
- expiresAtMillis: DateTime.toEpochMillis(intent.expiresAt),
2548
- }),
2549
- ),
2550
- ),
2551
- (error) => (error._tag === "PendingAuthenticationInvalid" ? unavailable() : error),
2552
- ).pipe(terminalD1),
2553
- read: (input: Parameters<SessionStepUpPersistence<Claims>["read"]>[0]) =>
2554
- readD1StepUp(mapping, input.digest).pipe(
2555
- Effect.flatMap(({ intent }) =>
2556
- intent.bindingDigest === input.bindingDigest
2557
- ? Effect.succeed(intent)
2558
- : Effect.fail(SessionStepUpInvalid.make({})),
2559
- ),
2560
- terminalD1,
2561
- ),
2562
- reject: <A>(
2563
- input: Parameters<SessionStepUpPersistence<Claims>["reject"]>[0],
2564
- prepare: PrepareSessionCommit<{ readonly _tag: "Rejected" }, A>,
2565
- ) =>
2566
- runPlanned(
2567
- Effect.gen(function* () {
2568
- const database = yield* CurrentD1PlanningDatabase;
2569
- const journal = yield* CurrentCommitJournal;
2570
-
2571
- const selected = yield* recoverD1StepUpRejectAbsence(
2572
- readD1StepUp(mapping, input.digest).pipe(
2573
- Effect.map((value) => ({ _tag: "Found" as const, value })),
2574
- ),
2575
- { _tag: "Rejected" as const },
2576
- );
2577
-
2578
- if (selected._tag === "Rejected")
2579
- return { receipt: prepare({ _tag: "Rejected" }, journal), statements: [] };
2580
- const { row, intent, nativeSubjectId, nativeSessionId } = selected.value;
2581
-
2582
- // Rejection does not replace immutable intent identity. Concurrent contenders
2583
- // increment the authoritative counter under the same cap; completion pins it.
2584
- const condition = and(
2585
- eq(c.digest, intent.digest),
2586
- eq(c.version, intent.version),
2587
- eq(c.snapshot, row[m.snapshot]),
2588
- eq(c.subjectId, nativeSubjectId),
2589
- eq(c.bindingDigest, intent.bindingDigest),
2590
- eq(c.flowId, intent.flowId),
2591
- eq(c.expiresAt, m.encodeInstant(intent.expiresAt)),
2592
- eq(c.attemptLimit, intent.attemptLimit),
2593
- eq(c.consumed, false),
2594
- sql`${c.failedAttempts} >= 0`,
2595
- sql`${c.failedAttempts} < ${c.attemptLimit}`,
2596
- gt(c.expiresAt, mapping.d1.engineNow),
2597
- authorityCondition(mapping, nativeSubjectId, intent),
2598
- stepUpSourceCondition(mapping, intent, nativeSubjectId, nativeSessionId),
2599
- )!;
2600
-
2601
- const write = database
2602
- .update(m.table)
2603
- .set(updateValues<any>([[m.failedAttempts, sql`${c.failedAttempts} + 1`]]))
2604
- .where(condition);
2605
-
2606
- // A valid still-chargeable row cannot survive a zero-write trigger. A row
2607
- // consumed/expired/exhausted while queued legitimately returns Rejected.
2608
- const settled = sql`changes() = 1 or not exists(select 1 from ${m.table} where ${condition})`;
2609
-
2610
- return {
2611
- receipt: prepare({ _tag: "Rejected" }, journal),
2612
- statements: [yield* statement(write), yield* assertion(settled)],
2613
- };
2614
- }),
2615
- undefined,
2616
- Effect.fail(unavailable()),
2617
- ),
2618
- complete: <A>(
2619
- plan: SessionStepUpCompletionPlan<Claims>,
2620
- prepare: PrepareSessionCommit<PreparedValue<SessionStepUpPersistence<Claims>["complete"]>, A>,
2621
- ) =>
2622
- runPlanned(
2623
- Effect.gen(function* () {
2624
- const database = yield* CurrentD1PlanningDatabase;
2625
- const journal = yield* CurrentCommitJournal;
2626
-
2627
- yield* validateStepUpPlan(plan);
2628
- const stored = yield* readD1StepUp(mapping, plan.intent.digest);
2629
-
2630
- if (
2631
- (yield* encodeStepUpIntent(stored.intent)) !== (yield* encodeStepUpIntent(plan.intent))
2632
- )
2633
- return yield* SessionStepUpInvalid.make({});
2634
- const base = yield* requirement(mapping, plan.evidence);
2635
-
2636
- if (
2637
- !(yield* assessAuthentication(plan.evidence, base)).satisfied ||
2638
- !(yield* assessAuthentication(plan.evidence, stored.intent.requirement)).satisfied
2639
- )
2640
- return yield* stale();
2641
-
2642
- const { row, intent, nativeSubjectId, nativeSessionId } = stored,
2643
- replacement = plan.replacement;
2644
-
2645
- const target = replacement.inspection.session;
2646
-
2647
- if (
2648
- DateTime.toEpochMillis(target.expiresAt) >
2649
- DateTime.toEpochMillis(intent.sourceAbsoluteExpiresAt)
2650
- )
2651
- return yield* SessionStepUpInvalid.make({});
2652
-
2653
- const live = and(
2654
- authorityCondition(mapping, nativeSubjectId, plan.evidence),
2655
- freshnessCondition(mapping, plan.evidence, base, true),
2656
- freshnessCondition(mapping, plan.evidence, intent.requirement, true),
2657
- sql`${mapping.d1.engineNowMillis} >= ${DateTime.toEpochMillis(intent.sourceAuthenticatedAt)}`,
2658
- sql`${mapping.d1.engineNowMillis} < ${DateTime.toEpochMillis(intent.expiresAt)}`,
2659
- sql`${mapping.d1.engineNowMillis} < ${DateTime.toEpochMillis(intent.sourceExpiresAt)}`,
2660
- sql`${mapping.d1.engineNowMillis} < ${DateTime.toEpochMillis(intent.sourceAbsoluteExpiresAt)}`,
2661
- sql`${mapping.d1.engineNowMillis} >= ${DateTime.toEpochMillis(target.issuedAt)}`,
2662
- sql`${mapping.d1.engineNowMillis} < ${DateTime.toEpochMillis(target.expiresAt)}`,
2663
- )!;
2664
-
2665
- const source = stepUpSourceCondition(
2666
- mapping,
2667
- intent,
2668
- nativeSubjectId,
2669
- nativeSessionId,
2670
- plan,
2671
- );
2672
-
2673
- const version = yield* allocate(m.allocateVersion, m.allocateVersionSync);
2674
-
2675
- if (version === intent.version) return yield* unavailable();
2676
- const snapshot = yield* encodeStepUpIntent({ ...intent, version });
2677
-
2678
- const consume = database
2679
- .update(m.table)
2680
- .set(
2681
- updateValues<any>([
2682
- [m.consumed, true],
2683
- [m.version, version],
2684
- [m.snapshot, snapshot],
2685
- ]),
2686
- )
2687
- .where(and(stepUpRowCondition(mapping, row, intent, nativeSubjectId), live, source));
2688
-
2689
- const marker = sql`exists(select 1 from ${m.table} where ${c.digest}=${sql.param(intent.digest, c.digest)} and ${c.version}=${sql.param(version, c.version)} and ${c.snapshot}=${sql.param(snapshot, c.snapshot)} and ${c.consumed}=${sql.param(true, c.consumed)} and ${c.failedAttempts}=${row[m.failedAttempts]})`;
2690
-
2691
- const statements: Statement<any>[] = [
2692
- yield* statement(consume),
2693
- yield* assertion(sql`changes() = 1`),
2694
- ];
2695
-
2696
- let post: SQL = sql`true`;
2697
-
2698
- if (replacement._tag === "Stateful") {
2699
- if (mapping.source.kind !== "Stateful") return yield* SessionStepUpInvalid.make({});
2700
-
2701
- const s = mapping.source.session,
2702
- sc = sessionColumns(mapping.source);
2703
-
2704
- const rowVersion = yield* allocate(s.allocateVersion, s.allocateVersionSync);
2705
-
2706
- const next = {
2707
- ...target,
2708
- provenance: replacement.inspection.provenance,
2709
- credentialVersion: replacement.inspection.credentialVersion,
2710
- digest: replacement.nextDigest,
2711
- version: rowVersion,
2712
- };
2713
-
2714
- const rotation = {
2715
- ...s.encodeRotation(next),
2716
- ...updateValues<any>([
2717
- [s.version, rowVersion],
2718
- [s.digest, next.digest],
2719
- [s.credentialVersion, next.credentialVersion],
2720
- [s.authenticatedAt, s.encodeInstant(target.assurance.authenticatedAt)],
2721
- [s.issuedAt, s.encodeInstant(target.issuedAt)],
2722
- [s.expiresAt, s.encodeInstant(target.expiresAt)],
2723
- [s.absoluteExpiresAt, s.encodeInstant(target.absoluteExpiresAt)],
2724
- ]),
2725
- };
2726
-
2727
- const write = database
2728
- .update(s.table)
2729
- .set(rotation)
2730
- .where(
2731
- and(
2732
- eq(sc.sessionId, nativeSessionId),
2733
- eq(sc.digest, replacement.expectedDigest),
2734
- eq(sc.version, replacement.expectedRowVersion),
2735
- marker,
2736
- source,
2737
- live,
2738
- ),
2739
- );
2740
-
2741
- statements.push(yield* statement(write), yield* assertion(sql`changes() = 1`));
2742
-
2743
- const exact = and(
2744
- eq(sc.sessionId, nativeSessionId),
2745
- eq(sc.subjectId, nativeSubjectId),
2746
- eq(sc.version, rowVersion),
2747
- eq(sc.digest, next.digest),
2748
- eq(sc.securityRevision, target.securityRevision),
2749
- eq(column(s.table, s.credentialVersion), next.credentialVersion),
2750
- eq(
2751
- column(s.table, s.authenticatedAt),
2752
- s.encodeInstant(target.assurance.authenticatedAt),
2753
- ),
2754
- eq(sc.issuedAt, s.encodeInstant(target.issuedAt)),
2755
- eq(sc.expiresAt, s.encodeInstant(target.expiresAt)),
2756
- eq(sc.absoluteExpiresAt, s.encodeInstant(target.absoluteExpiresAt)),
2757
- )!;
2758
-
2759
- post = sql`exists(select 1 from ${s.table} where ${exact})`;
2760
- } else if (replacement._tag === "StateAssistedSigned") {
2761
- if (mapping.source.kind !== "StateAssistedSigned")
2762
- return yield* SessionStepUpInvalid.make({});
2763
-
2764
- const t = mapping.source.tombstone,
2765
- owner = column(t.table, t.subjectId),
2766
- id = column(t.table, t.sessionId),
2767
- expiry = column(t.table, t.absoluteExpiresAt);
2768
-
2769
- statements.push(
2770
- yield* statement(
2771
- database
2772
- .delete(t.table)
2773
- .where(
2774
- and(
2775
- eq(owner, nativeSubjectId),
2776
- eq(id, nativeSessionId),
2777
- sql`${expiry} <= ${mapping.d1.engineNow}`,
2778
- marker,
2779
- ),
2780
- ),
2781
- ),
2782
- );
2783
-
2784
- const values = t.encodeInsert({
2785
- subjectId: nativeSubjectId,
2786
- sessionId: nativeSessionId,
2787
- absoluteExpiresAt: replacement.tombstoneUntil,
2788
- });
2789
-
2790
- statements.push(
2791
- yield* statement(database.insert(t.table).values(values)),
2792
- yield* assertion(sql`changes() = 1`),
2793
- );
2794
-
2795
- const exact = and(
2796
- eq(owner, nativeSubjectId),
2797
- eq(id, nativeSessionId),
2798
- eq(expiry, t.encodeInstant(replacement.tombstoneUntil)),
2799
- )!;
2800
-
2801
- post = sql`exists(select 1 from ${t.table} where ${exact})`;
2802
- }
2803
- statements.push(yield* assertion(and(marker, post, live)!));
2804
-
2805
- return { receipt: prepare(replacement.inspection, journal), statements };
2806
- }),
2807
- mapping.isConstraintConflict,
2808
- recover,
2809
- ),
2810
- };
2811
- };
2812
-
2813
- export const makeD1SessionStepUpServices = Effect.fnUntraced(function* <
2814
- Claims,
2815
- Id,
2816
- S extends AnySQLiteTable,
2817
- C extends AnySQLiteTable,
2818
- I extends AnySQLiteTable,
2819
- Session extends AnySQLiteTable,
2820
- T extends AnySQLiteTable,
2821
- NativeId,
2822
- NativeSessionId,
2823
- >(
2824
- mapping: D1SessionStepUpMapping<NoInfer<Claims>, S, C, I, Session, T, NativeId, NativeSessionId>,
2825
- target: Context.Service<Id, SessionStepUpPersistence<Claims>>,
2826
- ) {
2827
- const database = yield* DatabaseService;
2828
-
2829
- yield* validateDrizzleStorage(mapping).pipe(
2830
- Effect.provideServiceEffect(NativeDatabase, nativeDatabase(DatabaseService)),
2831
- Effect.mapError(unavailable),
2832
- );
2833
-
2834
- const hooks = yield* LifecycleHooks;
2835
- const raw = makeD1SessionStepUp<Claims>(mapping as any);
2836
-
2837
- const run = <A, E, R>(effect: Effect.Effect<A, E, R>) =>
2838
- Effect.gen(function* () {
2839
- if (
2840
- (yield* Effect.serviceOption(CurrentD1SessionBatch))._tag === "Some" ||
2841
- (yield* hasCommitScope)
2842
- )
2843
- return yield* unavailable();
2844
-
2845
- return yield* effect.pipe(
2846
- Effect.provideService(CurrentD1PlanningDatabase, database),
2847
- Effect.provideService(LifecycleHooks, hooks),
2848
- );
2849
- });
2850
-
2851
- const sessionStepUpPersistence = target.of({
2852
- create: (input, now, prepare) => run(raw.create(input, now, prepare)),
2853
- context: (input) => run(raw.context(input)),
2854
- read: (input) => run(raw.read(input)),
2855
- reject: (input, prepare) => run(raw.reject(input, prepare)),
2856
- complete: (plan, prepare) => run(raw.complete(plan, prepare)),
2857
- });
2858
-
2859
- return { sessionStepUpPersistence };
2860
- });
2861
-
2862
- export function coordinateD1SessionStepUp<
2863
- Claims,
2864
- Id,
2865
- S extends AnySQLiteTable,
2866
- C extends AnySQLiteTable,
2867
- I extends AnySQLiteTable,
2868
- Session extends AnySQLiteTable,
2869
- T extends AnySQLiteTable,
2870
- NativeId,
2871
- NativeSessionId,
2872
- A,
2873
- E,
2874
- R,
2875
- DatabaseError,
2876
- DatabaseRequirements,
2877
- >(
2878
- acquire: Effect.Effect<Database, DatabaseError, DatabaseRequirements>,
2879
- options: {
2880
- readonly mapping: D1SessionStepUpMapping<
2881
- NoInfer<Claims>,
2882
- S,
2883
- C,
2884
- I,
2885
- Session,
2886
- T,
2887
- NativeId,
2888
- NativeSessionId
2889
- >;
2890
- readonly target: SuppliedService<Id, SessionStepUpPersistence<Claims>>;
2891
- },
2892
- body: Effect.Effect<A, E, R>,
2893
- ): Effect.Effect<
2894
- A,
2895
- D1CoordinatorError<E> | DatabaseError,
2896
- Exclude<R, Id | D1BatchStatements> | LifecycleHooks | DatabaseRequirements
2897
- > {
2898
- return Effect.flatMap(nativeDatabase(acquire), (database) =>
2899
- coordinateD1SessionBatch(
2900
- database,
2901
- options.mapping,
2902
- (batch) =>
2903
- Effect.gen(function* () {
2904
- const hooks = yield* LifecycleHooks;
2905
- const raw = makeD1SessionStepUp<Claims>(options.mapping as any);
2906
-
2907
- const run = <Out, Failure, Requirements>(
2908
- effect: Effect.Effect<Out, Failure, Requirements>,
2909
- ) =>
2910
- batch.owner.run(
2911
- effect.pipe(
2912
- Effect.provideService(CurrentD1PlanningDatabase, database),
2913
- Effect.provideService(LifecycleHooks, hooks),
2914
- Effect.provideService(CurrentD1SessionBatch, batch),
2915
- ),
2916
- );
2917
-
2918
- const sessionStepUpPersistence = options.target.of({
2919
- create: (input, now, prepare) => run(raw.create(input, now, prepare)),
2920
- context: (input) => run(raw.context(input)),
2921
- read: (input) => run(raw.read(input)),
2922
- reject: (input, prepare) => run(raw.reject(input, prepare)),
2923
- complete: (plan, prepare) => run(raw.complete(plan, prepare)),
2924
- });
2925
-
2926
- return { sessionStepUpPersistence };
2927
- }),
2928
- (
2929
- ownedServices: { readonly sessionStepUpPersistence: SessionStepUpPersistence<Claims> },
2930
- batch,
2931
- ) => {
2932
- const provided = Context.make(options.target, ownedServices.sessionStepUpPersistence).pipe(
2933
- Context.add(D1BatchStatements, batch.owner.collector),
2934
- );
2935
-
2936
- return Effect.provideContext(body, provided);
2937
- },
2938
- ).pipe(Effect.provideService(NativeDatabase, database)),
2939
- );
2940
- }