@yielded/auth-persistence 0.1.0-beta.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (526) hide show
  1. package/README.md +21 -0
  2. package/dist/Drizzle.d.mts +28 -0
  3. package/dist/Drizzle.mjs +28 -0
  4. package/dist/DrizzleD1.d.mts +963 -0
  5. package/dist/DrizzleD1.mjs +29 -0
  6. package/dist/DrizzleD1.mjs.map +1 -0
  7. package/dist/DrizzleLibsql.d.mts +2284 -0
  8. package/dist/DrizzleLibsql.mjs +92 -0
  9. package/dist/DrizzleLibsql.mjs.map +1 -0
  10. package/dist/DrizzleMysql2.d.mts +1820 -0
  11. package/dist/DrizzleMysql2.mjs +55 -0
  12. package/dist/DrizzleMysql2.mjs.map +1 -0
  13. package/dist/DrizzlePglite.d.mts +1826 -0
  14. package/dist/DrizzlePglite.mjs +53 -0
  15. package/dist/DrizzlePglite.mjs.map +1 -0
  16. package/dist/DrizzlePostgres.d.mts +1826 -0
  17. package/dist/DrizzlePostgres.mjs +53 -0
  18. package/dist/DrizzlePostgres.mjs.map +1 -0
  19. package/dist/DrizzleSqliteBun.d.mts +2279 -0
  20. package/dist/DrizzleSqliteBun.mjs +70 -0
  21. package/dist/DrizzleSqliteBun.mjs.map +1 -0
  22. package/dist/DrizzleSqliteDo.d.mts +2329 -0
  23. package/dist/DrizzleSqliteDo.mjs +105 -0
  24. package/dist/DrizzleSqliteDo.mjs.map +1 -0
  25. package/dist/DrizzleSqliteNode.d.mts +2279 -0
  26. package/dist/DrizzleSqliteNode.mjs +70 -0
  27. package/dist/DrizzleSqliteNode.mjs.map +1 -0
  28. package/dist/DrizzleSqliteWasm.d.mts +2282 -0
  29. package/dist/DrizzleSqliteWasm.mjs +70 -0
  30. package/dist/DrizzleSqliteWasm.mjs.map +1 -0
  31. package/dist/drizzle/D1BatchStatements.d.mts +11 -0
  32. package/dist/drizzle/D1BatchStatements.mjs +8 -0
  33. package/dist/drizzle/D1BatchStatements.mjs.map +1 -0
  34. package/dist/drizzle/PasswordPreparedPostconditions.mjs +12 -0
  35. package/dist/drizzle/PasswordPreparedPostconditions.mjs.map +1 -0
  36. package/dist/drizzle/SuppliedService.d.mts +10 -0
  37. package/dist/drizzle/d1-emails.d.mts +62 -0
  38. package/dist/drizzle/d1-emails.mjs +580 -0
  39. package/dist/drizzle/d1-emails.mjs.map +1 -0
  40. package/dist/drizzle/d1-generated-statement.d.mts +2 -0
  41. package/dist/drizzle/d1-generated-statement.mjs +79 -0
  42. package/dist/drizzle/d1-generated-statement.mjs.map +1 -0
  43. package/dist/drizzle/d1-identity.d.mts +31 -0
  44. package/dist/drizzle/d1-identity.mjs +119 -0
  45. package/dist/drizzle/d1-identity.mjs.map +1 -0
  46. package/dist/drizzle/d1-oauth.d.mts +301 -0
  47. package/dist/drizzle/d1-oauth.mjs +76 -0
  48. package/dist/drizzle/d1-oauth.mjs.map +1 -0
  49. package/dist/drizzle/d1-passkey.d.mts +51 -0
  50. package/dist/drizzle/d1-passkey.mjs +95 -0
  51. package/dist/drizzle/d1-passkey.mjs.map +1 -0
  52. package/dist/drizzle/d1-password-prepared.d.mts +25 -0
  53. package/dist/drizzle/d1-password-prepared.mjs +461 -0
  54. package/dist/drizzle/d1-password-prepared.mjs.map +1 -0
  55. package/dist/drizzle/d1-passwords.d.mts +87 -0
  56. package/dist/drizzle/d1-passwords.mjs +1004 -0
  57. package/dist/drizzle/d1-passwords.mjs.map +1 -0
  58. package/dist/drizzle/d1-phone.d.mts +26 -0
  59. package/dist/drizzle/d1-phone.mjs +30 -0
  60. package/dist/drizzle/d1-phone.mjs.map +1 -0
  61. package/dist/drizzle/d1-planning.d.mts +12 -0
  62. package/dist/drizzle/d1-planning.mjs +43 -0
  63. package/dist/drizzle/d1-planning.mjs.map +1 -0
  64. package/dist/drizzle/d1-proofs.d.mts +80 -0
  65. package/dist/drizzle/d1-proofs.mjs +830 -0
  66. package/dist/drizzle/d1-proofs.mjs.map +1 -0
  67. package/dist/drizzle/d1-sessions.d.mts +67 -0
  68. package/dist/drizzle/d1-sessions.mjs +966 -0
  69. package/dist/drizzle/d1-sessions.mjs.map +1 -0
  70. package/dist/drizzle/d1-totp.d.mts +26 -0
  71. package/dist/drizzle/d1-totp.mjs +27 -0
  72. package/dist/drizzle/d1-totp.mjs.map +1 -0
  73. package/dist/drizzle/email-model.d.mts +354 -0
  74. package/dist/drizzle/email-model.mjs +21 -0
  75. package/dist/drizzle/email-model.mjs.map +1 -0
  76. package/dist/drizzle/email-registration.d.mts +30 -0
  77. package/dist/drizzle/email-registration.mjs +238 -0
  78. package/dist/drizzle/email-registration.mjs.map +1 -0
  79. package/dist/drizzle/email-sql.d.mts +4 -0
  80. package/dist/drizzle/email-sql.mjs +9 -0
  81. package/dist/drizzle/email-sql.mjs.map +1 -0
  82. package/dist/drizzle/email-target.d.mts +20 -0
  83. package/dist/drizzle/email-target.mjs +72 -0
  84. package/dist/drizzle/email-target.mjs.map +1 -0
  85. package/dist/drizzle/model.d.mts +250 -0
  86. package/dist/drizzle/model.mjs +58 -0
  87. package/dist/drizzle/model.mjs.map +1 -0
  88. package/dist/drizzle/mysql-emails.d.mts +71 -0
  89. package/dist/drizzle/mysql-emails.mjs +38 -0
  90. package/dist/drizzle/mysql-emails.mjs.map +1 -0
  91. package/dist/drizzle/mysql-identity.d.mts +22 -0
  92. package/dist/drizzle/mysql-identity.mjs +94 -0
  93. package/dist/drizzle/mysql-identity.mjs.map +1 -0
  94. package/dist/drizzle/mysql-password-prepared.d.mts +30 -0
  95. package/dist/drizzle/mysql-password-prepared.mjs +16 -0
  96. package/dist/drizzle/mysql-password-prepared.mjs.map +1 -0
  97. package/dist/drizzle/mysql-passwords.d.mts +86 -0
  98. package/dist/drizzle/mysql-passwords.mjs +38 -0
  99. package/dist/drizzle/mysql-passwords.mjs.map +1 -0
  100. package/dist/drizzle/mysql-proofs.d.mts +75 -0
  101. package/dist/drizzle/mysql-proofs.mjs +23 -0
  102. package/dist/drizzle/mysql-proofs.mjs.map +1 -0
  103. package/dist/drizzle/mysql-sessions.d.mts +237 -0
  104. package/dist/drizzle/mysql-sessions.mjs +53 -0
  105. package/dist/drizzle/mysql-sessions.mjs.map +1 -0
  106. package/dist/drizzle/mysql.d.mts +70 -0
  107. package/dist/drizzle/mysql.mjs +218 -0
  108. package/dist/drizzle/mysql.mjs.map +1 -0
  109. package/dist/drizzle/oauth-accounts.mjs +358 -0
  110. package/dist/drizzle/oauth-accounts.mjs.map +1 -0
  111. package/dist/drizzle/oauth-connected-access.mjs +210 -0
  112. package/dist/drizzle/oauth-connected-access.mjs.map +1 -0
  113. package/dist/drizzle/oauth-connected-collection.mjs +202 -0
  114. package/dist/drizzle/oauth-connected-collection.mjs.map +1 -0
  115. package/dist/drizzle/oauth-connected-custody.mjs +146 -0
  116. package/dist/drizzle/oauth-connected-custody.mjs.map +1 -0
  117. package/dist/drizzle/oauth-connected-drivers.mjs +76 -0
  118. package/dist/drizzle/oauth-connected-drivers.mjs.map +1 -0
  119. package/dist/drizzle/oauth-connected-flow.mjs +247 -0
  120. package/dist/drizzle/oauth-connected-flow.mjs.map +1 -0
  121. package/dist/drizzle/oauth-connected-input.mjs +103 -0
  122. package/dist/drizzle/oauth-connected-input.mjs.map +1 -0
  123. package/dist/drizzle/oauth-connected-maintenance.mjs +214 -0
  124. package/dist/drizzle/oauth-connected-maintenance.mjs.map +1 -0
  125. package/dist/drizzle/oauth-connected-management.mjs +228 -0
  126. package/dist/drizzle/oauth-connected-management.mjs.map +1 -0
  127. package/dist/drizzle/oauth-connected-model.d.mts +278 -0
  128. package/dist/drizzle/oauth-connected-model.mjs +22 -0
  129. package/dist/drizzle/oauth-connected-model.mjs.map +1 -0
  130. package/dist/drizzle/oauth-connected-reference.d.mts +17 -0
  131. package/dist/drizzle/oauth-connected-reference.mjs +53 -0
  132. package/dist/drizzle/oauth-connected-reference.mjs.map +1 -0
  133. package/dist/drizzle/oauth-connected-settlement.mjs +117 -0
  134. package/dist/drizzle/oauth-connected-settlement.mjs.map +1 -0
  135. package/dist/drizzle/oauth-connected-state.d.mts +14 -0
  136. package/dist/drizzle/oauth-connected-state.mjs +422 -0
  137. package/dist/drizzle/oauth-connected-state.mjs.map +1 -0
  138. package/dist/drizzle/oauth-connected-target.d.mts +753 -0
  139. package/dist/drizzle/oauth-connected-target.mjs +91 -0
  140. package/dist/drizzle/oauth-connected-target.mjs.map +1 -0
  141. package/dist/drizzle/oauth-drivers.mjs +51 -0
  142. package/dist/drizzle/oauth-drivers.mjs.map +1 -0
  143. package/dist/drizzle/oauth-flow.d.mts +5 -0
  144. package/dist/drizzle/oauth-flow.mjs +253 -0
  145. package/dist/drizzle/oauth-flow.mjs.map +1 -0
  146. package/dist/drizzle/oauth-input.mjs +127 -0
  147. package/dist/drizzle/oauth-input.mjs.map +1 -0
  148. package/dist/drizzle/oauth-model.d.mts +463 -0
  149. package/dist/drizzle/oauth-model.mjs +49 -0
  150. package/dist/drizzle/oauth-model.mjs.map +1 -0
  151. package/dist/drizzle/oauth-mysql.mjs +8 -0
  152. package/dist/drizzle/oauth-mysql.mjs.map +1 -0
  153. package/dist/drizzle/oauth-owner.d.mts +4 -0
  154. package/dist/drizzle/oauth-owner.mjs +10 -0
  155. package/dist/drizzle/oauth-owner.mjs.map +1 -0
  156. package/dist/drizzle/oauth-registration.d.mts +6 -0
  157. package/dist/drizzle/oauth-registration.mjs +374 -0
  158. package/dist/drizzle/oauth-registration.mjs.map +1 -0
  159. package/dist/drizzle/oauth-state.mjs +107 -0
  160. package/dist/drizzle/oauth-state.mjs.map +1 -0
  161. package/dist/drizzle/oauth-target.d.mts +111 -0
  162. package/dist/drizzle/oauth-target.mjs +246 -0
  163. package/dist/drizzle/oauth-target.mjs.map +1 -0
  164. package/dist/drizzle/passkey-drivers.mjs +34 -0
  165. package/dist/drizzle/passkey-drivers.mjs.map +1 -0
  166. package/dist/drizzle/passkey-kernel.mjs +8 -0
  167. package/dist/drizzle/passkey-kernel.mjs.map +1 -0
  168. package/dist/drizzle/passkey-model.d.mts +272 -0
  169. package/dist/drizzle/passkey-model.mjs +34 -0
  170. package/dist/drizzle/passkey-model.mjs.map +1 -0
  171. package/dist/drizzle/passkey-registration-ceremony-model.d.mts +50 -0
  172. package/dist/drizzle/passkey-registration-ceremony-model.mjs +18 -0
  173. package/dist/drizzle/passkey-registration-ceremony-model.mjs.map +1 -0
  174. package/dist/drizzle/passkey-state.mjs +7 -0
  175. package/dist/drizzle/passkey-state.mjs.map +1 -0
  176. package/dist/drizzle/passkey-target.d.mts +9 -0
  177. package/dist/drizzle/passkey-target.mjs +7 -0
  178. package/dist/drizzle/passkey-target.mjs.map +1 -0
  179. package/dist/drizzle/passkey-write-drivers.mjs +32 -0
  180. package/dist/drizzle/passkey-write-drivers.mjs.map +1 -0
  181. package/dist/drizzle/passkey-write-model.d.mts +190 -0
  182. package/dist/drizzle/passkey-write-model.mjs +13 -0
  183. package/dist/drizzle/passkey-write-model.mjs.map +1 -0
  184. package/dist/drizzle/passkey-write-target.d.mts +11 -0
  185. package/dist/drizzle/passkey-write-target.mjs +7 -0
  186. package/dist/drizzle/passkey-write-target.mjs.map +1 -0
  187. package/dist/drizzle/password-model.d.mts +329 -0
  188. package/dist/drizzle/password-model.mjs +23 -0
  189. package/dist/drizzle/password-model.mjs.map +1 -0
  190. package/dist/drizzle/password-prepared-model.d.mts +94 -0
  191. package/dist/drizzle/password-prepared-model.mjs +11 -0
  192. package/dist/drizzle/password-prepared-model.mjs.map +1 -0
  193. package/dist/drizzle/password-prepared-sql.mjs +390 -0
  194. package/dist/drizzle/password-prepared-sql.mjs.map +1 -0
  195. package/dist/drizzle/password-prepared-state.mjs +104 -0
  196. package/dist/drizzle/password-prepared-state.mjs.map +1 -0
  197. package/dist/drizzle/password-prepared-target.d.mts +12 -0
  198. package/dist/drizzle/password-prepared-target.mjs +56 -0
  199. package/dist/drizzle/password-prepared-target.mjs.map +1 -0
  200. package/dist/drizzle/password-registration.d.mts +19 -0
  201. package/dist/drizzle/password-registration.mjs +135 -0
  202. package/dist/drizzle/password-registration.mjs.map +1 -0
  203. package/dist/drizzle/password-sql.d.mts +5 -0
  204. package/dist/drizzle/password-sql.mjs +9 -0
  205. package/dist/drizzle/password-sql.mjs.map +1 -0
  206. package/dist/drizzle/password-target.d.mts +17 -0
  207. package/dist/drizzle/password-target.mjs +61 -0
  208. package/dist/drizzle/password-target.mjs.map +1 -0
  209. package/dist/drizzle/pg-emails.d.mts +72 -0
  210. package/dist/drizzle/pg-emails.mjs +38 -0
  211. package/dist/drizzle/pg-emails.mjs.map +1 -0
  212. package/dist/drizzle/pg-password-prepared.d.mts +31 -0
  213. package/dist/drizzle/pg-password-prepared.mjs +16 -0
  214. package/dist/drizzle/pg-password-prepared.mjs.map +1 -0
  215. package/dist/drizzle/pg-passwords.d.mts +87 -0
  216. package/dist/drizzle/pg-passwords.mjs +38 -0
  217. package/dist/drizzle/pg-passwords.mjs.map +1 -0
  218. package/dist/drizzle/pg-proofs.d.mts +76 -0
  219. package/dist/drizzle/pg-proofs.mjs +22 -0
  220. package/dist/drizzle/pg-proofs.mjs.map +1 -0
  221. package/dist/drizzle/pg-sessions.d.mts +238 -0
  222. package/dist/drizzle/pg-sessions.mjs +53 -0
  223. package/dist/drizzle/pg-sessions.mjs.map +1 -0
  224. package/dist/drizzle/pg.d.mts +81 -0
  225. package/dist/drizzle/pg.mjs +290 -0
  226. package/dist/drizzle/pg.mjs.map +1 -0
  227. package/dist/drizzle/phone-model.d.mts +115 -0
  228. package/dist/drizzle/phone-model.mjs +39 -0
  229. package/dist/drizzle/phone-model.mjs.map +1 -0
  230. package/dist/drizzle/phone-state.mjs +16 -0
  231. package/dist/drizzle/phone-state.mjs.map +1 -0
  232. package/dist/drizzle/phone-target.d.mts +17 -0
  233. package/dist/drizzle/phone-target.mjs +170 -0
  234. package/dist/drizzle/phone-target.mjs.map +1 -0
  235. package/dist/drizzle/proof-model.d.mts +286 -0
  236. package/dist/drizzle/proof-model.mjs +17 -0
  237. package/dist/drizzle/proof-model.mjs.map +1 -0
  238. package/dist/drizzle/proof-sql.d.mts +4 -0
  239. package/dist/drizzle/proof-sql.mjs +8 -0
  240. package/dist/drizzle/proof-sql.mjs.map +1 -0
  241. package/dist/drizzle/proof-target.d.mts +12 -0
  242. package/dist/drizzle/proof-target.mjs +36 -0
  243. package/dist/drizzle/proof-target.mjs.map +1 -0
  244. package/dist/drizzle/query-operations.mjs +26 -0
  245. package/dist/drizzle/query-operations.mjs.map +1 -0
  246. package/dist/drizzle/session-model.d.mts +200 -0
  247. package/dist/drizzle/session-model.mjs +20 -0
  248. package/dist/drizzle/session-model.mjs.map +1 -0
  249. package/dist/drizzle/session-sql.mjs +8 -0
  250. package/dist/drizzle/session-sql.mjs.map +1 -0
  251. package/dist/drizzle/session-target.d.mts +28 -0
  252. package/dist/drizzle/session-target.mjs +47 -0
  253. package/dist/drizzle/session-target.mjs.map +1 -0
  254. package/dist/drizzle/sqlite-emails.mjs +45 -0
  255. package/dist/drizzle/sqlite-emails.mjs.map +1 -0
  256. package/dist/drizzle/sqlite-identity.mjs +91 -0
  257. package/dist/drizzle/sqlite-identity.mjs.map +1 -0
  258. package/dist/drizzle/sqlite-password-prepared.mjs +21 -0
  259. package/dist/drizzle/sqlite-password-prepared.mjs.map +1 -0
  260. package/dist/drizzle/sqlite-passwords.mjs +49 -0
  261. package/dist/drizzle/sqlite-passwords.mjs.map +1 -0
  262. package/dist/drizzle/sqlite-proofs.mjs +29 -0
  263. package/dist/drizzle/sqlite-proofs.mjs.map +1 -0
  264. package/dist/drizzle/sqlite-sessions.mjs +74 -0
  265. package/dist/drizzle/sqlite-sessions.mjs.map +1 -0
  266. package/dist/drizzle/sqlite.d.mts +78 -0
  267. package/dist/drizzle/sqlite.mjs +217 -0
  268. package/dist/drizzle/sqlite.mjs.map +1 -0
  269. package/dist/drizzle/standalone-guard.mjs +19 -0
  270. package/dist/drizzle/standalone-guard.mjs.map +1 -0
  271. package/dist/drizzle/step-up-model.d.mts +65 -0
  272. package/dist/drizzle/step-up-model.mjs +9 -0
  273. package/dist/drizzle/step-up-model.mjs.map +1 -0
  274. package/dist/drizzle/step-up-state.mjs +32 -0
  275. package/dist/drizzle/step-up-state.mjs.map +1 -0
  276. package/dist/drizzle/totp-model.d.mts +84 -0
  277. package/dist/drizzle/totp-model.mjs +13 -0
  278. package/dist/drizzle/totp-model.mjs.map +1 -0
  279. package/dist/drizzle/totp-state.d.mts +38 -0
  280. package/dist/drizzle/totp-state.mjs +287 -0
  281. package/dist/drizzle/totp-state.mjs.map +1 -0
  282. package/dist/drizzle/totp-target.d.mts +17 -0
  283. package/dist/drizzle/totp-target.mjs +81 -0
  284. package/dist/drizzle/totp-target.mjs.map +1 -0
  285. package/dist/drizzle/transaction-execution.d.mts +4 -0
  286. package/dist/drizzle/transaction-execution.mjs +11 -0
  287. package/dist/drizzle/transaction-execution.mjs.map +1 -0
  288. package/dist/drizzle/transaction-mysql.mjs +46 -0
  289. package/dist/drizzle/transaction-mysql.mjs.map +1 -0
  290. package/dist/drizzle/transaction-owner.d.mts +2 -0
  291. package/dist/drizzle/transaction-owner.mjs +8 -0
  292. package/dist/drizzle/transaction-owner.mjs.map +1 -0
  293. package/dist/index.d.mts +5 -0
  294. package/dist/index.mjs +5 -0
  295. package/dist/internal/configuration.d.mts +174 -0
  296. package/dist/internal/configuration.mjs +7 -0
  297. package/dist/internal/configuration.mjs.map +1 -0
  298. package/dist/internal/drizzle-migrations.mjs +19 -0
  299. package/dist/internal/drizzle-migrations.mjs.map +1 -0
  300. package/dist/internal/drizzle-postgres.mjs +39 -0
  301. package/dist/internal/drizzle-postgres.mjs.map +1 -0
  302. package/dist/internal/drizzle-sqlite.mjs +37 -0
  303. package/dist/internal/drizzle-sqlite.mjs.map +1 -0
  304. package/dist/internal/email-kernel.d.mts +13 -0
  305. package/dist/internal/email-kernel.mjs +487 -0
  306. package/dist/internal/email-kernel.mjs.map +1 -0
  307. package/dist/internal/mapping-error.d.mts +11 -0
  308. package/dist/internal/mapping-error.mjs +30 -0
  309. package/dist/internal/mapping-error.mjs.map +1 -0
  310. package/dist/internal/passkey/admission.d.mts +7 -0
  311. package/dist/internal/passkey/admission.mjs +216 -0
  312. package/dist/internal/passkey/admission.mjs.map +1 -0
  313. package/dist/internal/passkey/credentials.d.mts +6 -0
  314. package/dist/internal/passkey/credentials.mjs +249 -0
  315. package/dist/internal/passkey/credentials.mjs.map +1 -0
  316. package/dist/internal/passkey/enrollment.mjs +138 -0
  317. package/dist/internal/passkey/enrollment.mjs.map +1 -0
  318. package/dist/internal/passkey/flow.d.mts +10 -0
  319. package/dist/internal/passkey/flow.mjs +452 -0
  320. package/dist/internal/passkey/flow.mjs.map +1 -0
  321. package/dist/internal/passkey/management.mjs +231 -0
  322. package/dist/internal/passkey/management.mjs.map +1 -0
  323. package/dist/internal/passkey/registration-ceremony.d.mts +9 -0
  324. package/dist/internal/passkey/registration-ceremony.mjs +129 -0
  325. package/dist/internal/passkey/registration-ceremony.mjs.map +1 -0
  326. package/dist/internal/passkey/registration-custody.d.mts +2 -0
  327. package/dist/internal/passkey/registration-custody.mjs +52 -0
  328. package/dist/internal/passkey/registration-custody.mjs.map +1 -0
  329. package/dist/internal/passkey/registration-write.mjs +211 -0
  330. package/dist/internal/passkey/registration-write.mjs.map +1 -0
  331. package/dist/internal/passkey/state.d.mts +5 -0
  332. package/dist/internal/passkey/state.mjs +234 -0
  333. package/dist/internal/passkey/state.mjs.map +1 -0
  334. package/dist/internal/passkey/target.d.mts +19 -0
  335. package/dist/internal/passkey/target.mjs +190 -0
  336. package/dist/internal/passkey/target.mjs.map +1 -0
  337. package/dist/internal/passkey/write-state.mjs +345 -0
  338. package/dist/internal/passkey/write-state.mjs.map +1 -0
  339. package/dist/internal/passkey/write-target.mjs +192 -0
  340. package/dist/internal/passkey/write-target.mjs.map +1 -0
  341. package/dist/internal/passkey-kernel.mjs +49 -0
  342. package/dist/internal/passkey-kernel.mjs.map +1 -0
  343. package/dist/internal/passkeys.mjs +367 -0
  344. package/dist/internal/passkeys.mjs.map +1 -0
  345. package/dist/internal/password-kernel.d.mts +13 -0
  346. package/dist/internal/password-kernel.mjs +704 -0
  347. package/dist/internal/password-kernel.mjs.map +1 -0
  348. package/dist/internal/persistence.mjs +289 -0
  349. package/dist/internal/persistence.mjs.map +1 -0
  350. package/dist/internal/phone-kernel.mjs +434 -0
  351. package/dist/internal/phone-kernel.mjs.map +1 -0
  352. package/dist/internal/proof-kernel.d.mts +9 -0
  353. package/dist/internal/proof-kernel.mjs +619 -0
  354. package/dist/internal/proof-kernel.mjs.map +1 -0
  355. package/dist/internal/query-operations.d.mts +3 -0
  356. package/dist/internal/registration.mjs +65 -0
  357. package/dist/internal/registration.mjs.map +1 -0
  358. package/dist/internal/session-kernel.mjs +737 -0
  359. package/dist/internal/session-kernel.mjs.map +1 -0
  360. package/dist/internal/sql-persistence.d.mts +11 -0
  361. package/dist/internal/sql-persistence.mjs +37 -0
  362. package/dist/internal/sql-persistence.mjs.map +1 -0
  363. package/dist/internal/sql-query.mjs +207 -0
  364. package/dist/internal/sql-query.mjs.map +1 -0
  365. package/dist/internal/sql-table.d.mts +37 -0
  366. package/dist/internal/sql-table.mjs +71 -0
  367. package/dist/internal/sql-table.mjs.map +1 -0
  368. package/dist/internal/storage-mapping.mjs +687 -0
  369. package/dist/internal/storage-mapping.mjs.map +1 -0
  370. package/dist/internal/storage-tables.d.mts +914 -0
  371. package/dist/internal/storage-tables.mjs +387 -0
  372. package/dist/internal/storage-tables.mjs.map +1 -0
  373. package/dist/internal/storage-validation.mjs +58 -0
  374. package/dist/internal/storage-validation.mjs.map +1 -0
  375. package/dist/internal/transaction-execution-kernel.d.mts +18 -0
  376. package/dist/internal/transaction-execution-kernel.mjs +118 -0
  377. package/dist/internal/transaction-execution-kernel.mjs.map +1 -0
  378. package/dist/internal/transaction-kernel.d.mts +7 -0
  379. package/dist/internal/transaction-kernel.mjs +260 -0
  380. package/dist/internal/transaction-kernel.mjs.map +1 -0
  381. package/package.json +1 -0
  382. package/src/Drizzle.ts +378 -0
  383. package/src/DrizzleD1.ts +136 -0
  384. package/src/DrizzleLibsql.ts +301 -0
  385. package/src/DrizzleMysql2.ts +184 -0
  386. package/src/DrizzlePglite.ts +174 -0
  387. package/src/DrizzlePostgres.ts +174 -0
  388. package/src/DrizzleSqliteBun.ts +258 -0
  389. package/src/DrizzleSqliteDo.ts +473 -0
  390. package/src/DrizzleSqliteNode.ts +259 -0
  391. package/src/DrizzleSqliteWasm.ts +263 -0
  392. package/src/drizzle/D1BatchStatements.ts +10 -0
  393. package/src/drizzle/PasswordPreparedPostconditions.ts +30 -0
  394. package/src/drizzle/SuppliedService.ts +10 -0
  395. package/src/drizzle/d1-emails.ts +1829 -0
  396. package/src/drizzle/d1-generated-statement.ts +129 -0
  397. package/src/drizzle/d1-identity.ts +375 -0
  398. package/src/drizzle/d1-oauth.ts +340 -0
  399. package/src/drizzle/d1-passkey.ts +373 -0
  400. package/src/drizzle/d1-password-prepared.ts +1270 -0
  401. package/src/drizzle/d1-passwords.ts +2656 -0
  402. package/src/drizzle/d1-phone.ts +96 -0
  403. package/src/drizzle/d1-planning.ts +104 -0
  404. package/src/drizzle/d1-proofs.ts +2305 -0
  405. package/src/drizzle/d1-sessions.ts +2857 -0
  406. package/src/drizzle/d1-totp.ts +78 -0
  407. package/src/drizzle/email-model.ts +516 -0
  408. package/src/drizzle/email-registration.ts +698 -0
  409. package/src/drizzle/email-sql.ts +24 -0
  410. package/src/drizzle/email-target.ts +243 -0
  411. package/src/drizzle/model.ts +463 -0
  412. package/src/drizzle/mysql-emails.ts +321 -0
  413. package/src/drizzle/mysql-identity.ts +324 -0
  414. package/src/drizzle/mysql-password-prepared.ts +204 -0
  415. package/src/drizzle/mysql-passwords.ts +339 -0
  416. package/src/drizzle/mysql-proofs.ts +181 -0
  417. package/src/drizzle/mysql-sessions.ts +741 -0
  418. package/src/drizzle/mysql.ts +687 -0
  419. package/src/drizzle/oauth-accounts.ts +804 -0
  420. package/src/drizzle/oauth-connected-access.ts +354 -0
  421. package/src/drizzle/oauth-connected-collection.ts +324 -0
  422. package/src/drizzle/oauth-connected-custody.ts +277 -0
  423. package/src/drizzle/oauth-connected-drivers.ts +465 -0
  424. package/src/drizzle/oauth-connected-flow.ts +441 -0
  425. package/src/drizzle/oauth-connected-input.ts +127 -0
  426. package/src/drizzle/oauth-connected-maintenance.ts +345 -0
  427. package/src/drizzle/oauth-connected-management.ts +365 -0
  428. package/src/drizzle/oauth-connected-model.ts +347 -0
  429. package/src/drizzle/oauth-connected-reference.ts +112 -0
  430. package/src/drizzle/oauth-connected-settlement.ts +226 -0
  431. package/src/drizzle/oauth-connected-state.ts +845 -0
  432. package/src/drizzle/oauth-connected-target.ts +264 -0
  433. package/src/drizzle/oauth-drivers.ts +569 -0
  434. package/src/drizzle/oauth-flow.ts +462 -0
  435. package/src/drizzle/oauth-input.ts +201 -0
  436. package/src/drizzle/oauth-model.ts +617 -0
  437. package/src/drizzle/oauth-mysql.ts +10 -0
  438. package/src/drizzle/oauth-owner.ts +29 -0
  439. package/src/drizzle/oauth-registration.ts +640 -0
  440. package/src/drizzle/oauth-state.ts +215 -0
  441. package/src/drizzle/oauth-target.ts +750 -0
  442. package/src/drizzle/passkey-drivers.ts +385 -0
  443. package/src/drizzle/passkey-kernel.ts +3 -0
  444. package/src/drizzle/passkey-model.ts +353 -0
  445. package/src/drizzle/passkey-registration-ceremony-model.ts +69 -0
  446. package/src/drizzle/passkey-state.ts +3 -0
  447. package/src/drizzle/passkey-target.ts +23 -0
  448. package/src/drizzle/passkey-write-drivers.ts +395 -0
  449. package/src/drizzle/passkey-write-model.ts +271 -0
  450. package/src/drizzle/passkey-write-target.ts +9 -0
  451. package/src/drizzle/password-model.ts +491 -0
  452. package/src/drizzle/password-prepared-model.ts +259 -0
  453. package/src/drizzle/password-prepared-sql.ts +1191 -0
  454. package/src/drizzle/password-prepared-state.ts +231 -0
  455. package/src/drizzle/password-prepared-target.ts +160 -0
  456. package/src/drizzle/password-registration.ts +374 -0
  457. package/src/drizzle/password-sql.ts +12 -0
  458. package/src/drizzle/password-target.ts +224 -0
  459. package/src/drizzle/pg-emails.ts +321 -0
  460. package/src/drizzle/pg-password-prepared.ts +205 -0
  461. package/src/drizzle/pg-passwords.ts +338 -0
  462. package/src/drizzle/pg-proofs.ts +191 -0
  463. package/src/drizzle/pg-sessions.ts +750 -0
  464. package/src/drizzle/pg.ts +984 -0
  465. package/src/drizzle/phone-model.ts +190 -0
  466. package/src/drizzle/phone-state.ts +21 -0
  467. package/src/drizzle/phone-target.ts +472 -0
  468. package/src/drizzle/proof-model.ts +398 -0
  469. package/src/drizzle/proof-sql.ts +9 -0
  470. package/src/drizzle/proof-target.ts +122 -0
  471. package/src/drizzle/query-operations.ts +37 -0
  472. package/src/drizzle/session-model.ts +370 -0
  473. package/src/drizzle/session-sql.ts +13 -0
  474. package/src/drizzle/session-target.ts +338 -0
  475. package/src/drizzle/sqlite-emails.ts +366 -0
  476. package/src/drizzle/sqlite-identity.ts +344 -0
  477. package/src/drizzle/sqlite-password-prepared.ts +260 -0
  478. package/src/drizzle/sqlite-passwords.ts +497 -0
  479. package/src/drizzle/sqlite-proofs.ts +202 -0
  480. package/src/drizzle/sqlite-sessions.ts +971 -0
  481. package/src/drizzle/sqlite.ts +705 -0
  482. package/src/drizzle/standalone-guard.ts +38 -0
  483. package/src/drizzle/step-up-model.ts +128 -0
  484. package/src/drizzle/step-up-state.ts +139 -0
  485. package/src/drizzle/totp-model.ts +92 -0
  486. package/src/drizzle/totp-state.ts +525 -0
  487. package/src/drizzle/totp-target.ts +308 -0
  488. package/src/drizzle/transaction-execution.ts +12 -0
  489. package/src/drizzle/transaction-mysql.ts +104 -0
  490. package/src/drizzle/transaction-owner.ts +10 -0
  491. package/src/index.ts +4 -0
  492. package/src/internal/configuration.ts +282 -0
  493. package/src/internal/drizzle-migrations.ts +30 -0
  494. package/src/internal/drizzle-postgres.ts +81 -0
  495. package/src/internal/drizzle-sqlite.ts +79 -0
  496. package/src/internal/email-kernel.ts +1532 -0
  497. package/src/internal/mapping-error.ts +36 -0
  498. package/src/internal/passkey/admission.ts +362 -0
  499. package/src/internal/passkey/credentials.ts +511 -0
  500. package/src/internal/passkey/enrollment.ts +326 -0
  501. package/src/internal/passkey/flow.ts +1062 -0
  502. package/src/internal/passkey/management.ts +422 -0
  503. package/src/internal/passkey/registration-ceremony.ts +283 -0
  504. package/src/internal/passkey/registration-custody.ts +91 -0
  505. package/src/internal/passkey/registration-write.ts +426 -0
  506. package/src/internal/passkey/state.ts +354 -0
  507. package/src/internal/passkey/target.ts +586 -0
  508. package/src/internal/passkey/write-state.ts +681 -0
  509. package/src/internal/passkey/write-target.ts +452 -0
  510. package/src/internal/passkey-kernel.ts +102 -0
  511. package/src/internal/passkeys.ts +597 -0
  512. package/src/internal/password-kernel.ts +2122 -0
  513. package/src/internal/persistence.ts +590 -0
  514. package/src/internal/phone-kernel.ts +843 -0
  515. package/src/internal/proof-kernel.ts +1949 -0
  516. package/src/internal/query-operations.ts +26 -0
  517. package/src/internal/registration.ts +160 -0
  518. package/src/internal/session-kernel.ts +2210 -0
  519. package/src/internal/sql-persistence.ts +45 -0
  520. package/src/internal/sql-query.ts +298 -0
  521. package/src/internal/sql-table.ts +89 -0
  522. package/src/internal/storage-mapping.ts +706 -0
  523. package/src/internal/storage-tables.ts +408 -0
  524. package/src/internal/storage-validation.ts +117 -0
  525. package/src/internal/transaction-execution-kernel.ts +317 -0
  526. package/src/internal/transaction-kernel.ts +582 -0
@@ -0,0 +1,2656 @@
1
+ /* oxlint-disable no-explicit-any -- D1 plans bridge consumer-owned Drizzle rows and SQL statements. */
2
+ import type { D1Client } from "@effect/sql-d1/D1Client";
3
+ import {
4
+ coordinateCommit,
5
+ CurrentCommitJournal,
6
+ hasCommitScope,
7
+ type CommitJournal,
8
+ type PreparedCommit,
9
+ LifecycleHooks,
10
+ HookConfigurationError,
11
+ } from "@yielded/auth/Hooks";
12
+ import type { LoginIdentifier } from "@yielded/auth/Identity";
13
+ import {
14
+ PasswordUnavailable,
15
+ PasswordPersistence,
16
+ type PasswordMutationInput,
17
+ type PreparePasswordCommit,
18
+ snapshotPasswordCredential,
19
+ snapshotPasswordRequirement,
20
+ snapshotPasswordRevision,
21
+ type PasswordAction,
22
+ type PasswordAttemptDecision,
23
+ type PasswordCredentialSnapshot,
24
+ type PasswordMutationDecision,
25
+ type PasswordAttemptPolicy,
26
+ } from "@yielded/auth/Password";
27
+ import { reportPersistenceFailure } from "@yielded/auth/Persistence";
28
+ import {
29
+ type ProofCompletionPlan,
30
+ type ProofCompletionDecision,
31
+ type ProofCompletionInput,
32
+ ProofUnavailable,
33
+ } from "@yielded/auth/Proofs";
34
+ import {
35
+ snapshotAuthenticationEvidence,
36
+ AuthenticationEvidence,
37
+ AuthenticationRequirement,
38
+ type SecurityRevision,
39
+ } from "@yielded/auth/Sessions";
40
+ import {
41
+ and,
42
+ count,
43
+ eq,
44
+ gte,
45
+ inArray,
46
+ lte,
47
+ notExists,
48
+ sql,
49
+ type AnyRelations,
50
+ type SQL,
51
+ type Table,
52
+ } from "drizzle-orm";
53
+ import type { EffectDrizzleQueryError } from "drizzle-orm/effect-core";
54
+ import type { EffectSQLiteD1Database } from "drizzle-orm/effect-d1";
55
+ import type { AnySQLiteTable } from "drizzle-orm/sqlite-core";
56
+ import { Cause, DateTime, Effect, Option, Predicate, Redacted, Schema, Context } from "effect";
57
+ import type { Statement } from "effect/unstable/sql/Statement";
58
+
59
+ import { balancedD1And } from "./d1-generated-statement";
60
+ import { CurrentD1PlanningDatabase, makeD1Owner } from "./d1-planning";
61
+ import { compileD1ProofCompletionPlan, type D1ProtectedProofMutation } from "./d1-proofs";
62
+ import { D1BatchStatements } from "./D1BatchStatements";
63
+ import { column, isMappedConstraintConflict, PersistenceMappingError, updateValues } from "./model";
64
+ import {
65
+ type AnyPasswordRegistrationMapping,
66
+ type D1PasswordPersistenceMapping,
67
+ type PasswordAttemptRecord,
68
+ type PasswordRateScopeKind,
69
+ type PasswordRegistrationMapping,
70
+ type PasswordScopeKeys,
71
+ requiredPasswordConstraints,
72
+ requiredPasswordRegistrationConstraints,
73
+ } from "./password-model";
74
+ import type { PasswordRegistrationAuthority } from "./password-registration";
75
+ import type { D1ProofPersistenceMapping } from "./proof-model";
76
+ import type { SuppliedService } from "./SuppliedService";
77
+
78
+ type PlanPrepare<Method extends (...args: any[]) => any, A> = (
79
+ value: Parameters<Parameters<Method>[1]>[0],
80
+ journal: CommitJournal,
81
+ ) => PreparedCommit<A>;
82
+
83
+ type Database = EffectSQLiteD1Database<AnyRelations> & { readonly $client: D1Client };
84
+ type Mapping = D1PasswordPersistenceMapping<
85
+ Table,
86
+ Table,
87
+ Table,
88
+ Table,
89
+ Table,
90
+ Table,
91
+ Table,
92
+ Table,
93
+ unknown
94
+ >;
95
+ type RegistrationMapping<Registration> = AnyPasswordRegistrationMapping<Registration>;
96
+ type AdapterFailure = PersistenceMappingError | EffectDrizzleQueryError;
97
+ const mapAdapter = <A>(effect: Effect.Effect<A, AdapterFailure>) => effect;
98
+
99
+ interface Planned<A> {
100
+ readonly receipt: A;
101
+ readonly statements: ReadonlyArray<Statement<any>>;
102
+ readonly retryable?: (cause: unknown) => boolean;
103
+ readonly journalGuard?: PreparedCommit<void>;
104
+ }
105
+
106
+ const unavailable = () => PasswordUnavailable.make({});
107
+
108
+ const everyFailureMatches = (
109
+ cause: Cause.Cause<unknown>,
110
+ classify: ((cause: unknown) => boolean) | undefined,
111
+ ): boolean =>
112
+ classify !== undefined &&
113
+ cause.reasons.length > 0 &&
114
+ cause.reasons.every((reason) => Cause.isFailReason(reason) && classify(Cause.fail(reason.error)));
115
+
116
+ const validConstraints = (mapping: Mapping) =>
117
+ Object.entries(requiredPasswordConstraints).every(
118
+ ([key, value]) =>
119
+ mapping.constraints[key as keyof typeof requiredPasswordConstraints] === value,
120
+ );
121
+
122
+ const validRegistrationConstraints = <Registration>(mapping: RegistrationMapping<Registration>) =>
123
+ Object.entries(requiredPasswordRegistrationConstraints).every(
124
+ ([key, value]) =>
125
+ mapping.constraints[key as keyof typeof requiredPasswordRegistrationConstraints] === value,
126
+ );
127
+
128
+ const containsFailure = (failure: unknown, predicate: (value: unknown) => boolean): boolean => {
129
+ const seen = new Set<unknown>();
130
+ const pending: Array<unknown> = [failure];
131
+
132
+ for (let inspected = 0; inspected < 32 && pending.length > 0; inspected++) {
133
+ const current = pending.shift();
134
+
135
+ if (current === undefined || current === null || seen.has(current)) continue;
136
+ seen.add(current);
137
+ if (predicate(current)) return true;
138
+ if (Cause.isCause(current)) {
139
+ for (const reason of current.reasons) {
140
+ if (Cause.isFailReason(reason)) pending.push(reason.error);
141
+ else if (Cause.isDieReason(reason)) pending.push(reason.defect);
142
+ }
143
+ continue;
144
+ }
145
+ if (typeof current !== "object") continue;
146
+
147
+ const wrapped = current as {
148
+ readonly cause?: unknown;
149
+ readonly reason?: unknown;
150
+ readonly message?: unknown;
151
+ };
152
+
153
+ if (wrapped.cause !== undefined) pending.push(wrapped.cause);
154
+ if (wrapped.reason !== undefined) pending.push(wrapped.reason);
155
+ if (wrapped.message !== undefined) pending.push(wrapped.message);
156
+ }
157
+
158
+ return false;
159
+ };
160
+
161
+ // Match only workerd's native SQLite diagnostic. A wrapper that merely echoes
162
+ // the bound marker is not evidence that the guard ran and rolled back the batch.
163
+ const isGuardFailure = (cause: unknown, marker: string) => {
164
+ const path = `$[${marker.replaceAll("'", "''")}]`;
165
+ const native = `bad JSON path: '${path}': SQLITE_ERROR`;
166
+
167
+ return containsFailure(cause, (value) => value === native || value === `D1_ERROR: ${native}`);
168
+ };
169
+
170
+ const statement = Effect.fn(function* (query: {
171
+ readonly toSQL: () => { readonly sql: string; readonly params: unknown[] };
172
+ }) {
173
+ const database = yield* CurrentD1PlanningDatabase;
174
+ const rendered = query.toSQL();
175
+
176
+ return database.$client.unsafe(rendered.sql, rendered.params);
177
+ });
178
+
179
+ const assertion = Effect.fn(function* (condition: SQL, marker: string) {
180
+ const database = yield* CurrentD1PlanningDatabase;
181
+
182
+ return yield* statement(
183
+ database
184
+ .select({
185
+ ok: sql`case when ${condition} then 1 else json_extract('[]', ${`$[${marker}]`}) end`.as(
186
+ "ok",
187
+ ),
188
+ })
189
+ .from(sql`(select 1)`),
190
+ );
191
+ });
192
+
193
+ const readEngineNowMillis = Effect.fn("Drizzle.readEngineNowMillis")(function* (mapping: Mapping) {
194
+ const database = yield* CurrentD1PlanningDatabase;
195
+
196
+ return yield* database
197
+ .select({ value: mapping.d1.engineNowMillis.as("engine_now_millis") })
198
+ .from(sql`(select 1)`)
199
+ .pipe(
200
+ Effect.flatMap((rows) => {
201
+ const value = Number(rows[0]?.value);
202
+
203
+ return Number.isFinite(value) ? Effect.succeed(value) : Effect.fail(unavailable());
204
+ }),
205
+ );
206
+ });
207
+
208
+ const driverValues = (values: object, entries: ReadonlyArray<readonly [string, unknown]>) => ({
209
+ ...values,
210
+ ...Object.fromEntries(entries),
211
+ });
212
+
213
+ const allocate = <A>(
214
+ asyncValue: Effect.Effect<A, PersistenceMappingError> | undefined,
215
+ syncValue: (() => A) | undefined,
216
+ ) =>
217
+ asyncValue !== undefined
218
+ ? asyncValue
219
+ : syncValue === undefined
220
+ ? Effect.fail(unavailable())
221
+ : Effect.try({
222
+ try: syncValue,
223
+ catch: (cause) => new PersistenceMappingError({ operation: "mapping", cause }),
224
+ });
225
+
226
+ const allocateNextSecurityRevision = (mapping: Mapping, current: SecurityRevision) =>
227
+ allocate(
228
+ mapping.subject.nextSecurityRevision?.(current) ?? mapping.allocateRevision,
229
+ mapping.subject.nextSecurityRevisionSync === undefined
230
+ ? mapping.allocateRevisionSync
231
+ : () => mapping.subject.nextSecurityRevisionSync!(current),
232
+ );
233
+
234
+ const subjectColumns = (mapping: Mapping) => ({
235
+ id: column(mapping.subject.table, mapping.subject.id),
236
+ status: column(mapping.subject.table, mapping.subject.status),
237
+ securityRevision: column(mapping.subject.table, mapping.subject.securityRevision),
238
+ });
239
+
240
+ const identifierColumns = (mapping: Mapping) => ({
241
+ namespace: column(mapping.identifier.table, mapping.identifier.namespace),
242
+ value: column(mapping.identifier.table, mapping.identifier.value),
243
+ subjectId: column(mapping.identifier.table, mapping.identifier.subjectId),
244
+ verifiedAt: column(mapping.identifier.table, mapping.identifier.verifiedAt),
245
+ bindingRevision: column(mapping.identifier.table, mapping.identifier.bindingRevision),
246
+ });
247
+
248
+ const credentialColumns = (mapping: Mapping) => ({
249
+ moduleId: column(mapping.credential.table, mapping.credential.moduleId),
250
+ subjectId: column(mapping.credential.table, mapping.credential.subjectId),
251
+ credentialId: column(mapping.credential.table, mapping.credential.credentialId),
252
+ credentialRevision: column(mapping.credential.table, mapping.credential.credentialRevision),
253
+ verifierVersion: column(mapping.credential.table, mapping.credential.verifierVersion),
254
+ verifier: column(mapping.credential.table, mapping.credential.verifier),
255
+ normalization: column(mapping.credential.table, mapping.credential.normalization),
256
+ });
257
+
258
+ const authorityColumns = (mapping: Mapping) => ({
259
+ subjectId: column(mapping.authorityCredential.table, mapping.authorityCredential.subjectId),
260
+ credentialId: column(mapping.authorityCredential.table, mapping.authorityCredential.credentialId),
261
+ revision: column(mapping.authorityCredential.table, mapping.authorityCredential.revision),
262
+ status:
263
+ mapping.authorityCredential.status === undefined
264
+ ? undefined
265
+ : column(mapping.authorityCredential.table, mapping.authorityCredential.status),
266
+ });
267
+
268
+ const attemptColumns = (mapping: Mapping) => ({
269
+ moduleId: column(mapping.attempt.table, mapping.attempt.moduleId),
270
+ action: column(mapping.attempt.table, mapping.attempt.action),
271
+ attemptId: column(mapping.attempt.table, mapping.attempt.attemptId),
272
+ identifierNamespace: column(mapping.attempt.table, mapping.attempt.identifierNamespace),
273
+ identifierValue: column(mapping.attempt.table, mapping.attempt.identifierValue),
274
+ subjectId: column(mapping.attempt.table, mapping.attempt.subjectId),
275
+ credentialId: column(mapping.attempt.table, mapping.attempt.credentialId),
276
+ securityRevision: column(mapping.attempt.table, mapping.attempt.securityRevision),
277
+ credentialRevision: column(mapping.attempt.table, mapping.attempt.credentialRevision),
278
+ verifierVersion: column(mapping.attempt.table, mapping.attempt.verifierVersion),
279
+ identifierBindingRevision: column(
280
+ mapping.attempt.table,
281
+ mapping.attempt.identifierBindingRevision,
282
+ ),
283
+ admittedAt: column(mapping.attempt.table, mapping.attempt.admittedAt),
284
+ deadline: column(mapping.attempt.table, mapping.attempt.deadline),
285
+ retentionUntil: column(mapping.attempt.table, mapping.attempt.retentionUntil),
286
+ state: column(mapping.attempt.table, mapping.attempt.state),
287
+ });
288
+
289
+ const scopeColumns = (mapping: Mapping) => ({
290
+ moduleId: column(mapping.rateScope.table, mapping.rateScope.moduleId),
291
+ action: column(mapping.rateScope.table, mapping.rateScope.action),
292
+ scopeKind: column(mapping.rateScope.table, mapping.rateScope.scopeKind),
293
+ scopeKey: column(mapping.rateScope.table, mapping.rateScope.scopeKey),
294
+ });
295
+
296
+ const chargeColumns = (mapping: Mapping) => ({
297
+ moduleId: column(mapping.charge.table, mapping.charge.moduleId),
298
+ action: column(mapping.charge.table, mapping.charge.action),
299
+ scopeKind: column(mapping.charge.table, mapping.charge.scopeKind),
300
+ scopeKey: column(mapping.charge.table, mapping.charge.scopeKey),
301
+ attemptId: column(mapping.charge.table, mapping.charge.attemptId),
302
+ occurredAt: column(mapping.charge.table, mapping.charge.occurredAt),
303
+ retentionUntil: column(mapping.charge.table, mapping.charge.retentionUntil),
304
+ });
305
+
306
+ const commandColumns = (mapping: Mapping) => ({
307
+ moduleId: column(mapping.command.table, mapping.command.moduleId),
308
+ commandId: column(mapping.command.table, mapping.command.commandId),
309
+ action: column(mapping.command.table, mapping.command.action),
310
+ bindingDigest: column(mapping.command.table, mapping.command.bindingDigest),
311
+ decision: column(mapping.command.table, mapping.command.decision),
312
+ retentionUntil: column(mapping.command.table, mapping.command.retentionUntil),
313
+ });
314
+
315
+ const readIdentifier = Effect.fn("Drizzle.readIdentifier")(function* (
316
+ mapping: Mapping,
317
+ identifier: LoginIdentifier,
318
+ ) {
319
+ const database = yield* CurrentD1PlanningDatabase;
320
+
321
+ const c = identifierColumns(mapping);
322
+
323
+ return yield* database
324
+ .select()
325
+ .from(mapping.identifier.table)
326
+ .where(and(eq(c.namespace, identifier.namespace), eq(c.value, identifier.value)))
327
+ .limit(1);
328
+ });
329
+
330
+ const readSubjectNative = Effect.fn("Drizzle.readSubjectNative")(function* (
331
+ mapping: Mapping,
332
+ nativeSubjectId: unknown,
333
+ ) {
334
+ const database = yield* CurrentD1PlanningDatabase;
335
+
336
+ return yield* database
337
+ .select()
338
+ .from(mapping.subject.table)
339
+ .where(eq(subjectColumns(mapping).id, nativeSubjectId))
340
+ .limit(1);
341
+ });
342
+
343
+ const readCredentialNative = Effect.fn("Drizzle.readCredentialNative")(function* (
344
+ mapping: Mapping,
345
+ moduleId: string,
346
+ nativeSubjectId: unknown,
347
+ ) {
348
+ const database = yield* CurrentD1PlanningDatabase;
349
+
350
+ const c = credentialColumns(mapping);
351
+
352
+ return yield* database
353
+ .select()
354
+ .from(mapping.credential.table)
355
+ .where(and(eq(c.moduleId, moduleId), eq(c.subjectId, nativeSubjectId)))
356
+ .limit(1);
357
+ });
358
+
359
+ interface Resolved {
360
+ readonly nativeSubjectId: unknown;
361
+ readonly subject: any;
362
+ readonly identifier: any;
363
+ readonly credential?: any;
364
+ readonly snapshot?: PasswordCredentialSnapshot;
365
+ }
366
+
367
+ const resolveCredential = Effect.fn("DrizzleD1Password.resolveCredential")(function* (
368
+ mapping: Mapping,
369
+ moduleId: string,
370
+ identifier: LoginIdentifier,
371
+ requestedSubjectId?: string,
372
+ ) {
373
+ const identifierRow = (yield* readIdentifier(mapping, identifier))[0];
374
+
375
+ if (identifierRow === undefined || !mapping.identifier.isCurrent(identifierRow)) return undefined;
376
+ const nativeSubjectId = identifierRow[mapping.identifier.subjectId];
377
+ const subject = (yield* readSubjectNative(mapping, nativeSubjectId))[0];
378
+
379
+ if (subject === undefined || !mapping.subject.isActiveStatus(subject[mapping.subject.status]))
380
+ return undefined;
381
+ const subjectId = yield* mapping.subjectId.toSubject(nativeSubjectId);
382
+
383
+ if (requestedSubjectId !== undefined && subjectId !== requestedSubjectId) return undefined;
384
+ const credential = (yield* readCredentialNative(mapping, moduleId, nativeSubjectId))[0];
385
+
386
+ if (credential === undefined) return { nativeSubjectId, subject, identifier: identifierRow };
387
+
388
+ const snapshot = yield* mapping.credential
389
+ .decode({ moduleId, subject, identifier: identifierRow, credential })
390
+ .pipe(Effect.flatMap(snapshotPasswordCredential));
391
+
392
+ return { nativeSubjectId, subject, identifier: identifierRow, credential, snapshot };
393
+ });
394
+
395
+ interface ScopeEntry {
396
+ readonly kind: PasswordRateScopeKind;
397
+ readonly key: string;
398
+ readonly limit: number;
399
+ readonly windowMillis: number;
400
+ }
401
+
402
+ const scopeEntries = (keys: PasswordScopeKeys, policy: PasswordAttemptPolicy) => {
403
+ const entries: ScopeEntry[] = [
404
+ { kind: "action", key: keys.action, ...policy.action },
405
+ { kind: "identifier", key: keys.identifier, ...policy.identifier },
406
+ ];
407
+
408
+ if (keys.subject !== undefined)
409
+ entries.push({ kind: "subject", key: keys.subject, ...policy.subject });
410
+
411
+ return entries;
412
+ };
413
+
414
+ const scopeAdmissionCondition = (
415
+ mapping: Mapping,
416
+ moduleId: string,
417
+ action: "sign-in" | "change",
418
+ entry: ScopeEntry,
419
+ policy: PasswordAttemptPolicy,
420
+ identifier: LoginIdentifier,
421
+ nativeSubjectId: unknown | undefined,
422
+ ) => {
423
+ const c = chargeColumns(mapping);
424
+ const a = attemptColumns(mapping);
425
+
426
+ const pending =
427
+ entry.kind === "action"
428
+ ? and(eq(a.moduleId, moduleId), eq(a.action, action), eq(a.state, "pending"))
429
+ : entry.kind === "identifier"
430
+ ? and(
431
+ eq(a.moduleId, moduleId),
432
+ eq(a.action, action),
433
+ eq(a.identifierNamespace, identifier.namespace),
434
+ eq(a.identifierValue, identifier.value),
435
+ eq(a.state, "pending"),
436
+ )
437
+ : and(
438
+ eq(a.moduleId, moduleId),
439
+ eq(a.action, action),
440
+ eq(a.subjectId, nativeSubjectId),
441
+ eq(a.state, "pending"),
442
+ );
443
+
444
+ return sql`(select count(*) from ${mapping.charge.table} where ${c.moduleId} = ${sql.param(moduleId, c.moduleId)} and ${c.action} = ${sql.param(action, c.action)} and ${c.scopeKind} = ${sql.param(entry.kind, c.scopeKind)} and ${c.scopeKey} = ${sql.param(entry.key, c.scopeKey)} and ${c.occurredAt} >= ${mapping.d1.engineInstantMinus(entry.windowMillis)}) < ${entry.limit} and (select count(*) from ${mapping.attempt.table} where ${pending}) < ${policy.maximumPending}`;
445
+ };
446
+
447
+ const readScopeAdmission = Effect.fn("DrizzleD1Password.readScopeAdmission")(function* (
448
+ mapping: Mapping,
449
+ moduleId: string,
450
+ action: "sign-in" | "change",
451
+ entries: ReadonlyArray<ScopeEntry>,
452
+ policy: PasswordAttemptPolicy,
453
+ identifier: LoginIdentifier,
454
+ nativeSubjectId: unknown | undefined,
455
+ now: number,
456
+ ) {
457
+ const database = yield* CurrentD1PlanningDatabase;
458
+
459
+ const c = chargeColumns(mapping);
460
+ const a = attemptColumns(mapping);
461
+ const admitted: ScopeEntry[] = [];
462
+
463
+ for (let index = 0; index < entries.length; index++) {
464
+ const entry = entries[index]!;
465
+
466
+ const chargeRows = yield* mapAdapter(
467
+ database
468
+ .select({ total: count() })
469
+ .from(mapping.charge.table)
470
+ .where(
471
+ and(
472
+ eq(c.moduleId, moduleId),
473
+ eq(c.action, action),
474
+ eq(c.scopeKind, entry.kind),
475
+ eq(c.scopeKey, entry.key),
476
+ gte(c.occurredAt, mapping.encodeInstant(now - entry.windowMillis)),
477
+ ),
478
+ ),
479
+ );
480
+
481
+ const pendingWhere =
482
+ entry.kind === "action"
483
+ ? and(eq(a.moduleId, moduleId), eq(a.action, action), eq(a.state, "pending"))
484
+ : entry.kind === "identifier"
485
+ ? and(
486
+ eq(a.moduleId, moduleId),
487
+ eq(a.action, action),
488
+ eq(a.identifierNamespace, identifier.namespace),
489
+ eq(a.identifierValue, identifier.value),
490
+ eq(a.state, "pending"),
491
+ )
492
+ : and(
493
+ eq(a.moduleId, moduleId),
494
+ eq(a.action, action),
495
+ eq(a.subjectId, nativeSubjectId),
496
+ eq(a.state, "pending"),
497
+ );
498
+
499
+ const pendingRows = yield* mapAdapter(
500
+ database.select({ total: count() }).from(mapping.attempt.table).where(pendingWhere),
501
+ );
502
+
503
+ const admittedHere =
504
+ Number(chargeRows[0]?.total ?? 0) < entry.limit &&
505
+ Number(pendingRows[0]?.total ?? 0) < policy.maximumPending;
506
+
507
+ if (admittedHere) admitted.push(entry);
508
+ else if (index === 0) break;
509
+ }
510
+
511
+ return admitted;
512
+ });
513
+
514
+ const evidenceProofsJson = Schema.encodeSync(
515
+ Schema.fromJsonString(AuthenticationEvidence.fields.proofs),
516
+ );
517
+
518
+ const alternativesJson = Schema.encodeSync(
519
+ Schema.fromJsonString(AuthenticationRequirement.fields.alternatives),
520
+ );
521
+
522
+ const assuranceCondition = (
523
+ mapping: Mapping,
524
+ evidence: PasswordMutationInput["authorization"]["evidence"],
525
+ requirement: PasswordMutationInput["authorization"]["requirement"],
526
+ ) => {
527
+ const proofs = evidenceProofsJson(evidence.proofs);
528
+ const fresh = sql`json_extract(proof.value, '$.verifiedAt') <= ${mapping.d1.engineNowMillis} and ${mapping.d1.engineNowMillis} - json_extract(proof.value, '$.verifiedAt') < ${requirement.maximumAgeMillis}`;
529
+
530
+ return sql`exists(
531
+ select 1 from json_each(${alternativesJson(requirement.alternatives)}) as alternative
532
+ where not exists(select 1 from json_each(${proofs}) as proof where json_extract(proof.value, '$.verifiedAt') > ${mapping.d1.engineNowMillis})
533
+ and not exists(select 1 from json_each(alternative.value, '$.factors') as factor where not exists(select 1 from json_each(${proofs}) as proof, json_each(proof.value, '$.factors') as held where ${fresh} and held.value = factor.value))
534
+ and (select count(distinct json_extract(proof.value, '$.credentialId')) from json_each(${proofs}) as proof where ${fresh}) >= json_extract(alternative.value, '$.minimumCredentials')
535
+ and exists(select 1 from json_each(${proofs}) as proof where ${fresh} and (json_extract(alternative.value, '$.userVerified') = 0 or json_extract(proof.value, '$.userVerified') = 1) and (json_extract(alternative.value, '$.phishingResistant') = 0 or json_extract(proof.value, '$.phishingResistant') = 1))
536
+ )`;
537
+ };
538
+
539
+ const sameIdentifier = (left: LoginIdentifier, right: LoginIdentifier) =>
540
+ left.namespace === right.namespace && left.value === right.value;
541
+
542
+ const sameSnapshot = (left: PasswordCredentialSnapshot, right: PasswordCredentialSnapshot) =>
543
+ left.moduleId === right.moduleId &&
544
+ left.revision.subjectId === right.revision.subjectId &&
545
+ left.revision.securityRevision === right.revision.securityRevision &&
546
+ left.credentialId === right.credentialId &&
547
+ left.credentialRevision === right.credentialRevision &&
548
+ left.verifierVersion === right.verifierVersion &&
549
+ left.normalization === right.normalization &&
550
+ left.identifierVerifiedAtMillis === right.identifierVerifiedAtMillis &&
551
+ left.revision.credentials.length === right.revision.credentials.length &&
552
+ left.revision.credentials.every((entry) =>
553
+ right.revision.credentials.some(
554
+ (other) => other.credentialId === entry.credentialId && other.revision === entry.revision,
555
+ ),
556
+ ) &&
557
+ left.identifierBindingRevision === right.identifierBindingRevision &&
558
+ sameIdentifier(left.identifier, right.identifier) &&
559
+ Redacted.value(left.verifier) === Redacted.value(right.verifier);
560
+
561
+ const evidenceSatisfiedAt = (
562
+ evidence: PasswordMutationInput["authorization"]["evidence"],
563
+ requirement: PasswordMutationInput["authorization"]["requirement"],
564
+ now: number,
565
+ ) => {
566
+ if (evidence.proofs.length > 64 || evidence.revision.credentials.length > 64) return false;
567
+ const revisions = new Map<string, string>();
568
+
569
+ for (const revision of evidence.revision.credentials) {
570
+ if (revisions.has(revision.credentialId)) return false;
571
+ revisions.set(revision.credentialId, revision.revision);
572
+ }
573
+ if (evidence.proofs.some((proof) => !revisions.has(proof.credentialId))) return false;
574
+ if (evidence.proofs.some((proof) => now < DateTime.toEpochMillis(proof.verifiedAt))) return false;
575
+
576
+ const fresh = evidence.proofs.filter(
577
+ (proof) => now - DateTime.toEpochMillis(proof.verifiedAt) < requirement.maximumAgeMillis,
578
+ );
579
+
580
+ const factors = new Set(fresh.flatMap((proof) => proof.factors));
581
+ const credentials = new Set(fresh.map((proof) => proof.credentialId));
582
+
583
+ return requirement.alternatives.some(
584
+ (alternative) =>
585
+ alternative.factors.every((factor) => factors.has(factor)) &&
586
+ credentials.size >= alternative.minimumCredentials &&
587
+ fresh.some(
588
+ (proof) =>
589
+ (!alternative.userVerified || proof.userVerified) &&
590
+ (!alternative.phishingResistant || proof.phishingResistant),
591
+ ),
592
+ );
593
+ };
594
+
595
+ const snapshotMutationInput = Effect.fn("DrizzleD1Password.snapshotMutationInput")(function* (
596
+ input: PasswordMutationInput,
597
+ ) {
598
+ const expectedRevision = snapshotPasswordRevision(input.expectedRevision);
599
+
600
+ const evidence = yield* snapshotAuthenticationEvidence(input.authorization.evidence).pipe(
601
+ Effect.mapError(unavailable),
602
+ );
603
+
604
+ const requirement = yield* snapshotPasswordRequirement(input.authorization.requirement);
605
+
606
+ const credential =
607
+ input.credential === undefined
608
+ ? undefined
609
+ : yield* snapshotPasswordCredential(input.credential);
610
+
611
+ return Object.freeze({
612
+ ...input,
613
+ expectedRevision,
614
+ replacement: Object.freeze({
615
+ verifier: Redacted.make(Redacted.value(input.replacement.verifier)),
616
+ normalization: input.replacement.normalization,
617
+ }),
618
+ invalidation: Object.freeze({ ...input.invalidation }),
619
+ ...(credential === undefined ? {} : { credential }),
620
+ authorization: Object.freeze({
621
+ challenge: Object.freeze({
622
+ ...input.authorization.challenge,
623
+ revision: snapshotPasswordRevision(input.authorization.challenge.revision),
624
+ }),
625
+ evidence,
626
+ requirement,
627
+ }),
628
+ });
629
+ });
630
+
631
+ const completionMatchesPassword = (
632
+ input: PasswordMutationInput & { readonly completion: ProofCompletionPlan },
633
+ ) => {
634
+ const completion = input.completion.input;
635
+ const binding = completion.binding;
636
+
637
+ if (
638
+ completion.moduleId !== `${input.moduleId}/reset` ||
639
+ completion.purpose !== "password-reset" ||
640
+ binding._tag !== "Subject" ||
641
+ input.credential === undefined ||
642
+ binding.revision.subjectId !== input.expectedRevision.subjectId ||
643
+ binding.revision.securityRevision !== input.expectedRevision.securityRevision ||
644
+ !sameIdentifier(binding.identifier, input.credential.identifier)
645
+ )
646
+ return false;
647
+
648
+ const expected = [...input.expectedRevision.credentials].sort((left, right) =>
649
+ left.credentialId.localeCompare(right.credentialId),
650
+ );
651
+
652
+ const actual = [...binding.revision.credentials].sort((left, right) =>
653
+ left.credentialId.localeCompare(right.credentialId),
654
+ );
655
+
656
+ return (
657
+ expected.length === actual.length &&
658
+ expected.every(
659
+ (item, index) =>
660
+ item.credentialId === actual[index]!.credentialId &&
661
+ item.revision === actual[index]!.revision,
662
+ )
663
+ );
664
+ };
665
+
666
+ const probeProofCompletion = Effect.fn("DrizzleD1.probeProofCompletion")(function* (
667
+ mapping: D1ProofPersistenceMapping<any, any, any, any, any, any, any, any, any, any, any, any>,
668
+ input: ProofCompletionInput,
669
+ ) {
670
+ const database = yield* CurrentD1PlanningDatabase;
671
+
672
+ const plan: ProofCompletionPlan = {
673
+ input,
674
+ prepare: (decision, journal, project) => journal.prepare(project(decision)),
675
+ };
676
+
677
+ const marker = `effect-auth-proof-guard:protected:${input.continuationId}`;
678
+
679
+ const once = coordinateCommit(
680
+ () =>
681
+ Effect.gen(function* () {
682
+ const compiled = yield* compileD1ProofCompletionPlan(
683
+ mapping,
684
+ plan,
685
+ { statements: [], appliedCondition: sql`false` },
686
+ () => false,
687
+ );
688
+
689
+ if (compiled.statements.length === 0) return false;
690
+ yield* database.$client.batch(compiled.statements);
691
+
692
+ return false;
693
+ }),
694
+ { mode: "batch" },
695
+ );
696
+
697
+ return yield* once.pipe(
698
+ Effect.as(false),
699
+ Effect.catchCause((cause) =>
700
+ isGuardFailure(cause, marker) ? Effect.succeed(true) : Effect.failCause(cause),
701
+ ),
702
+ );
703
+ });
704
+
705
+ const identifierCurrentCondition = (
706
+ mapping: Mapping,
707
+ input: PasswordMutationInput,
708
+ nativeSubjectId: unknown,
709
+ nativeVerifiedAt: unknown,
710
+ ) => {
711
+ if (input.credential === undefined) return sql`true`;
712
+
713
+ const ic = identifierColumns(mapping),
714
+ snapshot = input.credential;
715
+
716
+ return and(
717
+ mapping.identifier.d1CurrentCondition({ identifier: snapshot.identifier, nativeSubjectId }),
718
+ sql`exists(select 1 from ${mapping.identifier.table} where ${ic.namespace} = ${sql.param(snapshot.identifier.namespace, ic.namespace)} and ${ic.value} = ${sql.param(snapshot.identifier.value, ic.value)} and ${ic.subjectId} = ${sql.param(nativeSubjectId, ic.subjectId)} and ${ic.bindingRevision} = ${sql.param(snapshot.identifierBindingRevision, ic.bindingRevision)} and ${Predicate.isNullish(nativeVerifiedAt) ? sql`${ic.verifiedAt} is null` : sql`${ic.verifiedAt} = ${sql.param(nativeVerifiedAt, ic.verifiedAt)}`})`,
719
+ )!;
720
+ };
721
+
722
+ const readAuthority = Effect.fn("DrizzleD1Password.readAuthority")(function* (
723
+ mapping: Mapping,
724
+ input: PasswordMutationInput,
725
+ action: PasswordAction,
726
+ ) {
727
+ const database = yield* CurrentD1PlanningDatabase;
728
+
729
+ if (
730
+ input.authorization.evidence.proofs.length > 64 ||
731
+ input.authorization.evidence.revision.credentials.length > 64
732
+ )
733
+ return undefined;
734
+
735
+ const revisionIds = new Set(
736
+ input.authorization.evidence.revision.credentials.map((item) => item.credentialId),
737
+ );
738
+
739
+ if (
740
+ revisionIds.size !== input.authorization.evidence.revision.credentials.length ||
741
+ input.authorization.evidence.proofs.some((proof) => !revisionIds.has(proof.credentialId))
742
+ )
743
+ return undefined;
744
+ const nativeSubjectId = yield* mapping.subjectId.toNative(input.expectedRevision.subjectId);
745
+ const subject = (yield* readSubjectNative(mapping, nativeSubjectId))[0];
746
+
747
+ if (
748
+ subject === undefined ||
749
+ !mapping.subject.isActiveStatus(subject[mapping.subject.status]) ||
750
+ subject[mapping.subject.securityRevision] !== input.expectedRevision.securityRevision ||
751
+ input.authorization.challenge.moduleId !== input.moduleId ||
752
+ input.authorization.challenge.action !== action ||
753
+ input.authorization.challenge.commandId !== input.commandId ||
754
+ input.authorization.challenge.revision.subjectId !== input.expectedRevision.subjectId ||
755
+ input.authorization.challenge.revision.securityRevision !==
756
+ input.expectedRevision.securityRevision ||
757
+ input.authorization.evidence.revision.subjectId !== input.expectedRevision.subjectId ||
758
+ input.authorization.evidence.revision.securityRevision !==
759
+ input.expectedRevision.securityRevision ||
760
+ (input.authorization.evidence.flowId as string) !== (input.commandId as string) ||
761
+ input.authorization.evidence.bindingDigest !== input.authorization.challenge.bindingDigest ||
762
+ (action === "add-password" &&
763
+ (input.credential !== undefined ||
764
+ input.authorization.challenge.targetCredentialId !== undefined)) ||
765
+ input.expectedRevision.credentials.some(
766
+ (expected) =>
767
+ !input.authorization.evidence.revision.credentials.some(
768
+ (actual) =>
769
+ actual.credentialId === expected.credentialId && actual.revision === expected.revision,
770
+ ),
771
+ )
772
+ )
773
+ return undefined;
774
+ let identifierCondition: SQL = sql`true`;
775
+
776
+ if (input.credential !== undefined) {
777
+ const identifier = (yield* readIdentifier(mapping, input.credential.identifier))[0];
778
+
779
+ if (
780
+ identifier === undefined ||
781
+ input.authorization.challenge.targetCredentialId !== input.credential.credentialId ||
782
+ !mapping.identifier.isCurrent(identifier) ||
783
+ !mapping.subjectId.equals(identifier[mapping.identifier.subjectId], nativeSubjectId) ||
784
+ identifier[mapping.identifier.bindingRevision] !== input.credential.identifierBindingRevision
785
+ )
786
+ return undefined;
787
+ const credential = (yield* readCredentialNative(mapping, input.moduleId, nativeSubjectId))[0];
788
+
789
+ if (credential === undefined) return undefined;
790
+
791
+ const snapshot = yield* mapping.credential.decode({
792
+ moduleId: input.moduleId,
793
+ subject,
794
+ identifier,
795
+ credential,
796
+ });
797
+
798
+ if (!sameSnapshot(snapshot, input.credential)) return undefined;
799
+ identifierCondition = identifierCurrentCondition(
800
+ mapping,
801
+ input,
802
+ nativeSubjectId,
803
+ identifier[mapping.identifier.verifiedAt],
804
+ );
805
+ const ic = identifierColumns(mapping);
806
+
807
+ identifierCondition = and(
808
+ identifierCondition,
809
+ sql`exists(select 1 from ${mapping.identifier.table} where ${ic.namespace} = ${sql.param(input.credential.identifier.namespace, ic.namespace)} and ${ic.value} = ${sql.param(input.credential.identifier.value, ic.value)} and ${ic.subjectId} = ${sql.param(nativeSubjectId, ic.subjectId)} and ${ic.bindingRevision} = ${sql.param(input.credential.identifierBindingRevision, ic.bindingRevision)})`,
810
+ )!;
811
+ }
812
+
813
+ const expected = [
814
+ ...new Map(
815
+ [
816
+ ...input.expectedRevision.credentials,
817
+ ...input.authorization.evidence.revision.credentials,
818
+ ].map((item) => [item.credentialId, item]),
819
+ ).values(),
820
+ ].sort((left, right) => left.credentialId.localeCompare(right.credentialId));
821
+
822
+ const ac = authorityColumns(mapping);
823
+
824
+ const rows =
825
+ expected.length === 0
826
+ ? []
827
+ : yield* database
828
+ .select()
829
+ .from(mapping.authorityCredential.table)
830
+ .where(
831
+ and(
832
+ eq(ac.subjectId, nativeSubjectId),
833
+ inArray(
834
+ ac.credentialId,
835
+ expected.map((item) => item.credentialId),
836
+ ),
837
+ ),
838
+ );
839
+
840
+ const sortedRows = [...rows].sort((left: any, right: any) =>
841
+ String(left[mapping.authorityCredential.credentialId]).localeCompare(
842
+ String(right[mapping.authorityCredential.credentialId]),
843
+ ),
844
+ );
845
+
846
+ if (
847
+ sortedRows.length !== expected.length ||
848
+ sortedRows.some(
849
+ (row: any, index) =>
850
+ row[mapping.authorityCredential.credentialId] !== expected[index]!.credentialId ||
851
+ row[mapping.authorityCredential.revision] !== expected[index]!.revision ||
852
+ (mapping.authorityCredential.status !== undefined &&
853
+ mapping.authorityCredential.isActiveStatus?.(row[mapping.authorityCredential.status]) !==
854
+ true),
855
+ )
856
+ )
857
+ return undefined;
858
+ const requirement = yield* mapping.subject.decodeActionRequirement(subject, action);
859
+ const engineNow = yield* readEngineNowMillis(mapping);
860
+
861
+ if (
862
+ !evidenceSatisfiedAt(
863
+ input.authorization.evidence,
864
+ input.authorization.requirement,
865
+ engineNow,
866
+ ) ||
867
+ !evidenceSatisfiedAt(input.authorization.evidence, requirement, engineNow)
868
+ )
869
+ return undefined;
870
+ if (
871
+ input.invalidation.existingSessions === "immediate" &&
872
+ mapping.sessionInvalidation !== "same-authority-immediate"
873
+ )
874
+ return undefined;
875
+ const s = subjectColumns(mapping);
876
+
877
+ const credentialConditions = expected.map(
878
+ (item) =>
879
+ sql`exists(select 1 from ${mapping.authorityCredential.table} where ${ac.subjectId} = ${sql.param(nativeSubjectId, ac.subjectId)} and ${ac.credentialId} = ${sql.param(item.credentialId, ac.credentialId)} and ${ac.revision} = ${sql.param(item.revision, ac.revision)}${ac.status === undefined ? sql`` : sql` and ${ac.status} = ${sql.param(mapping.authorityCredential.d1ActiveStatusValue, ac.status)}`})`,
880
+ );
881
+
882
+ return {
883
+ nativeSubjectId,
884
+ subject,
885
+ requirement,
886
+ identifierCondition,
887
+ condition: balancedD1And(
888
+ sql`exists(select 1 from ${mapping.subject.table} where ${s.id} = ${sql.param(nativeSubjectId, s.id)} and ${s.status} = ${sql.param(mapping.subject.d1ActiveStatusValue, s.status)} and ${s.securityRevision} = ${sql.param(input.expectedRevision.securityRevision, s.securityRevision)})`,
889
+ identifierCondition,
890
+ ...credentialConditions,
891
+ assuranceCondition(mapping, input.authorization.evidence, input.authorization.requirement),
892
+ assuranceCondition(mapping, input.authorization.evidence, requirement),
893
+ )!,
894
+ };
895
+ });
896
+
897
+ const commandAbsentCondition = (mapping: Mapping, moduleId: string, commandId: string) => {
898
+ const c = commandColumns(mapping);
899
+
900
+ return sql`not exists(select 1 from ${mapping.command.table} where ${c.moduleId} = ${sql.param(moduleId, c.moduleId)} and ${c.commandId} = ${sql.param(commandId, c.commandId)})`;
901
+ };
902
+
903
+ const credentialCurrentCondition = (
904
+ mapping: Mapping,
905
+ input: PasswordMutationInput,
906
+ nativeSubjectId: unknown,
907
+ ) => {
908
+ const c = credentialColumns(mapping);
909
+
910
+ return input.credential === undefined
911
+ ? sql`false`
912
+ : sql`exists(select 1 from ${mapping.credential.table} where ${c.moduleId} = ${sql.param(input.moduleId, c.moduleId)} and ${c.subjectId} = ${sql.param(nativeSubjectId, c.subjectId)} and ${c.credentialId} = ${sql.param(input.credential.credentialId, c.credentialId)} and ${c.credentialRevision} = ${sql.param(input.credential.credentialRevision, c.credentialRevision)} and ${c.verifierVersion} = ${sql.param(input.credential.verifierVersion, c.verifierVersion)} and ${c.verifier} = ${sql.param(Redacted.value(input.credential.verifier), c.verifier)} and ${c.normalization} = ${sql.param(input.credential.normalization, c.normalization)})`;
913
+ };
914
+
915
+ const admittedSnapshotCondition = (
916
+ mapping: Mapping,
917
+ resolved: Resolved | undefined,
918
+ moduleId: string,
919
+ ) => {
920
+ const snapshot = resolved?.snapshot;
921
+
922
+ if (snapshot === undefined || resolved === undefined) return sql`true`;
923
+ const s = subjectColumns(mapping);
924
+ const i = identifierColumns(mapping);
925
+ const c = credentialColumns(mapping);
926
+ const ac = authorityColumns(mapping);
927
+
928
+ return and(
929
+ sql`exists(select 1 from ${mapping.subject.table} where ${s.id} = ${sql.param(resolved.nativeSubjectId, s.id)} and ${s.status} = ${sql.param(mapping.subject.d1ActiveStatusValue, s.status)} and ${s.securityRevision} = ${sql.param(snapshot.revision.securityRevision, s.securityRevision)})`,
930
+ mapping.identifier.d1CurrentCondition({
931
+ identifier: snapshot.identifier,
932
+ nativeSubjectId: resolved.nativeSubjectId,
933
+ }),
934
+ sql`exists(select 1 from ${mapping.identifier.table} where ${i.namespace} = ${sql.param(snapshot.identifier.namespace, i.namespace)} and ${i.value} = ${sql.param(snapshot.identifier.value, i.value)} and ${i.subjectId} = ${sql.param(resolved.nativeSubjectId, i.subjectId)} and ${i.bindingRevision} = ${sql.param(snapshot.identifierBindingRevision, i.bindingRevision)})`,
935
+ sql`exists(select 1 from ${mapping.credential.table} where ${c.moduleId} = ${sql.param(moduleId, c.moduleId)} and ${c.subjectId} = ${sql.param(resolved.nativeSubjectId, c.subjectId)} and ${c.credentialId} = ${sql.param(snapshot.credentialId, c.credentialId)} and ${c.credentialRevision} = ${sql.param(snapshot.credentialRevision, c.credentialRevision)} and ${c.verifierVersion} = ${sql.param(snapshot.verifierVersion, c.verifierVersion)} and ${c.verifier} = ${sql.param(Redacted.value(snapshot.verifier), c.verifier)} and ${c.normalization} = ${sql.param(snapshot.normalization, c.normalization)})`,
936
+ ...snapshot.revision.credentials.map(
937
+ (item) =>
938
+ sql`exists(select 1 from ${mapping.authorityCredential.table} where ${ac.subjectId} = ${sql.param(resolved.nativeSubjectId, ac.subjectId)} and ${ac.credentialId} = ${sql.param(item.credentialId, ac.credentialId)} and ${ac.revision} = ${sql.param(item.revision, ac.revision)}${ac.status === undefined ? sql`` : sql` and ${ac.status} = ${sql.param(mapping.authorityCredential.d1ActiveStatusValue, ac.status)}`})`,
939
+ ),
940
+ )!;
941
+ };
942
+
943
+ const mutationStatements = Effect.fn(function* (
944
+ mapping: Mapping,
945
+ input: PasswordMutationInput,
946
+ nativeSubjectId: unknown,
947
+ revisions: {
948
+ readonly credentialRevision: SecurityRevision;
949
+ readonly verifierVersion: SecurityRevision;
950
+ readonly nextSecurityRevision: SecurityRevision;
951
+ },
952
+ now: number,
953
+ ) {
954
+ const database = yield* CurrentD1PlanningDatabase;
955
+ const c = credentialColumns(mapping);
956
+ const s = subjectColumns(mapping);
957
+ const ac = authorityColumns(mapping);
958
+
959
+ return [
960
+ yield* statement(
961
+ database
962
+ .update(mapping.credential.table)
963
+ .set(
964
+ mapping.credential.encodeReplacement({
965
+ replacement: input.replacement,
966
+ credentialRevision: revisions.credentialRevision,
967
+ verifierVersion: revisions.verifierVersion,
968
+ }),
969
+ )
970
+ .where(
971
+ and(
972
+ eq(c.moduleId, input.moduleId),
973
+ eq(c.subjectId, nativeSubjectId),
974
+ eq(c.credentialId, input.credential!.credentialId),
975
+ eq(c.credentialRevision, input.credential!.credentialRevision),
976
+ eq(c.verifierVersion, input.credential!.verifierVersion),
977
+ eq(c.verifier, Redacted.value(input.credential!.verifier)),
978
+ eq(c.normalization, input.credential!.normalization),
979
+ ),
980
+ ),
981
+ ),
982
+ yield* statement(
983
+ database
984
+ .update(mapping.subject.table)
985
+ .set(updateValues([[mapping.subject.securityRevision, revisions.nextSecurityRevision]]))
986
+ .where(
987
+ and(
988
+ eq(s.id, nativeSubjectId),
989
+ eq(s.securityRevision, input.expectedRevision.securityRevision),
990
+ ),
991
+ ),
992
+ ),
993
+ yield* statement(
994
+ database
995
+ .update(mapping.authorityCredential.table)
996
+ .set(mapping.authorityCredential.encodeRevision(revisions.credentialRevision))
997
+ .where(
998
+ and(
999
+ eq(ac.subjectId, nativeSubjectId),
1000
+ eq(ac.credentialId, input.credential!.credentialId),
1001
+ eq(ac.revision, input.credential!.credentialRevision),
1002
+ ),
1003
+ ),
1004
+ ),
1005
+ yield* statement(
1006
+ database.insert(mapping.command.table).values(
1007
+ mapping.command.encodeInsert({
1008
+ moduleId: input.moduleId,
1009
+ commandId: input.commandId,
1010
+ action: input.authorization.challenge.action,
1011
+ bindingDigest: input.authorization.challenge.bindingDigest,
1012
+ decision: "changed",
1013
+ retentionUntilMillis: now + mapping.commandRetentionMillis,
1014
+ }),
1015
+ ),
1016
+ ),
1017
+ ];
1018
+ });
1019
+
1020
+ const replacementApplied = (
1021
+ mapping: Mapping,
1022
+ input: PasswordMutationInput,
1023
+ nativeSubjectId: unknown,
1024
+ revisions: {
1025
+ readonly credentialRevision: SecurityRevision;
1026
+ readonly verifierVersion: SecurityRevision;
1027
+ readonly nextSecurityRevision: SecurityRevision;
1028
+ },
1029
+ now: number,
1030
+ identifierCondition: SQL,
1031
+ ) => {
1032
+ const c = credentialColumns(mapping);
1033
+ const s = subjectColumns(mapping);
1034
+ const ac = authorityColumns(mapping);
1035
+ const mc = commandColumns(mapping);
1036
+
1037
+ return and(
1038
+ sql`exists(select 1 from ${mapping.credential.table} where ${c.moduleId} = ${sql.param(input.moduleId, c.moduleId)} and ${c.subjectId} = ${sql.param(nativeSubjectId, c.subjectId)} and ${c.credentialId} = ${sql.param(input.credential!.credentialId, c.credentialId)} and ${c.credentialRevision} = ${sql.param(revisions.credentialRevision, c.credentialRevision)} and ${c.verifierVersion} = ${sql.param(revisions.verifierVersion, c.verifierVersion)} and ${c.verifier} = ${sql.param(Redacted.value(input.replacement.verifier), c.verifier)} and ${c.normalization} = ${sql.param(input.replacement.normalization, c.normalization)})`,
1039
+ sql`exists(select 1 from ${mapping.subject.table} where ${s.id} = ${sql.param(nativeSubjectId, s.id)} and ${s.securityRevision} = ${sql.param(revisions.nextSecurityRevision, s.securityRevision)} and ${s.status} = ${sql.param(mapping.subject.d1ActiveStatusValue, s.status)})`,
1040
+ sql`exists(select 1 from ${mapping.authorityCredential.table} where ${ac.subjectId} = ${sql.param(nativeSubjectId, ac.subjectId)} and ${ac.credentialId} = ${sql.param(input.credential!.credentialId, ac.credentialId)} and ${ac.revision} = ${sql.param(revisions.credentialRevision, ac.revision)}${ac.status === undefined ? sql`` : sql` and ${ac.status} = ${sql.param(mapping.authorityCredential.d1ActiveStatusValue, ac.status)}`})`,
1041
+ sql`exists(select 1 from ${mapping.command.table} where ${mc.moduleId} = ${sql.param(input.moduleId, mc.moduleId)} and ${mc.commandId} = ${sql.param(input.commandId, mc.commandId)} and ${mc.bindingDigest} = ${sql.param(input.authorization.challenge.bindingDigest, mc.bindingDigest)} and ${mc.action} = ${sql.param(input.authorization.challenge.action, mc.action)} and ${mc.decision} = ${sql.param("changed", mc.decision)} and ${mc.retentionUntil} = ${sql.param(mapping.encodeInstant(now + mapping.commandRetentionMillis), mc.retentionUntil)})`,
1042
+ identifierCondition,
1043
+ sql`true`,
1044
+ )!;
1045
+ };
1046
+
1047
+ const compileAddMutation = Effect.fn("DrizzleD1Password.compileAddMutation")(function* (
1048
+ mapping: Mapping,
1049
+ input: PasswordMutationInput,
1050
+ revisions: {
1051
+ readonly credentialId: string;
1052
+ readonly credentialRevision: SecurityRevision;
1053
+ readonly verifierVersion: SecurityRevision;
1054
+ readonly nextSecurityRevision: SecurityRevision;
1055
+ },
1056
+ ) {
1057
+ const database = yield* CurrentD1PlanningDatabase;
1058
+
1059
+ const { credentialId, credentialRevision, verifierVersion, nextSecurityRevision } = revisions;
1060
+
1061
+ const authority = yield* readAuthority(mapping, input, "add-password");
1062
+
1063
+ if (authority === undefined) return undefined;
1064
+ const now = yield* readEngineNowMillis(mapping);
1065
+ const c = credentialColumns(mapping);
1066
+ const s = subjectColumns(mapping);
1067
+ const ac = authorityColumns(mapping);
1068
+ const mc = commandColumns(mapping);
1069
+ const marker = `effect-auth-password-guard:add:${input.commandId}`;
1070
+ const absent = sql`not exists(select 1 from ${mapping.credential.table} where ${c.moduleId} = ${sql.param(input.moduleId, c.moduleId)} and ${c.subjectId} = ${sql.param(authority.nativeSubjectId, c.subjectId)})`;
1071
+
1072
+ const guard = and(
1073
+ authority.condition,
1074
+ absent,
1075
+ commandAbsentCondition(mapping, input.moduleId, input.commandId),
1076
+ )!;
1077
+
1078
+ const appliedCondition = and(
1079
+ sql`exists(select 1 from ${mapping.credential.table} where ${c.moduleId} = ${sql.param(input.moduleId, c.moduleId)} and ${c.subjectId} = ${sql.param(authority.nativeSubjectId, c.subjectId)} and ${c.credentialId} = ${sql.param(credentialId, c.credentialId)} and ${c.credentialRevision} = ${sql.param(credentialRevision, c.credentialRevision)} and ${c.verifierVersion} = ${sql.param(verifierVersion, c.verifierVersion)} and ${c.verifier} = ${sql.param(Redacted.value(input.replacement.verifier), c.verifier)} and ${c.normalization} = ${sql.param(input.replacement.normalization, c.normalization)})`,
1080
+ sql`exists(select 1 from ${mapping.authorityCredential.table} where ${ac.subjectId} = ${sql.param(authority.nativeSubjectId, ac.subjectId)} and ${ac.credentialId} = ${sql.param(credentialId, ac.credentialId)} and ${ac.revision} = ${sql.param(credentialRevision, ac.revision)}${ac.status === undefined ? sql`` : sql` and ${ac.status} = ${sql.param(mapping.authorityCredential.d1ActiveStatusValue, ac.status)}`})`,
1081
+ sql`exists(select 1 from ${mapping.subject.table} where ${s.id} = ${sql.param(authority.nativeSubjectId, s.id)} and ${s.securityRevision} = ${sql.param(nextSecurityRevision, s.securityRevision)} and ${s.status} = ${sql.param(mapping.subject.d1ActiveStatusValue, s.status)})`,
1082
+ sql`exists(select 1 from ${mapping.command.table} where ${mc.moduleId} = ${sql.param(input.moduleId, mc.moduleId)} and ${mc.commandId} = ${sql.param(input.commandId, mc.commandId)} and ${mc.bindingDigest} = ${sql.param(input.authorization.challenge.bindingDigest, mc.bindingDigest)} and ${mc.action} = ${sql.param("add-password", mc.action)} and ${mc.decision} = ${sql.param("changed", mc.decision)} and ${mc.retentionUntil} = ${sql.param(mapping.encodeInstant(now + mapping.commandRetentionMillis), mc.retentionUntil)})`,
1083
+ )!;
1084
+
1085
+ const statements: Statement<any>[] = [
1086
+ yield* assertion(guard, marker),
1087
+ yield* statement(
1088
+ database.insert(mapping.credential.table).values(
1089
+ mapping.credential.encodeInsert({
1090
+ moduleId: input.moduleId,
1091
+ subjectId: authority.nativeSubjectId,
1092
+ credentialId,
1093
+ credentialRevision,
1094
+ verifierVersion,
1095
+ replacement: input.replacement,
1096
+ }),
1097
+ ),
1098
+ ),
1099
+ yield* statement(
1100
+ database.insert(mapping.authorityCredential.table).values(
1101
+ mapping.authorityCredential.encodeInsert({
1102
+ subjectId: authority.nativeSubjectId,
1103
+ credentialId,
1104
+ revision: credentialRevision,
1105
+ }),
1106
+ ),
1107
+ ),
1108
+ yield* statement(
1109
+ database
1110
+ .update(mapping.subject.table)
1111
+ .set(updateValues([[mapping.subject.securityRevision, nextSecurityRevision]]))
1112
+ .where(
1113
+ and(
1114
+ eq(s.id, authority.nativeSubjectId),
1115
+ eq(s.securityRevision, input.expectedRevision.securityRevision),
1116
+ ),
1117
+ ),
1118
+ ),
1119
+ yield* statement(
1120
+ database.insert(mapping.command.table).values(
1121
+ mapping.command.encodeInsert({
1122
+ moduleId: input.moduleId,
1123
+ commandId: input.commandId,
1124
+ action: "add-password",
1125
+ bindingDigest: input.authorization.challenge.bindingDigest,
1126
+ decision: "changed",
1127
+ retentionUntilMillis: now + mapping.commandRetentionMillis,
1128
+ }),
1129
+ ),
1130
+ ),
1131
+ yield* assertion(appliedCondition, marker),
1132
+ ];
1133
+
1134
+ return {
1135
+ statements,
1136
+ appliedCondition,
1137
+ retryable: (cause: unknown) => isGuardFailure(cause, marker),
1138
+ };
1139
+ });
1140
+
1141
+ const makePasswordPlans = (
1142
+ mapping: Mapping,
1143
+ proofMapping?: D1ProofPersistenceMapping<
1144
+ any,
1145
+ any,
1146
+ any,
1147
+ any,
1148
+ any,
1149
+ any,
1150
+ any,
1151
+ any,
1152
+ any,
1153
+ any,
1154
+ any,
1155
+ any
1156
+ >,
1157
+ ) => {
1158
+ const run = <A, E, R>(plan: Effect.Effect<Planned<A>, E, R>) =>
1159
+ Effect.gen(function* () {
1160
+ if (!validConstraints(mapping)) return yield* unavailable();
1161
+
1162
+ return yield* plan;
1163
+ });
1164
+
1165
+ return {
1166
+ admitAttempt: <A>(
1167
+ input: Parameters<PasswordPersistence["Service"]["admitAttempt"]>[0],
1168
+ prepare: PlanPrepare<PasswordPersistence["Service"]["admitAttempt"], A>,
1169
+ ) =>
1170
+ Effect.gen(function* () {
1171
+ const database = yield* CurrentD1PlanningDatabase;
1172
+
1173
+ const attemptId = yield* allocate(mapping.allocateAttemptId, mapping.allocateAttemptIdSync);
1174
+
1175
+ return yield* run(
1176
+ Effect.gen(function* () {
1177
+ const journal = yield* CurrentCommitJournal;
1178
+
1179
+ const now = yield* readEngineNowMillis(mapping);
1180
+
1181
+ const resolved = yield* resolveCredential(
1182
+ mapping,
1183
+ input.moduleId,
1184
+ input.identifier,
1185
+ input.subjectId,
1186
+ );
1187
+
1188
+ const keys = mapping.scopeKeys({
1189
+ moduleId: input.moduleId,
1190
+ action: input.action,
1191
+ identifier: input.identifier,
1192
+ ...(resolved?.snapshot === undefined
1193
+ ? {}
1194
+ : { subjectId: resolved.snapshot.revision.subjectId }),
1195
+ });
1196
+
1197
+ const scopes = scopeEntries(keys, input.policy);
1198
+
1199
+ const admitted = yield* readScopeAdmission(
1200
+ mapping,
1201
+ input.moduleId,
1202
+ input.action,
1203
+ scopes,
1204
+ input.policy,
1205
+ input.identifier,
1206
+ resolved?.nativeSubjectId,
1207
+ now,
1208
+ );
1209
+
1210
+ const allAdmitted = admitted.length === scopes.length;
1211
+ const considered = admitted.length === 0 ? scopes.slice(0, 1) : scopes;
1212
+
1213
+ const receipt = prepare(
1214
+ allAdmitted
1215
+ ? {
1216
+ _tag: "Admitted" as const,
1217
+ attemptId,
1218
+ ...(resolved?.snapshot === undefined ? {} : { credential: resolved.snapshot }),
1219
+ }
1220
+ : { _tag: "Denied" as const },
1221
+ journal,
1222
+ );
1223
+
1224
+ const marker = `effect-auth-password-guard:admit:${attemptId}`;
1225
+ const statements: Statement<any>[] = [];
1226
+
1227
+ for (const entry of considered) {
1228
+ statements.push(
1229
+ yield* statement(
1230
+ database
1231
+ .insert(mapping.rateScope.table)
1232
+ .values(
1233
+ mapping.rateScope.encodeInsert({
1234
+ moduleId: input.moduleId,
1235
+ action: input.action,
1236
+ scopeKind: entry.kind,
1237
+ scopeKey: entry.key,
1238
+ }),
1239
+ )
1240
+ .onConflictDoNothing(),
1241
+ ),
1242
+ );
1243
+ }
1244
+
1245
+ const conditions = considered.map((entry) => {
1246
+ const condition = scopeAdmissionCondition(
1247
+ mapping,
1248
+ input.moduleId,
1249
+ input.action,
1250
+ entry,
1251
+ input.policy,
1252
+ input.identifier,
1253
+ resolved?.nativeSubjectId,
1254
+ );
1255
+
1256
+ return admitted.includes(entry) ? condition : sql`not (${condition})`;
1257
+ });
1258
+
1259
+ statements.push(
1260
+ yield* assertion(
1261
+ and(
1262
+ ...conditions,
1263
+ ...(allAdmitted
1264
+ ? [admittedSnapshotCondition(mapping, resolved, input.moduleId)]
1265
+ : []),
1266
+ )!,
1267
+ marker,
1268
+ ),
1269
+ );
1270
+ for (const entry of admitted)
1271
+ statements.push(
1272
+ yield* statement(
1273
+ database.insert(mapping.charge.table).values(
1274
+ driverValues(
1275
+ mapping.charge.encodeInsert({
1276
+ moduleId: input.moduleId,
1277
+ action: input.action,
1278
+ scopeKind: entry.kind,
1279
+ scopeKey: entry.key,
1280
+ attemptId,
1281
+ occurredAtMillis: now,
1282
+ retentionUntilMillis: now + entry.windowMillis,
1283
+ }),
1284
+ [
1285
+ [mapping.charge.occurredAt, mapping.d1.engineNow],
1286
+ [
1287
+ mapping.charge.retentionUntil,
1288
+ mapping.d1.engineInstantPlus(entry.windowMillis),
1289
+ ],
1290
+ ],
1291
+ ),
1292
+ ),
1293
+ ),
1294
+ );
1295
+ if (allAdmitted) {
1296
+ const snapshot = resolved?.snapshot;
1297
+
1298
+ const record: PasswordAttemptRecord = {
1299
+ moduleId: input.moduleId,
1300
+ action: input.action,
1301
+ attemptId,
1302
+ identifier: input.identifier,
1303
+ ...(snapshot === undefined
1304
+ ? {}
1305
+ : {
1306
+ subjectId: snapshot.revision.subjectId,
1307
+ credentialId: snapshot.credentialId,
1308
+ securityRevision: snapshot.revision.securityRevision,
1309
+ credentialRevision: snapshot.credentialRevision,
1310
+ verifierVersion: snapshot.verifierVersion,
1311
+ identifierBindingRevision: snapshot.identifierBindingRevision,
1312
+ }),
1313
+ admittedAtMillis: now,
1314
+ deadlineMillis: now + input.policy.attemptLifetimeMillis,
1315
+ retentionUntilMillis: Math.max(
1316
+ now + input.policy.attemptLifetimeMillis,
1317
+ ...scopes.map((entry) => now + entry.windowMillis),
1318
+ ),
1319
+ };
1320
+
1321
+ statements.push(
1322
+ yield* statement(
1323
+ database.insert(mapping.attempt.table).values(
1324
+ driverValues(
1325
+ mapping.attempt.encodeInsert(record, {
1326
+ ...(resolved?.nativeSubjectId === undefined
1327
+ ? {}
1328
+ : { nativeSubjectId: resolved.nativeSubjectId }),
1329
+ state: "pending",
1330
+ }),
1331
+ [
1332
+ [mapping.attempt.admittedAt, mapping.d1.engineNow],
1333
+ [
1334
+ mapping.attempt.deadline,
1335
+ mapping.d1.engineInstantPlus(input.policy.attemptLifetimeMillis),
1336
+ ],
1337
+ [
1338
+ mapping.attempt.retentionUntil,
1339
+ mapping.d1.engineInstantPlus(
1340
+ Math.max(
1341
+ input.policy.attemptLifetimeMillis,
1342
+ ...scopes.map((entry) => entry.windowMillis),
1343
+ ),
1344
+ ),
1345
+ ],
1346
+ ],
1347
+ ),
1348
+ ),
1349
+ ),
1350
+ );
1351
+ }
1352
+
1353
+ return {
1354
+ receipt,
1355
+ statements,
1356
+ retryable: (cause: unknown) => isGuardFailure(cause, marker),
1357
+ };
1358
+ }),
1359
+ );
1360
+ }),
1361
+ settleAttempt: <A>(
1362
+ uncaptured: Parameters<PasswordPersistence["Service"]["settleAttempt"]>[0],
1363
+ prepare: PlanPrepare<PasswordPersistence["Service"]["settleAttempt"], A>,
1364
+ ) =>
1365
+ Effect.gen(function* () {
1366
+ const database = yield* CurrentD1PlanningDatabase;
1367
+
1368
+ const captured =
1369
+ uncaptured.captured === undefined
1370
+ ? undefined
1371
+ : yield* snapshotPasswordCredential(uncaptured.captured);
1372
+
1373
+ const input = { ...uncaptured, ...(captured === undefined ? {} : { captured }) };
1374
+
1375
+ return yield* run(
1376
+ Effect.gen(function* () {
1377
+ const journal = yield* CurrentCommitJournal;
1378
+
1379
+ const a = attemptColumns(mapping);
1380
+
1381
+ const rows = yield* database
1382
+ .select()
1383
+ .from(mapping.attempt.table)
1384
+ .where(and(eq(a.moduleId, input.moduleId), eq(a.attemptId, input.attemptId)))
1385
+ .limit(1);
1386
+
1387
+ const row = rows[0];
1388
+ const engineNow = yield* readEngineNowMillis(mapping);
1389
+
1390
+ const deadline =
1391
+ row === undefined
1392
+ ? undefined
1393
+ : yield* mapping.decodeInstant(row[mapping.attempt.deadline]);
1394
+
1395
+ let resolved: Resolved | undefined;
1396
+ let verified = false;
1397
+
1398
+ if (
1399
+ row !== undefined &&
1400
+ row[mapping.attempt.state] === "pending" &&
1401
+ input.outcome === "verified" &&
1402
+ input.captured !== undefined &&
1403
+ deadline !== undefined &&
1404
+ deadline > engineNow
1405
+ ) {
1406
+ resolved = yield* resolveCredential(
1407
+ mapping,
1408
+ input.moduleId,
1409
+ input.captured.identifier,
1410
+ input.captured.revision.subjectId,
1411
+ );
1412
+ verified =
1413
+ input.captured.moduleId === input.moduleId &&
1414
+ resolved?.snapshot !== undefined &&
1415
+ sameSnapshot(resolved.snapshot, input.captured) &&
1416
+ mapping.subjectId.equals(
1417
+ row[mapping.attempt.subjectId],
1418
+ resolved.nativeSubjectId,
1419
+ ) &&
1420
+ row[mapping.attempt.identifierNamespace] === input.captured.identifier.namespace &&
1421
+ row[mapping.attempt.identifierValue] === input.captured.identifier.value &&
1422
+ row[mapping.attempt.credentialId] === input.captured.credentialId &&
1423
+ row[mapping.attempt.securityRevision] ===
1424
+ input.captured.revision.securityRevision &&
1425
+ row[mapping.attempt.credentialRevision] === input.captured.credentialRevision &&
1426
+ row[mapping.attempt.verifierVersion] === input.captured.verifierVersion &&
1427
+ row[mapping.attempt.identifierBindingRevision] ===
1428
+ input.captured.identifierBindingRevision;
1429
+ }
1430
+ const decision: PasswordAttemptDecision = verified ? "verified" : "rejected";
1431
+ const receipt = prepare(decision, journal);
1432
+
1433
+ if (row === undefined || row[mapping.attempt.state] !== "pending")
1434
+ return { receipt, statements: [] };
1435
+ const marker = `effect-auth-password-guard:settle:${input.attemptId}`;
1436
+
1437
+ const base = and(
1438
+ eq(a.moduleId, input.moduleId),
1439
+ eq(a.attemptId, input.attemptId),
1440
+ eq(a.state, "pending"),
1441
+ )!;
1442
+
1443
+ const condition = verified
1444
+ ? and(base, sql`${a.deadline} > ${mapping.d1.engineNow}`)!
1445
+ : base;
1446
+
1447
+ const statements: Statement<any>[] = [
1448
+ yield* assertion(
1449
+ sql`exists(select 1 from ${mapping.attempt.table} where ${condition})`,
1450
+ marker,
1451
+ ),
1452
+ yield* statement(
1453
+ database
1454
+ .update(mapping.attempt.table)
1455
+ .set(updateValues([[mapping.attempt.state, decision]]))
1456
+ .where(condition),
1457
+ ),
1458
+ ];
1459
+
1460
+ if (verified && resolved?.snapshot !== undefined) {
1461
+ const captured = input.captured!;
1462
+ const s = subjectColumns(mapping);
1463
+ const i = identifierColumns(mapping);
1464
+ const c = credentialColumns(mapping);
1465
+
1466
+ const authority = and(
1467
+ sql`exists(select 1 from ${mapping.subject.table} where ${s.id} = ${sql.param(resolved.nativeSubjectId, s.id)} and ${s.status} = ${sql.param(mapping.subject.d1ActiveStatusValue, s.status)} and ${s.securityRevision} = ${sql.param(input.captured!.revision.securityRevision, s.securityRevision)})`,
1468
+ mapping.identifier.d1CurrentCondition({
1469
+ identifier: input.captured!.identifier,
1470
+ nativeSubjectId: resolved.nativeSubjectId,
1471
+ }),
1472
+ sql`exists(select 1 from ${mapping.credential.table} where ${c.moduleId} = ${sql.param(input.moduleId, c.moduleId)} and ${c.subjectId} = ${sql.param(resolved.nativeSubjectId, c.subjectId)} and ${c.credentialId} = ${sql.param(input.captured!.credentialId, c.credentialId)} and ${c.credentialRevision} = ${sql.param(input.captured!.credentialRevision, c.credentialRevision)} and ${c.verifierVersion} = ${sql.param(input.captured!.verifierVersion, c.verifierVersion)} and ${c.verifier} = ${sql.param(Redacted.value(input.captured!.verifier), c.verifier)})`,
1473
+ sql`exists(select 1 from ${mapping.identifier.table} where ${i.namespace} = ${sql.param(input.captured!.identifier.namespace, i.namespace)} and ${i.value} = ${sql.param(input.captured!.identifier.value, i.value)} and ${i.subjectId} = ${sql.param(resolved.nativeSubjectId, i.subjectId)} and ${i.bindingRevision} = ${sql.param(input.captured!.identifierBindingRevision, i.bindingRevision)})`,
1474
+ )!;
1475
+
1476
+ statements.splice(1, 0, yield* assertion(authority, marker));
1477
+ if (
1478
+ input.rehash !== undefined &&
1479
+ input.rehash.expectedVersion === captured.verifierVersion &&
1480
+ Redacted.value(input.rehash.expectedVerifier) === Redacted.value(captured.verifier)
1481
+ ) {
1482
+ const nextVersion = yield* allocate(
1483
+ mapping.allocateRevision,
1484
+ mapping.allocateRevisionSync,
1485
+ );
1486
+
1487
+ statements.push(
1488
+ yield* statement(
1489
+ database
1490
+ .update(mapping.credential.table)
1491
+ .set(
1492
+ mapping.credential.encodeVerifier(input.rehash.nextVerifier, nextVersion),
1493
+ )
1494
+ .where(
1495
+ and(
1496
+ eq(c.moduleId, input.moduleId),
1497
+ eq(c.subjectId, resolved.nativeSubjectId),
1498
+ eq(c.credentialId, captured.credentialId),
1499
+ eq(c.credentialRevision, captured.credentialRevision),
1500
+ eq(c.verifierVersion, input.rehash.expectedVersion),
1501
+ eq(c.verifier, Redacted.value(input.rehash.expectedVerifier)),
1502
+ ),
1503
+ ),
1504
+ ),
1505
+ );
1506
+ }
1507
+ }
1508
+
1509
+ return {
1510
+ receipt,
1511
+ statements,
1512
+ retryable: (cause: unknown) => isGuardFailure(cause, marker),
1513
+ };
1514
+ }),
1515
+ );
1516
+ }),
1517
+ readForSubject: (input: Parameters<PasswordPersistence["Service"]["readForSubject"]>[0]) =>
1518
+ Effect.gen(function* () {
1519
+ const database = yield* CurrentD1PlanningDatabase;
1520
+
1521
+ if (yield* hasCommitScope) return yield* unavailable();
1522
+ const nativeSubjectId = yield* mapping.subjectId.toNative(input.subjectId);
1523
+ const subject = (yield* readSubjectNative(mapping, nativeSubjectId))[0];
1524
+
1525
+ if (
1526
+ subject === undefined ||
1527
+ !mapping.subject.isActiveStatus(subject[mapping.subject.status])
1528
+ )
1529
+ return Option.none();
1530
+
1531
+ const credential = (yield* readCredentialNative(
1532
+ mapping,
1533
+ input.moduleId,
1534
+ nativeSubjectId,
1535
+ ))[0];
1536
+
1537
+ if (credential === undefined) return Option.none();
1538
+ const ic = identifierColumns(mapping);
1539
+
1540
+ const identifiers = yield* database
1541
+ .select()
1542
+ .from(mapping.identifier.table)
1543
+ .where(eq(ic.subjectId, nativeSubjectId));
1544
+
1545
+ const identifier = identifiers.find((row: any) => mapping.identifier.isCurrent(row));
1546
+
1547
+ if (identifier === undefined) return Option.none();
1548
+
1549
+ return Option.some(
1550
+ yield* mapping.credential
1551
+ .decode({ moduleId: input.moduleId, subject, identifier, credential })
1552
+ .pipe(Effect.flatMap(snapshotPasswordCredential)),
1553
+ );
1554
+ }),
1555
+ recoveryTarget: (input: Parameters<PasswordPersistence["Service"]["recoveryTarget"]>[0]) =>
1556
+ Effect.gen(function* () {
1557
+ if (yield* hasCommitScope) return yield* unavailable();
1558
+ const resolved = yield* resolveCredential(mapping, input.moduleId, input.identifier);
1559
+
1560
+ return resolved?.snapshot === undefined ||
1561
+ resolved.snapshot.identifierVerifiedAtMillis === undefined
1562
+ ? Option.none()
1563
+ : Option.some(resolved.snapshot);
1564
+ }),
1565
+ addIfAbsent: <A>(
1566
+ uncaptured: Parameters<PasswordPersistence["Service"]["addIfAbsent"]>[0],
1567
+ prepare: PlanPrepare<PasswordPersistence["Service"]["addIfAbsent"], A>,
1568
+ ) =>
1569
+ Effect.gen(function* () {
1570
+ const input = yield* snapshotMutationInput(uncaptured);
1571
+
1572
+ const credentialId = yield* allocate(
1573
+ mapping.allocateCredentialId,
1574
+ mapping.allocateCredentialIdSync,
1575
+ );
1576
+
1577
+ const credentialRevision = yield* allocate(
1578
+ mapping.allocateRevision,
1579
+ mapping.allocateRevisionSync,
1580
+ );
1581
+
1582
+ const verifierVersion = yield* allocate(
1583
+ mapping.allocateRevision,
1584
+ mapping.allocateRevisionSync,
1585
+ );
1586
+
1587
+ const nextSecurityRevision = yield* allocateNextSecurityRevision(
1588
+ mapping,
1589
+ input.expectedRevision.securityRevision,
1590
+ );
1591
+
1592
+ if (nextSecurityRevision === input.expectedRevision.securityRevision)
1593
+ return yield* unavailable();
1594
+
1595
+ return yield* run(
1596
+ Effect.gen(function* () {
1597
+ const journal = yield* CurrentCommitJournal;
1598
+
1599
+ const compiled = yield* compileAddMutation(mapping, input, {
1600
+ credentialId,
1601
+ credentialRevision,
1602
+ verifierVersion,
1603
+ nextSecurityRevision,
1604
+ });
1605
+
1606
+ return {
1607
+ receipt: prepare(compiled === undefined ? "rejected" : "changed", journal),
1608
+ statements: compiled?.statements ?? [],
1609
+ retryable: compiled?.retryable,
1610
+ };
1611
+ }),
1612
+ );
1613
+ }),
1614
+ replaceIfCurrent: <A>(
1615
+ uncaptured: Parameters<PasswordPersistence["Service"]["replaceIfCurrent"]>[0],
1616
+ prepare: PlanPrepare<PasswordPersistence["Service"]["replaceIfCurrent"], A>,
1617
+ ) =>
1618
+ Effect.flatMap(snapshotMutationInput(uncaptured), (input) =>
1619
+ replacementPlan(mapping, proofMapping, input, prepare, "change-password"),
1620
+ ),
1621
+ checkReset: (input: Parameters<PasswordPersistence["Service"]["checkReset"]>[0]) =>
1622
+ proofMapping === undefined
1623
+ ? Effect.fail(unavailable())
1624
+ : Effect.gen(function* () {
1625
+ if (yield* hasCommitScope) return yield* unavailable();
1626
+ if (
1627
+ input.moduleId.length <= "/reset".length ||
1628
+ !input.moduleId.endsWith("/reset") ||
1629
+ input.purpose !== "password-reset" ||
1630
+ input.binding._tag !== "Subject"
1631
+ )
1632
+ return false;
1633
+ const binding = input.binding;
1634
+
1635
+ const resolved = yield* resolveCredential(
1636
+ mapping,
1637
+ input.moduleId.slice(0, -"/reset".length),
1638
+ binding.identifier,
1639
+ binding.revision.subjectId,
1640
+ );
1641
+
1642
+ const current =
1643
+ resolved?.snapshot !== undefined &&
1644
+ resolved.snapshot.revision.securityRevision === binding.revision.securityRevision &&
1645
+ binding.revision.credentials.every((item) =>
1646
+ resolved.snapshot!.revision.credentials.some(
1647
+ (current) =>
1648
+ current.credentialId === item.credentialId &&
1649
+ current.revision === item.revision,
1650
+ ),
1651
+ );
1652
+
1653
+ return current ? yield* probeProofCompletion(proofMapping, input) : false;
1654
+ }),
1655
+ resetWithProof: <A>(
1656
+ uncaptured: Parameters<PasswordPersistence["Service"]["resetWithProof"]>[0],
1657
+ prepare: PlanPrepare<PasswordPersistence["Service"]["resetWithProof"], A>,
1658
+ ) =>
1659
+ proofMapping === undefined
1660
+ ? Effect.fail(unavailable())
1661
+ : Effect.flatMap(snapshotMutationInput(uncaptured), (captured) =>
1662
+ replacementPlan(
1663
+ mapping,
1664
+ proofMapping,
1665
+ Object.freeze({ ...captured, completion: uncaptured.completion }),
1666
+ prepare,
1667
+ "reset-password",
1668
+ ),
1669
+ ),
1670
+ cleanupAttempts: <A>(
1671
+ input: Parameters<PasswordPersistence["Service"]["cleanupAttempts"]>[0],
1672
+ prepare: PlanPrepare<PasswordPersistence["Service"]["cleanupAttempts"], A>,
1673
+ ) =>
1674
+ run(
1675
+ Effect.gen(function* () {
1676
+ return yield* cleanupPlan(mapping, input, prepare);
1677
+ }),
1678
+ ),
1679
+ };
1680
+ };
1681
+
1682
+ const compileReplacementMutation = Effect.fn("DrizzleD1Password.compileReplacementMutation")(
1683
+ function* (
1684
+ mapping: Mapping,
1685
+ input: PasswordMutationInput,
1686
+ action: "change-password" | "reset-password",
1687
+ revisions: {
1688
+ readonly credentialRevision: SecurityRevision;
1689
+ readonly verifierVersion: SecurityRevision;
1690
+ readonly nextSecurityRevision: SecurityRevision;
1691
+ },
1692
+ ) {
1693
+ const { credentialRevision, verifierVersion, nextSecurityRevision } = revisions;
1694
+ const authority = yield* readAuthority(mapping, input, action);
1695
+
1696
+ const current =
1697
+ authority === undefined || input.credential === undefined
1698
+ ? undefined
1699
+ : (yield* readCredentialNative(mapping, input.moduleId, authority.nativeSubjectId))[0];
1700
+
1701
+ const currentMatches =
1702
+ current !== undefined &&
1703
+ current[mapping.credential.credentialId] === input.credential!.credentialId &&
1704
+ current[mapping.credential.credentialRevision] === input.credential!.credentialRevision &&
1705
+ current[mapping.credential.verifierVersion] === input.credential!.verifierVersion &&
1706
+ current[mapping.credential.verifier] === Redacted.value(input.credential!.verifier) &&
1707
+ current[mapping.credential.normalization] === input.credential!.normalization;
1708
+
1709
+ if (authority === undefined || !currentMatches) return undefined;
1710
+ const now = yield* readEngineNowMillis(mapping);
1711
+
1712
+ const guard = and(
1713
+ authority.condition,
1714
+ credentialCurrentCondition(mapping, input, authority.nativeSubjectId),
1715
+ commandAbsentCondition(mapping, input.moduleId, input.commandId),
1716
+ )!;
1717
+
1718
+ const marker = `effect-auth-password-guard:${action}:${input.commandId}`;
1719
+
1720
+ const protectedMutation: D1ProtectedProofMutation = {
1721
+ statements: [
1722
+ yield* assertion(guard, marker),
1723
+ ...(yield* mutationStatements(
1724
+ mapping,
1725
+ input,
1726
+ authority.nativeSubjectId,
1727
+ { credentialRevision, verifierVersion, nextSecurityRevision },
1728
+ now,
1729
+ )),
1730
+ ],
1731
+ appliedCondition: replacementApplied(
1732
+ mapping,
1733
+ input,
1734
+ authority.nativeSubjectId,
1735
+ {
1736
+ credentialRevision,
1737
+ verifierVersion,
1738
+ nextSecurityRevision,
1739
+ },
1740
+ now,
1741
+ authority.identifierCondition,
1742
+ ),
1743
+ };
1744
+
1745
+ return { protectedMutation, marker };
1746
+ },
1747
+ );
1748
+
1749
+ const replacementPlan = Effect.fn("Drizzle.replacementPlan")(function* <A>(
1750
+ mapping: Mapping,
1751
+ proofMapping:
1752
+ | D1ProofPersistenceMapping<any, any, any, any, any, any, any, any, any, any, any, any>
1753
+ | undefined,
1754
+ input: PasswordMutationInput & { readonly completion?: any },
1755
+ prepare: PreparePasswordCommit<PasswordMutationDecision, A>,
1756
+ action: "change-password" | "reset-password",
1757
+ ) {
1758
+ const credentialRevision = yield* allocate(
1759
+ mapping.allocateRevision,
1760
+ mapping.allocateRevisionSync,
1761
+ );
1762
+
1763
+ const verifierVersion = yield* allocate(mapping.allocateRevision, mapping.allocateRevisionSync);
1764
+
1765
+ const nextSecurityRevision = yield* allocateNextSecurityRevision(
1766
+ mapping,
1767
+ input.expectedRevision.securityRevision,
1768
+ );
1769
+
1770
+ if (
1771
+ nextSecurityRevision === input.expectedRevision.securityRevision ||
1772
+ (input.credential !== undefined && credentialRevision === input.credential.credentialRevision)
1773
+ )
1774
+ return yield* unavailable();
1775
+
1776
+ const journal = yield* CurrentCommitJournal;
1777
+
1778
+ const compiledMutation = yield* compileReplacementMutation(mapping, input, action, {
1779
+ credentialRevision,
1780
+ verifierVersion,
1781
+ nextSecurityRevision,
1782
+ });
1783
+
1784
+ if (compiledMutation === undefined)
1785
+ return { receipt: prepare("rejected", journal), statements: [] };
1786
+ const { protectedMutation, marker } = compiledMutation;
1787
+
1788
+ if (action === "reset-password") {
1789
+ if (
1790
+ proofMapping === undefined ||
1791
+ input.completion === undefined ||
1792
+ !completionMatchesPassword(
1793
+ input as PasswordMutationInput & { readonly completion: ProofCompletionPlan },
1794
+ )
1795
+ )
1796
+ return { receipt: prepare("rejected", journal), statements: [] };
1797
+ let passwordReceipt: PreparedCommit<A> | undefined;
1798
+
1799
+ const compiled = yield* compileD1ProofCompletionPlan(
1800
+ proofMapping,
1801
+ input.completion,
1802
+ protectedMutation,
1803
+ (decision: ProofCompletionDecision) => {
1804
+ passwordReceipt = prepare(decision === "completed" ? "changed" : "rejected", journal);
1805
+
1806
+ return decision;
1807
+ },
1808
+ );
1809
+
1810
+ return {
1811
+ receipt: passwordReceipt ?? prepare("rejected", journal),
1812
+ statements: compiled.statements,
1813
+ retryable: (cause: unknown) =>
1814
+ isGuardFailure(cause, marker) ||
1815
+ isGuardFailure(
1816
+ cause,
1817
+ `effect-auth-proof-guard:complete:${input.completion.input.continuationId}`,
1818
+ ) ||
1819
+ isGuardFailure(
1820
+ cause,
1821
+ `effect-auth-proof-guard:protected:${input.completion.input.continuationId}`,
1822
+ ),
1823
+ };
1824
+ }
1825
+
1826
+ return {
1827
+ receipt: prepare("changed", journal),
1828
+ statements: [
1829
+ ...protectedMutation.statements,
1830
+ yield* assertion(protectedMutation.appliedCondition, marker),
1831
+ ],
1832
+ retryable: (cause: unknown) => isGuardFailure(cause, marker),
1833
+ };
1834
+ });
1835
+
1836
+ const cleanupPlan = Effect.fn("Drizzle.cleanupPlan")(function* <A>(
1837
+ mapping: Mapping,
1838
+ input: { readonly moduleId: string; readonly limit: number },
1839
+ prepare: PreparePasswordCommit<{ readonly removed: number; readonly hasMore: boolean }, A>,
1840
+ ) {
1841
+ const database = yield* CurrentD1PlanningDatabase;
1842
+ const journal = yield* CurrentCommitJournal;
1843
+
1844
+ const now = yield* readEngineNowMillis(mapping);
1845
+ const nativeNow = mapping.encodeInstant(now);
1846
+ const a = attemptColumns(mapping);
1847
+ const c = chargeColumns(mapping);
1848
+ const sc = scopeColumns(mapping);
1849
+ let remaining = input.limit;
1850
+ let hasMore = false;
1851
+
1852
+ const attempts = yield* database
1853
+ .select({ attemptId: a.attemptId })
1854
+ .from(mapping.attempt.table)
1855
+ .where(and(eq(a.moduleId, input.moduleId), lte(a.retentionUntil, nativeNow)))
1856
+ .limit(remaining + 1);
1857
+
1858
+ if (attempts.length > remaining) hasMore = true;
1859
+ const selectedAttempts = attempts.slice(0, remaining);
1860
+
1861
+ remaining -= selectedAttempts.length;
1862
+
1863
+ const charges = yield* database
1864
+ .select({
1865
+ action: c.action,
1866
+ scopeKind: c.scopeKind,
1867
+ scopeKey: c.scopeKey,
1868
+ attemptId: c.attemptId,
1869
+ })
1870
+ .from(mapping.charge.table)
1871
+ .where(and(eq(c.moduleId, input.moduleId), lte(c.retentionUntil, nativeNow)))
1872
+ .limit(remaining + 1);
1873
+
1874
+ if (charges.length > remaining) hasMore = true;
1875
+ const selectedCharges = charges.slice(0, remaining);
1876
+
1877
+ remaining -= selectedCharges.length;
1878
+
1879
+ const scopes = yield* database
1880
+ .select({ action: sc.action, scopeKind: sc.scopeKind, scopeKey: sc.scopeKey })
1881
+ .from(mapping.rateScope.table)
1882
+ .where(
1883
+ and(
1884
+ eq(sc.moduleId, input.moduleId),
1885
+ notExists(
1886
+ database
1887
+ .select({ one: sql`1` })
1888
+ .from(mapping.charge.table)
1889
+ .where(
1890
+ and(
1891
+ eq(c.moduleId, sc.moduleId),
1892
+ eq(c.action, sc.action),
1893
+ eq(c.scopeKind, sc.scopeKind),
1894
+ eq(c.scopeKey, sc.scopeKey),
1895
+ ),
1896
+ ),
1897
+ ),
1898
+ ),
1899
+ )
1900
+ .limit(remaining + 1);
1901
+
1902
+ if (scopes.length > remaining) hasMore = true;
1903
+ const selectedScopes = scopes.slice(0, remaining);
1904
+ const marker = `effect-auth-password-guard:cleanup:${input.moduleId}:${now}`;
1905
+ const statements: Statement<any>[] = [];
1906
+
1907
+ for (const row of selectedAttempts) {
1908
+ const condition = and(
1909
+ eq(a.moduleId, input.moduleId),
1910
+ eq(a.attemptId, row.attemptId),
1911
+ lte(a.retentionUntil, mapping.d1.engineNow),
1912
+ )!;
1913
+
1914
+ statements.push(
1915
+ yield* assertion(
1916
+ sql`exists(select 1 from ${mapping.attempt.table} where ${condition})`,
1917
+ marker,
1918
+ ),
1919
+ yield* statement(database.delete(mapping.attempt.table).where(condition)),
1920
+ );
1921
+ }
1922
+ for (const row of selectedCharges) {
1923
+ const condition = and(
1924
+ eq(c.moduleId, input.moduleId),
1925
+ eq(c.action, row.action),
1926
+ eq(c.scopeKind, row.scopeKind),
1927
+ eq(c.scopeKey, row.scopeKey),
1928
+ eq(c.attemptId, row.attemptId),
1929
+ lte(c.retentionUntil, mapping.d1.engineNow),
1930
+ )!;
1931
+
1932
+ statements.push(
1933
+ yield* assertion(
1934
+ sql`exists(select 1 from ${mapping.charge.table} where ${condition})`,
1935
+ marker,
1936
+ ),
1937
+ yield* statement(database.delete(mapping.charge.table).where(condition)),
1938
+ );
1939
+ }
1940
+ for (const row of selectedScopes) {
1941
+ const condition = and(
1942
+ eq(sc.moduleId, input.moduleId),
1943
+ eq(sc.action, row.action),
1944
+ eq(sc.scopeKind, row.scopeKind),
1945
+ eq(sc.scopeKey, row.scopeKey),
1946
+ notExists(
1947
+ database
1948
+ .select({ one: sql`1` })
1949
+ .from(mapping.charge.table)
1950
+ .where(
1951
+ and(
1952
+ eq(c.moduleId, sc.moduleId),
1953
+ eq(c.action, sc.action),
1954
+ eq(c.scopeKind, sc.scopeKind),
1955
+ eq(c.scopeKey, sc.scopeKey),
1956
+ ),
1957
+ ),
1958
+ ),
1959
+ )!;
1960
+
1961
+ statements.push(
1962
+ yield* assertion(
1963
+ sql`exists(select 1 from ${mapping.rateScope.table} where ${condition})`,
1964
+ marker,
1965
+ ),
1966
+ yield* statement(database.delete(mapping.rateScope.table).where(condition)),
1967
+ );
1968
+ }
1969
+
1970
+ return {
1971
+ receipt: prepare(
1972
+ {
1973
+ removed: selectedAttempts.length + selectedCharges.length + selectedScopes.length,
1974
+ hasMore,
1975
+ },
1976
+ journal,
1977
+ ),
1978
+ statements,
1979
+ retryable: (cause: unknown) => isGuardFailure(cause, marker),
1980
+ };
1981
+ });
1982
+
1983
+ export const makeD1PasswordPersistenceServices = <
1984
+ S extends AnySQLiteTable,
1985
+ I extends AnySQLiteTable,
1986
+ C extends AnySQLiteTable,
1987
+ AC extends AnySQLiteTable,
1988
+ A extends AnySQLiteTable,
1989
+ RS extends AnySQLiteTable,
1990
+ CE extends AnySQLiteTable,
1991
+ M extends AnySQLiteTable,
1992
+ NativeId,
1993
+ >(
1994
+ database: Database,
1995
+ mapping: D1PasswordPersistenceMapping<S, I, C, AC, A, RS, CE, M, NativeId>,
1996
+ proofMapping?: D1ProofPersistenceMapping<
1997
+ any,
1998
+ any,
1999
+ any,
2000
+ any,
2001
+ any,
2002
+ any,
2003
+ any,
2004
+ any,
2005
+ any,
2006
+ any,
2007
+ any,
2008
+ any
2009
+ >,
2010
+ ) =>
2011
+ Effect.gen(function* () {
2012
+ const hooks = yield* LifecycleHooks;
2013
+ const plans = makePasswordPlans(mapping as unknown as Mapping, proofMapping);
2014
+
2015
+ const run = <Out, Err, Env>(plan: Effect.Effect<Planned<Out>, Err, Env>) =>
2016
+ Effect.gen(function* () {
2017
+ if (yield* hasCommitScope) return yield* unavailable();
2018
+
2019
+ return yield* executeStandalone(plan, 2);
2020
+ }).pipe(
2021
+ Effect.provideService(CurrentD1PlanningDatabase, database),
2022
+ Effect.provideService(LifecycleHooks, hooks),
2023
+ );
2024
+
2025
+ const service: PasswordPersistence["Service"] = {
2026
+ admitAttempt: (input, prepare) =>
2027
+ run(plans.admitAttempt(input, prepare)).pipe(translateFailure),
2028
+ settleAttempt: (input, prepare) =>
2029
+ run(plans.settleAttempt(input, prepare)).pipe(translateFailure),
2030
+ readForSubject: (input) =>
2031
+ plans
2032
+ .readForSubject(input)
2033
+ .pipe(
2034
+ Effect.provideService(CurrentD1PlanningDatabase, database),
2035
+ Effect.provideService(LifecycleHooks, hooks),
2036
+ translateFailure,
2037
+ ),
2038
+ recoveryTarget: (input) =>
2039
+ plans
2040
+ .recoveryTarget(input)
2041
+ .pipe(
2042
+ Effect.provideService(CurrentD1PlanningDatabase, database),
2043
+ Effect.provideService(LifecycleHooks, hooks),
2044
+ translateFailure,
2045
+ ),
2046
+ addIfAbsent: (input, prepare) =>
2047
+ run(plans.addIfAbsent(input, prepare)).pipe(translateFailure),
2048
+ replaceIfCurrent: (input, prepare) =>
2049
+ run(plans.replaceIfCurrent(input, prepare)).pipe(translateFailure),
2050
+ checkReset: (input) =>
2051
+ plans
2052
+ .checkReset(input)
2053
+ .pipe(
2054
+ Effect.provideService(CurrentD1PlanningDatabase, database),
2055
+ Effect.provideService(LifecycleHooks, hooks),
2056
+ translateFailure,
2057
+ ),
2058
+ resetWithProof: (input, prepare) =>
2059
+ run(plans.resetWithProof(input, prepare)).pipe(translateFailure),
2060
+ cleanupAttempts: (input, prepare) =>
2061
+ run(plans.cleanupAttempts(input, prepare)).pipe(translateFailure),
2062
+ };
2063
+
2064
+ return { passwordPersistence: service };
2065
+ });
2066
+
2067
+ type CoordinatorError<E> = E | PasswordUnavailable | HookConfigurationError;
2068
+
2069
+ /**
2070
+ * Owns one password transition plus application statements in one D1 batch.
2071
+ * The owner Effect is invoked once and is never replayed after an ambiguous or
2072
+ * guarded batch failure. Standalone adapter-owned commands may replan boundedly.
2073
+ */
2074
+ export function coordinateD1PasswordPersistence<
2075
+ S extends AnySQLiteTable,
2076
+ I extends AnySQLiteTable,
2077
+ C extends AnySQLiteTable,
2078
+ AC extends AnySQLiteTable,
2079
+ At extends AnySQLiteTable,
2080
+ RS extends AnySQLiteTable,
2081
+ CE extends AnySQLiteTable,
2082
+ M extends AnySQLiteTable,
2083
+ NativeId,
2084
+ A,
2085
+ E,
2086
+ R,
2087
+ DatabaseError,
2088
+ DatabaseRequirements,
2089
+ >(
2090
+ acquire: Effect.Effect<Database, DatabaseError, DatabaseRequirements>,
2091
+ options: {
2092
+ readonly mapping: D1PasswordPersistenceMapping<S, I, C, AC, At, RS, CE, M, NativeId>;
2093
+ readonly proofMapping?:
2094
+ | D1ProofPersistenceMapping<any, any, any, any, any, any, any, any, any, any, any, any>
2095
+ | undefined;
2096
+ },
2097
+ body: Effect.Effect<A, E, R>,
2098
+ ): Effect.Effect<
2099
+ A,
2100
+ CoordinatorError<E> | DatabaseError,
2101
+ Exclude<R, PasswordPersistence | D1BatchStatements> | LifecycleHooks | DatabaseRequirements
2102
+ > {
2103
+ return Effect.flatMap(acquire, (database) =>
2104
+ Effect.gen(function* () {
2105
+ const hooks = yield* LifecycleHooks;
2106
+
2107
+ if (yield* hasCommitScope) return yield* unavailable();
2108
+
2109
+ const result = yield* coordinateCommit(
2110
+ () =>
2111
+ Effect.gen(function* () {
2112
+ const statements: Statement<any>[] = [];
2113
+ let mutation: Planned<any> | undefined;
2114
+
2115
+ const nativeCollector = D1BatchStatements.of({
2116
+ append: (statement) => Effect.sync(() => statements.push(statement)),
2117
+ });
2118
+
2119
+ const owner = yield* makeD1Owner(unavailable()).pipe(
2120
+ Effect.provideService(D1BatchStatements, nativeCollector),
2121
+ );
2122
+
2123
+ const plans = makePasswordPlans(
2124
+ options.mapping as unknown as Mapping,
2125
+ options.proofMapping,
2126
+ );
2127
+
2128
+ const run = <Out, Err, Env>(plan: Effect.Effect<Planned<Out>, Err, Env>) =>
2129
+ coordinateCommit(
2130
+ () =>
2131
+ Effect.gen(function* () {
2132
+ const journal = yield* CurrentCommitJournal;
2133
+ const planned = yield* plan;
2134
+
2135
+ yield* owner.check;
2136
+ if (mutation !== undefined) return yield* unavailable();
2137
+ const guarded = { ...planned, journalGuard: journal.prepare(undefined) };
2138
+
2139
+ mutation = guarded;
2140
+ statements.push(...guarded.statements);
2141
+
2142
+ return guarded.receipt;
2143
+ }),
2144
+ { mode: "batch" },
2145
+ ).pipe(
2146
+ Effect.map((result) => result.value),
2147
+ Effect.provideService(CurrentD1PlanningDatabase, database),
2148
+ Effect.provideService(LifecycleHooks, hooks),
2149
+ );
2150
+
2151
+ const service: PasswordPersistence["Service"] = {
2152
+ admitAttempt: (input, prepare) =>
2153
+ owner.run(run(plans.admitAttempt(input, prepare)).pipe(translateFailure)),
2154
+ settleAttempt: (input, prepare) =>
2155
+ owner.run(run(plans.settleAttempt(input, prepare)).pipe(translateFailure)),
2156
+ readForSubject: (input) =>
2157
+ owner.run(
2158
+ plans
2159
+ .readForSubject(input)
2160
+ .pipe(
2161
+ Effect.provideService(CurrentD1PlanningDatabase, database),
2162
+ Effect.provideService(LifecycleHooks, hooks),
2163
+ translateFailure,
2164
+ ),
2165
+ ),
2166
+ recoveryTarget: (input) =>
2167
+ owner.run(
2168
+ plans
2169
+ .recoveryTarget(input)
2170
+ .pipe(
2171
+ Effect.provideService(CurrentD1PlanningDatabase, database),
2172
+ Effect.provideService(LifecycleHooks, hooks),
2173
+ translateFailure,
2174
+ ),
2175
+ ),
2176
+ addIfAbsent: (input, prepare) =>
2177
+ owner.run(run(plans.addIfAbsent(input, prepare)).pipe(translateFailure)),
2178
+ replaceIfCurrent: (input, prepare) =>
2179
+ owner.run(run(plans.replaceIfCurrent(input, prepare)).pipe(translateFailure)),
2180
+ checkReset: (input) =>
2181
+ owner.run(
2182
+ plans
2183
+ .checkReset(input)
2184
+ .pipe(
2185
+ Effect.provideService(CurrentD1PlanningDatabase, database),
2186
+ Effect.provideService(LifecycleHooks, hooks),
2187
+ translateFailure,
2188
+ ),
2189
+ ),
2190
+ resetWithProof: (input, prepare) =>
2191
+ owner.run(run(plans.resetWithProof(input, prepare)).pipe(translateFailure)),
2192
+ cleanupAttempts: (input, prepare) =>
2193
+ owner.run(run(plans.cleanupAttempts(input, prepare)).pipe(translateFailure)),
2194
+ };
2195
+
2196
+ const provided = Context.make(PasswordPersistence, service).pipe(
2197
+ Context.add(D1BatchStatements, owner.collector),
2198
+ );
2199
+
2200
+ const value = yield* owner.close(Effect.provideContext(body, provided));
2201
+
2202
+ if (mutation?.journalGuard !== undefined) {
2203
+ const status = yield* Effect.result(mutation.journalGuard.read);
2204
+
2205
+ if (status._tag === "Success" || status.failure._tag !== "CommitPending")
2206
+ return yield* unavailable();
2207
+ }
2208
+ yield* database.$client.batch(statements).pipe(
2209
+ Effect.catchCause((cause) =>
2210
+ Effect.failCause(
2211
+ Cause.map(cause, (error) =>
2212
+ everyFailureMatches(cause, mutation?.retryable) ? unavailable() : error,
2213
+ ),
2214
+ ),
2215
+ ),
2216
+ translateFailure,
2217
+ );
2218
+
2219
+ return value;
2220
+ }),
2221
+ { mode: "batch" },
2222
+ ).pipe(Effect.provideService(LifecycleHooks, hooks));
2223
+
2224
+ return result.value;
2225
+ }),
2226
+ );
2227
+ }
2228
+
2229
+ const registrationRows = Effect.fn("Drizzle.registrationRows")(function* <Registration>(
2230
+ mapping: RegistrationMapping<Registration>,
2231
+ moduleId: string,
2232
+ requestId: string,
2233
+ ) {
2234
+ const database = yield* CurrentD1PlanningDatabase;
2235
+
2236
+ const module = column(mapping.registration.table, mapping.registration.moduleId);
2237
+ const request = column(mapping.registration.table, mapping.registration.requestId);
2238
+
2239
+ return yield* database
2240
+ .select()
2241
+ .from(mapping.registration.table)
2242
+ .where(and(eq(module, moduleId), eq(request, requestId)))
2243
+ .limit(1);
2244
+ });
2245
+
2246
+ const registrationIdentifierRows = Effect.fn("Drizzle.registrationIdentifierRows")(function* <
2247
+ Registration,
2248
+ >(mapping: RegistrationMapping<Registration>, identifier: LoginIdentifier) {
2249
+ const database = yield* CurrentD1PlanningDatabase;
2250
+
2251
+ const namespace = column(mapping.identifier.table, mapping.identifier.namespace);
2252
+ const value = column(mapping.identifier.table, mapping.identifier.value);
2253
+
2254
+ return yield* database
2255
+ .select()
2256
+ .from(mapping.identifier.table)
2257
+ .where(and(eq(namespace, identifier.namespace), eq(value, identifier.value)))
2258
+ .limit(1);
2259
+ });
2260
+
2261
+ const planRegistration = Effect.fn("Drizzle.planRegistration")(function* <Registration, A>(
2262
+ mapping: RegistrationMapping<Registration>,
2263
+ input: {
2264
+ readonly moduleId: string;
2265
+ readonly requestId: string;
2266
+ readonly identifier: LoginIdentifier;
2267
+ readonly registration: Registration;
2268
+ readonly replacement: PasswordMutationInput["replacement"];
2269
+ },
2270
+ allocated: {
2271
+ readonly credentialId: string;
2272
+ readonly securityRevision: SecurityRevision;
2273
+ readonly identifierRevision: SecurityRevision;
2274
+ readonly credentialRevision: SecurityRevision;
2275
+ readonly verifierVersion: SecurityRevision;
2276
+ readonly nativeSubjectId?: unknown;
2277
+ readonly recoveryReference?: any;
2278
+ },
2279
+ prepare: PreparePasswordCommit<import("@yielded/auth/Password").PasswordRegistrationDecision, A>,
2280
+ ) {
2281
+ const database = yield* CurrentD1PlanningDatabase;
2282
+ const journal = yield* CurrentCommitJournal;
2283
+
2284
+ const existing = (yield* registrationRows(mapping, input.moduleId, input.requestId))[0];
2285
+
2286
+ if (existing !== undefined)
2287
+ return {
2288
+ receipt: prepare(yield* mapping.registration.decodeReplay(existing), journal),
2289
+ statements: [],
2290
+ };
2291
+
2292
+ const intent = {
2293
+ moduleId: input.moduleId,
2294
+ requestId: input.requestId,
2295
+ identifier: input.identifier,
2296
+ registration: input.registration,
2297
+ replacement: input.replacement,
2298
+ };
2299
+
2300
+ if (mapping.mode === "pending") {
2301
+ if (allocated.recoveryReference === undefined) return yield* unavailable();
2302
+
2303
+ return {
2304
+ receipt: prepare({ _tag: "Pending", reference: allocated.recoveryReference }, journal),
2305
+ statements: [
2306
+ yield* statement(
2307
+ database.insert(mapping.registration.table).values(
2308
+ mapping.registration.encodeInsert(intent, {
2309
+ state: "pending",
2310
+ recoveryReference: allocated.recoveryReference,
2311
+ }),
2312
+ ),
2313
+ ),
2314
+ ],
2315
+ retryable: (cause: unknown) => isMappedConstraintConflict(mapping.isRequestConflict, cause),
2316
+ };
2317
+ }
2318
+ if (mapping.provisioning.idMode === "generated" || allocated.nativeSubjectId === undefined)
2319
+ return yield* unavailable();
2320
+ if ((yield* registrationIdentifierRows(mapping, input.identifier)).length > 0)
2321
+ return { receipt: prepare({ _tag: "Suppressed" }, journal), statements: [] };
2322
+ const nativeSubjectId = allocated.nativeSubjectId;
2323
+ const subjectId = yield* mapping.subjectId.toSubject(nativeSubjectId);
2324
+
2325
+ return {
2326
+ receipt: prepare({ _tag: "Created", subjectId }, journal),
2327
+ statements: [
2328
+ yield* statement(
2329
+ database.insert(mapping.subject.table).values(
2330
+ mapping.provisioning.encodeSubjectInsert(intent, {
2331
+ nativeSubjectId,
2332
+ securityRevision: allocated.securityRevision,
2333
+ }),
2334
+ ),
2335
+ ),
2336
+ yield* statement(
2337
+ database
2338
+ .insert(mapping.identifier.table)
2339
+ .values(
2340
+ mapping.identifier.encodeInitialInsert(
2341
+ input.identifier,
2342
+ nativeSubjectId,
2343
+ allocated.identifierRevision,
2344
+ ),
2345
+ ),
2346
+ ),
2347
+ yield* statement(
2348
+ database.insert(mapping.credential.table).values(
2349
+ mapping.credential.encodeInsert({
2350
+ moduleId: input.moduleId,
2351
+ subjectId: nativeSubjectId,
2352
+ credentialId: allocated.credentialId,
2353
+ credentialRevision: allocated.credentialRevision,
2354
+ verifierVersion: allocated.verifierVersion,
2355
+ replacement: input.replacement,
2356
+ }),
2357
+ ),
2358
+ ),
2359
+ yield* statement(
2360
+ database.insert(mapping.authorityCredential.table).values(
2361
+ mapping.authorityCredential.encodeInsert({
2362
+ subjectId: nativeSubjectId,
2363
+ credentialId: allocated.credentialId,
2364
+ revision: allocated.credentialRevision,
2365
+ }),
2366
+ ),
2367
+ ),
2368
+ yield* statement(
2369
+ database.insert(mapping.registration.table).values(
2370
+ mapping.registration.encodeInsert(intent, {
2371
+ state: "created",
2372
+ nativeSubjectId,
2373
+ }),
2374
+ ),
2375
+ ),
2376
+ ],
2377
+ retryable: (cause: unknown) =>
2378
+ isMappedConstraintConflict(mapping.isRequestConflict, cause) ||
2379
+ isMappedConstraintConflict(mapping.isIdentifierConflict, cause),
2380
+ };
2381
+ });
2382
+
2383
+ const allocateRegistration = <Registration>(mapping: RegistrationMapping<Registration>) =>
2384
+ Effect.gen(function* () {
2385
+ const credentialId = yield* allocate(
2386
+ mapping.allocateCredentialId,
2387
+ mapping.allocateCredentialIdSync,
2388
+ );
2389
+
2390
+ const securityRevision = yield* allocate(
2391
+ mapping.allocateRevision,
2392
+ mapping.allocateRevisionSync,
2393
+ );
2394
+
2395
+ const identifierRevision = yield* allocate(
2396
+ mapping.allocateRevision,
2397
+ mapping.allocateRevisionSync,
2398
+ );
2399
+
2400
+ const credentialRevision = yield* allocate(
2401
+ mapping.allocateRevision,
2402
+ mapping.allocateRevisionSync,
2403
+ );
2404
+
2405
+ const verifierVersion = yield* allocate(mapping.allocateRevision, mapping.allocateRevisionSync);
2406
+
2407
+ const nativeSubjectId =
2408
+ mapping.mode !== "atomic" || mapping.provisioning.idMode === "generated"
2409
+ ? undefined
2410
+ : yield* allocate(
2411
+ mapping.provisioning.allocateSubjectId,
2412
+ mapping.provisioning.allocateSubjectIdSync,
2413
+ );
2414
+
2415
+ const recoveryReference =
2416
+ mapping.mode === "pending"
2417
+ ? yield* allocate(mapping.allocateRecoveryReference, mapping.allocateRecoveryReferenceSync)
2418
+ : undefined;
2419
+
2420
+ return {
2421
+ credentialId,
2422
+ securityRevision,
2423
+ identifierRevision,
2424
+ credentialRevision,
2425
+ verifierVersion,
2426
+ ...(nativeSubjectId === undefined ? {} : { nativeSubjectId }),
2427
+ ...(recoveryReference === undefined ? {} : { recoveryReference }),
2428
+ };
2429
+ });
2430
+
2431
+ const makeRegistrationPlans = <Registration>(mapping: RegistrationMapping<Registration>) => ({
2432
+ register: <A>(
2433
+ input: Parameters<PasswordRegistrationAuthority<Registration>["register"]>[0],
2434
+ prepare: PlanPrepare<PasswordRegistrationAuthority<Registration>["register"], A>,
2435
+ ) =>
2436
+ Effect.gen(function* () {
2437
+ if (!validRegistrationConstraints(mapping)) return yield* unavailable();
2438
+ if (mapping.mode === "atomic" && mapping.provisioning.idMode === "generated")
2439
+ return yield* unavailable();
2440
+ const allocated = yield* allocateRegistration(mapping);
2441
+
2442
+ return yield* planRegistration(mapping, input, allocated, prepare);
2443
+ }),
2444
+ });
2445
+
2446
+ export const makeD1PasswordRegistrationServices = <
2447
+ Registration,
2448
+ S extends AnySQLiteTable,
2449
+ I extends AnySQLiteTable,
2450
+ C extends AnySQLiteTable,
2451
+ AC extends AnySQLiteTable,
2452
+ Rq extends AnySQLiteTable,
2453
+ NativeId,
2454
+ >(
2455
+ database: Database,
2456
+ mapping: PasswordRegistrationMapping<Registration, S, I, C, AC, Rq, NativeId>,
2457
+ ) =>
2458
+ Effect.gen(function* () {
2459
+ const hooks = yield* LifecycleHooks;
2460
+ const plans = makeRegistrationPlans(mapping as unknown as RegistrationMapping<Registration>);
2461
+
2462
+ const run = <Out, Err, Env>(plan: Effect.Effect<Planned<Out>, Err, Env>) =>
2463
+ Effect.gen(function* () {
2464
+ if (yield* hasCommitScope) return yield* unavailable();
2465
+
2466
+ return yield* executeStandalone(plan, 2);
2467
+ }).pipe(
2468
+ Effect.provideService(CurrentD1PlanningDatabase, database),
2469
+ Effect.provideService(LifecycleHooks, hooks),
2470
+ );
2471
+
2472
+ const service: PasswordRegistrationAuthority<Registration> = {
2473
+ register: (input, prepare) => run(plans.register(input, prepare)).pipe(translateFailure),
2474
+ };
2475
+
2476
+ return { registrationAuthority: service };
2477
+ });
2478
+
2479
+ export function coordinateD1PasswordRegistration<
2480
+ TargetId,
2481
+ Registration,
2482
+ S extends AnySQLiteTable,
2483
+ I extends AnySQLiteTable,
2484
+ C extends AnySQLiteTable,
2485
+ AC extends AnySQLiteTable,
2486
+ Rq extends AnySQLiteTable,
2487
+ NativeId,
2488
+ A,
2489
+ E,
2490
+ R,
2491
+ DatabaseError,
2492
+ DatabaseRequirements,
2493
+ >(
2494
+ acquire: Effect.Effect<Database, DatabaseError, DatabaseRequirements>,
2495
+ options: {
2496
+ readonly mapping: PasswordRegistrationMapping<NoInfer<Registration>, S, I, C, AC, Rq, NativeId>;
2497
+ readonly target: SuppliedService<TargetId, PasswordRegistrationAuthority<Registration>>;
2498
+ },
2499
+ body: Effect.Effect<A, E, R>,
2500
+ ): Effect.Effect<
2501
+ A,
2502
+ CoordinatorError<E> | DatabaseError,
2503
+ Exclude<R, TargetId | D1BatchStatements> | LifecycleHooks | DatabaseRequirements
2504
+ > {
2505
+ return Effect.flatMap(acquire, (database) =>
2506
+ Effect.gen(function* () {
2507
+ const hooks = yield* LifecycleHooks;
2508
+
2509
+ if (yield* hasCommitScope) return yield* unavailable();
2510
+
2511
+ const result = yield* coordinateCommit(
2512
+ () =>
2513
+ Effect.gen(function* () {
2514
+ const statements: Statement<any>[] = [];
2515
+ let mutation: Planned<any> | undefined;
2516
+
2517
+ const nativeCollector = D1BatchStatements.of({
2518
+ append: (statement) => Effect.sync(() => statements.push(statement)),
2519
+ });
2520
+
2521
+ const owner = yield* makeD1Owner(unavailable()).pipe(
2522
+ Effect.provideService(D1BatchStatements, nativeCollector),
2523
+ );
2524
+
2525
+ const plans = makeRegistrationPlans(
2526
+ options.mapping as unknown as RegistrationMapping<Registration>,
2527
+ );
2528
+
2529
+ const run = <Out, Err, Env>(plan: Effect.Effect<Planned<Out>, Err, Env>) =>
2530
+ coordinateCommit(
2531
+ () =>
2532
+ Effect.gen(function* () {
2533
+ const journal = yield* CurrentCommitJournal;
2534
+ const planned = yield* plan;
2535
+
2536
+ yield* owner.check;
2537
+ if (mutation !== undefined) return yield* unavailable();
2538
+ const guarded = { ...planned, journalGuard: journal.prepare(undefined) };
2539
+
2540
+ mutation = guarded;
2541
+ statements.push(...guarded.statements);
2542
+
2543
+ return guarded.receipt;
2544
+ }),
2545
+ { mode: "batch" },
2546
+ ).pipe(
2547
+ Effect.map((result) => result.value),
2548
+ Effect.provideService(CurrentD1PlanningDatabase, database),
2549
+ Effect.provideService(LifecycleHooks, hooks),
2550
+ );
2551
+
2552
+ const service: PasswordRegistrationAuthority<Registration> = {
2553
+ register: (input, prepare) =>
2554
+ owner.run(run(plans.register(input, prepare)).pipe(translateFailure)),
2555
+ };
2556
+
2557
+ const provided = Context.make(options.target, service).pipe(
2558
+ Context.add(D1BatchStatements, owner.collector),
2559
+ );
2560
+
2561
+ const value = yield* owner.close(Effect.provideContext(body, provided));
2562
+
2563
+ if (mutation?.journalGuard !== undefined) {
2564
+ const status = yield* Effect.result(mutation.journalGuard.read);
2565
+
2566
+ if (status._tag === "Success" || status.failure._tag !== "CommitPending")
2567
+ return yield* unavailable();
2568
+ }
2569
+ yield* database.$client.batch(statements).pipe(
2570
+ Effect.catchCause((cause) =>
2571
+ Effect.failCause(
2572
+ Cause.map(cause, (error) =>
2573
+ everyFailureMatches(cause, mutation?.retryable) ? unavailable() : error,
2574
+ ),
2575
+ ),
2576
+ ),
2577
+ translateFailure,
2578
+ );
2579
+
2580
+ return value;
2581
+ }),
2582
+ { mode: "batch" },
2583
+ ).pipe(Effect.provideService(LifecycleHooks, hooks));
2584
+
2585
+ return result.value;
2586
+ }),
2587
+ );
2588
+ }
2589
+
2590
+ /** Private prepared compiler seam: it never replays a caller or prepared callback. */
2591
+ export const passwordD1Kernel = {
2592
+ statement,
2593
+ assertion,
2594
+ readEngineNowMillis,
2595
+ allocate,
2596
+ allocateNextSecurityRevision,
2597
+ subjectColumns,
2598
+ identifierColumns,
2599
+ credentialColumns,
2600
+ authorityColumns,
2601
+ readAuthority,
2602
+ assuranceCondition,
2603
+ credentialCurrentCondition,
2604
+ commandAbsentCondition,
2605
+ compileAddMutation,
2606
+ compileReplacementMutation,
2607
+ completionMatchesPassword,
2608
+ isGuardFailure,
2609
+ };
2610
+
2611
+ const translateFailure = <A, E, R>(
2612
+ effect: Effect.Effect<A, E, R>,
2613
+ ): Effect.Effect<A, PasswordUnavailable, R> =>
2614
+ reportPersistenceFailure(
2615
+ effect,
2616
+ (error) =>
2617
+ Schema.is(PasswordUnavailable)(error) ||
2618
+ Schema.is(ProofUnavailable)(error) ||
2619
+ Schema.is(HookConfigurationError)(error),
2620
+ ).pipe(Effect.catchCause((cause) => Effect.failCause(Cause.map(cause, () => unavailable()))));
2621
+
2622
+ const executeStandalone = <A, E, R>(
2623
+ plan: Effect.Effect<Planned<A>, E, R>,
2624
+ retries = 2,
2625
+ ): Effect.Effect<
2626
+ A,
2627
+ | E
2628
+ | import("@yielded/auth/Hooks").HookConfigurationError
2629
+ | import("effect/unstable/sql/SqlError").SqlError,
2630
+ Exclude<R, CurrentCommitJournal> | CurrentD1PlanningDatabase | LifecycleHooks
2631
+ > =>
2632
+ Effect.suspend(() => {
2633
+ let retryable: ((cause: unknown) => boolean) | undefined;
2634
+
2635
+ const once = coordinateCommit(
2636
+ () =>
2637
+ Effect.gen(function* () {
2638
+ const database = yield* CurrentD1PlanningDatabase;
2639
+ const planned = yield* plan;
2640
+
2641
+ retryable = planned.retryable;
2642
+ yield* database.$client.batch(planned.statements);
2643
+
2644
+ return planned.receipt;
2645
+ }),
2646
+ { mode: "batch" },
2647
+ ).pipe(Effect.map((result) => result.value));
2648
+
2649
+ return once.pipe(
2650
+ Effect.catchCause((cause) =>
2651
+ retries > 0 && everyFailureMatches(cause, retryable)
2652
+ ? executeStandalone(plan, retries - 1)
2653
+ : Effect.failCause(cause),
2654
+ ),
2655
+ );
2656
+ });