@shware/workflow 0.0.0-stage → 0.1.0

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 (442) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +154 -2
  3. package/dist/_virtual/_rolldown/runtime.cjs +23 -0
  4. package/dist/apns-relay.cjs +105 -0
  5. package/dist/apns-relay.cjs.map +1 -0
  6. package/dist/apns-relay.d.cts +28 -0
  7. package/dist/apns-relay.d.cts.map +1 -0
  8. package/dist/apns-relay.d.mts +28 -0
  9. package/dist/apns-relay.d.mts.map +1 -0
  10. package/dist/apns-relay.mjs +104 -0
  11. package/dist/apns-relay.mjs.map +1 -0
  12. package/dist/apns.cjs +121 -0
  13. package/dist/apns.cjs.map +1 -0
  14. package/dist/apns.d.cts +63 -0
  15. package/dist/apns.d.cts.map +1 -0
  16. package/dist/apns.d.mts +63 -0
  17. package/dist/apns.d.mts.map +1 -0
  18. package/dist/apns.mjs +116 -0
  19. package/dist/apns.mjs.map +1 -0
  20. package/dist/client.cjs +125 -0
  21. package/dist/client.cjs.map +1 -0
  22. package/dist/client.d.cts +108 -0
  23. package/dist/client.d.cts.map +1 -0
  24. package/dist/client.d.mts +108 -0
  25. package/dist/client.d.mts.map +1 -0
  26. package/dist/client.mjs +122 -0
  27. package/dist/client.mjs.map +1 -0
  28. package/dist/cloudflare/bindings.cjs +28 -0
  29. package/dist/cloudflare/bindings.cjs.map +1 -0
  30. package/dist/cloudflare/bindings.d.cts +144 -0
  31. package/dist/cloudflare/bindings.d.cts.map +1 -0
  32. package/dist/cloudflare/bindings.d.mts +144 -0
  33. package/dist/cloudflare/bindings.d.mts.map +1 -0
  34. package/dist/cloudflare/bindings.mjs +25 -0
  35. package/dist/cloudflare/bindings.mjs.map +1 -0
  36. package/dist/cloudflare/channels.cjs +164 -0
  37. package/dist/cloudflare/channels.cjs.map +1 -0
  38. package/dist/cloudflare/channels.d.cts +29 -0
  39. package/dist/cloudflare/channels.d.cts.map +1 -0
  40. package/dist/cloudflare/channels.d.mts +29 -0
  41. package/dist/cloudflare/channels.d.mts.map +1 -0
  42. package/dist/cloudflare/channels.mjs +161 -0
  43. package/dist/cloudflare/channels.mjs.map +1 -0
  44. package/dist/cloudflare/d1-env.cjs +17 -0
  45. package/dist/cloudflare/d1-env.cjs.map +1 -0
  46. package/dist/cloudflare/d1-env.d.cts +12 -0
  47. package/dist/cloudflare/d1-env.d.cts.map +1 -0
  48. package/dist/cloudflare/d1-env.d.mts +12 -0
  49. package/dist/cloudflare/d1-env.d.mts.map +1 -0
  50. package/dist/cloudflare/d1-env.mjs +16 -0
  51. package/dist/cloudflare/d1-env.mjs.map +1 -0
  52. package/dist/cloudflare/index.cjs +54 -0
  53. package/dist/cloudflare/index.d.cts +19 -0
  54. package/dist/cloudflare/index.d.mts +19 -0
  55. package/dist/cloudflare/index.mjs +16 -0
  56. package/dist/cloudflare/router.cjs +516 -0
  57. package/dist/cloudflare/router.cjs.map +1 -0
  58. package/dist/cloudflare/router.d.cts +157 -0
  59. package/dist/cloudflare/router.d.cts.map +1 -0
  60. package/dist/cloudflare/router.d.mts +157 -0
  61. package/dist/cloudflare/router.d.mts.map +1 -0
  62. package/dist/cloudflare/router.mjs +503 -0
  63. package/dist/cloudflare/router.mjs.map +1 -0
  64. package/dist/cloudflare/runner.cjs +225 -0
  65. package/dist/cloudflare/runner.cjs.map +1 -0
  66. package/dist/cloudflare/runner.d.cts +73 -0
  67. package/dist/cloudflare/runner.d.cts.map +1 -0
  68. package/dist/cloudflare/runner.d.mts +73 -0
  69. package/dist/cloudflare/runner.d.mts.map +1 -0
  70. package/dist/cloudflare/runner.mjs +224 -0
  71. package/dist/cloudflare/runner.mjs.map +1 -0
  72. package/dist/cloudflare/schema.sql +127 -0
  73. package/dist/cloudflare/senders.cjs +114 -0
  74. package/dist/cloudflare/senders.cjs.map +1 -0
  75. package/dist/cloudflare/senders.d.cts +68 -0
  76. package/dist/cloudflare/senders.d.cts.map +1 -0
  77. package/dist/cloudflare/senders.d.mts +68 -0
  78. package/dist/cloudflare/senders.d.mts.map +1 -0
  79. package/dist/cloudflare/senders.mjs +108 -0
  80. package/dist/cloudflare/senders.mjs.map +1 -0
  81. package/dist/cloudflare/worker.cjs +228 -0
  82. package/dist/cloudflare/worker.cjs.map +1 -0
  83. package/dist/cloudflare/worker.d.cts +92 -0
  84. package/dist/cloudflare/worker.d.cts.map +1 -0
  85. package/dist/cloudflare/worker.d.mts +92 -0
  86. package/dist/cloudflare/worker.d.mts.map +1 -0
  87. package/dist/cloudflare/worker.mjs +224 -0
  88. package/dist/cloudflare/worker.mjs.map +1 -0
  89. package/dist/cloudflare/workers.d.cjs +0 -0
  90. package/dist/cloudflare/workers.d.cts +44 -0
  91. package/dist/cloudflare/workers.d.cts.map +1 -0
  92. package/dist/cloudflare/workers.d.mts +44 -0
  93. package/dist/cloudflare/workers.d.mts.map +1 -0
  94. package/dist/config.cjs +151 -0
  95. package/dist/config.cjs.map +1 -0
  96. package/dist/config.d.cts +149 -0
  97. package/dist/config.d.cts.map +1 -0
  98. package/dist/config.d.mts +149 -0
  99. package/dist/config.d.mts.map +1 -0
  100. package/dist/config.mjs +143 -0
  101. package/dist/config.mjs.map +1 -0
  102. package/dist/contract.cjs +212 -0
  103. package/dist/contract.cjs.map +1 -0
  104. package/dist/contract.d.cts +90 -0
  105. package/dist/contract.d.cts.map +1 -0
  106. package/dist/contract.d.mts +90 -0
  107. package/dist/contract.d.mts.map +1 -0
  108. package/dist/contract.mjs +206 -0
  109. package/dist/contract.mjs.map +1 -0
  110. package/dist/dsl/action.cjs +29 -0
  111. package/dist/dsl/action.cjs.map +1 -0
  112. package/dist/dsl/action.d.cts +64 -0
  113. package/dist/dsl/action.d.cts.map +1 -0
  114. package/dist/dsl/action.d.mts +64 -0
  115. package/dist/dsl/action.d.mts.map +1 -0
  116. package/dist/dsl/action.mjs +28 -0
  117. package/dist/dsl/action.mjs.map +1 -0
  118. package/dist/dsl/base.cjs +34 -0
  119. package/dist/dsl/base.cjs.map +1 -0
  120. package/dist/dsl/base.d.cts +45 -0
  121. package/dist/dsl/base.d.cts.map +1 -0
  122. package/dist/dsl/base.d.mts +45 -0
  123. package/dist/dsl/base.d.mts.map +1 -0
  124. package/dist/dsl/base.mjs +30 -0
  125. package/dist/dsl/base.mjs.map +1 -0
  126. package/dist/dsl/bundle.cjs +207 -0
  127. package/dist/dsl/bundle.cjs.map +1 -0
  128. package/dist/dsl/bundle.d.cts +19 -0
  129. package/dist/dsl/bundle.d.cts.map +1 -0
  130. package/dist/dsl/bundle.d.mts +19 -0
  131. package/dist/dsl/bundle.d.mts.map +1 -0
  132. package/dist/dsl/bundle.mjs +206 -0
  133. package/dist/dsl/bundle.mjs.map +1 -0
  134. package/dist/dsl/condition.cjs +177 -0
  135. package/dist/dsl/condition.cjs.map +1 -0
  136. package/dist/dsl/condition.d.cts +140 -0
  137. package/dist/dsl/condition.d.cts.map +1 -0
  138. package/dist/dsl/condition.d.mts +140 -0
  139. package/dist/dsl/condition.d.mts.map +1 -0
  140. package/dist/dsl/condition.mjs +156 -0
  141. package/dist/dsl/condition.mjs.map +1 -0
  142. package/dist/dsl/flow.cjs +228 -0
  143. package/dist/dsl/flow.cjs.map +1 -0
  144. package/dist/dsl/flow.d.cts +223 -0
  145. package/dist/dsl/flow.d.cts.map +1 -0
  146. package/dist/dsl/flow.d.mts +223 -0
  147. package/dist/dsl/flow.d.mts.map +1 -0
  148. package/dist/dsl/flow.mjs +226 -0
  149. package/dist/dsl/flow.mjs.map +1 -0
  150. package/dist/dsl/index.cjs +38 -0
  151. package/dist/dsl/index.d.cts +10 -0
  152. package/dist/dsl/index.d.mts +10 -0
  153. package/dist/dsl/index.mjs +9 -0
  154. package/dist/dsl/refs.cjs +59 -0
  155. package/dist/dsl/refs.cjs.map +1 -0
  156. package/dist/dsl/refs.d.cts +88 -0
  157. package/dist/dsl/refs.d.cts.map +1 -0
  158. package/dist/dsl/refs.d.mts +88 -0
  159. package/dist/dsl/refs.d.mts.map +1 -0
  160. package/dist/dsl/refs.mjs +55 -0
  161. package/dist/dsl/refs.mjs.map +1 -0
  162. package/dist/dsl/template.cjs +65 -0
  163. package/dist/dsl/template.cjs.map +1 -0
  164. package/dist/dsl/template.d.cts +75 -0
  165. package/dist/dsl/template.d.cts.map +1 -0
  166. package/dist/dsl/template.d.mts +75 -0
  167. package/dist/dsl/template.d.mts.map +1 -0
  168. package/dist/dsl/template.mjs +63 -0
  169. package/dist/dsl/template.mjs.map +1 -0
  170. package/dist/dsl/trigger.cjs +40 -0
  171. package/dist/dsl/trigger.cjs.map +1 -0
  172. package/dist/dsl/trigger.d.cts +65 -0
  173. package/dist/dsl/trigger.d.cts.map +1 -0
  174. package/dist/dsl/trigger.d.mts +65 -0
  175. package/dist/dsl/trigger.d.mts.map +1 -0
  176. package/dist/dsl/trigger.mjs +39 -0
  177. package/dist/dsl/trigger.mjs.map +1 -0
  178. package/dist/dsl/workflow.cjs +121 -0
  179. package/dist/dsl/workflow.cjs.map +1 -0
  180. package/dist/dsl/workflow.d.cts +59 -0
  181. package/dist/dsl/workflow.d.cts.map +1 -0
  182. package/dist/dsl/workflow.d.mts +59 -0
  183. package/dist/dsl/workflow.d.mts.map +1 -0
  184. package/dist/dsl/workflow.mjs +120 -0
  185. package/dist/dsl/workflow.mjs.map +1 -0
  186. package/dist/engine/actions.cjs +27 -0
  187. package/dist/engine/actions.cjs.map +1 -0
  188. package/dist/engine/actions.d.cts +40 -0
  189. package/dist/engine/actions.d.cts.map +1 -0
  190. package/dist/engine/actions.d.mts +40 -0
  191. package/dist/engine/actions.d.mts.map +1 -0
  192. package/dist/engine/actions.mjs +26 -0
  193. package/dist/engine/actions.mjs.map +1 -0
  194. package/dist/engine/bucket.cjs +72 -0
  195. package/dist/engine/bucket.cjs.map +1 -0
  196. package/dist/engine/bucket.d.cts +32 -0
  197. package/dist/engine/bucket.d.cts.map +1 -0
  198. package/dist/engine/bucket.d.mts +32 -0
  199. package/dist/engine/bucket.d.mts.map +1 -0
  200. package/dist/engine/bucket.mjs +69 -0
  201. package/dist/engine/bucket.mjs.map +1 -0
  202. package/dist/engine/condition.cjs +166 -0
  203. package/dist/engine/condition.cjs.map +1 -0
  204. package/dist/engine/condition.d.cts +52 -0
  205. package/dist/engine/condition.d.cts.map +1 -0
  206. package/dist/engine/condition.d.mts +52 -0
  207. package/dist/engine/condition.d.mts.map +1 -0
  208. package/dist/engine/condition.mjs +161 -0
  209. package/dist/engine/condition.mjs.map +1 -0
  210. package/dist/engine/index.cjs +20 -0
  211. package/dist/engine/index.d.cts +8 -0
  212. package/dist/engine/index.d.mts +8 -0
  213. package/dist/engine/index.mjs +7 -0
  214. package/dist/engine/interpreter.cjs +428 -0
  215. package/dist/engine/interpreter.cjs.map +1 -0
  216. package/dist/engine/interpreter.d.cts +6 -0
  217. package/dist/engine/interpreter.d.cts.map +1 -0
  218. package/dist/engine/interpreter.d.mts +6 -0
  219. package/dist/engine/interpreter.d.mts.map +1 -0
  220. package/dist/engine/interpreter.mjs +427 -0
  221. package/dist/engine/interpreter.mjs.map +1 -0
  222. package/dist/engine/ports.cjs +0 -0
  223. package/dist/engine/ports.d.cts +252 -0
  224. package/dist/engine/ports.d.cts.map +1 -0
  225. package/dist/engine/ports.d.mts +252 -0
  226. package/dist/engine/ports.d.mts.map +1 -0
  227. package/dist/engine/ports.mjs +1 -0
  228. package/dist/engine/recipient.cjs +23 -0
  229. package/dist/engine/recipient.cjs.map +1 -0
  230. package/dist/engine/recipient.d.cts +13 -0
  231. package/dist/engine/recipient.d.cts.map +1 -0
  232. package/dist/engine/recipient.d.mts +13 -0
  233. package/dist/engine/recipient.d.mts.map +1 -0
  234. package/dist/engine/recipient.mjs +22 -0
  235. package/dist/engine/recipient.mjs.map +1 -0
  236. package/dist/engine/time-scale.cjs +54 -0
  237. package/dist/engine/time-scale.cjs.map +1 -0
  238. package/dist/engine/time-scale.d.cts +31 -0
  239. package/dist/engine/time-scale.d.cts.map +1 -0
  240. package/dist/engine/time-scale.d.mts +31 -0
  241. package/dist/engine/time-scale.d.mts.map +1 -0
  242. package/dist/engine/time-scale.mjs +52 -0
  243. package/dist/engine/time-scale.mjs.map +1 -0
  244. package/dist/engine/time-window.cjs +149 -0
  245. package/dist/engine/time-window.cjs.map +1 -0
  246. package/dist/engine/time-window.d.cts +22 -0
  247. package/dist/engine/time-window.d.cts.map +1 -0
  248. package/dist/engine/time-window.d.mts +22 -0
  249. package/dist/engine/time-window.d.mts.map +1 -0
  250. package/dist/engine/time-window.mjs +147 -0
  251. package/dist/engine/time-window.mjs.map +1 -0
  252. package/dist/fcm.cjs +125 -0
  253. package/dist/fcm.cjs.map +1 -0
  254. package/dist/fcm.d.cts +48 -0
  255. package/dist/fcm.d.cts.map +1 -0
  256. package/dist/fcm.d.mts +48 -0
  257. package/dist/fcm.d.mts.map +1 -0
  258. package/dist/fcm.mjs +121 -0
  259. package/dist/fcm.mjs.map +1 -0
  260. package/dist/hash.cjs +245 -0
  261. package/dist/hash.cjs.map +1 -0
  262. package/dist/hash.d.cts +30 -0
  263. package/dist/hash.d.cts.map +1 -0
  264. package/dist/hash.d.mts +30 -0
  265. package/dist/hash.d.mts.map +1 -0
  266. package/dist/hash.mjs +239 -0
  267. package/dist/hash.mjs.map +1 -0
  268. package/dist/index.cjs +100 -0
  269. package/dist/index.d.cts +18 -0
  270. package/dist/index.d.mts +18 -0
  271. package/dist/index.mjs +17 -0
  272. package/dist/ir.cjs +366 -0
  273. package/dist/ir.cjs.map +1 -0
  274. package/dist/ir.d.cts +486 -0
  275. package/dist/ir.d.cts.map +1 -0
  276. package/dist/ir.d.mts +486 -0
  277. package/dist/ir.d.mts.map +1 -0
  278. package/dist/ir.mjs +344 -0
  279. package/dist/ir.mjs.map +1 -0
  280. package/dist/jwt.cjs +22 -0
  281. package/dist/jwt.cjs.map +1 -0
  282. package/dist/jwt.d.cts +9 -0
  283. package/dist/jwt.d.cts.map +1 -0
  284. package/dist/jwt.d.mts +9 -0
  285. package/dist/jwt.d.mts.map +1 -0
  286. package/dist/jwt.mjs +20 -0
  287. package/dist/jwt.mjs.map +1 -0
  288. package/dist/plan.cjs +223 -0
  289. package/dist/plan.cjs.map +1 -0
  290. package/dist/plan.d.cts +68 -0
  291. package/dist/plan.d.cts.map +1 -0
  292. package/dist/plan.d.mts +68 -0
  293. package/dist/plan.d.mts.map +1 -0
  294. package/dist/plan.mjs +221 -0
  295. package/dist/plan.mjs.map +1 -0
  296. package/dist/postgres-js.cjs +42 -0
  297. package/dist/postgres-js.cjs.map +1 -0
  298. package/dist/postgres-js.d.cts +28 -0
  299. package/dist/postgres-js.d.cts.map +1 -0
  300. package/dist/postgres-js.d.mts +28 -0
  301. package/dist/postgres-js.d.mts.map +1 -0
  302. package/dist/postgres-js.mjs +38 -0
  303. package/dist/postgres-js.mjs.map +1 -0
  304. package/dist/provenance.cjs +67 -0
  305. package/dist/provenance.cjs.map +1 -0
  306. package/dist/provenance.d.cts +41 -0
  307. package/dist/provenance.d.cts.map +1 -0
  308. package/dist/provenance.d.mts +41 -0
  309. package/dist/provenance.d.mts.map +1 -0
  310. package/dist/provenance.mjs +65 -0
  311. package/dist/provenance.mjs.map +1 -0
  312. package/dist/push.cjs +14 -0
  313. package/dist/push.cjs.map +1 -0
  314. package/dist/push.d.cts +16 -0
  315. package/dist/push.d.cts.map +1 -0
  316. package/dist/push.d.mts +16 -0
  317. package/dist/push.d.mts.map +1 -0
  318. package/dist/push.mjs +13 -0
  319. package/dist/push.mjs.map +1 -0
  320. package/dist/react-email.cjs +48 -0
  321. package/dist/react-email.cjs.map +1 -0
  322. package/dist/react-email.d.cts +7 -0
  323. package/dist/react-email.d.cts.map +1 -0
  324. package/dist/react-email.d.mts +7 -0
  325. package/dist/react-email.d.mts.map +1 -0
  326. package/dist/react-email.mjs +47 -0
  327. package/dist/react-email.mjs.map +1 -0
  328. package/dist/reporting/classify.cjs +42 -0
  329. package/dist/reporting/classify.cjs.map +1 -0
  330. package/dist/reporting/classify.d.cts +60 -0
  331. package/dist/reporting/classify.d.cts.map +1 -0
  332. package/dist/reporting/classify.d.mts +60 -0
  333. package/dist/reporting/classify.d.mts.map +1 -0
  334. package/dist/reporting/classify.mjs +38 -0
  335. package/dist/reporting/classify.mjs.map +1 -0
  336. package/dist/reporting/observer.cjs +63 -0
  337. package/dist/reporting/observer.cjs.map +1 -0
  338. package/dist/reporting/observer.d.cts +18 -0
  339. package/dist/reporting/observer.d.cts.map +1 -0
  340. package/dist/reporting/observer.d.mts +18 -0
  341. package/dist/reporting/observer.d.mts.map +1 -0
  342. package/dist/reporting/observer.mjs +62 -0
  343. package/dist/reporting/observer.mjs.map +1 -0
  344. package/dist/reporting/tracking.cjs +153 -0
  345. package/dist/reporting/tracking.cjs.map +1 -0
  346. package/dist/reporting/tracking.d.cts +43 -0
  347. package/dist/reporting/tracking.d.cts.map +1 -0
  348. package/dist/reporting/tracking.d.mts +43 -0
  349. package/dist/reporting/tracking.d.mts.map +1 -0
  350. package/dist/reporting/tracking.mjs +151 -0
  351. package/dist/reporting/tracking.mjs.map +1 -0
  352. package/dist/runtime.cjs +19 -0
  353. package/dist/runtime.cjs.map +1 -0
  354. package/dist/runtime.d.cts +30 -0
  355. package/dist/runtime.d.cts.map +1 -0
  356. package/dist/runtime.d.mts +30 -0
  357. package/dist/runtime.d.mts.map +1 -0
  358. package/dist/runtime.mjs +18 -0
  359. package/dist/runtime.mjs.map +1 -0
  360. package/dist/store/d1.cjs +185 -0
  361. package/dist/store/d1.cjs.map +1 -0
  362. package/dist/store/d1.d.cts +55 -0
  363. package/dist/store/d1.d.cts.map +1 -0
  364. package/dist/store/d1.d.mts +55 -0
  365. package/dist/store/d1.d.mts.map +1 -0
  366. package/dist/store/d1.mjs +184 -0
  367. package/dist/store/d1.mjs.map +1 -0
  368. package/dist/store/facts.cjs +60 -0
  369. package/dist/store/facts.cjs.map +1 -0
  370. package/dist/store/facts.d.cts +38 -0
  371. package/dist/store/facts.d.cts.map +1 -0
  372. package/dist/store/facts.d.mts +38 -0
  373. package/dist/store/facts.d.mts.map +1 -0
  374. package/dist/store/facts.mjs +58 -0
  375. package/dist/store/facts.mjs.map +1 -0
  376. package/dist/store/index.cjs +31 -0
  377. package/dist/store/index.cjs.map +1 -0
  378. package/dist/store/index.d.cts +291 -0
  379. package/dist/store/index.d.cts.map +1 -0
  380. package/dist/store/index.d.mts +291 -0
  381. package/dist/store/index.d.mts.map +1 -0
  382. package/dist/store/index.mjs +22 -0
  383. package/dist/store/index.mjs.map +1 -0
  384. package/dist/store/postgres-reporting.cjs +213 -0
  385. package/dist/store/postgres-reporting.cjs.map +1 -0
  386. package/dist/store/postgres-reporting.d.cts +63 -0
  387. package/dist/store/postgres-reporting.d.cts.map +1 -0
  388. package/dist/store/postgres-reporting.d.mts +63 -0
  389. package/dist/store/postgres-reporting.d.mts.map +1 -0
  390. package/dist/store/postgres-reporting.mjs +212 -0
  391. package/dist/store/postgres-reporting.mjs.map +1 -0
  392. package/dist/store/postgres.cjs +309 -0
  393. package/dist/store/postgres.cjs.map +1 -0
  394. package/dist/store/postgres.d.cts +77 -0
  395. package/dist/store/postgres.d.cts.map +1 -0
  396. package/dist/store/postgres.d.mts +77 -0
  397. package/dist/store/postgres.d.mts.map +1 -0
  398. package/dist/store/postgres.mjs +306 -0
  399. package/dist/store/postgres.mjs.map +1 -0
  400. package/dist/store/sql.cjs +24 -0
  401. package/dist/store/sql.cjs.map +1 -0
  402. package/dist/store/sql.d.cts +25 -0
  403. package/dist/store/sql.d.cts.map +1 -0
  404. package/dist/store/sql.d.mts +25 -0
  405. package/dist/store/sql.d.mts.map +1 -0
  406. package/dist/store/sql.mjs +23 -0
  407. package/dist/store/sql.mjs.map +1 -0
  408. package/dist/store/tables.cjs +404 -0
  409. package/dist/store/tables.cjs.map +1 -0
  410. package/dist/store/tables.d.cts +81 -0
  411. package/dist/store/tables.d.cts.map +1 -0
  412. package/dist/store/tables.d.mts +81 -0
  413. package/dist/store/tables.d.mts.map +1 -0
  414. package/dist/store/tables.mjs +399 -0
  415. package/dist/store/tables.mjs.map +1 -0
  416. package/dist/templates.cjs +247 -0
  417. package/dist/templates.cjs.map +1 -0
  418. package/dist/templates.d.cts +187 -0
  419. package/dist/templates.d.cts.map +1 -0
  420. package/dist/templates.d.mts +187 -0
  421. package/dist/templates.d.mts.map +1 -0
  422. package/dist/templates.mjs +230 -0
  423. package/dist/templates.mjs.map +1 -0
  424. package/dist/webhooks.cjs +96 -0
  425. package/dist/webhooks.cjs.map +1 -0
  426. package/dist/webhooks.d.cts +25 -0
  427. package/dist/webhooks.d.cts.map +1 -0
  428. package/dist/webhooks.d.mts +25 -0
  429. package/dist/webhooks.d.mts.map +1 -0
  430. package/dist/webhooks.mjs +94 -0
  431. package/dist/webhooks.mjs.map +1 -0
  432. package/dist/webpush.cjs +180 -0
  433. package/dist/webpush.cjs.map +1 -0
  434. package/dist/webpush.d.cts +53 -0
  435. package/dist/webpush.d.cts.map +1 -0
  436. package/dist/webpush.d.mts +53 -0
  437. package/dist/webpush.d.mts.map +1 -0
  438. package/dist/webpush.mjs +175 -0
  439. package/dist/webpush.mjs.map +1 -0
  440. package/package.json +189 -3
  441. package/src/cloudflare/schema.sql +127 -0
  442. package/src/cloudflare/workers.d.ts +44 -0
@@ -0,0 +1,185 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_ir = require("../ir.cjs");
3
+ //#region src/store/d1.ts
4
+ /**
5
+ * The original data plane: D1 for the tables (see ../cloudflare/schema.sql)
6
+ * and KV for the content-addressed workflow bodies (`wf:${contentHash}`).
7
+ * The SQL below is exactly what the router and fact source used to inline —
8
+ * the in-memory FakeD1 in the tests dispatches on these strings verbatim.
9
+ */
10
+ var D1JourneyStore = class {
11
+ db;
12
+ kv;
13
+ constructor(db, kv) {
14
+ this.db = db;
15
+ this.kv = kv;
16
+ }
17
+ async insertEvent(input) {
18
+ return ((await this.db.prepare("INSERT OR IGNORE INTO events (id, user_id, name, ts, payload) VALUES (?, ?, ?, ?, ?)").bind(input.id, input.userId, input.event, input.ts, JSON.stringify(input.payload)).run()).meta?.changes ?? 1) > 0;
19
+ }
20
+ /** The event rows of one user and name, within the window's bounds (both inclusive). */
21
+ eventWindow(userId, event, window) {
22
+ const params = [userId, event];
23
+ let where = "user_id = ? AND name = ?";
24
+ if (window?.sinceMs !== void 0) {
25
+ where += " AND ts >= ?";
26
+ params.push(window.sinceMs);
27
+ }
28
+ if (window?.untilMs !== void 0) {
29
+ where += " AND ts <= ?";
30
+ params.push(window.untilMs);
31
+ }
32
+ return {
33
+ where,
34
+ params
35
+ };
36
+ }
37
+ async countEvents(userId, event, window) {
38
+ const { where, params } = this.eventWindow(userId, event, window);
39
+ return (await this.db.prepare(`SELECT COUNT(*) AS c FROM events WHERE ${where}`).bind(...params).first())?.c ?? 0;
40
+ }
41
+ async listEventPayloads(userId, event, window) {
42
+ const { where, params } = this.eventWindow(userId, event, window);
43
+ const { results } = await this.db.prepare(`SELECT payload FROM events WHERE ${where}`).bind(...params).all();
44
+ return results.map((row) => JSON.parse(row.payload));
45
+ }
46
+ async getProfile(userId) {
47
+ const row = await this.db.prepare("SELECT props FROM profiles WHERE user_id = ?").bind(userId).first();
48
+ return row ? JSON.parse(row.props) : void 0;
49
+ }
50
+ async mergeProfile(userId, props) {
51
+ const patch = JSON.stringify(props);
52
+ const row = await this.db.prepare("INSERT INTO profiles (user_id, props) VALUES (?, json_patch('{}', ?)) ON CONFLICT(user_id) DO UPDATE SET props = json_patch(profiles.props, ?) RETURNING props").bind(userId, patch, patch).first();
53
+ return row === null ? stripNulls(props) : JSON.parse(row.props);
54
+ }
55
+ async listUserIds() {
56
+ const { results } = await this.db.prepare("SELECT user_id FROM profiles UNION SELECT DISTINCT user_id FROM events").bind().all();
57
+ return results.map((row) => row.user_id);
58
+ }
59
+ async listDevices(userId) {
60
+ const { results } = await this.db.prepare("SELECT token, platform FROM devices WHERE user_id = ? ORDER BY ts DESC").bind(userId).all();
61
+ return results;
62
+ }
63
+ async upsertDevice(device) {
64
+ await this.db.prepare("INSERT INTO devices (token, user_id, platform, ts) VALUES (?, ?, ?, ?) ON CONFLICT (token) DO UPDATE SET user_id = excluded.user_id, platform = excluded.platform, ts = excluded.ts").bind(device.token, device.userId, device.platform, device.ts).run();
65
+ }
66
+ async requestErasure(userId, at) {
67
+ await this.db.prepare("INSERT OR IGNORE INTO erasures (user_id, ts) VALUES (?, ?)").bind(userId, at).run();
68
+ }
69
+ async pendingErasures() {
70
+ const { results } = await this.db.prepare("SELECT user_id FROM erasures ORDER BY ts").bind().all();
71
+ return results.map((row) => row.user_id);
72
+ }
73
+ async runningInstances(userId) {
74
+ const { results } = await this.db.prepare("SELECT instance_id FROM entries WHERE user_id = ? AND status = 'running'").bind(userId).all();
75
+ return results.map((row) => row.instance_id);
76
+ }
77
+ async forgetUser(userId) {
78
+ await this.db.batch([
79
+ "entries",
80
+ "subscriptions",
81
+ "segment_starts",
82
+ "segment_members",
83
+ "devices",
84
+ "events",
85
+ "profiles",
86
+ "erasures"
87
+ ].map((table) => this.db.prepare(`DELETE FROM ${table} WHERE user_id = ?`).bind(userId)));
88
+ }
89
+ async removeDevice(token) {
90
+ return (await this.db.prepare("DELETE FROM devices WHERE token = ? RETURNING user_id").bind(token).first())?.user_id ?? void 0;
91
+ }
92
+ async unbindDevice(token) {
93
+ const row = await this.db.prepare("SELECT user_id FROM devices WHERE token = ?").bind(token).first();
94
+ await this.db.prepare("UPDATE devices SET user_id = NULL WHERE token = ?").bind(token).run();
95
+ return row?.user_id ?? void 0;
96
+ }
97
+ async listSegments() {
98
+ const { results } = await this.db.prepare("SELECT id, condition FROM segments ORDER BY id").bind().all();
99
+ return results.map((row) => ({
100
+ id: row.id,
101
+ condition: require_ir.ConditionIR.parse(JSON.parse(row.condition))
102
+ }));
103
+ }
104
+ async getSegmentCondition(id) {
105
+ const row = await this.db.prepare("SELECT condition FROM segments WHERE id = ?").bind(id).first();
106
+ return row ? require_ir.ConditionIR.parse(JSON.parse(row.condition)) : void 0;
107
+ }
108
+ async findTriggers(event) {
109
+ const { results } = await this.db.prepare("SELECT workflow, hash, \"where\" AS whereClause, filter FROM triggers WHERE event = ?").bind(event).all();
110
+ return results.map((row) => ({
111
+ workflow: row.workflow,
112
+ hash: row.hash,
113
+ where: row.whereClause === null ? null : require_ir.ConditionIR.parse(JSON.parse(row.whereClause)),
114
+ filter: row.filter === null ? null : require_ir.ConditionIR.parse(JSON.parse(row.filter))
115
+ }));
116
+ }
117
+ async listSegmentTriggers() {
118
+ const { results } = await this.db.prepare("SELECT workflow, hash, segment FROM segment_triggers").all();
119
+ return results;
120
+ }
121
+ async putWorkflowIR(workflow) {
122
+ await this.kv.put(`wf:${workflow.contentHash}`, JSON.stringify(workflow));
123
+ }
124
+ async getWorkflowIR(contentHash) {
125
+ const raw = await this.kv.get(`wf:${contentHash}`);
126
+ return raw === null ? void 0 : require_ir.WorkflowIR.parse(JSON.parse(raw));
127
+ }
128
+ async replaceRoutes(bundle) {
129
+ await this.db.batch([
130
+ this.db.prepare("DELETE FROM triggers"),
131
+ this.db.prepare("DELETE FROM segment_triggers"),
132
+ this.db.prepare("DELETE FROM segments"),
133
+ ...bundle.segments.map((segment) => this.db.prepare("INSERT INTO segments (id, condition, hash) VALUES (?, ?, ?)").bind(segment.id, JSON.stringify(segment.condition), segment.contentHash)),
134
+ ...bundle.workflows.flatMap((workflow) => {
135
+ if (workflow.trigger.type === "event") return [this.db.prepare("INSERT INTO triggers (workflow, hash, event, \"where\", filter) VALUES (?, ?, ?, ?, ?)").bind(workflow.id, workflow.contentHash, workflow.trigger.event, workflow.trigger.where !== void 0 ? JSON.stringify(workflow.trigger.where) : null, workflow.trigger.filter !== void 0 ? JSON.stringify(workflow.trigger.filter) : null)];
136
+ if (workflow.trigger.type === "segment") return [this.db.prepare("INSERT INTO segment_triggers (workflow, hash, segment) VALUES (?, ?, ?)").bind(workflow.id, workflow.contentHash, workflow.trigger.segment)];
137
+ return [];
138
+ })
139
+ ]);
140
+ }
141
+ async isSegmentMember(segment, userId) {
142
+ return await this.db.prepare("SELECT 1 AS x FROM segment_members WHERE segment = ? AND user_id = ?").bind(segment, userId).first() !== null;
143
+ }
144
+ async addSegmentMember(segment, userId, ts, workflows) {
145
+ await this.db.batch([this.db.prepare("INSERT OR IGNORE INTO segment_members (segment, user_id, ts) VALUES (?, ?, ?)").bind(segment, userId, ts), ...workflows.map((workflow) => this.db.prepare("INSERT OR IGNORE INTO segment_starts (segment, workflow, user_id, ts) VALUES (?, ?, ?, ?)").bind(segment, workflow, userId, ts))]);
146
+ }
147
+ async pendingSegmentStarts(segment, userId) {
148
+ const { results } = await this.db.prepare("SELECT workflow FROM segment_starts WHERE segment = ? AND user_id = ?").bind(segment, userId).all();
149
+ return results.map((row) => row.workflow);
150
+ }
151
+ async completeSegmentStart(segment, userId, workflow) {
152
+ await this.db.prepare("DELETE FROM segment_starts WHERE segment = ? AND user_id = ? AND workflow = ?").bind(segment, userId, workflow).run();
153
+ }
154
+ async removeSegmentMember(segment, userId) {
155
+ await this.db.batch([this.db.prepare("DELETE FROM segment_starts WHERE segment = ? AND user_id = ?").bind(segment, userId), this.db.prepare("DELETE FROM segment_members WHERE segment = ? AND user_id = ?").bind(segment, userId)]);
156
+ }
157
+ async enterJourney(entry) {
158
+ if (((await this.db.prepare("INSERT OR IGNORE INTO entries (workflow, user_id, instance_id, hash, status, ts) VALUES (?, ?, ?, ?, ?, ?)").bind(entry.workflow, entry.userId, entry.instanceId, entry.hash, "running", entry.ts).run()).meta?.changes ?? 1) !== 0) return "entered";
159
+ return ((await this.db.prepare("UPDATE entries SET instance_id = ?, hash = ?, status = 'running', ts = ? WHERE workflow = ? AND user_id = ? AND status = 'failed'").bind(entry.instanceId, entry.hash, entry.ts, entry.workflow, entry.userId).run()).meta?.changes ?? 0) > 0 ? "reclaimed" : null;
160
+ }
161
+ async removeEntry(instanceId) {
162
+ await this.db.prepare("DELETE FROM entries WHERE instance_id = ?").bind(instanceId).run();
163
+ }
164
+ async setEntryStatus(instanceId, status) {
165
+ await this.db.prepare("UPDATE entries SET status = ? WHERE instance_id = ?").bind(status, instanceId).run();
166
+ }
167
+ async findWakeHandles(userId, event) {
168
+ const { results } = await this.db.prepare("SELECT DISTINCT wake_handle AS handle FROM subscriptions WHERE user_id = ? AND event = ?").bind(userId, event).all();
169
+ return results.map((row) => row.handle);
170
+ }
171
+ async subscribe(handle, userId, events, ts) {
172
+ for (const event of events) await this.db.prepare("INSERT OR IGNORE INTO subscriptions (user_id, event, wake_handle, ts) VALUES (?, ?, ?, ?)").bind(userId, event, handle, ts).run();
173
+ }
174
+ async unsubscribe(handle) {
175
+ await this.db.prepare("DELETE FROM subscriptions WHERE wake_handle = ?").bind(handle).run();
176
+ }
177
+ };
178
+ /** What a merge-patch of `props` into an empty profile yields: the patch minus its nulls. */
179
+ function stripNulls(props) {
180
+ return Object.fromEntries(Object.entries(props).filter(([, value]) => value !== null));
181
+ }
182
+ //#endregion
183
+ exports.D1JourneyStore = D1JourneyStore;
184
+
185
+ //# sourceMappingURL=d1.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"d1.cjs","names":["ConditionIR","WorkflowIR"],"sources":["../../src/store/d1.ts"],"sourcesContent":["import type { D1DatabaseLike, KVNamespaceLike } from '../cloudflare/bindings';\nimport type { PushDevice } from '../engine/ports';\nimport { type BundleIR, ConditionIR, WorkflowIR } from '../ir';\nimport type {\n DeviceInput,\n EntryInput,\n EntryOutcome,\n EventInput,\n EventWindow,\n JourneyStore,\n ProfileProps,\n SegmentTriggerRoute,\n TriggerRoute,\n} from './index';\n\n/**\n * The original data plane: D1 for the tables (see ../cloudflare/schema.sql)\n * and KV for the content-addressed workflow bodies (`wf:${contentHash}`).\n * The SQL below is exactly what the router and fact source used to inline —\n * the in-memory FakeD1 in the tests dispatches on these strings verbatim.\n */\nexport class D1JourneyStore implements JourneyStore {\n constructor(\n private readonly db: D1DatabaseLike,\n private readonly kv: KVNamespaceLike\n ) {}\n\n async insertEvent(input: EventInput): Promise<boolean> {\n const written = await this.db\n .prepare(\n 'INSERT OR IGNORE INTO events (id, user_id, name, ts, payload) VALUES (?, ?, ?, ?, ?)'\n )\n .bind(input.id, input.userId, input.event, input.ts, JSON.stringify(input.payload))\n .run();\n return (written.meta?.changes ?? 1) > 0;\n }\n\n /** The event rows of one user and name, within the window's bounds (both inclusive). */\n private eventWindow(userId: string, event: string, window?: EventWindow) {\n const params: unknown[] = [userId, event];\n let where = 'user_id = ? AND name = ?';\n if (window?.sinceMs !== undefined) {\n where += ' AND ts >= ?';\n params.push(window.sinceMs);\n }\n if (window?.untilMs !== undefined) {\n where += ' AND ts <= ?';\n params.push(window.untilMs);\n }\n return { where, params };\n }\n\n async countEvents(userId: string, event: string, window?: EventWindow): Promise<number> {\n const { where, params } = this.eventWindow(userId, event, window);\n const row = await this.db\n .prepare(`SELECT COUNT(*) AS c FROM events WHERE ${where}`)\n .bind(...params)\n .first<{ c: number }>();\n return row?.c ?? 0;\n }\n\n async listEventPayloads(\n userId: string,\n event: string,\n window?: EventWindow\n ): Promise<Record<string, unknown>[]> {\n const { where, params } = this.eventWindow(userId, event, window);\n const { results } = await this.db\n .prepare(`SELECT payload FROM events WHERE ${where}`)\n .bind(...params)\n .all<{ payload: string }>();\n return results.map((row) => JSON.parse(row.payload) as Record<string, unknown>);\n }\n\n async getProfile(userId: string): Promise<ProfileProps | undefined> {\n const row = await this.db\n .prepare('SELECT props FROM profiles WHERE user_id = ?')\n .bind(userId)\n .first<{ props: string }>();\n return row ? (JSON.parse(row.props) as ProfileProps) : undefined;\n }\n\n async mergeProfile(userId: string, props: ProfileProps): Promise<ProfileProps> {\n // SQLite's json_patch is RFC 7386 merge-patch: a null removes its key. The\n // merge happens in the statement, so two concurrent identifies cannot lose\n // each other's fields the way a read-modify-write did.\n const patch = JSON.stringify(props);\n const row = await this.db\n .prepare(\n \"INSERT INTO profiles (user_id, props) VALUES (?, json_patch('{}', ?)) ON CONFLICT(user_id) DO UPDATE SET props = json_patch(profiles.props, ?) RETURNING props\"\n )\n .bind(userId, patch, patch)\n .first<{ props: string }>();\n // RETURNING always yields the row it just wrote; the fallback only covers a\n // driver that declines to return one, and reports what was asked for.\n return row === null ? stripNulls(props) : (JSON.parse(row.props) as ProfileProps);\n }\n\n async listUserIds(): Promise<string[]> {\n const { results } = await this.db\n .prepare('SELECT user_id FROM profiles UNION SELECT DISTINCT user_id FROM events')\n .bind()\n .all<{ user_id: string }>();\n return results.map((row) => row.user_id);\n }\n\n async listDevices(userId: string): Promise<PushDevice[]> {\n const { results } = await this.db\n .prepare('SELECT token, platform FROM devices WHERE user_id = ? ORDER BY ts DESC')\n .bind(userId)\n .all<PushDevice>();\n return results;\n }\n\n async upsertDevice(device: DeviceInput): Promise<void> {\n await this.db\n .prepare(\n 'INSERT INTO devices (token, user_id, platform, ts) VALUES (?, ?, ?, ?) ON CONFLICT (token) DO UPDATE SET user_id = excluded.user_id, platform = excluded.platform, ts = excluded.ts'\n )\n .bind(device.token, device.userId, device.platform, device.ts)\n .run();\n }\n\n async requestErasure(userId: string, at: number): Promise<void> {\n await this.db\n .prepare('INSERT OR IGNORE INTO erasures (user_id, ts) VALUES (?, ?)')\n .bind(userId, at)\n .run();\n }\n\n async pendingErasures(): Promise<string[]> {\n const { results } = await this.db\n .prepare('SELECT user_id FROM erasures ORDER BY ts')\n .bind()\n .all<{ user_id: string }>();\n return results.map((row) => row.user_id);\n }\n\n async runningInstances(userId: string): Promise<string[]> {\n const { results } = await this.db\n .prepare(\"SELECT instance_id FROM entries WHERE user_id = ? AND status = 'running'\")\n .bind(userId)\n .all<{ instance_id: string }>();\n return results.map((row) => row.instance_id);\n }\n\n async forgetUser(userId: string): Promise<void> {\n // One batch: D1 runs it as a transaction; the request goes last, with the data it erases\n await this.db.batch(\n [\n 'entries',\n 'subscriptions',\n 'segment_starts',\n 'segment_members',\n 'devices',\n 'events',\n 'profiles',\n 'erasures',\n ].map((table) => this.db.prepare(`DELETE FROM ${table} WHERE user_id = ?`).bind(userId))\n );\n }\n\n async removeDevice(token: string): Promise<string | undefined> {\n const row = await this.db\n .prepare('DELETE FROM devices WHERE token = ? RETURNING user_id')\n .bind(token)\n .first<{ user_id: string | null }>();\n return row?.user_id ?? undefined;\n }\n\n async unbindDevice(token: string): Promise<string | undefined> {\n const row = await this.db\n .prepare('SELECT user_id FROM devices WHERE token = ?')\n .bind(token)\n .first<{ user_id: string | null }>();\n await this.db.prepare('UPDATE devices SET user_id = NULL WHERE token = ?').bind(token).run();\n return row?.user_id ?? undefined;\n }\n\n async listSegments(): Promise<{ id: string; condition: ConditionIR }[]> {\n const { results } = await this.db\n .prepare('SELECT id, condition FROM segments ORDER BY id')\n .bind()\n .all<{ id: string; condition: string }>();\n return results.map((row) => ({\n id: row.id,\n condition: ConditionIR.parse(JSON.parse(row.condition)),\n }));\n }\n\n async getSegmentCondition(id: string): Promise<ConditionIR | undefined> {\n const row = await this.db\n .prepare('SELECT condition FROM segments WHERE id = ?')\n .bind(id)\n .first<{ condition: string }>();\n return row ? ConditionIR.parse(JSON.parse(row.condition)) : undefined;\n }\n\n async findTriggers(event: string): Promise<TriggerRoute[]> {\n const { results } = await this.db\n .prepare(\n 'SELECT workflow, hash, \"where\" AS whereClause, filter FROM triggers WHERE event = ?'\n )\n .bind(event)\n .all<{ workflow: string; hash: string; whereClause: string | null; filter: string | null }>();\n return results.map((row) => ({\n workflow: row.workflow,\n hash: row.hash,\n where: row.whereClause === null ? null : ConditionIR.parse(JSON.parse(row.whereClause)),\n filter: row.filter === null ? null : ConditionIR.parse(JSON.parse(row.filter)),\n }));\n }\n\n async listSegmentTriggers(): Promise<SegmentTriggerRoute[]> {\n const { results } = await this.db\n .prepare('SELECT workflow, hash, segment FROM segment_triggers')\n .all<SegmentTriggerRoute>();\n return results;\n }\n\n async putWorkflowIR(workflow: WorkflowIR): Promise<void> {\n await this.kv.put(`wf:${workflow.contentHash}`, JSON.stringify(workflow));\n }\n\n async getWorkflowIR(contentHash: string): Promise<WorkflowIR | undefined> {\n const raw = await this.kv.get(`wf:${contentHash}`);\n return raw === null ? undefined : WorkflowIR.parse(JSON.parse(raw));\n }\n\n async replaceRoutes(bundle: BundleIR): Promise<void> {\n // One atomic batch: all-or-nothing, and a concurrent ingest never observes\n // the half-empty routing table a delete-then-insert loop would expose.\n await this.db.batch([\n this.db.prepare('DELETE FROM triggers'),\n this.db.prepare('DELETE FROM segment_triggers'),\n this.db.prepare('DELETE FROM segments'),\n ...bundle.segments.map((segment) =>\n this.db\n .prepare('INSERT INTO segments (id, condition, hash) VALUES (?, ?, ?)')\n .bind(segment.id, JSON.stringify(segment.condition), segment.contentHash)\n ),\n ...bundle.workflows.flatMap((workflow) => {\n if (workflow.trigger.type === 'event') {\n return [\n this.db\n .prepare(\n 'INSERT INTO triggers (workflow, hash, event, \"where\", filter) VALUES (?, ?, ?, ?, ?)'\n )\n .bind(\n workflow.id,\n workflow.contentHash,\n workflow.trigger.event,\n workflow.trigger.where !== undefined\n ? JSON.stringify(workflow.trigger.where)\n : null,\n workflow.trigger.filter !== undefined\n ? JSON.stringify(workflow.trigger.filter)\n : null\n ),\n ];\n }\n if (workflow.trigger.type === 'segment') {\n return [\n this.db\n .prepare('INSERT INTO segment_triggers (workflow, hash, segment) VALUES (?, ?, ?)')\n .bind(workflow.id, workflow.contentHash, workflow.trigger.segment),\n ];\n }\n return [];\n }),\n ]);\n }\n\n async isSegmentMember(segment: string, userId: string): Promise<boolean> {\n const row = await this.db\n .prepare('SELECT 1 AS x FROM segment_members WHERE segment = ? AND user_id = ?')\n .bind(segment, userId)\n .first<{ x: number }>();\n return row !== null;\n }\n\n async addSegmentMember(\n segment: string,\n userId: string,\n ts: number,\n workflows: readonly string[]\n ): Promise<void> {\n await this.db.batch([\n this.db\n .prepare('INSERT OR IGNORE INTO segment_members (segment, user_id, ts) VALUES (?, ?, ?)')\n .bind(segment, userId, ts),\n ...workflows.map((workflow) =>\n this.db\n .prepare(\n 'INSERT OR IGNORE INTO segment_starts (segment, workflow, user_id, ts) VALUES (?, ?, ?, ?)'\n )\n .bind(segment, workflow, userId, ts)\n ),\n ]);\n }\n\n async pendingSegmentStarts(segment: string, userId: string): Promise<string[]> {\n const { results } = await this.db\n .prepare('SELECT workflow FROM segment_starts WHERE segment = ? AND user_id = ?')\n .bind(segment, userId)\n .all<{ workflow: string }>();\n return results.map((row) => row.workflow);\n }\n\n async completeSegmentStart(segment: string, userId: string, workflow: string): Promise<void> {\n await this.db\n .prepare('DELETE FROM segment_starts WHERE segment = ? AND user_id = ? AND workflow = ?')\n .bind(segment, userId, workflow)\n .run();\n }\n\n async removeSegmentMember(segment: string, userId: string): Promise<void> {\n await this.db.batch([\n this.db\n .prepare('DELETE FROM segment_starts WHERE segment = ? AND user_id = ?')\n .bind(segment, userId),\n this.db\n .prepare('DELETE FROM segment_members WHERE segment = ? AND user_id = ?')\n .bind(segment, userId),\n ]);\n }\n\n async enterJourney(entry: EntryInput): Promise<EntryOutcome> {\n const inserted = await this.db\n .prepare(\n 'INSERT OR IGNORE INTO entries (workflow, user_id, instance_id, hash, status, ts) VALUES (?, ?, ?, ?, ?, ?)'\n )\n .bind(entry.workflow, entry.userId, entry.instanceId, entry.hash, 'running', entry.ts)\n .run();\n if ((inserted.meta?.changes ?? 1) !== 0) return 'entered';\n\n // A row already there blocks the entry, unless it records an instance that\n // died: 'failed' is reclaimed rather than barring the user forever over one\n // outage. The status test lives inside the UPDATE, so of two concurrent\n // retries exactly one sees a change.\n const claimed = await this.db\n .prepare(\n \"UPDATE entries SET instance_id = ?, hash = ?, status = 'running', ts = ? WHERE workflow = ? AND user_id = ? AND status = 'failed'\"\n )\n .bind(entry.instanceId, entry.hash, entry.ts, entry.workflow, entry.userId)\n .run();\n return (claimed.meta?.changes ?? 0) > 0 ? 'reclaimed' : null;\n }\n\n async removeEntry(instanceId: string): Promise<void> {\n await this.db.prepare('DELETE FROM entries WHERE instance_id = ?').bind(instanceId).run();\n }\n\n async setEntryStatus(instanceId: string, status: string): Promise<void> {\n await this.db\n .prepare('UPDATE entries SET status = ? WHERE instance_id = ?')\n .bind(status, instanceId)\n .run();\n }\n\n async findWakeHandles(userId: string, event: string): Promise<string[]> {\n const { results } = await this.db\n .prepare(\n 'SELECT DISTINCT wake_handle AS handle FROM subscriptions WHERE user_id = ? AND event = ?'\n )\n .bind(userId, event)\n .all<{ handle: string }>();\n return results.map((row) => row.handle);\n }\n\n async subscribe(\n handle: string,\n userId: string,\n events: readonly string[],\n ts: number\n ): Promise<void> {\n for (const event of events) {\n // OR IGNORE on the (wake_handle, event) PK: the interpreter re-subscribes on\n // every wait attempt (one-shot-callback contract) and rows persist here.\n await this.db\n .prepare(\n 'INSERT OR IGNORE INTO subscriptions (user_id, event, wake_handle, ts) VALUES (?, ?, ?, ?)'\n )\n .bind(userId, event, handle, ts)\n .run();\n }\n }\n\n async unsubscribe(handle: string): Promise<void> {\n await this.db.prepare('DELETE FROM subscriptions WHERE wake_handle = ?').bind(handle).run();\n }\n}\n\n/** What a merge-patch of `props` into an empty profile yields: the patch minus its nulls. */\nfunction stripNulls(props: ProfileProps): ProfileProps {\n return Object.fromEntries(Object.entries(props).filter(([, value]) => value !== null));\n}\n"],"mappings":";;;;;;;;;AAqBA,IAAa,iBAAb,MAAoD;CAE/B;CACA;CAFnB,YACE,IACA,IACA;EAFiB,KAAA,KAAA;EACA,KAAA,KAAA;CAChB;CAEH,MAAM,YAAY,OAAqC;EAOrD,SAAQ,MANc,KAAK,GACxB,QACC,sFACF,CAAC,CACA,KAAK,MAAM,IAAI,MAAM,QAAQ,MAAM,OAAO,MAAM,IAAI,KAAK,UAAU,MAAM,OAAO,CAAC,CAAC,CAClF,IAAI,EAAA,CACS,MAAM,WAAW,KAAK;CACxC;;CAGA,YAAoB,QAAgB,OAAe,QAAsB;EACvE,MAAM,SAAoB,CAAC,QAAQ,KAAK;EACxC,IAAI,QAAQ;EACZ,IAAI,QAAQ,YAAY,KAAA,GAAW;GACjC,SAAS;GACT,OAAO,KAAK,OAAO,OAAO;EAC5B;EACA,IAAI,QAAQ,YAAY,KAAA,GAAW;GACjC,SAAS;GACT,OAAO,KAAK,OAAO,OAAO;EAC5B;EACA,OAAO;GAAE;GAAO;EAAO;CACzB;CAEA,MAAM,YAAY,QAAgB,OAAe,QAAuC;EACtF,MAAM,EAAE,OAAO,WAAW,KAAK,YAAY,QAAQ,OAAO,MAAM;EAKhE,QAAO,MAJW,KAAK,GACpB,QAAQ,0CAA0C,OAAO,CAAC,CAC1D,KAAK,GAAG,MAAM,CAAC,CACf,MAAqB,EAAA,EACZ,KAAK;CACnB;CAEA,MAAM,kBACJ,QACA,OACA,QACoC;EACpC,MAAM,EAAE,OAAO,WAAW,KAAK,YAAY,QAAQ,OAAO,MAAM;EAChE,MAAM,EAAE,YAAY,MAAM,KAAK,GAC5B,QAAQ,oCAAoC,OAAO,CAAC,CACpD,KAAK,GAAG,MAAM,CAAC,CACf,IAAyB;EAC5B,OAAO,QAAQ,KAAK,QAAQ,KAAK,MAAM,IAAI,OAAO,CAA4B;CAChF;CAEA,MAAM,WAAW,QAAmD;EAClE,MAAM,MAAM,MAAM,KAAK,GACpB,QAAQ,8CAA8C,CAAC,CACvD,KAAK,MAAM,CAAC,CACZ,MAAyB;EAC5B,OAAO,MAAO,KAAK,MAAM,IAAI,KAAK,IAAqB,KAAA;CACzD;CAEA,MAAM,aAAa,QAAgB,OAA4C;EAI7E,MAAM,QAAQ,KAAK,UAAU,KAAK;EAClC,MAAM,MAAM,MAAM,KAAK,GACpB,QACC,gKACF,CAAC,CACA,KAAK,QAAQ,OAAO,KAAK,CAAC,CAC1B,MAAyB;EAG5B,OAAO,QAAQ,OAAO,WAAW,KAAK,IAAK,KAAK,MAAM,IAAI,KAAK;CACjE;CAEA,MAAM,cAAiC;EACrC,MAAM,EAAE,YAAY,MAAM,KAAK,GAC5B,QAAQ,wEAAwE,CAAC,CACjF,KAAK,CAAC,CACN,IAAyB;EAC5B,OAAO,QAAQ,KAAK,QAAQ,IAAI,OAAO;CACzC;CAEA,MAAM,YAAY,QAAuC;EACvD,MAAM,EAAE,YAAY,MAAM,KAAK,GAC5B,QAAQ,wEAAwE,CAAC,CACjF,KAAK,MAAM,CAAC,CACZ,IAAgB;EACnB,OAAO;CACT;CAEA,MAAM,aAAa,QAAoC;EACrD,MAAM,KAAK,GACR,QACC,qLACF,CAAC,CACA,KAAK,OAAO,OAAO,OAAO,QAAQ,OAAO,UAAU,OAAO,EAAE,CAAC,CAC7D,IAAI;CACT;CAEA,MAAM,eAAe,QAAgB,IAA2B;EAC9D,MAAM,KAAK,GACR,QAAQ,4DAA4D,CAAC,CACrE,KAAK,QAAQ,EAAE,CAAC,CAChB,IAAI;CACT;CAEA,MAAM,kBAAqC;EACzC,MAAM,EAAE,YAAY,MAAM,KAAK,GAC5B,QAAQ,0CAA0C,CAAC,CACnD,KAAK,CAAC,CACN,IAAyB;EAC5B,OAAO,QAAQ,KAAK,QAAQ,IAAI,OAAO;CACzC;CAEA,MAAM,iBAAiB,QAAmC;EACxD,MAAM,EAAE,YAAY,MAAM,KAAK,GAC5B,QAAQ,0EAA0E,CAAC,CACnF,KAAK,MAAM,CAAC,CACZ,IAA6B;EAChC,OAAO,QAAQ,KAAK,QAAQ,IAAI,WAAW;CAC7C;CAEA,MAAM,WAAW,QAA+B;EAE9C,MAAM,KAAK,GAAG,MACZ;GACE;GACA;GACA;GACA;GACA;GACA;GACA;GACA;EACF,CAAC,CAAC,KAAK,UAAU,KAAK,GAAG,QAAQ,eAAe,MAAM,mBAAmB,CAAC,CAAC,KAAK,MAAM,CAAC,CACzF;CACF;CAEA,MAAM,aAAa,OAA4C;EAK7D,QAAO,MAJW,KAAK,GACpB,QAAQ,uDAAuD,CAAC,CAChE,KAAK,KAAK,CAAC,CACX,MAAkC,EAAA,EACzB,WAAW,KAAA;CACzB;CAEA,MAAM,aAAa,OAA4C;EAC7D,MAAM,MAAM,MAAM,KAAK,GACpB,QAAQ,6CAA6C,CAAC,CACtD,KAAK,KAAK,CAAC,CACX,MAAkC;EACrC,MAAM,KAAK,GAAG,QAAQ,mDAAmD,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,IAAI;EAC3F,OAAO,KAAK,WAAW,KAAA;CACzB;CAEA,MAAM,eAAkE;EACtE,MAAM,EAAE,YAAY,MAAM,KAAK,GAC5B,QAAQ,gDAAgD,CAAC,CACzD,KAAK,CAAC,CACN,IAAuC;EAC1C,OAAO,QAAQ,KAAK,SAAS;GAC3B,IAAI,IAAI;GACR,WAAWA,WAAAA,YAAY,MAAM,KAAK,MAAM,IAAI,SAAS,CAAC;EACxD,EAAE;CACJ;CAEA,MAAM,oBAAoB,IAA8C;EACtE,MAAM,MAAM,MAAM,KAAK,GACpB,QAAQ,6CAA6C,CAAC,CACtD,KAAK,EAAE,CAAC,CACR,MAA6B;EAChC,OAAO,MAAMA,WAAAA,YAAY,MAAM,KAAK,MAAM,IAAI,SAAS,CAAC,IAAI,KAAA;CAC9D;CAEA,MAAM,aAAa,OAAwC;EACzD,MAAM,EAAE,YAAY,MAAM,KAAK,GAC5B,QACC,uFACF,CAAC,CACA,KAAK,KAAK,CAAC,CACX,IAA2F;EAC9F,OAAO,QAAQ,KAAK,SAAS;GAC3B,UAAU,IAAI;GACd,MAAM,IAAI;GACV,OAAO,IAAI,gBAAgB,OAAO,OAAOA,WAAAA,YAAY,MAAM,KAAK,MAAM,IAAI,WAAW,CAAC;GACtF,QAAQ,IAAI,WAAW,OAAO,OAAOA,WAAAA,YAAY,MAAM,KAAK,MAAM,IAAI,MAAM,CAAC;EAC/E,EAAE;CACJ;CAEA,MAAM,sBAAsD;EAC1D,MAAM,EAAE,YAAY,MAAM,KAAK,GAC5B,QAAQ,sDAAsD,CAAC,CAC/D,IAAyB;EAC5B,OAAO;CACT;CAEA,MAAM,cAAc,UAAqC;EACvD,MAAM,KAAK,GAAG,IAAI,MAAM,SAAS,eAAe,KAAK,UAAU,QAAQ,CAAC;CAC1E;CAEA,MAAM,cAAc,aAAsD;EACxE,MAAM,MAAM,MAAM,KAAK,GAAG,IAAI,MAAM,aAAa;EACjD,OAAO,QAAQ,OAAO,KAAA,IAAYC,WAAAA,WAAW,MAAM,KAAK,MAAM,GAAG,CAAC;CACpE;CAEA,MAAM,cAAc,QAAiC;EAGnD,MAAM,KAAK,GAAG,MAAM;GAClB,KAAK,GAAG,QAAQ,sBAAsB;GACtC,KAAK,GAAG,QAAQ,8BAA8B;GAC9C,KAAK,GAAG,QAAQ,sBAAsB;GACtC,GAAG,OAAO,SAAS,KAAK,YACtB,KAAK,GACF,QAAQ,6DAA6D,CAAC,CACtE,KAAK,QAAQ,IAAI,KAAK,UAAU,QAAQ,SAAS,GAAG,QAAQ,WAAW,CAC5E;GACA,GAAG,OAAO,UAAU,SAAS,aAAa;IACxC,IAAI,SAAS,QAAQ,SAAS,SAC5B,OAAO,CACL,KAAK,GACF,QACC,wFACF,CAAC,CACA,KACC,SAAS,IACT,SAAS,aACT,SAAS,QAAQ,OACjB,SAAS,QAAQ,UAAU,KAAA,IACvB,KAAK,UAAU,SAAS,QAAQ,KAAK,IACrC,MACJ,SAAS,QAAQ,WAAW,KAAA,IACxB,KAAK,UAAU,SAAS,QAAQ,MAAM,IACtC,IACN,CACJ;IAEF,IAAI,SAAS,QAAQ,SAAS,WAC5B,OAAO,CACL,KAAK,GACF,QAAQ,yEAAyE,CAAC,CAClF,KAAK,SAAS,IAAI,SAAS,aAAa,SAAS,QAAQ,OAAO,CACrE;IAEF,OAAO,CAAC;GACV,CAAC;EACH,CAAC;CACH;CAEA,MAAM,gBAAgB,SAAiB,QAAkC;EAKvE,OAAO,MAJW,KAAK,GACpB,QAAQ,sEAAsE,CAAC,CAC/E,KAAK,SAAS,MAAM,CAAC,CACrB,MAAqB,MACT;CACjB;CAEA,MAAM,iBACJ,SACA,QACA,IACA,WACe;EACf,MAAM,KAAK,GAAG,MAAM,CAClB,KAAK,GACF,QAAQ,+EAA+E,CAAC,CACxF,KAAK,SAAS,QAAQ,EAAE,GAC3B,GAAG,UAAU,KAAK,aAChB,KAAK,GACF,QACC,2FACF,CAAC,CACA,KAAK,SAAS,UAAU,QAAQ,EAAE,CACvC,CACF,CAAC;CACH;CAEA,MAAM,qBAAqB,SAAiB,QAAmC;EAC7E,MAAM,EAAE,YAAY,MAAM,KAAK,GAC5B,QAAQ,uEAAuE,CAAC,CAChF,KAAK,SAAS,MAAM,CAAC,CACrB,IAA0B;EAC7B,OAAO,QAAQ,KAAK,QAAQ,IAAI,QAAQ;CAC1C;CAEA,MAAM,qBAAqB,SAAiB,QAAgB,UAAiC;EAC3F,MAAM,KAAK,GACR,QAAQ,+EAA+E,CAAC,CACxF,KAAK,SAAS,QAAQ,QAAQ,CAAC,CAC/B,IAAI;CACT;CAEA,MAAM,oBAAoB,SAAiB,QAA+B;EACxE,MAAM,KAAK,GAAG,MAAM,CAClB,KAAK,GACF,QAAQ,8DAA8D,CAAC,CACvE,KAAK,SAAS,MAAM,GACvB,KAAK,GACF,QAAQ,+DAA+D,CAAC,CACxE,KAAK,SAAS,MAAM,CACzB,CAAC;CACH;CAEA,MAAM,aAAa,OAA0C;EAO3D,MAAK,MANkB,KAAK,GACzB,QACC,4GACF,CAAC,CACA,KAAK,MAAM,UAAU,MAAM,QAAQ,MAAM,YAAY,MAAM,MAAM,WAAW,MAAM,EAAE,CAAC,CACrF,IAAI,EAAA,CACO,MAAM,WAAW,OAAO,GAAG,OAAO;EAYhD,SAAQ,MANc,KAAK,GACxB,QACC,mIACF,CAAC,CACA,KAAK,MAAM,YAAY,MAAM,MAAM,MAAM,IAAI,MAAM,UAAU,MAAM,MAAM,CAAC,CAC1E,IAAI,EAAA,CACS,MAAM,WAAW,KAAK,IAAI,cAAc;CAC1D;CAEA,MAAM,YAAY,YAAmC;EACnD,MAAM,KAAK,GAAG,QAAQ,2CAA2C,CAAC,CAAC,KAAK,UAAU,CAAC,CAAC,IAAI;CAC1F;CAEA,MAAM,eAAe,YAAoB,QAA+B;EACtE,MAAM,KAAK,GACR,QAAQ,qDAAqD,CAAC,CAC9D,KAAK,QAAQ,UAAU,CAAC,CACxB,IAAI;CACT;CAEA,MAAM,gBAAgB,QAAgB,OAAkC;EACtE,MAAM,EAAE,YAAY,MAAM,KAAK,GAC5B,QACC,0FACF,CAAC,CACA,KAAK,QAAQ,KAAK,CAAC,CACnB,IAAwB;EAC3B,OAAO,QAAQ,KAAK,QAAQ,IAAI,MAAM;CACxC;CAEA,MAAM,UACJ,QACA,QACA,QACA,IACe;EACf,KAAK,MAAM,SAAS,QAGlB,MAAM,KAAK,GACR,QACC,2FACF,CAAC,CACA,KAAK,QAAQ,OAAO,QAAQ,EAAE,CAAC,CAC/B,IAAI;CAEX;CAEA,MAAM,YAAY,QAA+B;EAC/C,MAAM,KAAK,GAAG,QAAQ,iDAAiD,CAAC,CAAC,KAAK,MAAM,CAAC,CAAC,IAAI;CAC5F;AACF;;AAGA,SAAS,WAAW,OAAmC;CACrD,OAAO,OAAO,YAAY,OAAO,QAAQ,KAAK,CAAC,CAAC,QAAQ,GAAG,WAAW,UAAU,IAAI,CAAC;AACvF"}
@@ -0,0 +1,55 @@
1
+ import { BundleIR, ConditionIR, WorkflowIR } from "../ir.cjs";
2
+ import { PushDevice } from "../engine/ports.cjs";
3
+ import { D1DatabaseLike, KVNamespaceLike } from "../cloudflare/bindings.cjs";
4
+ import { DeviceInput, EntryInput, EntryOutcome, EventInput, EventWindow, JourneyStore, ProfileProps, SegmentTriggerRoute, TriggerRoute } from "./index.cjs";
5
+ //#region src/store/d1.d.ts
6
+ /**
7
+ * The original data plane: D1 for the tables (see ../cloudflare/schema.sql)
8
+ * and KV for the content-addressed workflow bodies (`wf:${contentHash}`).
9
+ * The SQL below is exactly what the router and fact source used to inline —
10
+ * the in-memory FakeD1 in the tests dispatches on these strings verbatim.
11
+ */
12
+ export declare class D1JourneyStore implements JourneyStore {
13
+ private readonly db;
14
+ private readonly kv;
15
+ constructor(db: D1DatabaseLike, kv: KVNamespaceLike);
16
+ insertEvent(input: EventInput): Promise<boolean>;
17
+ /** The event rows of one user and name, within the window's bounds (both inclusive). */
18
+ private eventWindow;
19
+ countEvents(userId: string, event: string, window?: EventWindow): Promise<number>;
20
+ listEventPayloads(userId: string, event: string, window?: EventWindow): Promise<Record<string, unknown>[]>;
21
+ getProfile(userId: string): Promise<ProfileProps | undefined>;
22
+ mergeProfile(userId: string, props: ProfileProps): Promise<ProfileProps>;
23
+ listUserIds(): Promise<string[]>;
24
+ listDevices(userId: string): Promise<PushDevice[]>;
25
+ upsertDevice(device: DeviceInput): Promise<void>;
26
+ requestErasure(userId: string, at: number): Promise<void>;
27
+ pendingErasures(): Promise<string[]>;
28
+ runningInstances(userId: string): Promise<string[]>;
29
+ forgetUser(userId: string): Promise<void>;
30
+ removeDevice(token: string): Promise<string | undefined>;
31
+ unbindDevice(token: string): Promise<string | undefined>;
32
+ listSegments(): Promise<{
33
+ id: string;
34
+ condition: ConditionIR;
35
+ }[]>;
36
+ getSegmentCondition(id: string): Promise<ConditionIR | undefined>;
37
+ findTriggers(event: string): Promise<TriggerRoute[]>;
38
+ listSegmentTriggers(): Promise<SegmentTriggerRoute[]>;
39
+ putWorkflowIR(workflow: WorkflowIR): Promise<void>;
40
+ getWorkflowIR(contentHash: string): Promise<WorkflowIR | undefined>;
41
+ replaceRoutes(bundle: BundleIR): Promise<void>;
42
+ isSegmentMember(segment: string, userId: string): Promise<boolean>;
43
+ addSegmentMember(segment: string, userId: string, ts: number, workflows: readonly string[]): Promise<void>;
44
+ pendingSegmentStarts(segment: string, userId: string): Promise<string[]>;
45
+ completeSegmentStart(segment: string, userId: string, workflow: string): Promise<void>;
46
+ removeSegmentMember(segment: string, userId: string): Promise<void>;
47
+ enterJourney(entry: EntryInput): Promise<EntryOutcome>;
48
+ removeEntry(instanceId: string): Promise<void>;
49
+ setEntryStatus(instanceId: string, status: string): Promise<void>;
50
+ findWakeHandles(userId: string, event: string): Promise<string[]>;
51
+ subscribe(handle: string, userId: string, events: readonly string[], ts: number): Promise<void>;
52
+ unsubscribe(handle: string): Promise<void>;
53
+ }
54
+ //#endregion
55
+ //# sourceMappingURL=d1.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"d1.d.cts","names":[],"sources":["../../src/store/d1.ts"],"mappings":";;;;;;;;;;;qBAqBa,0BAA0B;mBAElB;mBACA;EAFnB,YACmB,IAAI,gBACJ,IAAI;EAGjB,YAAY,OAAO,aAAa;;UAW9B;EAcF,YAAY,gBAAgB,eAAe,SAAS,cAAc;EASlE,kBACJ,gBACA,eACA,SAAS,cACR,QAAQ;EASL,WAAW,iBAAiB,QAAQ;EAQpC,aAAa,gBAAgB,OAAO,eAAe,QAAQ;EAgB3D,eAAe;EAQf,YAAY,iBAAiB,QAAQ;EAQrC,aAAa,QAAQ,cAAc;EASnC,eAAe,gBAAgB,aAAa;EAO5C,mBAAmB;EAQnB,iBAAiB,iBAAiB;EAQlC,WAAW,iBAAiB;EAgB5B,aAAa,gBAAgB;EAQ7B,aAAa,gBAAgB;EAS7B,gBAAgB;IAAU;IAAY,WAAW;;EAWjD,oBAAoB,aAAa,QAAQ;EAQzC,aAAa,gBAAgB,QAAQ;EAerC,uBAAuB,QAAQ;EAO/B,cAAc,UAAU,aAAa;EAIrC,cAAc,sBAAsB,QAAQ;EAK5C,cAAc,QAAQ,WAAW;EA4CjC,gBAAgB,iBAAiB,iBAAiB;EAQlD,iBACJ,iBACA,gBACA,YACA,+BACC;EAeG,qBAAqB,iBAAiB,iBAAiB;EAQvD,qBAAqB,iBAAiB,gBAAgB,mBAAmB;EAOzE,oBAAoB,iBAAiB,iBAAiB;EAWtD,aAAa,OAAO,aAAa,QAAQ;EAsBzC,YAAY,qBAAqB;EAIjC,eAAe,oBAAoB,iBAAiB;EAOpD,gBAAgB,gBAAgB,gBAAgB;EAUhD,UACJ,gBACA,gBACA,2BACA,aACC;EAaG,YAAY,iBAAiB"}
@@ -0,0 +1,55 @@
1
+ import { BundleIR, ConditionIR, WorkflowIR } from "../ir.mjs";
2
+ import { PushDevice } from "../engine/ports.mjs";
3
+ import { D1DatabaseLike, KVNamespaceLike } from "../cloudflare/bindings.mjs";
4
+ import { DeviceInput, EntryInput, EntryOutcome, EventInput, EventWindow, JourneyStore, ProfileProps, SegmentTriggerRoute, TriggerRoute } from "./index.mjs";
5
+ //#region src/store/d1.d.ts
6
+ /**
7
+ * The original data plane: D1 for the tables (see ../cloudflare/schema.sql)
8
+ * and KV for the content-addressed workflow bodies (`wf:${contentHash}`).
9
+ * The SQL below is exactly what the router and fact source used to inline —
10
+ * the in-memory FakeD1 in the tests dispatches on these strings verbatim.
11
+ */
12
+ export declare class D1JourneyStore implements JourneyStore {
13
+ private readonly db;
14
+ private readonly kv;
15
+ constructor(db: D1DatabaseLike, kv: KVNamespaceLike);
16
+ insertEvent(input: EventInput): Promise<boolean>;
17
+ /** The event rows of one user and name, within the window's bounds (both inclusive). */
18
+ private eventWindow;
19
+ countEvents(userId: string, event: string, window?: EventWindow): Promise<number>;
20
+ listEventPayloads(userId: string, event: string, window?: EventWindow): Promise<Record<string, unknown>[]>;
21
+ getProfile(userId: string): Promise<ProfileProps | undefined>;
22
+ mergeProfile(userId: string, props: ProfileProps): Promise<ProfileProps>;
23
+ listUserIds(): Promise<string[]>;
24
+ listDevices(userId: string): Promise<PushDevice[]>;
25
+ upsertDevice(device: DeviceInput): Promise<void>;
26
+ requestErasure(userId: string, at: number): Promise<void>;
27
+ pendingErasures(): Promise<string[]>;
28
+ runningInstances(userId: string): Promise<string[]>;
29
+ forgetUser(userId: string): Promise<void>;
30
+ removeDevice(token: string): Promise<string | undefined>;
31
+ unbindDevice(token: string): Promise<string | undefined>;
32
+ listSegments(): Promise<{
33
+ id: string;
34
+ condition: ConditionIR;
35
+ }[]>;
36
+ getSegmentCondition(id: string): Promise<ConditionIR | undefined>;
37
+ findTriggers(event: string): Promise<TriggerRoute[]>;
38
+ listSegmentTriggers(): Promise<SegmentTriggerRoute[]>;
39
+ putWorkflowIR(workflow: WorkflowIR): Promise<void>;
40
+ getWorkflowIR(contentHash: string): Promise<WorkflowIR | undefined>;
41
+ replaceRoutes(bundle: BundleIR): Promise<void>;
42
+ isSegmentMember(segment: string, userId: string): Promise<boolean>;
43
+ addSegmentMember(segment: string, userId: string, ts: number, workflows: readonly string[]): Promise<void>;
44
+ pendingSegmentStarts(segment: string, userId: string): Promise<string[]>;
45
+ completeSegmentStart(segment: string, userId: string, workflow: string): Promise<void>;
46
+ removeSegmentMember(segment: string, userId: string): Promise<void>;
47
+ enterJourney(entry: EntryInput): Promise<EntryOutcome>;
48
+ removeEntry(instanceId: string): Promise<void>;
49
+ setEntryStatus(instanceId: string, status: string): Promise<void>;
50
+ findWakeHandles(userId: string, event: string): Promise<string[]>;
51
+ subscribe(handle: string, userId: string, events: readonly string[], ts: number): Promise<void>;
52
+ unsubscribe(handle: string): Promise<void>;
53
+ }
54
+ //#endregion
55
+ //# sourceMappingURL=d1.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"d1.d.mts","names":[],"sources":["../../src/store/d1.ts"],"mappings":";;;;;;;;;;;qBAqBa,0BAA0B;mBAElB;mBACA;EAFnB,YACmB,IAAI,gBACJ,IAAI;EAGjB,YAAY,OAAO,aAAa;;UAW9B;EAcF,YAAY,gBAAgB,eAAe,SAAS,cAAc;EASlE,kBACJ,gBACA,eACA,SAAS,cACR,QAAQ;EASL,WAAW,iBAAiB,QAAQ;EAQpC,aAAa,gBAAgB,OAAO,eAAe,QAAQ;EAgB3D,eAAe;EAQf,YAAY,iBAAiB,QAAQ;EAQrC,aAAa,QAAQ,cAAc;EASnC,eAAe,gBAAgB,aAAa;EAO5C,mBAAmB;EAQnB,iBAAiB,iBAAiB;EAQlC,WAAW,iBAAiB;EAgB5B,aAAa,gBAAgB;EAQ7B,aAAa,gBAAgB;EAS7B,gBAAgB;IAAU;IAAY,WAAW;;EAWjD,oBAAoB,aAAa,QAAQ;EAQzC,aAAa,gBAAgB,QAAQ;EAerC,uBAAuB,QAAQ;EAO/B,cAAc,UAAU,aAAa;EAIrC,cAAc,sBAAsB,QAAQ;EAK5C,cAAc,QAAQ,WAAW;EA4CjC,gBAAgB,iBAAiB,iBAAiB;EAQlD,iBACJ,iBACA,gBACA,YACA,+BACC;EAeG,qBAAqB,iBAAiB,iBAAiB;EAQvD,qBAAqB,iBAAiB,gBAAgB,mBAAmB;EAOzE,oBAAoB,iBAAiB,iBAAiB;EAWtD,aAAa,OAAO,aAAa,QAAQ;EAsBzC,YAAY,qBAAqB;EAIjC,eAAe,oBAAoB,iBAAiB;EAOpD,gBAAgB,gBAAgB,gBAAgB;EAUhD,UACJ,gBACA,gBACA,2BACA,aACC;EAaG,YAAY,iBAAiB"}
@@ -0,0 +1,184 @@
1
+ import { ConditionIR, WorkflowIR } from "../ir.mjs";
2
+ //#region src/store/d1.ts
3
+ /**
4
+ * The original data plane: D1 for the tables (see ../cloudflare/schema.sql)
5
+ * and KV for the content-addressed workflow bodies (`wf:${contentHash}`).
6
+ * The SQL below is exactly what the router and fact source used to inline —
7
+ * the in-memory FakeD1 in the tests dispatches on these strings verbatim.
8
+ */
9
+ var D1JourneyStore = class {
10
+ db;
11
+ kv;
12
+ constructor(db, kv) {
13
+ this.db = db;
14
+ this.kv = kv;
15
+ }
16
+ async insertEvent(input) {
17
+ return ((await this.db.prepare("INSERT OR IGNORE INTO events (id, user_id, name, ts, payload) VALUES (?, ?, ?, ?, ?)").bind(input.id, input.userId, input.event, input.ts, JSON.stringify(input.payload)).run()).meta?.changes ?? 1) > 0;
18
+ }
19
+ /** The event rows of one user and name, within the window's bounds (both inclusive). */
20
+ eventWindow(userId, event, window) {
21
+ const params = [userId, event];
22
+ let where = "user_id = ? AND name = ?";
23
+ if (window?.sinceMs !== void 0) {
24
+ where += " AND ts >= ?";
25
+ params.push(window.sinceMs);
26
+ }
27
+ if (window?.untilMs !== void 0) {
28
+ where += " AND ts <= ?";
29
+ params.push(window.untilMs);
30
+ }
31
+ return {
32
+ where,
33
+ params
34
+ };
35
+ }
36
+ async countEvents(userId, event, window) {
37
+ const { where, params } = this.eventWindow(userId, event, window);
38
+ return (await this.db.prepare(`SELECT COUNT(*) AS c FROM events WHERE ${where}`).bind(...params).first())?.c ?? 0;
39
+ }
40
+ async listEventPayloads(userId, event, window) {
41
+ const { where, params } = this.eventWindow(userId, event, window);
42
+ const { results } = await this.db.prepare(`SELECT payload FROM events WHERE ${where}`).bind(...params).all();
43
+ return results.map((row) => JSON.parse(row.payload));
44
+ }
45
+ async getProfile(userId) {
46
+ const row = await this.db.prepare("SELECT props FROM profiles WHERE user_id = ?").bind(userId).first();
47
+ return row ? JSON.parse(row.props) : void 0;
48
+ }
49
+ async mergeProfile(userId, props) {
50
+ const patch = JSON.stringify(props);
51
+ const row = await this.db.prepare("INSERT INTO profiles (user_id, props) VALUES (?, json_patch('{}', ?)) ON CONFLICT(user_id) DO UPDATE SET props = json_patch(profiles.props, ?) RETURNING props").bind(userId, patch, patch).first();
52
+ return row === null ? stripNulls(props) : JSON.parse(row.props);
53
+ }
54
+ async listUserIds() {
55
+ const { results } = await this.db.prepare("SELECT user_id FROM profiles UNION SELECT DISTINCT user_id FROM events").bind().all();
56
+ return results.map((row) => row.user_id);
57
+ }
58
+ async listDevices(userId) {
59
+ const { results } = await this.db.prepare("SELECT token, platform FROM devices WHERE user_id = ? ORDER BY ts DESC").bind(userId).all();
60
+ return results;
61
+ }
62
+ async upsertDevice(device) {
63
+ await this.db.prepare("INSERT INTO devices (token, user_id, platform, ts) VALUES (?, ?, ?, ?) ON CONFLICT (token) DO UPDATE SET user_id = excluded.user_id, platform = excluded.platform, ts = excluded.ts").bind(device.token, device.userId, device.platform, device.ts).run();
64
+ }
65
+ async requestErasure(userId, at) {
66
+ await this.db.prepare("INSERT OR IGNORE INTO erasures (user_id, ts) VALUES (?, ?)").bind(userId, at).run();
67
+ }
68
+ async pendingErasures() {
69
+ const { results } = await this.db.prepare("SELECT user_id FROM erasures ORDER BY ts").bind().all();
70
+ return results.map((row) => row.user_id);
71
+ }
72
+ async runningInstances(userId) {
73
+ const { results } = await this.db.prepare("SELECT instance_id FROM entries WHERE user_id = ? AND status = 'running'").bind(userId).all();
74
+ return results.map((row) => row.instance_id);
75
+ }
76
+ async forgetUser(userId) {
77
+ await this.db.batch([
78
+ "entries",
79
+ "subscriptions",
80
+ "segment_starts",
81
+ "segment_members",
82
+ "devices",
83
+ "events",
84
+ "profiles",
85
+ "erasures"
86
+ ].map((table) => this.db.prepare(`DELETE FROM ${table} WHERE user_id = ?`).bind(userId)));
87
+ }
88
+ async removeDevice(token) {
89
+ return (await this.db.prepare("DELETE FROM devices WHERE token = ? RETURNING user_id").bind(token).first())?.user_id ?? void 0;
90
+ }
91
+ async unbindDevice(token) {
92
+ const row = await this.db.prepare("SELECT user_id FROM devices WHERE token = ?").bind(token).first();
93
+ await this.db.prepare("UPDATE devices SET user_id = NULL WHERE token = ?").bind(token).run();
94
+ return row?.user_id ?? void 0;
95
+ }
96
+ async listSegments() {
97
+ const { results } = await this.db.prepare("SELECT id, condition FROM segments ORDER BY id").bind().all();
98
+ return results.map((row) => ({
99
+ id: row.id,
100
+ condition: ConditionIR.parse(JSON.parse(row.condition))
101
+ }));
102
+ }
103
+ async getSegmentCondition(id) {
104
+ const row = await this.db.prepare("SELECT condition FROM segments WHERE id = ?").bind(id).first();
105
+ return row ? ConditionIR.parse(JSON.parse(row.condition)) : void 0;
106
+ }
107
+ async findTriggers(event) {
108
+ const { results } = await this.db.prepare("SELECT workflow, hash, \"where\" AS whereClause, filter FROM triggers WHERE event = ?").bind(event).all();
109
+ return results.map((row) => ({
110
+ workflow: row.workflow,
111
+ hash: row.hash,
112
+ where: row.whereClause === null ? null : ConditionIR.parse(JSON.parse(row.whereClause)),
113
+ filter: row.filter === null ? null : ConditionIR.parse(JSON.parse(row.filter))
114
+ }));
115
+ }
116
+ async listSegmentTriggers() {
117
+ const { results } = await this.db.prepare("SELECT workflow, hash, segment FROM segment_triggers").all();
118
+ return results;
119
+ }
120
+ async putWorkflowIR(workflow) {
121
+ await this.kv.put(`wf:${workflow.contentHash}`, JSON.stringify(workflow));
122
+ }
123
+ async getWorkflowIR(contentHash) {
124
+ const raw = await this.kv.get(`wf:${contentHash}`);
125
+ return raw === null ? void 0 : WorkflowIR.parse(JSON.parse(raw));
126
+ }
127
+ async replaceRoutes(bundle) {
128
+ await this.db.batch([
129
+ this.db.prepare("DELETE FROM triggers"),
130
+ this.db.prepare("DELETE FROM segment_triggers"),
131
+ this.db.prepare("DELETE FROM segments"),
132
+ ...bundle.segments.map((segment) => this.db.prepare("INSERT INTO segments (id, condition, hash) VALUES (?, ?, ?)").bind(segment.id, JSON.stringify(segment.condition), segment.contentHash)),
133
+ ...bundle.workflows.flatMap((workflow) => {
134
+ if (workflow.trigger.type === "event") return [this.db.prepare("INSERT INTO triggers (workflow, hash, event, \"where\", filter) VALUES (?, ?, ?, ?, ?)").bind(workflow.id, workflow.contentHash, workflow.trigger.event, workflow.trigger.where !== void 0 ? JSON.stringify(workflow.trigger.where) : null, workflow.trigger.filter !== void 0 ? JSON.stringify(workflow.trigger.filter) : null)];
135
+ if (workflow.trigger.type === "segment") return [this.db.prepare("INSERT INTO segment_triggers (workflow, hash, segment) VALUES (?, ?, ?)").bind(workflow.id, workflow.contentHash, workflow.trigger.segment)];
136
+ return [];
137
+ })
138
+ ]);
139
+ }
140
+ async isSegmentMember(segment, userId) {
141
+ return await this.db.prepare("SELECT 1 AS x FROM segment_members WHERE segment = ? AND user_id = ?").bind(segment, userId).first() !== null;
142
+ }
143
+ async addSegmentMember(segment, userId, ts, workflows) {
144
+ await this.db.batch([this.db.prepare("INSERT OR IGNORE INTO segment_members (segment, user_id, ts) VALUES (?, ?, ?)").bind(segment, userId, ts), ...workflows.map((workflow) => this.db.prepare("INSERT OR IGNORE INTO segment_starts (segment, workflow, user_id, ts) VALUES (?, ?, ?, ?)").bind(segment, workflow, userId, ts))]);
145
+ }
146
+ async pendingSegmentStarts(segment, userId) {
147
+ const { results } = await this.db.prepare("SELECT workflow FROM segment_starts WHERE segment = ? AND user_id = ?").bind(segment, userId).all();
148
+ return results.map((row) => row.workflow);
149
+ }
150
+ async completeSegmentStart(segment, userId, workflow) {
151
+ await this.db.prepare("DELETE FROM segment_starts WHERE segment = ? AND user_id = ? AND workflow = ?").bind(segment, userId, workflow).run();
152
+ }
153
+ async removeSegmentMember(segment, userId) {
154
+ await this.db.batch([this.db.prepare("DELETE FROM segment_starts WHERE segment = ? AND user_id = ?").bind(segment, userId), this.db.prepare("DELETE FROM segment_members WHERE segment = ? AND user_id = ?").bind(segment, userId)]);
155
+ }
156
+ async enterJourney(entry) {
157
+ if (((await this.db.prepare("INSERT OR IGNORE INTO entries (workflow, user_id, instance_id, hash, status, ts) VALUES (?, ?, ?, ?, ?, ?)").bind(entry.workflow, entry.userId, entry.instanceId, entry.hash, "running", entry.ts).run()).meta?.changes ?? 1) !== 0) return "entered";
158
+ return ((await this.db.prepare("UPDATE entries SET instance_id = ?, hash = ?, status = 'running', ts = ? WHERE workflow = ? AND user_id = ? AND status = 'failed'").bind(entry.instanceId, entry.hash, entry.ts, entry.workflow, entry.userId).run()).meta?.changes ?? 0) > 0 ? "reclaimed" : null;
159
+ }
160
+ async removeEntry(instanceId) {
161
+ await this.db.prepare("DELETE FROM entries WHERE instance_id = ?").bind(instanceId).run();
162
+ }
163
+ async setEntryStatus(instanceId, status) {
164
+ await this.db.prepare("UPDATE entries SET status = ? WHERE instance_id = ?").bind(status, instanceId).run();
165
+ }
166
+ async findWakeHandles(userId, event) {
167
+ const { results } = await this.db.prepare("SELECT DISTINCT wake_handle AS handle FROM subscriptions WHERE user_id = ? AND event = ?").bind(userId, event).all();
168
+ return results.map((row) => row.handle);
169
+ }
170
+ async subscribe(handle, userId, events, ts) {
171
+ for (const event of events) await this.db.prepare("INSERT OR IGNORE INTO subscriptions (user_id, event, wake_handle, ts) VALUES (?, ?, ?, ?)").bind(userId, event, handle, ts).run();
172
+ }
173
+ async unsubscribe(handle) {
174
+ await this.db.prepare("DELETE FROM subscriptions WHERE wake_handle = ?").bind(handle).run();
175
+ }
176
+ };
177
+ /** What a merge-patch of `props` into an empty profile yields: the patch minus its nulls. */
178
+ function stripNulls(props) {
179
+ return Object.fromEntries(Object.entries(props).filter(([, value]) => value !== null));
180
+ }
181
+ //#endregion
182
+ export { D1JourneyStore };
183
+
184
+ //# sourceMappingURL=d1.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"d1.mjs","names":[],"sources":["../../src/store/d1.ts"],"sourcesContent":["import type { D1DatabaseLike, KVNamespaceLike } from '../cloudflare/bindings';\nimport type { PushDevice } from '../engine/ports';\nimport { type BundleIR, ConditionIR, WorkflowIR } from '../ir';\nimport type {\n DeviceInput,\n EntryInput,\n EntryOutcome,\n EventInput,\n EventWindow,\n JourneyStore,\n ProfileProps,\n SegmentTriggerRoute,\n TriggerRoute,\n} from './index';\n\n/**\n * The original data plane: D1 for the tables (see ../cloudflare/schema.sql)\n * and KV for the content-addressed workflow bodies (`wf:${contentHash}`).\n * The SQL below is exactly what the router and fact source used to inline —\n * the in-memory FakeD1 in the tests dispatches on these strings verbatim.\n */\nexport class D1JourneyStore implements JourneyStore {\n constructor(\n private readonly db: D1DatabaseLike,\n private readonly kv: KVNamespaceLike\n ) {}\n\n async insertEvent(input: EventInput): Promise<boolean> {\n const written = await this.db\n .prepare(\n 'INSERT OR IGNORE INTO events (id, user_id, name, ts, payload) VALUES (?, ?, ?, ?, ?)'\n )\n .bind(input.id, input.userId, input.event, input.ts, JSON.stringify(input.payload))\n .run();\n return (written.meta?.changes ?? 1) > 0;\n }\n\n /** The event rows of one user and name, within the window's bounds (both inclusive). */\n private eventWindow(userId: string, event: string, window?: EventWindow) {\n const params: unknown[] = [userId, event];\n let where = 'user_id = ? AND name = ?';\n if (window?.sinceMs !== undefined) {\n where += ' AND ts >= ?';\n params.push(window.sinceMs);\n }\n if (window?.untilMs !== undefined) {\n where += ' AND ts <= ?';\n params.push(window.untilMs);\n }\n return { where, params };\n }\n\n async countEvents(userId: string, event: string, window?: EventWindow): Promise<number> {\n const { where, params } = this.eventWindow(userId, event, window);\n const row = await this.db\n .prepare(`SELECT COUNT(*) AS c FROM events WHERE ${where}`)\n .bind(...params)\n .first<{ c: number }>();\n return row?.c ?? 0;\n }\n\n async listEventPayloads(\n userId: string,\n event: string,\n window?: EventWindow\n ): Promise<Record<string, unknown>[]> {\n const { where, params } = this.eventWindow(userId, event, window);\n const { results } = await this.db\n .prepare(`SELECT payload FROM events WHERE ${where}`)\n .bind(...params)\n .all<{ payload: string }>();\n return results.map((row) => JSON.parse(row.payload) as Record<string, unknown>);\n }\n\n async getProfile(userId: string): Promise<ProfileProps | undefined> {\n const row = await this.db\n .prepare('SELECT props FROM profiles WHERE user_id = ?')\n .bind(userId)\n .first<{ props: string }>();\n return row ? (JSON.parse(row.props) as ProfileProps) : undefined;\n }\n\n async mergeProfile(userId: string, props: ProfileProps): Promise<ProfileProps> {\n // SQLite's json_patch is RFC 7386 merge-patch: a null removes its key. The\n // merge happens in the statement, so two concurrent identifies cannot lose\n // each other's fields the way a read-modify-write did.\n const patch = JSON.stringify(props);\n const row = await this.db\n .prepare(\n \"INSERT INTO profiles (user_id, props) VALUES (?, json_patch('{}', ?)) ON CONFLICT(user_id) DO UPDATE SET props = json_patch(profiles.props, ?) RETURNING props\"\n )\n .bind(userId, patch, patch)\n .first<{ props: string }>();\n // RETURNING always yields the row it just wrote; the fallback only covers a\n // driver that declines to return one, and reports what was asked for.\n return row === null ? stripNulls(props) : (JSON.parse(row.props) as ProfileProps);\n }\n\n async listUserIds(): Promise<string[]> {\n const { results } = await this.db\n .prepare('SELECT user_id FROM profiles UNION SELECT DISTINCT user_id FROM events')\n .bind()\n .all<{ user_id: string }>();\n return results.map((row) => row.user_id);\n }\n\n async listDevices(userId: string): Promise<PushDevice[]> {\n const { results } = await this.db\n .prepare('SELECT token, platform FROM devices WHERE user_id = ? ORDER BY ts DESC')\n .bind(userId)\n .all<PushDevice>();\n return results;\n }\n\n async upsertDevice(device: DeviceInput): Promise<void> {\n await this.db\n .prepare(\n 'INSERT INTO devices (token, user_id, platform, ts) VALUES (?, ?, ?, ?) ON CONFLICT (token) DO UPDATE SET user_id = excluded.user_id, platform = excluded.platform, ts = excluded.ts'\n )\n .bind(device.token, device.userId, device.platform, device.ts)\n .run();\n }\n\n async requestErasure(userId: string, at: number): Promise<void> {\n await this.db\n .prepare('INSERT OR IGNORE INTO erasures (user_id, ts) VALUES (?, ?)')\n .bind(userId, at)\n .run();\n }\n\n async pendingErasures(): Promise<string[]> {\n const { results } = await this.db\n .prepare('SELECT user_id FROM erasures ORDER BY ts')\n .bind()\n .all<{ user_id: string }>();\n return results.map((row) => row.user_id);\n }\n\n async runningInstances(userId: string): Promise<string[]> {\n const { results } = await this.db\n .prepare(\"SELECT instance_id FROM entries WHERE user_id = ? AND status = 'running'\")\n .bind(userId)\n .all<{ instance_id: string }>();\n return results.map((row) => row.instance_id);\n }\n\n async forgetUser(userId: string): Promise<void> {\n // One batch: D1 runs it as a transaction; the request goes last, with the data it erases\n await this.db.batch(\n [\n 'entries',\n 'subscriptions',\n 'segment_starts',\n 'segment_members',\n 'devices',\n 'events',\n 'profiles',\n 'erasures',\n ].map((table) => this.db.prepare(`DELETE FROM ${table} WHERE user_id = ?`).bind(userId))\n );\n }\n\n async removeDevice(token: string): Promise<string | undefined> {\n const row = await this.db\n .prepare('DELETE FROM devices WHERE token = ? RETURNING user_id')\n .bind(token)\n .first<{ user_id: string | null }>();\n return row?.user_id ?? undefined;\n }\n\n async unbindDevice(token: string): Promise<string | undefined> {\n const row = await this.db\n .prepare('SELECT user_id FROM devices WHERE token = ?')\n .bind(token)\n .first<{ user_id: string | null }>();\n await this.db.prepare('UPDATE devices SET user_id = NULL WHERE token = ?').bind(token).run();\n return row?.user_id ?? undefined;\n }\n\n async listSegments(): Promise<{ id: string; condition: ConditionIR }[]> {\n const { results } = await this.db\n .prepare('SELECT id, condition FROM segments ORDER BY id')\n .bind()\n .all<{ id: string; condition: string }>();\n return results.map((row) => ({\n id: row.id,\n condition: ConditionIR.parse(JSON.parse(row.condition)),\n }));\n }\n\n async getSegmentCondition(id: string): Promise<ConditionIR | undefined> {\n const row = await this.db\n .prepare('SELECT condition FROM segments WHERE id = ?')\n .bind(id)\n .first<{ condition: string }>();\n return row ? ConditionIR.parse(JSON.parse(row.condition)) : undefined;\n }\n\n async findTriggers(event: string): Promise<TriggerRoute[]> {\n const { results } = await this.db\n .prepare(\n 'SELECT workflow, hash, \"where\" AS whereClause, filter FROM triggers WHERE event = ?'\n )\n .bind(event)\n .all<{ workflow: string; hash: string; whereClause: string | null; filter: string | null }>();\n return results.map((row) => ({\n workflow: row.workflow,\n hash: row.hash,\n where: row.whereClause === null ? null : ConditionIR.parse(JSON.parse(row.whereClause)),\n filter: row.filter === null ? null : ConditionIR.parse(JSON.parse(row.filter)),\n }));\n }\n\n async listSegmentTriggers(): Promise<SegmentTriggerRoute[]> {\n const { results } = await this.db\n .prepare('SELECT workflow, hash, segment FROM segment_triggers')\n .all<SegmentTriggerRoute>();\n return results;\n }\n\n async putWorkflowIR(workflow: WorkflowIR): Promise<void> {\n await this.kv.put(`wf:${workflow.contentHash}`, JSON.stringify(workflow));\n }\n\n async getWorkflowIR(contentHash: string): Promise<WorkflowIR | undefined> {\n const raw = await this.kv.get(`wf:${contentHash}`);\n return raw === null ? undefined : WorkflowIR.parse(JSON.parse(raw));\n }\n\n async replaceRoutes(bundle: BundleIR): Promise<void> {\n // One atomic batch: all-or-nothing, and a concurrent ingest never observes\n // the half-empty routing table a delete-then-insert loop would expose.\n await this.db.batch([\n this.db.prepare('DELETE FROM triggers'),\n this.db.prepare('DELETE FROM segment_triggers'),\n this.db.prepare('DELETE FROM segments'),\n ...bundle.segments.map((segment) =>\n this.db\n .prepare('INSERT INTO segments (id, condition, hash) VALUES (?, ?, ?)')\n .bind(segment.id, JSON.stringify(segment.condition), segment.contentHash)\n ),\n ...bundle.workflows.flatMap((workflow) => {\n if (workflow.trigger.type === 'event') {\n return [\n this.db\n .prepare(\n 'INSERT INTO triggers (workflow, hash, event, \"where\", filter) VALUES (?, ?, ?, ?, ?)'\n )\n .bind(\n workflow.id,\n workflow.contentHash,\n workflow.trigger.event,\n workflow.trigger.where !== undefined\n ? JSON.stringify(workflow.trigger.where)\n : null,\n workflow.trigger.filter !== undefined\n ? JSON.stringify(workflow.trigger.filter)\n : null\n ),\n ];\n }\n if (workflow.trigger.type === 'segment') {\n return [\n this.db\n .prepare('INSERT INTO segment_triggers (workflow, hash, segment) VALUES (?, ?, ?)')\n .bind(workflow.id, workflow.contentHash, workflow.trigger.segment),\n ];\n }\n return [];\n }),\n ]);\n }\n\n async isSegmentMember(segment: string, userId: string): Promise<boolean> {\n const row = await this.db\n .prepare('SELECT 1 AS x FROM segment_members WHERE segment = ? AND user_id = ?')\n .bind(segment, userId)\n .first<{ x: number }>();\n return row !== null;\n }\n\n async addSegmentMember(\n segment: string,\n userId: string,\n ts: number,\n workflows: readonly string[]\n ): Promise<void> {\n await this.db.batch([\n this.db\n .prepare('INSERT OR IGNORE INTO segment_members (segment, user_id, ts) VALUES (?, ?, ?)')\n .bind(segment, userId, ts),\n ...workflows.map((workflow) =>\n this.db\n .prepare(\n 'INSERT OR IGNORE INTO segment_starts (segment, workflow, user_id, ts) VALUES (?, ?, ?, ?)'\n )\n .bind(segment, workflow, userId, ts)\n ),\n ]);\n }\n\n async pendingSegmentStarts(segment: string, userId: string): Promise<string[]> {\n const { results } = await this.db\n .prepare('SELECT workflow FROM segment_starts WHERE segment = ? AND user_id = ?')\n .bind(segment, userId)\n .all<{ workflow: string }>();\n return results.map((row) => row.workflow);\n }\n\n async completeSegmentStart(segment: string, userId: string, workflow: string): Promise<void> {\n await this.db\n .prepare('DELETE FROM segment_starts WHERE segment = ? AND user_id = ? AND workflow = ?')\n .bind(segment, userId, workflow)\n .run();\n }\n\n async removeSegmentMember(segment: string, userId: string): Promise<void> {\n await this.db.batch([\n this.db\n .prepare('DELETE FROM segment_starts WHERE segment = ? AND user_id = ?')\n .bind(segment, userId),\n this.db\n .prepare('DELETE FROM segment_members WHERE segment = ? AND user_id = ?')\n .bind(segment, userId),\n ]);\n }\n\n async enterJourney(entry: EntryInput): Promise<EntryOutcome> {\n const inserted = await this.db\n .prepare(\n 'INSERT OR IGNORE INTO entries (workflow, user_id, instance_id, hash, status, ts) VALUES (?, ?, ?, ?, ?, ?)'\n )\n .bind(entry.workflow, entry.userId, entry.instanceId, entry.hash, 'running', entry.ts)\n .run();\n if ((inserted.meta?.changes ?? 1) !== 0) return 'entered';\n\n // A row already there blocks the entry, unless it records an instance that\n // died: 'failed' is reclaimed rather than barring the user forever over one\n // outage. The status test lives inside the UPDATE, so of two concurrent\n // retries exactly one sees a change.\n const claimed = await this.db\n .prepare(\n \"UPDATE entries SET instance_id = ?, hash = ?, status = 'running', ts = ? WHERE workflow = ? AND user_id = ? AND status = 'failed'\"\n )\n .bind(entry.instanceId, entry.hash, entry.ts, entry.workflow, entry.userId)\n .run();\n return (claimed.meta?.changes ?? 0) > 0 ? 'reclaimed' : null;\n }\n\n async removeEntry(instanceId: string): Promise<void> {\n await this.db.prepare('DELETE FROM entries WHERE instance_id = ?').bind(instanceId).run();\n }\n\n async setEntryStatus(instanceId: string, status: string): Promise<void> {\n await this.db\n .prepare('UPDATE entries SET status = ? WHERE instance_id = ?')\n .bind(status, instanceId)\n .run();\n }\n\n async findWakeHandles(userId: string, event: string): Promise<string[]> {\n const { results } = await this.db\n .prepare(\n 'SELECT DISTINCT wake_handle AS handle FROM subscriptions WHERE user_id = ? AND event = ?'\n )\n .bind(userId, event)\n .all<{ handle: string }>();\n return results.map((row) => row.handle);\n }\n\n async subscribe(\n handle: string,\n userId: string,\n events: readonly string[],\n ts: number\n ): Promise<void> {\n for (const event of events) {\n // OR IGNORE on the (wake_handle, event) PK: the interpreter re-subscribes on\n // every wait attempt (one-shot-callback contract) and rows persist here.\n await this.db\n .prepare(\n 'INSERT OR IGNORE INTO subscriptions (user_id, event, wake_handle, ts) VALUES (?, ?, ?, ?)'\n )\n .bind(userId, event, handle, ts)\n .run();\n }\n }\n\n async unsubscribe(handle: string): Promise<void> {\n await this.db.prepare('DELETE FROM subscriptions WHERE wake_handle = ?').bind(handle).run();\n }\n}\n\n/** What a merge-patch of `props` into an empty profile yields: the patch minus its nulls. */\nfunction stripNulls(props: ProfileProps): ProfileProps {\n return Object.fromEntries(Object.entries(props).filter(([, value]) => value !== null));\n}\n"],"mappings":"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"}