@storyshelf/core 0.6.2 → 0.8.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 (727) hide show
  1. package/README.md +49 -32
  2. package/dist/adapter/capture-queue.d.mts +44 -4
  3. package/dist/adapter/capture-runner.d.mts +134 -12
  4. package/dist/adapter/capture-runner.mjs +9 -12
  5. package/dist/adapter/database.d.mts +89 -10
  6. package/dist/adapter/database.mjs +4 -3
  7. package/dist/adapter/database.mjs.map +1 -1
  8. package/dist/adapter/design.d.mts +83 -0
  9. package/dist/adapter/design.mjs +27 -0
  10. package/dist/adapter/design.mjs.map +1 -0
  11. package/dist/adapter/email-sender.d.mts +0 -2
  12. package/dist/adapter/metadata.d.mts +3 -12
  13. package/dist/adapter/notifier.d.mts +6 -3
  14. package/dist/adapter/notifier.mjs +5 -1
  15. package/dist/adapter/oauth.d.mts +50 -0
  16. package/dist/adapter/setup.d.mts +4 -7
  17. package/dist/adapter/setup.mjs +3 -3
  18. package/dist/adapter/setup.mjs.map +1 -1
  19. package/dist/adapter/storage.d.mts +1 -3
  20. package/dist/adapter/webhook-events.d.mts +0 -2
  21. package/dist/adapter/webhook-events.mjs +13 -14
  22. package/dist/adapter/webhook-events.mjs.map +1 -1
  23. package/dist/adapters/capture-target-offers.d.mts +26 -0
  24. package/dist/adapters/capture-target-offers.mjs +57 -0
  25. package/dist/adapters/capture-target-offers.mjs.map +1 -0
  26. package/dist/adapters/capture-target.d.mts +69 -0
  27. package/dist/adapters/capture-target.mjs +75 -0
  28. package/dist/adapters/capture-target.mjs.map +1 -0
  29. package/dist/adapters/notifier/build-comment.d.mts +22 -0
  30. package/dist/adapters/notifier/build-comment.mjs +23 -0
  31. package/dist/adapters/notifier/build-comment.mjs.map +1 -0
  32. package/dist/adapters/notifier/build-state.d.mts +97 -0
  33. package/dist/adapters/notifier/build-state.mjs +217 -0
  34. package/dist/adapters/notifier/build-state.mjs.map +1 -0
  35. package/dist/adapters/notifier/events.d.mts +4 -4
  36. package/dist/adapters/notifier/events.mjs +32 -20
  37. package/dist/adapters/notifier/events.mjs.map +1 -1
  38. package/dist/adapters/notifier/format.d.mts +0 -2
  39. package/dist/adapters/notifier/format.mjs +1 -2
  40. package/dist/adapters/notifier/provider.d.mts +53 -9
  41. package/dist/adapters/notifier/provider.mjs +11 -0
  42. package/dist/adapters/notifier/provider.mjs.map +1 -0
  43. package/dist/adapters/notifier/state.d.mts +22 -0
  44. package/dist/adapters/notifier/state.mjs +65 -0
  45. package/dist/adapters/notifier/state.mjs.map +1 -0
  46. package/dist/adapters/notifier/types.d.mts +51 -4
  47. package/dist/adapters/remote-browser.d.mts +168 -0
  48. package/dist/adapters/remote-browser.mjs +201 -0
  49. package/dist/adapters/remote-browser.mjs.map +1 -0
  50. package/dist/adapters/remote-lease-attempt.mjs +39 -0
  51. package/dist/adapters/remote-lease-attempt.mjs.map +1 -0
  52. package/dist/adapters/remote-lease-error.d.mts +25 -0
  53. package/dist/adapters/remote-lease-error.mjs +54 -0
  54. package/dist/adapters/remote-lease-error.mjs.map +1 -0
  55. package/dist/adapters/remote-lease-held.d.mts +15 -0
  56. package/dist/adapters/remote-lease-held.mjs +86 -0
  57. package/dist/adapters/remote-lease-held.mjs.map +1 -0
  58. package/dist/adapters/remote-lease-shutdown.d.mts +12 -0
  59. package/dist/adapters/remote-lease-shutdown.mjs +32 -0
  60. package/dist/adapters/remote-lease-shutdown.mjs.map +1 -0
  61. package/dist/adapters/remote-lease.d.mts +39 -0
  62. package/dist/adapters/remote-lease.mjs +175 -0
  63. package/dist/adapters/remote-lease.mjs.map +1 -0
  64. package/dist/adapters/remote-limiter.mjs +121 -0
  65. package/dist/adapters/remote-limiter.mjs.map +1 -0
  66. package/dist/adapters/remote-pool.d.mts +56 -0
  67. package/dist/adapters/remote-pool.mjs +195 -0
  68. package/dist/adapters/remote-pool.mjs.map +1 -0
  69. package/dist/adapters/render-runs.d.mts +23 -0
  70. package/dist/adapters/render-runs.mjs +40 -0
  71. package/dist/adapters/render-runs.mjs.map +1 -0
  72. package/dist/adapters/setup-runner.d.mts +0 -2
  73. package/dist/adapters/setup-runner.mjs +3 -6
  74. package/dist/adapters/setup-runner.mjs.map +1 -1
  75. package/dist/adapters/sources.d.mts +10 -0
  76. package/dist/adapters/sources.mjs +19 -0
  77. package/dist/adapters/sources.mjs.map +1 -0
  78. package/dist/adapters/validate.d.mts +0 -2
  79. package/dist/adapters/validate.mjs +27 -20
  80. package/dist/adapters/validate.mjs.map +1 -1
  81. package/dist/ai.d.mts +103 -0
  82. package/dist/ai.mjs +18 -0
  83. package/dist/ai.mjs.map +1 -0
  84. package/dist/auth.d.mts +0 -2
  85. package/dist/auth.mjs +1 -2
  86. package/dist/brand-packs.d.mts +0 -2
  87. package/dist/brand-packs.mjs +0 -1
  88. package/dist/capture/a11y/axe-source.d.mts +16 -0
  89. package/dist/capture/a11y/axe-source.mjs +36 -0
  90. package/dist/capture/a11y/axe-source.mjs.map +1 -0
  91. package/dist/capture/a11y/in-page.d.mts +25 -0
  92. package/dist/capture/a11y/in-page.mjs +59 -0
  93. package/dist/capture/a11y/in-page.mjs.map +1 -0
  94. package/dist/capture/a11y/map.d.mts +22 -0
  95. package/dist/capture/a11y/map.mjs +47 -0
  96. package/dist/capture/a11y/map.mjs.map +1 -0
  97. package/dist/capture/a11y/options.d.mts +21 -0
  98. package/dist/capture/a11y/options.mjs +74 -0
  99. package/dist/capture/a11y/options.mjs.map +1 -0
  100. package/dist/capture/adapter.d.mts +46 -5
  101. package/dist/capture/adapter.mjs +1 -2
  102. package/dist/capture/adapter.mjs.map +1 -1
  103. package/dist/capture/affected.d.mts +2 -2
  104. package/dist/capture/affected.mjs +14 -6
  105. package/dist/capture/affected.mjs.map +1 -1
  106. package/dist/capture/attempt-logger.mjs +83 -0
  107. package/dist/capture/attempt-logger.mjs.map +1 -0
  108. package/dist/capture/browsers.d.mts +33 -0
  109. package/dist/capture/browsers.mjs +66 -0
  110. package/dist/capture/browsers.mjs.map +1 -0
  111. package/dist/capture/build-state/deliver.d.mts +30 -0
  112. package/dist/capture/build-state/deliver.mjs +117 -0
  113. package/dist/capture/build-state/deliver.mjs.map +1 -0
  114. package/dist/capture/build-state/index.d.mts +8 -0
  115. package/dist/capture/build-state/notify-job.d.mts +54 -0
  116. package/dist/capture/build-state/notify-job.mjs +132 -0
  117. package/dist/capture/build-state/notify-job.mjs.map +1 -0
  118. package/dist/capture/build-state/publisher.d.mts +21 -0
  119. package/dist/capture/build-state/publisher.mjs +106 -0
  120. package/dist/capture/build-state/publisher.mjs.map +1 -0
  121. package/dist/capture/build-state/resume.d.mts +7 -0
  122. package/dist/capture/build-state/resume.mjs +46 -0
  123. package/dist/capture/build-state/resume.mjs.map +1 -0
  124. package/dist/capture/build-state/types.d.mts +47 -0
  125. package/dist/capture/catalog/catalogue.d.mts +20 -0
  126. package/dist/capture/catalog/catalogue.mjs +52 -0
  127. package/dist/capture/catalog/catalogue.mjs.map +1 -0
  128. package/dist/capture/catalog/components.d.mts +21 -0
  129. package/dist/capture/catalog/components.mjs +54 -0
  130. package/dist/capture/catalog/components.mjs.map +1 -0
  131. package/dist/capture/catalog/history.d.mts +28 -0
  132. package/dist/capture/catalog/history.mjs +50 -0
  133. package/dist/capture/catalog/history.mjs.map +1 -0
  134. package/dist/capture/catalog/hook.d.mts +12 -0
  135. package/dist/capture/catalog/hook.mjs +18 -0
  136. package/dist/capture/catalog/hook.mjs.map +1 -0
  137. package/dist/capture/catalog/index.d.mts +11 -0
  138. package/dist/capture/catalog/load.d.mts +24 -0
  139. package/dist/capture/catalog/load.mjs +48 -0
  140. package/dist/capture/catalog/load.mjs.map +1 -0
  141. package/dist/capture/catalog/parse.d.mts +13 -0
  142. package/dist/capture/catalog/parse.mjs +55 -0
  143. package/dist/capture/catalog/parse.mjs.map +1 -0
  144. package/dist/capture/catalog/read.d.mts +15 -0
  145. package/dist/capture/catalog/read.mjs +39 -0
  146. package/dist/capture/catalog/read.mjs.map +1 -0
  147. package/dist/capture/catalog/types.d.mts +31 -0
  148. package/dist/capture/comparison-reference.d.mts +27 -0
  149. package/dist/capture/comparison-reference.mjs +75 -0
  150. package/dist/capture/comparison-reference.mjs.map +1 -0
  151. package/dist/capture/consumer/capture-handler.d.mts +56 -0
  152. package/dist/capture/consumer/capture-handler.mjs +117 -0
  153. package/dist/capture/consumer/capture-handler.mjs.map +1 -0
  154. package/dist/capture/consumer/idle.d.mts +15 -0
  155. package/dist/capture/consumer/idle.mjs +24 -0
  156. package/dist/capture/consumer/idle.mjs.map +1 -0
  157. package/dist/capture/consumer/index.d.mts +11 -0
  158. package/dist/capture/consumer/index.mjs +138 -0
  159. package/dist/capture/consumer/index.mjs.map +1 -0
  160. package/dist/capture/consumer/loop.mjs +109 -0
  161. package/dist/capture/consumer/loop.mjs.map +1 -0
  162. package/dist/capture/consumer/metrics.d.mts +39 -0
  163. package/dist/capture/consumer/metrics.mjs +37 -0
  164. package/dist/capture/consumer/metrics.mjs.map +1 -0
  165. package/dist/capture/consumer/retry.d.mts +31 -0
  166. package/dist/capture/consumer/retry.mjs +33 -0
  167. package/dist/capture/consumer/retry.mjs.map +1 -0
  168. package/dist/capture/consumer/run-job.mjs +194 -0
  169. package/dist/capture/consumer/run-job.mjs.map +1 -0
  170. package/dist/capture/consumer/startup.mjs +37 -0
  171. package/dist/capture/consumer/startup.mjs.map +1 -0
  172. package/dist/capture/consumer/types.d.mts +95 -0
  173. package/dist/capture/crash-failure.d.mts +23 -0
  174. package/dist/capture/crash-failure.mjs +69 -0
  175. package/dist/capture/crash-failure.mjs.map +1 -0
  176. package/dist/capture/db-queue/cadence.d.mts +30 -0
  177. package/dist/capture/db-queue/cadence.mjs +37 -0
  178. package/dist/capture/db-queue/cadence.mjs.map +1 -0
  179. package/dist/capture/db-queue/claim.d.mts +7 -0
  180. package/dist/capture/db-queue/claim.mjs +148 -0
  181. package/dist/capture/db-queue/claim.mjs.map +1 -0
  182. package/dist/capture/db-queue/clock.mjs +11 -0
  183. package/dist/capture/db-queue/clock.mjs.map +1 -0
  184. package/dist/capture/db-queue/codec.mjs +75 -0
  185. package/dist/capture/db-queue/codec.mjs.map +1 -0
  186. package/dist/capture/db-queue/index.d.mts +18 -0
  187. package/dist/capture/db-queue/index.mjs +89 -0
  188. package/dist/capture/db-queue/index.mjs.map +1 -0
  189. package/dist/capture/db-queue/keeper.d.mts +34 -0
  190. package/dist/capture/db-queue/keeper.mjs +91 -0
  191. package/dist/capture/db-queue/keeper.mjs.map +1 -0
  192. package/dist/capture/db-queue/lease.mjs +45 -0
  193. package/dist/capture/db-queue/lease.mjs.map +1 -0
  194. package/dist/capture/db-queue/lifecycle.mjs +35 -0
  195. package/dist/capture/db-queue/lifecycle.mjs.map +1 -0
  196. package/dist/capture/db-queue/operations.mjs +93 -0
  197. package/dist/capture/db-queue/operations.mjs.map +1 -0
  198. package/dist/capture/db-queue/poll-loop.d.mts +28 -0
  199. package/dist/capture/db-queue/poll-loop.mjs +88 -0
  200. package/dist/capture/db-queue/poll-loop.mjs.map +1 -0
  201. package/dist/capture/db-queue/poll.d.mts +8 -0
  202. package/dist/capture/db-queue/poll.mjs +68 -0
  203. package/dist/capture/db-queue/poll.mjs.map +1 -0
  204. package/dist/capture/db-queue/timers.d.mts +10 -0
  205. package/dist/capture/db-queue/timers.mjs +36 -0
  206. package/dist/capture/db-queue/timers.mjs.map +1 -0
  207. package/dist/capture/db-queue/types.d.mts +81 -0
  208. package/dist/capture/db-queue/types.mjs +11 -0
  209. package/dist/capture/db-queue/types.mjs.map +1 -0
  210. package/dist/capture/db-queue/wake.mjs +47 -0
  211. package/dist/capture/db-queue/wake.mjs.map +1 -0
  212. package/dist/capture/design.mjs +38 -0
  213. package/dist/capture/design.mjs.map +1 -0
  214. package/dist/capture/diff-record.mjs +24 -5
  215. package/dist/capture/diff-record.mjs.map +1 -1
  216. package/dist/capture/dispatch.d.mts +13 -10
  217. package/dist/capture/dispatch.mjs +102 -111
  218. package/dist/capture/dispatch.mjs.map +1 -1
  219. package/dist/capture/enrich.mjs +59 -0
  220. package/dist/capture/enrich.mjs.map +1 -0
  221. package/dist/capture/failure-sets.mjs +21 -0
  222. package/dist/capture/failure-sets.mjs.map +1 -0
  223. package/dist/capture/fingerprint.d.mts +40 -0
  224. package/dist/capture/fingerprint.mjs +82 -0
  225. package/dist/capture/fingerprint.mjs.map +1 -0
  226. package/dist/capture/forked/channel.d.mts +23 -0
  227. package/dist/capture/forked/child.d.mts +32 -0
  228. package/dist/capture/forked/child.mjs +201 -0
  229. package/dist/capture/forked/child.mjs.map +1 -0
  230. package/dist/capture/forked/crash-tracker.mjs +22 -0
  231. package/dist/capture/forked/crash-tracker.mjs.map +1 -0
  232. package/dist/capture/forked/decision.d.mts +24 -0
  233. package/dist/capture/forked/decision.mjs +26 -0
  234. package/dist/capture/forked/decision.mjs.map +1 -0
  235. package/dist/capture/forked/exit.mjs +11 -0
  236. package/dist/capture/forked/exit.mjs.map +1 -0
  237. package/dist/capture/forked/fork.d.mts +29 -0
  238. package/dist/capture/forked/fork.mjs +92 -0
  239. package/dist/capture/forked/fork.mjs.map +1 -0
  240. package/dist/capture/forked/forked-capture.d.mts +38 -0
  241. package/dist/capture/forked/forked-capture.mjs +239 -0
  242. package/dist/capture/forked/forked-capture.mjs.map +1 -0
  243. package/dist/capture/forked/lease-book.mjs +45 -0
  244. package/dist/capture/forked/lease-book.mjs.map +1 -0
  245. package/dist/capture/forked/protocol.d.mts +36 -0
  246. package/dist/capture/forked/protocol.mjs +24 -0
  247. package/dist/capture/forked/protocol.mjs.map +1 -0
  248. package/dist/capture/forked/runs.mjs +74 -0
  249. package/dist/capture/forked/runs.mjs.map +1 -0
  250. package/dist/capture/guards.mjs +1 -2
  251. package/dist/capture/ignore-regions.d.mts +36 -0
  252. package/dist/capture/ignore-regions.mjs +65 -0
  253. package/dist/capture/ignore-regions.mjs.map +1 -0
  254. package/dist/capture/isolate.mjs +140 -0
  255. package/dist/capture/isolate.mjs.map +1 -0
  256. package/dist/capture/job-lease.d.mts +26 -0
  257. package/dist/capture/job-lease.mjs +56 -0
  258. package/dist/capture/job-lease.mjs.map +1 -0
  259. package/dist/capture/matrix.mjs +298 -0
  260. package/dist/capture/matrix.mjs.map +1 -0
  261. package/dist/capture/modes.d.mts +23 -0
  262. package/dist/capture/modes.mjs +106 -0
  263. package/dist/capture/modes.mjs.map +1 -0
  264. package/dist/capture/orchestrator.d.mts +37 -7
  265. package/dist/capture/orchestrator.mjs +120 -110
  266. package/dist/capture/orchestrator.mjs.map +1 -1
  267. package/dist/capture/pipeline.d.mts +9 -4
  268. package/dist/capture/pipeline.mjs +284 -48
  269. package/dist/capture/pipeline.mjs.map +1 -1
  270. package/dist/capture/progress.d.mts +9 -0
  271. package/dist/capture/progress.mjs +27 -0
  272. package/dist/capture/progress.mjs.map +1 -0
  273. package/dist/capture/publish/accept.d.mts +37 -0
  274. package/dist/capture/publish/accept.mjs +27 -0
  275. package/dist/capture/publish/accept.mjs.map +1 -0
  276. package/dist/capture/publish/complete.d.mts +44 -0
  277. package/dist/capture/publish/complete.mjs +63 -0
  278. package/dist/capture/publish/complete.mjs.map +1 -0
  279. package/dist/capture/publish/extract.d.mts +18 -0
  280. package/dist/capture/publish/extract.mjs +120 -0
  281. package/dist/capture/publish/extract.mjs.map +1 -0
  282. package/dist/capture/publish/handler.d.mts +26 -0
  283. package/dist/capture/publish/handler.mjs +61 -0
  284. package/dist/capture/publish/handler.mjs.map +1 -0
  285. package/dist/capture/publish/index.d.mts +11 -0
  286. package/dist/capture/publish/invalid-zip.d.mts +8 -0
  287. package/dist/capture/publish/invalid-zip.mjs +11 -0
  288. package/dist/capture/publish/invalid-zip.mjs.map +1 -0
  289. package/dist/capture/publish/limits.d.mts +35 -0
  290. package/dist/capture/publish/limits.mjs +38 -0
  291. package/dist/capture/publish/limits.mjs.map +1 -0
  292. package/dist/capture/publish/published.d.mts +13 -0
  293. package/dist/capture/publish/published.mjs +17 -0
  294. package/dist/capture/publish/published.mjs.map +1 -0
  295. package/dist/capture/publish/run.d.mts +31 -0
  296. package/dist/capture/publish/run.mjs +29 -0
  297. package/dist/capture/publish/run.mjs.map +1 -0
  298. package/dist/capture/rediff.d.mts +1 -3
  299. package/dist/capture/rediff.mjs +23 -9
  300. package/dist/capture/rediff.mjs.map +1 -1
  301. package/dist/capture/render.mjs +52 -0
  302. package/dist/capture/render.mjs.map +1 -0
  303. package/dist/capture/scratch.d.mts +42 -0
  304. package/dist/capture/scratch.mjs +136 -0
  305. package/dist/capture/scratch.mjs.map +1 -0
  306. package/dist/capture/shard-plan.mjs +127 -0
  307. package/dist/capture/shard-plan.mjs.map +1 -0
  308. package/dist/capture/shards/context.mjs +51 -0
  309. package/dist/capture/shards/context.mjs.map +1 -0
  310. package/dist/capture/shards/execute.mjs +175 -0
  311. package/dist/capture/shards/execute.mjs.map +1 -0
  312. package/dist/capture/shards/fan-out.mjs +125 -0
  313. package/dist/capture/shards/fan-out.mjs.map +1 -0
  314. package/dist/capture/shards/history.mjs +34 -0
  315. package/dist/capture/shards/history.mjs.map +1 -0
  316. package/dist/capture/shards/reduce.mjs +96 -0
  317. package/dist/capture/shards/reduce.mjs.map +1 -0
  318. package/dist/capture/sizing.d.mts +0 -2
  319. package/dist/capture/sizing.mjs +2 -13
  320. package/dist/capture/sizing.mjs.map +1 -1
  321. package/dist/capture/skip-checks.mjs +3 -67
  322. package/dist/capture/skip-checks.mjs.map +1 -1
  323. package/dist/capture/statics.d.mts +7 -10
  324. package/dist/capture/statics.mjs +26 -74
  325. package/dist/capture/statics.mjs.map +1 -1
  326. package/dist/capture/storybook-urls.d.mts +38 -0
  327. package/dist/capture/storybook-urls.mjs +41 -0
  328. package/dist/capture/storybook-urls.mjs.map +1 -0
  329. package/dist/capture/storybook.d.mts +12 -6
  330. package/dist/capture/storybook.mjs +63 -19
  331. package/dist/capture/storybook.mjs.map +1 -1
  332. package/dist/capture/target.mjs +20 -0
  333. package/dist/capture/target.mjs.map +1 -0
  334. package/dist/capture/targets.d.mts +23 -0
  335. package/dist/capture/targets.mjs +31 -0
  336. package/dist/capture/targets.mjs.map +1 -0
  337. package/dist/capture/tests.d.mts +35 -0
  338. package/dist/capture/tests.mjs +136 -0
  339. package/dist/capture/tests.mjs.map +1 -0
  340. package/dist/capture/token-chunks.mjs +91 -0
  341. package/dist/capture/token-chunks.mjs.map +1 -0
  342. package/dist/capture.d.mts +63 -7
  343. package/dist/capture.mjs +51 -3
  344. package/dist/config.d.mts +136 -14
  345. package/dist/config.mjs +14 -8
  346. package/dist/config.mjs.map +1 -1
  347. package/dist/diff.d.mts +7 -3
  348. package/dist/diff.mjs +15 -3
  349. package/dist/diff.mjs.map +1 -0
  350. package/dist/index.d.mts +3 -3
  351. package/dist/index.mjs +2 -2
  352. package/dist/insights/budget.d.mts +18 -0
  353. package/dist/insights/budget.mjs +32 -0
  354. package/dist/insights/budget.mjs.map +1 -0
  355. package/dist/insights/envelope.d.mts +57 -0
  356. package/dist/insights/envelope.mjs +59 -0
  357. package/dist/insights/envelope.mjs.map +1 -0
  358. package/dist/insights/evidence-health.d.mts +30 -0
  359. package/dist/insights/evidence-health.mjs +27 -0
  360. package/dist/insights/evidence-health.mjs.map +1 -0
  361. package/dist/insights/evidence.d.mts +53 -0
  362. package/dist/insights/evidence.mjs +63 -0
  363. package/dist/insights/evidence.mjs.map +1 -0
  364. package/dist/insights/hash.d.mts +16 -0
  365. package/dist/insights/hash.mjs +20 -0
  366. package/dist/insights/hash.mjs.map +1 -0
  367. package/dist/insights/limits.d.mts +22 -0
  368. package/dist/insights/limits.mjs +23 -0
  369. package/dist/insights/limits.mjs.map +1 -0
  370. package/dist/insights/prompts/v1.d.mts +11 -0
  371. package/dist/insights/prompts/v1.mjs +33 -0
  372. package/dist/insights/prompts/v1.mjs.map +1 -0
  373. package/dist/insights/redact.d.mts +7 -0
  374. package/dist/insights/redact.mjs +23 -0
  375. package/dist/insights/redact.mjs.map +1 -0
  376. package/dist/insights/text.mjs +43 -0
  377. package/dist/insights/text.mjs.map +1 -0
  378. package/dist/insights.d.mts +11 -0
  379. package/dist/insights.mjs +12 -0
  380. package/dist/logger.d.mts +0 -2
  381. package/dist/logger.mjs +18 -3
  382. package/dist/logger.mjs.map +1 -1
  383. package/dist/models/ai-budget-alert.d.mts +21 -0
  384. package/dist/models/ai-budget-alert.mjs +37 -0
  385. package/dist/models/ai-budget-alert.mjs.map +1 -0
  386. package/dist/models/ai-usage.d.mts +38 -0
  387. package/dist/models/ai-usage.mjs +60 -0
  388. package/dist/models/ai-usage.mjs.map +1 -0
  389. package/dist/models/baseline-approval.d.mts +18 -0
  390. package/dist/models/baseline-approval.mjs +41 -0
  391. package/dist/models/baseline-approval.mjs.map +1 -0
  392. package/dist/models/baseline-archive.d.mts +82 -0
  393. package/dist/models/baseline-archive.mjs +105 -0
  394. package/dist/models/baseline-archive.mjs.map +1 -0
  395. package/dist/models/baseline-atomic.mjs +66 -0
  396. package/dist/models/baseline-atomic.mjs.map +1 -0
  397. package/dist/models/baseline-conflict.d.mts +6 -0
  398. package/dist/models/baseline-conflict.mjs +9 -0
  399. package/dist/models/baseline-conflict.mjs.map +1 -0
  400. package/dist/models/baseline-export.d.mts +17 -0
  401. package/dist/models/baseline-export.mjs +135 -0
  402. package/dist/models/baseline-export.mjs.map +1 -0
  403. package/dist/models/baseline-import.mjs +117 -0
  404. package/dist/models/baseline-import.mjs.map +1 -0
  405. package/dist/models/baseline-origin.d.mts +16 -0
  406. package/dist/models/baseline-origin.mjs +35 -0
  407. package/dist/models/baseline-origin.mjs.map +1 -0
  408. package/dist/models/baseline-status.d.mts +0 -2
  409. package/dist/models/baseline-status.mjs +1 -2
  410. package/dist/models/baseline-transfer.d.mts +48 -0
  411. package/dist/models/baseline-transfer.mjs +149 -0
  412. package/dist/models/baseline-transfer.mjs.map +1 -0
  413. package/dist/models/baseline.d.mts +37 -7
  414. package/dist/models/baseline.mjs +152 -28
  415. package/dist/models/baseline.mjs.map +1 -1
  416. package/dist/models/build-component.d.mts +26 -0
  417. package/dist/models/build-component.mjs +54 -0
  418. package/dist/models/build-component.mjs.map +1 -0
  419. package/dist/models/build-review-status.d.mts +27 -0
  420. package/dist/models/build-review-status.mjs +36 -0
  421. package/dist/models/build-review-status.mjs.map +1 -0
  422. package/dist/models/build-review.d.mts +26 -0
  423. package/dist/models/build-review.mjs +53 -0
  424. package/dist/models/build-review.mjs.map +1 -0
  425. package/dist/models/build.d.mts +25 -5
  426. package/dist/models/build.mjs +53 -15
  427. package/dist/models/build.mjs.map +1 -1
  428. package/dist/models/capture-attempt.d.mts +7 -3
  429. package/dist/models/capture-attempt.mjs +30 -6
  430. package/dist/models/capture-attempt.mjs.map +1 -1
  431. package/dist/models/capture-job.d.mts +104 -0
  432. package/dist/models/capture-job.mjs +287 -0
  433. package/dist/models/capture-job.mjs.map +1 -0
  434. package/dist/models/capture-log.d.mts +1 -3
  435. package/dist/models/capture-log.mjs +5 -5
  436. package/dist/models/capture-log.mjs.map +1 -1
  437. package/dist/models/capture-shard.d.mts +60 -0
  438. package/dist/models/capture-shard.mjs +189 -0
  439. package/dist/models/capture-shard.mjs.map +1 -0
  440. package/dist/models/changeset.d.mts +46 -0
  441. package/dist/models/changeset.mjs +80 -0
  442. package/dist/models/changeset.mjs.map +1 -0
  443. package/dist/models/columns.mjs +18 -0
  444. package/dist/models/columns.mjs.map +1 -0
  445. package/dist/models/comment.d.mts +4 -3
  446. package/dist/models/comment.mjs +8 -6
  447. package/dist/models/comment.mjs.map +1 -1
  448. package/dist/models/component.d.mts +41 -0
  449. package/dist/models/component.mjs +59 -0
  450. package/dist/models/component.mjs.map +1 -0
  451. package/dist/models/connections.d.mts +39 -0
  452. package/dist/models/connections.mjs +111 -0
  453. package/dist/models/connections.mjs.map +1 -0
  454. package/dist/models/content-ref.d.mts +1 -3
  455. package/dist/models/content-ref.mjs +2 -3
  456. package/dist/models/content-ref.mjs.map +1 -1
  457. package/dist/models/credentials.d.mts +0 -2
  458. package/dist/models/credentials.mjs +13 -4
  459. package/dist/models/credentials.mjs.map +1 -1
  460. package/dist/models/design-credentials.d.mts +39 -0
  461. package/dist/models/design-credentials.mjs +85 -0
  462. package/dist/models/design-credentials.mjs.map +1 -0
  463. package/dist/models/design-health.d.mts +14 -0
  464. package/dist/models/design-health.mjs +24 -0
  465. package/dist/models/design-health.mjs.map +1 -0
  466. package/dist/models/design-nodes.d.mts +81 -0
  467. package/dist/models/design-nodes.mjs +221 -0
  468. package/dist/models/design-nodes.mjs.map +1 -0
  469. package/dist/models/design-renders.mjs +73 -0
  470. package/dist/models/design-renders.mjs.map +1 -0
  471. package/dist/models/design-resolve.d.mts +27 -0
  472. package/dist/models/design-resolve.mjs +40 -0
  473. package/dist/models/design-resolve.mjs.map +1 -0
  474. package/dist/models/design.d.mts +37 -0
  475. package/dist/models/design.mjs +89 -0
  476. package/dist/models/design.mjs.map +1 -0
  477. package/dist/models/inherited-ratio.d.mts +19 -0
  478. package/dist/models/inherited-ratio.mjs +21 -0
  479. package/dist/models/inherited-ratio.mjs.map +1 -0
  480. package/dist/models/insight.d.mts +68 -0
  481. package/dist/models/insight.mjs +161 -0
  482. package/dist/models/insight.mjs.map +1 -0
  483. package/dist/models/label.d.mts +4 -4
  484. package/dist/models/label.mjs +26 -12
  485. package/dist/models/label.mjs.map +1 -1
  486. package/dist/models/member.d.mts +1 -3
  487. package/dist/models/member.mjs +6 -6
  488. package/dist/models/member.mjs.map +1 -1
  489. package/dist/models/notification-channel.d.mts +37 -5
  490. package/dist/models/notification-channel.mjs +89 -11
  491. package/dist/models/notification-channel.mjs.map +1 -1
  492. package/dist/models/notification-subscription.d.mts +1 -4
  493. package/dist/models/notification-subscription.mjs +8 -11
  494. package/dist/models/notification-subscription.mjs.map +1 -1
  495. package/dist/models/project-group-mapping.d.mts +6 -3
  496. package/dist/models/project-group-mapping.mjs +15 -6
  497. package/dist/models/project-group-mapping.mjs.map +1 -1
  498. package/dist/models/project.d.mts +9 -3
  499. package/dist/models/project.mjs +10 -6
  500. package/dist/models/project.mjs.map +1 -1
  501. package/dist/models/public-origin.d.mts +61 -0
  502. package/dist/models/public-origin.mjs +113 -0
  503. package/dist/models/public-origin.mjs.map +1 -0
  504. package/dist/models/review-switch-off.d.mts +12 -0
  505. package/dist/models/review-switch-off.mjs +25 -0
  506. package/dist/models/review-switch-off.mjs.map +1 -0
  507. package/dist/models/site-settings.d.mts +21 -0
  508. package/dist/models/site-settings.mjs +47 -0
  509. package/dist/models/site-settings.mjs.map +1 -0
  510. package/dist/models/snapshot.d.mts +39 -3
  511. package/dist/models/snapshot.mjs +54 -5
  512. package/dist/models/snapshot.mjs.map +1 -1
  513. package/dist/models/token.d.mts +13 -3
  514. package/dist/models/token.mjs +24 -7
  515. package/dist/models/token.mjs.map +1 -1
  516. package/dist/models/usage.d.mts +74 -0
  517. package/dist/models/usage.mjs +148 -0
  518. package/dist/models/usage.mjs.map +1 -0
  519. package/dist/models/user.d.mts +1 -3
  520. package/dist/models/user.mjs +5 -5
  521. package/dist/models/user.mjs.map +1 -1
  522. package/dist/models/webhook.d.mts +15 -3
  523. package/dist/models/webhook.mjs +34 -6
  524. package/dist/models/webhook.mjs.map +1 -1
  525. package/dist/models.d.mts +30 -4
  526. package/dist/models.mjs +31 -6
  527. package/dist/options.d.mts +16 -0
  528. package/dist/options.mjs +88 -0
  529. package/dist/options.mjs.map +1 -0
  530. package/dist/paths.d.mts +21 -6
  531. package/dist/paths.mjs +57 -12
  532. package/dist/paths.mjs.map +1 -1
  533. package/dist/retention/ai.d.mts +16 -0
  534. package/dist/retention/ai.mjs +22 -0
  535. package/dist/retention/ai.mjs.map +1 -0
  536. package/dist/retention/pending.mjs +46 -0
  537. package/dist/retention/pending.mjs.map +1 -0
  538. package/dist/retention/published-gzip-sweep.mjs +28 -0
  539. package/dist/retention/published-gzip-sweep.mjs.map +1 -0
  540. package/dist/retention/screenshot-sweep.d.mts +49 -0
  541. package/dist/retention/screenshot-sweep.mjs +127 -0
  542. package/dist/retention/screenshot-sweep.mjs.map +1 -0
  543. package/dist/retention.d.mts +16 -4
  544. package/dist/retention.mjs +76 -16
  545. package/dist/retention.mjs.map +1 -1
  546. package/dist/schema/baseline.d.mts +8 -2
  547. package/dist/schema/build-component.d.mts +23 -0
  548. package/dist/schema/build-review.d.mts +15 -0
  549. package/dist/schema/build.d.mts +7 -2
  550. package/dist/schema/capture-attempt.d.mts +0 -2
  551. package/dist/schema/capture-job.d.mts +58 -0
  552. package/dist/schema/capture-shard.d.mts +33 -0
  553. package/dist/schema/comment.d.mts +2 -2
  554. package/dist/schema/connection.d.mts +22 -0
  555. package/dist/schema/content-ref.d.mts +0 -2
  556. package/dist/schema/design.d.mts +56 -0
  557. package/dist/schema/insight.d.mts +52 -0
  558. package/dist/schema/label.d.mts +0 -2
  559. package/dist/schema/member.d.mts +0 -2
  560. package/dist/schema/notification.d.mts +4 -2
  561. package/dist/schema/project-group-mapping.d.mts +0 -2
  562. package/dist/schema/project.d.mts +21 -2
  563. package/dist/schema/site-setting.d.mts +19 -0
  564. package/dist/schema/snapshot.d.mts +17 -2
  565. package/dist/schema/token.d.mts +0 -2
  566. package/dist/schema/usage.d.mts +28 -0
  567. package/dist/schema/user.d.mts +0 -2
  568. package/dist/schema/webhook.d.mts +0 -2
  569. package/dist/schema.d.mts +12 -6
  570. package/dist/serverless.d.mts +19 -0
  571. package/dist/serverless.mjs +36 -0
  572. package/dist/serverless.mjs.map +1 -0
  573. package/dist/test-helpers/build-reference-contract.d.mts +14 -0
  574. package/dist/test-helpers/build-reference-contract.mjs +66 -0
  575. package/dist/test-helpers/build-reference-contract.mjs.map +1 -0
  576. package/dist/test-helpers/create-project.d.mts +1 -3
  577. package/dist/test-helpers/create-project.mjs +1 -2
  578. package/dist/test-helpers/database-contract.d.mts +0 -2
  579. package/dist/test-helpers/database-contract.mjs +37 -2
  580. package/dist/test-helpers/database-contract.mjs.map +1 -1
  581. package/dist/test-helpers/deferred.d.mts +37 -0
  582. package/dist/test-helpers/deferred.mjs +60 -0
  583. package/dist/test-helpers/deferred.mjs.map +1 -0
  584. package/dist/test-helpers/design-contract.d.mts +19 -0
  585. package/dist/test-helpers/design-contract.mjs +39 -0
  586. package/dist/test-helpers/design-contract.mjs.map +1 -0
  587. package/dist/test-helpers/fake-adapters.d.mts +0 -2
  588. package/dist/test-helpers/fake-adapters.mjs +1 -2
  589. package/dist/test-helpers/fake-database.d.mts +0 -2
  590. package/dist/test-helpers/fake-database.mjs +21 -4
  591. package/dist/test-helpers/fake-database.mjs.map +1 -1
  592. package/dist/test-helpers/fake-design.d.mts +41 -0
  593. package/dist/test-helpers/fake-design.mjs +113 -0
  594. package/dist/test-helpers/fake-design.mjs.map +1 -0
  595. package/dist/test-helpers/fake-notifier.d.mts +19 -0
  596. package/dist/test-helpers/fake-notifier.mjs +43 -0
  597. package/dist/test-helpers/fake-notifier.mjs.map +1 -0
  598. package/dist/test-helpers/fake-storage.d.mts +0 -2
  599. package/dist/test-helpers/fake-storage.mjs +1 -2
  600. package/dist/test-helpers/fake-tables.mjs +260 -69
  601. package/dist/test-helpers/fake-tables.mjs.map +1 -1
  602. package/dist/test-helpers/manual-clock.d.mts +25 -0
  603. package/dist/test-helpers/manual-clock.mjs +55 -0
  604. package/dist/test-helpers/manual-clock.mjs.map +1 -0
  605. package/dist/test-helpers/notifier-contract.d.mts +7 -3
  606. package/dist/test-helpers/notifier-contract.mjs +33 -5
  607. package/dist/test-helpers/notifier-contract.mjs.map +1 -1
  608. package/dist/test-helpers/queue-contract.d.mts +16 -4
  609. package/dist/test-helpers/queue-contract.mjs +196 -3
  610. package/dist/test-helpers/queue-contract.mjs.map +1 -1
  611. package/dist/test-helpers/queue-multi-instance.d.mts +18 -0
  612. package/dist/test-helpers/queue-multi-instance.mjs +205 -0
  613. package/dist/test-helpers/queue-multi-instance.mjs.map +1 -0
  614. package/dist/test-helpers/runner-contract.d.mts +0 -2
  615. package/dist/test-helpers/runner-contract.mjs +12 -2
  616. package/dist/test-helpers/runner-contract.mjs.map +1 -1
  617. package/dist/test-helpers/sql-chunks.mjs +25 -2
  618. package/dist/test-helpers/sql-chunks.mjs.map +1 -1
  619. package/dist/test-helpers/storage-contract.d.mts +0 -2
  620. package/dist/test-helpers/storage-contract.mjs +2 -3
  621. package/dist/test-helpers.d.mts +10 -4
  622. package/dist/test-helpers.mjs +9 -3
  623. package/dist/tracing.mjs +36 -6
  624. package/dist/tracing.mjs.map +1 -1
  625. package/dist/types.d.mts +14 -5
  626. package/dist/types.mjs +22 -6
  627. package/dist/types.mjs.map +1 -1
  628. package/dist/urls.d.mts +4 -2
  629. package/dist/urls.mjs +12 -4
  630. package/dist/urls.mjs.map +1 -1
  631. package/dist/utils/adaptive-concurrency.d.mts +64 -0
  632. package/dist/utils/adaptive-concurrency.mjs +148 -0
  633. package/dist/utils/adaptive-concurrency.mjs.map +1 -0
  634. package/dist/utils/connect-retry.d.mts +26 -0
  635. package/dist/utils/connect-retry.mjs +42 -0
  636. package/dist/utils/connect-retry.mjs.map +1 -0
  637. package/dist/utils/encrypt.d.mts +0 -2
  638. package/dist/utils/encrypt.mjs +1 -2
  639. package/dist/utils/environment.d.mts +17 -0
  640. package/dist/utils/environment.mjs +58 -0
  641. package/dist/utils/environment.mjs.map +1 -0
  642. package/dist/utils/hash.d.mts +0 -2
  643. package/dist/utils/hash.mjs +1 -2
  644. package/dist/utils/http.d.mts +29 -3
  645. package/dist/utils/http.mjs +100 -19
  646. package/dist/utils/http.mjs.map +1 -1
  647. package/dist/utils/map-adaptive.d.mts +13 -0
  648. package/dist/utils/map-adaptive.mjs +71 -0
  649. package/dist/utils/map-adaptive.mjs.map +1 -0
  650. package/dist/utils/map-limit.d.mts +8 -0
  651. package/dist/utils/map-limit.mjs +31 -0
  652. package/dist/utils/map-limit.mjs.map +1 -0
  653. package/dist/utils/mime.d.mts +0 -2
  654. package/dist/utils/mime.mjs +1 -2
  655. package/dist/utils/page-limiter.d.mts +35 -0
  656. package/dist/utils/page-limiter.mjs +55 -0
  657. package/dist/utils/page-limiter.mjs.map +1 -0
  658. package/dist/utils/path-security.d.mts +0 -2
  659. package/dist/utils/path-security.mjs +2 -4
  660. package/dist/utils/path-security.mjs.map +1 -1
  661. package/dist/utils/public-origin.d.mts +18 -0
  662. package/dist/utils/public-origin.mjs +74 -0
  663. package/dist/utils/public-origin.mjs.map +1 -0
  664. package/dist/utils/redact.d.mts +0 -2
  665. package/dist/utils/redact.mjs +4 -2
  666. package/dist/utils/redact.mjs.map +1 -1
  667. package/dist/utils/remote-environment.d.mts +76 -0
  668. package/dist/utils/remote-environment.mjs +163 -0
  669. package/dist/utils/remote-environment.mjs.map +1 -0
  670. package/dist/utils/resources.d.mts +62 -0
  671. package/dist/utils/resources.mjs +121 -0
  672. package/dist/utils/resources.mjs.map +1 -0
  673. package/dist/utils/screenshot-store.d.mts +13 -0
  674. package/dist/utils/screenshot-store.mjs +40 -0
  675. package/dist/utils/screenshot-store.mjs.map +1 -0
  676. package/dist/utils/session-gate.d.mts +23 -0
  677. package/dist/utils/session-gate.mjs +69 -0
  678. package/dist/utils/session-gate.mjs.map +1 -0
  679. package/dist/utils/storybook-token.d.mts +89 -0
  680. package/dist/utils/storybook-token.mjs +135 -0
  681. package/dist/utils/storybook-token.mjs.map +1 -0
  682. package/dist/utils/storybook-url.d.mts +13 -0
  683. package/dist/utils/storybook-url.mjs +20 -0
  684. package/dist/utils/storybook-url.mjs.map +1 -0
  685. package/dist/utils/timing.d.mts +0 -2
  686. package/dist/utils/timing.mjs +2 -3
  687. package/dist/utils/timing.mjs.map +1 -1
  688. package/dist/utils/ulid.d.mts +0 -2
  689. package/dist/utils/ulid.mjs +1 -2
  690. package/dist/utils/zip-read.mjs +128 -0
  691. package/dist/utils/zip-read.mjs.map +1 -0
  692. package/dist/utils/zip-write.d.mts +16 -0
  693. package/dist/utils/zip-write.mjs +187 -0
  694. package/dist/utils/zip-write.mjs.map +1 -0
  695. package/dist/utils.d.mts +17 -3
  696. package/dist/utils.mjs +18 -4
  697. package/package.json +35 -26
  698. package/dist/adapter/capture-runner.mjs.map +0 -1
  699. package/dist/adapter/git-host/comments.d.mts +0 -41
  700. package/dist/adapter/git-host/comments.mjs +0 -54
  701. package/dist/adapter/git-host/comments.mjs.map +0 -1
  702. package/dist/adapter/git-host/helpers.d.mts +0 -13
  703. package/dist/adapter/git-host/helpers.mjs +0 -30
  704. package/dist/adapter/git-host/helpers.mjs.map +0 -1
  705. package/dist/adapter/git-host.d.mts +0 -42
  706. package/dist/capture/queue.d.mts +0 -38
  707. package/dist/capture/queue.mjs +0 -98
  708. package/dist/capture/queue.mjs.map +0 -1
  709. package/dist/capture/recovery.d.mts +0 -21
  710. package/dist/capture/recovery.mjs +0 -83
  711. package/dist/capture/recovery.mjs.map +0 -1
  712. package/dist/capture/status-fanout.mjs +0 -72
  713. package/dist/capture/status-fanout.mjs.map +0 -1
  714. package/dist/diff/engine.d.mts +0 -16
  715. package/dist/diff/engine.mjs +0 -87
  716. package/dist/diff/engine.mjs.map +0 -1
  717. package/dist/diff/options.d.mts +0 -42
  718. package/dist/diff/options.mjs +0 -15
  719. package/dist/diff/options.mjs.map +0 -1
  720. package/dist/models/status-config.d.mts +0 -38
  721. package/dist/models/status-config.mjs +0 -63
  722. package/dist/models/status-config.mjs.map +0 -1
  723. package/dist/schema/status-config.d.mts +0 -16
  724. package/dist/test-helpers/git-contract.d.mts +0 -11
  725. package/dist/test-helpers/git-contract.mjs +0 -34
  726. package/dist/test-helpers/git-contract.mjs.map +0 -1
  727. /package/dist/adapter/{git-host.mjs → oauth.mjs} +0 -0
@@ -0,0 +1,53 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ import { ulid } from "../utils/ulid.mjs";
5
+ import { col } from "./columns.mjs";
6
+ import { latestVerdicts } from "./build-review-status.mjs";
7
+ import { asc, eq } from "drizzle-orm";
8
+ // src/models/build-review.ts
9
+ /** Build-level review verdicts (ADR 0013, decision 3). */
10
+ /** Data operations for the verdicts reviewers give a build. */
11
+ var BuildReviewModel = class {
12
+ #db;
13
+ #table;
14
+ constructor(db, tables) {
15
+ this.#db = db;
16
+ this.#table = (tables ?? { buildReviews: db.tables.buildReviews }).buildReviews;
17
+ }
18
+ /**
19
+ * Record a verdict. A reviewer rewrites theirs by adding another row; the newest row counts.
20
+ * `createdAt` is strictly increasing within a build, so "newest" never ties.
21
+ */
22
+ async add(buildId, userId, verdict, body) {
23
+ const newest = (await this.listByBuild(buildId)).at(-1)?.createdAt;
24
+ return await this.#db.insert(this.#table, {
25
+ id: ulid(),
26
+ buildId,
27
+ userId,
28
+ verdict,
29
+ body: body === void 0 || body.length === 0 ? null : body,
30
+ createdAt: after(newest)
31
+ });
32
+ }
33
+ /** Every verdict of a build, oldest first. */
34
+ async listByBuild(buildId) {
35
+ return (await this.#db.list(this.#table, {
36
+ where: eq(col(this.#table, "buildId"), buildId),
37
+ orderBy: asc(col(this.#table, "createdAt"))
38
+ })).toSorted((a, b) => a.createdAt.localeCompare(b.createdAt) || a.id.localeCompare(b.id));
39
+ }
40
+ /** Each reviewer's latest verdict on a build. */
41
+ async latestByBuild(buildId) {
42
+ return latestVerdicts(await this.listByBuild(buildId));
43
+ }
44
+ };
45
+ /** Now, or one millisecond past `newest` when the clock has not moved on. */
46
+ function after(newest) {
47
+ const now = Date.now();
48
+ const floor = newest === void 0 ? Number.NEGATIVE_INFINITY : Date.parse(newest) + 1;
49
+ return new Date(Math.max(now, floor)).toISOString();
50
+ }
51
+ export { BuildReviewModel };
52
+
53
+ //# sourceMappingURL=build-review.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build-review.mjs","names":[],"sources":["../../src/models/build-review.ts"],"sourcesContent":["/** Build-level review verdicts (ADR 0013, decision 3). */\nimport { asc, eq } from \"drizzle-orm\";\nimport type { Table } from \"drizzle-orm\";\nimport type { DatabaseAdapter } from \"../adapters/database.ts\";\nimport type { BuildReview } from \"../schema/build-review.ts\";\nimport type { ReviewVerdict } from \"../types.ts\";\nimport { ulid } from \"../utils/ulid.ts\";\nimport { latestVerdicts } from \"./build-review-status.ts\";\nimport { col } from \"./columns.ts\";\n\n/** Tables required by {@link BuildReviewModel}. */\nexport interface BuildReviewTables {\n buildReviews: Table;\n}\n\n/** Data operations for the verdicts reviewers give a build. */\nexport class BuildReviewModel {\n readonly #db: DatabaseAdapter;\n readonly #table: Table;\n\n constructor(db: DatabaseAdapter, tables?: BuildReviewTables) {\n this.#db = db;\n this.#table = (tables ?? { buildReviews: db.tables.buildReviews }).buildReviews;\n }\n\n /**\n * Record a verdict. A reviewer rewrites theirs by adding another row; the newest row counts.\n * `createdAt` is strictly increasing within a build, so \"newest\" never ties.\n */\n async add(\n buildId: string,\n userId: string | null,\n verdict: ReviewVerdict,\n body?: string,\n ): Promise<BuildReview> {\n const newest = (await this.listByBuild(buildId)).at(-1)?.createdAt;\n return (await this.#db.insert(this.#table, {\n id: ulid(),\n buildId,\n userId,\n verdict,\n body: body === undefined || body.length === 0 ? null : body,\n createdAt: after(newest),\n })) as unknown as BuildReview;\n }\n\n /** Every verdict of a build, oldest first. */\n async listByBuild(buildId: string): Promise<BuildReview[]> {\n const rows = (await this.#db.list(this.#table, {\n where: eq(col(this.#table, \"buildId\"), buildId),\n orderBy: asc(col(this.#table, \"createdAt\")),\n })) as unknown as BuildReview[];\n return rows.toSorted(\n (a, b) => a.createdAt.localeCompare(b.createdAt) || a.id.localeCompare(b.id),\n );\n }\n\n /** Each reviewer's latest verdict on a build. */\n async latestByBuild(buildId: string): Promise<BuildReview[]> {\n return latestVerdicts(await this.listByBuild(buildId));\n }\n}\n\n/** Now, or one millisecond past `newest` when the clock has not moved on. */\nfunction after(newest: string | undefined): string {\n const now = Date.now();\n const floor = newest === undefined ? Number.NEGATIVE_INFINITY : Date.parse(newest) + 1;\n return new Date(Math.max(now, floor)).toISOString();\n}\n"],"mappings":";;;;;;;;;;AAgBA,IAAa,mBAAb,MAA8B;CAC5B;CACA;CAEA,YAAY,IAAqB,QAA4B;EAC3D,KAAK,MAAM;EACX,KAAK,UAAU,UAAU,EAAE,cAAc,GAAG,OAAO,aAAa,EAAA,CAAG;CACrE;;;;;CAMA,MAAM,IACJ,SACA,QACA,SACA,MACsB;EACtB,MAAM,UAAU,MAAM,KAAK,YAAY,OAAO,EAAA,CAAG,GAAG,EAAE,CAAC,EAAE;EACzD,OAAQ,MAAM,KAAK,IAAI,OAAO,KAAK,QAAQ;GACzC,IAAI,KAAK;GACT;GACA;GACA;GACA,MAAM,SAAS,KAAA,KAAa,KAAK,WAAW,IAAI,OAAO;GACvD,WAAW,MAAM,MAAM;EACzB,CAAC;CACH;;CAGA,MAAM,YAAY,SAAyC;EAKzD,QAAO,MAJa,KAAK,IAAI,KAAK,KAAK,QAAQ;GAC7C,OAAO,GAAG,IAAI,KAAK,QAAQ,SAAS,GAAG,OAAO;GAC9C,SAAS,IAAI,IAAI,KAAK,QAAQ,WAAW,CAAC;EAC5C,CAAC,EAAA,CACW,UACT,GAAG,MAAM,EAAE,UAAU,cAAc,EAAE,SAAS,KAAK,EAAE,GAAG,cAAc,EAAE,EAAE,CAC7E;CACF;;CAGA,MAAM,cAAc,SAAyC;EAC3D,OAAO,eAAe,MAAM,KAAK,YAAY,OAAO,CAAC;CACvD;AACF;;AAGA,SAAS,MAAM,QAAoC;CACjD,MAAM,MAAM,KAAK,IAAI;CACrB,MAAM,QAAQ,WAAW,KAAA,IAAY,OAAO,oBAAoB,KAAK,MAAM,MAAM,IAAI;CACrF,OAAO,IAAI,KAAK,KAAK,IAAI,KAAK,KAAK,CAAC,CAAC,CAAC,YAAY;AACpD"}
@@ -1,10 +1,10 @@
1
1
  import __tsdown_shims_path from 'node:path';
2
2
  import __tsdown_shims_url from 'node:url';
3
+ import { Logger } from "../logger.mjs";
3
4
  import { DatabaseAdapter } from "../adapter/database.mjs";
4
5
  import { BuildStatus } from "../types.mjs";
5
6
  import { Build } from "../schema/build.mjs";
6
7
  import { Table } from "drizzle-orm";
7
- //#region src/models/build.d.ts
8
8
  /** Tables required by {@link BuildModel}. */
9
9
  interface BuildTables {
10
10
  builds: Table;
@@ -13,9 +13,11 @@ interface BuildTables {
13
13
  }
14
14
  /** Data operations for build records. */
15
15
  declare class BuildModel {
16
- private readonly db;
17
16
  private readonly tables;
18
- constructor(db: DatabaseAdapter, tables?: BuildTables);
17
+ private readonly db;
18
+ private readonly logger;
19
+ /** `logger` only reports a status write refused by a `cancelled` build, at debug. */
20
+ constructor(db: DatabaseAdapter, tables?: BuildTables, logger?: Logger);
19
21
  create(projectId: string, input: BuildCreateInput): Promise<Build>;
20
22
  get(id: string): Promise<Build | null>;
21
23
  list(projectId: string, filter?: BuildListFilter): Promise<Build[]>;
@@ -30,13 +32,30 @@ declare class BuildModel {
30
32
  id: string;
31
33
  gitDefaultBranch: string;
32
34
  }): Promise<Build | null>;
33
- update(id: string, patch: Partial<Pick<Build, "status" | "public" | "message" | "authorEmail" | "authorName">>): Promise<Build>;
35
+ /**
36
+ * Patch a build. A `cancelled` build is terminal (ADR 0006): a patch that carries another status
37
+ * is refused, the row is returned as it is, so a capture that ends after a cancel cannot move
38
+ * the build. Only {@link BuildModel.resetStatus} leaves `cancelled`.
39
+ */
40
+ update(id: string, patch: Partial<Pick<Build, "status" | "public" | "message" | "authorEmail" | "authorName">>, options?: {
41
+ reset?: boolean;
42
+ }): Promise<Build>;
34
43
  setStatus(id: string, status: BuildStatus): Promise<Build>;
44
+ /**
45
+ * Put a finished build back to `status` for a retry. The one write that leaves `cancelled`
46
+ * (a user chose it), so it skips the terminal guard of {@link BuildModel.update}.
47
+ */
48
+ resetStatus(id: string, status: BuildStatus): Promise<Build>;
35
49
  /**
36
50
  * Record the affected-capture computation for a build. Idempotent: the CLI
37
51
  * posts this once after creating the build and before uploading the bundle.
38
52
  */
39
53
  setAffected(id: string, input: AffectedComputation): Promise<Build>;
54
+ /**
55
+ * Store the results of the build's `.test()` entries (`TestResult[]`, as JSON), or clear them
56
+ * with null. Written once per capture run, by the run that ran the tests.
57
+ */
58
+ setTestResults(id: string, results: readonly unknown[] | null): Promise<Build>;
40
59
  /**
41
60
  * Parse a build's affected import paths. Returns `null` for full capture
42
61
  * (no computation, or an unreadable payload) — callers render everything.
@@ -81,7 +100,8 @@ interface BuildListFilter {
81
100
  branch?: string;
82
101
  labelKey?: string;
83
102
  labelValue?: string;
103
+ /** Newest builds to return at most. */
104
+ limit?: number;
84
105
  }
85
- //#endregion
86
106
  export { AffectedComputation, BuildCreateInput, BuildListFilter, BuildModel, BuildTables, isPublicBuild };
87
107
  //# sourceMappingURL=build.d.mts.map
@@ -2,15 +2,19 @@ import "node:path";
2
2
  import "node:url";
3
3
  import.meta.url;
4
4
  import { ulid } from "../utils/ulid.mjs";
5
- import { and, desc, eq, getTableColumns, inArray } from "drizzle-orm";
6
- //#region src/models/build.ts
5
+ import { col } from "./columns.mjs";
6
+ import { and, desc, eq, inArray, ne } from "drizzle-orm";
7
+ // src/models/build.ts
7
8
  /** Build records, status transitions, and publication helpers. */
8
9
  /** Data operations for build records. */
9
10
  var BuildModel = class {
10
- db;
11
11
  tables;
12
- constructor(db, tables) {
12
+ db;
13
+ logger;
14
+ /** `logger` only reports a status write refused by a `cancelled` build, at debug. */
15
+ constructor(db, tables, logger) {
13
16
  this.db = db;
17
+ this.logger = logger;
14
18
  this.tables = tables ?? {
15
19
  builds: db.tables.builds,
16
20
  buildLabels: db.tables.buildLabels,
@@ -42,17 +46,18 @@ var BuildModel = class {
42
46
  return await this.db.get(this.tables.builds, id);
43
47
  }
44
48
  async list(projectId, filter = {}) {
45
- const conditions = [eq(getTableColumns(this.tables.builds)["projectId"], projectId)];
46
- if (filter.status) conditions.push(eq(getTableColumns(this.tables.builds)["status"], filter.status));
47
- if (filter.branch) conditions.push(eq(getTableColumns(this.tables.builds)["gitBranch"], filter.branch));
49
+ const conditions = [eq(col(this.tables.builds, "projectId"), projectId)];
50
+ if (filter.status) conditions.push(eq(col(this.tables.builds, "status"), filter.status));
51
+ if (filter.branch) conditions.push(eq(col(this.tables.builds, "gitBranch"), filter.branch));
48
52
  if (filter.labelKey && filter.labelValue) {
49
- const ids = (await this.db.list(this.tables.buildLabels, { where: and(eq(getTableColumns(this.tables.buildLabels)["projectId"], projectId), eq(getTableColumns(this.tables.buildLabels)["typeKey"], filter.labelKey), eq(getTableColumns(this.tables.buildLabels)["value"], filter.labelValue)) })).map((l) => l.buildId);
53
+ const ids = (await this.db.list(this.tables.buildLabels, { where: and(eq(col(this.tables.buildLabels, "projectId"), projectId), eq(col(this.tables.buildLabels, "typeKey"), filter.labelKey), eq(col(this.tables.buildLabels, "value"), filter.labelValue)) })).map((l) => l.buildId);
50
54
  if (ids.length === 0) return [];
51
- conditions.push(inArray(getTableColumns(this.tables.builds)["id"], ids));
55
+ conditions.push(inArray(col(this.tables.builds, "id"), ids));
52
56
  }
53
57
  return await this.db.list(this.tables.builds, {
54
58
  where: and(...conditions),
55
- orderBy: desc(getTableColumns(this.tables.builds)["createdAt"])
59
+ orderBy: desc(col(this.tables.builds, "createdAt")),
60
+ ...filter.limit === void 0 ? {} : { limit: filter.limit }
56
61
  });
57
62
  }
58
63
  /**
@@ -68,16 +73,38 @@ var BuildModel = class {
68
73
  status: "approved"
69
74
  }))[0] ?? null;
70
75
  }
71
- async update(id, patch) {
72
- return await this.db.update(this.tables.builds, id, {
76
+ /**
77
+ * Patch a build. A `cancelled` build is terminal (ADR 0006): a patch that carries another status
78
+ * is refused, the row is returned as it is, so a capture that ends after a cancel cannot move
79
+ * the build. Only {@link BuildModel.resetStatus} leaves `cancelled`.
80
+ */
81
+ async update(id, patch, options = {}) {
82
+ const values = {
73
83
  ...patch,
74
84
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
75
- });
85
+ };
86
+ if (patch.status !== void 0 && patch.status !== "cancelled" && options.reset !== true) {
87
+ const current = await this.db.updateWhere(this.tables.builds, id, values, ne(col(this.tables.builds, "status"), "cancelled"));
88
+ if (!current) throw new Error("Build not found");
89
+ if (current.status === "cancelled") this.logger?.debug({
90
+ buildId: id,
91
+ refused: patch.status
92
+ }, "build is cancelled; status write refused");
93
+ return current;
94
+ }
95
+ return await this.db.update(this.tables.builds, id, values);
76
96
  }
77
97
  async setStatus(id, status) {
78
98
  return await this.update(id, { status });
79
99
  }
80
100
  /**
101
+ * Put a finished build back to `status` for a retry. The one write that leaves `cancelled`
102
+ * (a user chose it), so it skips the terminal guard of {@link BuildModel.update}.
103
+ */
104
+ async resetStatus(id, status) {
105
+ return await this.update(id, { status }, { reset: true });
106
+ }
107
+ /**
81
108
  * Record the affected-capture computation for a build. Idempotent: the CLI
82
109
  * posts this once after creating the build and before uploading the bundle.
83
110
  */
@@ -90,6 +117,16 @@ var BuildModel = class {
90
117
  });
91
118
  }
92
119
  /**
120
+ * Store the results of the build's `.test()` entries (`TestResult[]`, as JSON), or clear them
121
+ * with null. Written once per capture run, by the run that ran the tests.
122
+ */
123
+ async setTestResults(id, results) {
124
+ return await this.db.update(this.tables.builds, id, {
125
+ testResults: results === null ? null : JSON.stringify(results),
126
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
127
+ });
128
+ }
129
+ /**
93
130
  * Parse a build's affected import paths. Returns `null` for full capture
94
131
  * (no computation, or an unreadable payload) — callers render everything.
95
132
  */
@@ -97,16 +134,18 @@ var BuildModel = class {
97
134
  return parsePaths(build.affectedImportPaths);
98
135
  }
99
136
  async updateCounts(id) {
100
- const rows = await this.db.list(this.tables.snapshots, { where: eq(getTableColumns(this.tables.snapshots)["buildId"], id) });
137
+ const rows = await this.db.list(this.tables.snapshots, { where: eq(col(this.tables.snapshots, "buildId"), id) });
101
138
  const snapshotCount = rows.length;
102
139
  const changedCount = rows.filter((s) => s.status === "changed" || s.status === "new").length;
103
140
  const approvedCount = rows.filter((s) => s.status === "approved" || s.status === "unchanged").length;
104
141
  const rejectedCount = rows.filter((s) => s.status === "rejected").length;
142
+ const inheritedCount = rows.filter((s) => s.inherited).length;
105
143
  return await this.db.update(this.tables.builds, id, {
106
144
  snapshotCount,
107
145
  changedCount,
108
146
  approvedCount,
109
147
  rejectedCount,
148
+ inheritedCount,
110
149
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
111
150
  });
112
151
  }
@@ -141,7 +180,6 @@ function parsePaths(raw) {
141
180
  return null;
142
181
  }
143
182
  }
144
- //#endregion
145
183
  export { BuildModel, isPublicBuild };
146
184
 
147
185
  //# sourceMappingURL=build.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"build.mjs","names":[],"sources":["../../src/models/build.ts"],"sourcesContent":["/** Build records, status transitions, and publication helpers. */\nimport { and, desc, eq, getTableColumns, inArray } from \"drizzle-orm\";\nimport type { SQLWrapper, Table } from \"drizzle-orm\";\nimport type { DatabaseAdapter } from \"../adapters/database.ts\";\nimport type { Build } from \"../schema/build.ts\";\nimport type { BuildStatus } from \"../types.ts\";\nimport { ulid } from \"../utils/ulid.ts\";\n\n/** Tables required by {@link BuildModel}. */\nexport interface BuildTables {\n builds: Table;\n buildLabels: Table;\n snapshots: Table;\n}\n\n/** Data operations for build records. */\nexport class BuildModel {\n private readonly tables: BuildTables;\n constructor(\n private readonly db: DatabaseAdapter,\n tables?: BuildTables,\n ) {\n this.tables = tables ?? {\n builds: db.tables.builds,\n buildLabels: db.tables.buildLabels,\n snapshots: db.tables.snapshots,\n };\n }\n\n async create(projectId: string, input: BuildCreateInput): Promise<Build> {\n const now = new Date().toISOString();\n return (await this.db.insert(this.tables.builds, {\n id: ulid(),\n projectId,\n gitSha: input.gitSha,\n gitBranch: input.gitBranch,\n isDefault: input.isDefault ?? false,\n authorEmail: input.authorEmail,\n authorName: input.authorName,\n message: input.message,\n public: input.public ?? false,\n affectedOnly: input.affectedOnly ?? true,\n baselineSha: input.baselineSha ?? null,\n changedFiles: serializePaths(input.changedFiles),\n affectedImportPaths: serializePaths(input.affectedImportPaths),\n status: \"pending\",\n createdAt: now,\n updatedAt: now,\n })) as unknown as Build;\n }\n\n async get(id: string): Promise<Build | null> {\n return (await this.db.get(this.tables.builds, id)) as unknown as Build | null;\n }\n\n async list(projectId: string, filter: BuildListFilter = {}): Promise<Build[]> {\n const conditions = [\n eq(getTableColumns(this.tables.builds)[\"projectId\"] as unknown as SQLWrapper, projectId),\n ];\n if (filter.status) {\n conditions.push(\n eq(getTableColumns(this.tables.builds)[\"status\"] as unknown as SQLWrapper, filter.status),\n );\n }\n if (filter.branch) {\n conditions.push(\n eq(\n getTableColumns(this.tables.builds)[\"gitBranch\"] as unknown as SQLWrapper,\n filter.branch,\n ),\n );\n }\n if (filter.labelKey && filter.labelValue) {\n const labels = (await this.db.list(this.tables.buildLabels, {\n where: and(\n eq(\n getTableColumns(this.tables.buildLabels)[\"projectId\"] as unknown as SQLWrapper,\n projectId,\n ),\n eq(\n getTableColumns(this.tables.buildLabels)[\"typeKey\"] as unknown as SQLWrapper,\n filter.labelKey,\n ),\n eq(\n getTableColumns(this.tables.buildLabels)[\"value\"] as unknown as SQLWrapper,\n filter.labelValue,\n ),\n ),\n })) as unknown as { buildId: string }[];\n const ids = labels.map((l) => l.buildId);\n if (ids.length === 0) {\n return [];\n }\n conditions.push(\n inArray(getTableColumns(this.tables.builds)[\"id\"] as unknown as SQLWrapper, ids),\n );\n }\n return (await this.db.list(this.tables.builds, {\n where: and(...conditions),\n orderBy: desc(getTableColumns(this.tables.builds)[\"createdAt\"] as unknown as SQLWrapper),\n })) as unknown as Build[];\n }\n\n /**\n * Return the build a project-slug Storybook link resolves to: the most recent\n * `approved` build on the project's default branch. Approved builds on other\n * branches never qualify (their PR can still be dismissed), and neither do\n * pending, failed, or unreviewed default-branch builds. Visibility (public or\n * authenticated) is checked separately by the caller.\n */\n async latestApprovedDefault(project: {\n id: string;\n gitDefaultBranch: string;\n }): Promise<Build | null> {\n const rows = await this.list(project.id, {\n branch: project.gitDefaultBranch,\n status: \"approved\",\n });\n return rows[0] ?? null;\n }\n\n async update(\n id: string,\n patch: Partial<Pick<Build, \"status\" | \"public\" | \"message\" | \"authorEmail\" | \"authorName\">>,\n ): Promise<Build> {\n return (await this.db.update(this.tables.builds, id, {\n ...patch,\n updatedAt: new Date().toISOString(),\n })) as unknown as Build;\n }\n\n async setStatus(id: string, status: BuildStatus): Promise<Build> {\n return await this.update(id, { status });\n }\n\n /**\n * Record the affected-capture computation for a build. Idempotent: the CLI\n * posts this once after creating the build and before uploading the bundle.\n */\n async setAffected(id: string, input: AffectedComputation): Promise<Build> {\n return (await this.db.update(this.tables.builds, id, {\n baselineSha: input.baselineSha,\n changedFiles: serializePaths(input.changedFiles),\n affectedImportPaths: serializePaths(input.affectedImportPaths),\n updatedAt: new Date().toISOString(),\n })) as unknown as Build;\n }\n\n /**\n * Parse a build's affected import paths. Returns `null` for full capture\n * (no computation, or an unreadable payload) — callers render everything.\n */\n static affectedPaths(build: Pick<Build, \"affectedImportPaths\">): string[] | null {\n return parsePaths(build.affectedImportPaths);\n }\n\n async updateCounts(id: string): Promise<Build> {\n const rows = (await this.db.list(this.tables.snapshots, {\n where: eq(getTableColumns(this.tables.snapshots)[\"buildId\"] as unknown as SQLWrapper, id),\n })) as unknown as { status: string }[];\n const snapshotCount = rows.length;\n const changedCount = rows.filter((s) => s.status === \"changed\" || s.status === \"new\").length;\n const approvedCount = rows.filter(\n (s) => s.status === \"approved\" || s.status === \"unchanged\",\n ).length;\n const rejectedCount = rows.filter((s) => s.status === \"rejected\").length;\n return (await this.db.update(this.tables.builds, id, {\n snapshotCount,\n changedCount,\n approvedCount,\n rejectedCount,\n updatedAt: new Date().toISOString(),\n })) as unknown as Build;\n }\n\n async remove(id: string): Promise<void> {\n await this.db.remove(this.tables.builds, id);\n }\n}\n\n/**\n * Return whether a build is publicly viewable without a session.\n *\n * A build is public iff `builds.public` is set or its branch matches the\n * project's `public_branch_regex` (ADR 0011). Supplying an empty/nonexistent\n * regex makes every build require auth.\n */\nexport function isPublicBuild(\n project: { publicBranchRegex: string | null },\n build: Pick<Build, \"public\" | \"gitBranch\">,\n): boolean {\n if (build.public) {\n return true;\n }\n if (!project.publicBranchRegex) {\n return false;\n }\n return new RegExp(project.publicBranchRegex, \"u\").test(build.gitBranch);\n}\n\n/** Input for creating a build. */\nexport interface BuildCreateInput {\n gitSha: string;\n gitBranch: string;\n isDefault?: boolean;\n authorEmail?: string;\n authorName?: string;\n message?: string;\n public?: boolean;\n affectedOnly?: boolean;\n baselineSha?: string | null;\n changedFiles?: string[] | null;\n affectedImportPaths?: string[] | null;\n}\n\n/** Affected-capture computation posted after build creation. */\nexport interface AffectedComputation {\n baselineSha: string | null;\n changedFiles: string[];\n affectedImportPaths: string[] | null;\n}\n\n/** Serialize an optional path list to its JSON text form (or null). */\nfunction serializePaths(paths: string[] | null | undefined): string | null {\n if (!paths) {\n return null;\n }\n return JSON.stringify(paths);\n}\n\n/** Parse a JSON path list; null (full capture) on missing/invalid input. */\nfunction parsePaths(raw: string | null): string[] | null {\n if (!raw) {\n return null;\n }\n try {\n const parsed: unknown = JSON.parse(raw);\n return Array.isArray(parsed) && parsed.every((entry) => typeof entry === \"string\")\n ? (parsed as string[])\n : null;\n } catch {\n return null;\n }\n}\n\n/** Filter options for listing builds. */\nexport interface BuildListFilter {\n status?: BuildStatus;\n branch?: string;\n labelKey?: string;\n labelValue?: string;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"build.mjs","names":[],"sources":["../../src/models/build.ts"],"sourcesContent":["/** Build records, status transitions, and publication helpers. */\nimport { and, desc, eq, inArray, ne } from \"drizzle-orm\";\nimport type { Table } from \"drizzle-orm\";\nimport type { DatabaseAdapter } from \"../adapters/database.ts\";\nimport type { Logger } from \"../logger.ts\";\nimport type { Build } from \"../schema/build.ts\";\nimport type { BuildStatus } from \"../types.ts\";\nimport { ulid } from \"../utils/ulid.ts\";\nimport { col } from \"./columns.ts\";\n\n/** Tables required by {@link BuildModel}. */\nexport interface BuildTables {\n builds: Table;\n buildLabels: Table;\n snapshots: Table;\n}\n\n/** Data operations for build records. */\nexport class BuildModel {\n private readonly tables: BuildTables;\n private readonly db: DatabaseAdapter;\n private readonly logger: Logger | undefined;\n\n /** `logger` only reports a status write refused by a `cancelled` build, at debug. */\n constructor(db: DatabaseAdapter, tables?: BuildTables, logger?: Logger) {\n this.db = db;\n this.logger = logger;\n this.tables = tables ?? {\n builds: db.tables.builds,\n buildLabels: db.tables.buildLabels,\n snapshots: db.tables.snapshots,\n };\n }\n\n async create(projectId: string, input: BuildCreateInput): Promise<Build> {\n const now = new Date().toISOString();\n return (await this.db.insert(this.tables.builds, {\n id: ulid(),\n projectId,\n gitSha: input.gitSha,\n gitBranch: input.gitBranch,\n isDefault: input.isDefault ?? false,\n authorEmail: input.authorEmail,\n authorName: input.authorName,\n message: input.message,\n public: input.public ?? false,\n affectedOnly: input.affectedOnly ?? true,\n baselineSha: input.baselineSha ?? null,\n changedFiles: serializePaths(input.changedFiles),\n affectedImportPaths: serializePaths(input.affectedImportPaths),\n status: \"pending\",\n createdAt: now,\n updatedAt: now,\n })) as unknown as Build;\n }\n\n async get(id: string): Promise<Build | null> {\n return (await this.db.get(this.tables.builds, id)) as unknown as Build | null;\n }\n\n async list(projectId: string, filter: BuildListFilter = {}): Promise<Build[]> {\n const conditions = [eq(col(this.tables.builds, \"projectId\"), projectId)];\n if (filter.status) {\n conditions.push(eq(col(this.tables.builds, \"status\"), filter.status));\n }\n if (filter.branch) {\n conditions.push(eq(col(this.tables.builds, \"gitBranch\"), filter.branch));\n }\n if (filter.labelKey && filter.labelValue) {\n const labels = (await this.db.list(this.tables.buildLabels, {\n where: and(\n eq(col(this.tables.buildLabels, \"projectId\"), projectId),\n eq(col(this.tables.buildLabels, \"typeKey\"), filter.labelKey),\n eq(col(this.tables.buildLabels, \"value\"), filter.labelValue),\n ),\n })) as unknown as { buildId: string }[];\n const ids = labels.map((l) => l.buildId);\n if (ids.length === 0) {\n return [];\n }\n conditions.push(inArray(col(this.tables.builds, \"id\"), ids));\n }\n return (await this.db.list(this.tables.builds, {\n where: and(...conditions),\n orderBy: desc(col(this.tables.builds, \"createdAt\")),\n ...(filter.limit === undefined ? {} : { limit: filter.limit }),\n })) as unknown as Build[];\n }\n\n /**\n * Return the build a project-slug Storybook link resolves to: the most recent\n * `approved` build on the project's default branch. Approved builds on other\n * branches never qualify (their PR can still be dismissed), and neither do\n * pending, failed, or unreviewed default-branch builds. Visibility (public or\n * authenticated) is checked separately by the caller.\n */\n async latestApprovedDefault(project: {\n id: string;\n gitDefaultBranch: string;\n }): Promise<Build | null> {\n const rows = await this.list(project.id, {\n branch: project.gitDefaultBranch,\n status: \"approved\",\n });\n return rows[0] ?? null;\n }\n\n /**\n * Patch a build. A `cancelled` build is terminal (ADR 0006): a patch that carries another status\n * is refused, the row is returned as it is, so a capture that ends after a cancel cannot move\n * the build. Only {@link BuildModel.resetStatus} leaves `cancelled`.\n */\n async update(\n id: string,\n patch: Partial<Pick<Build, \"status\" | \"public\" | \"message\" | \"authorEmail\" | \"authorName\">>,\n options: { reset?: boolean } = {},\n ): Promise<Build> {\n const values = { ...patch, updatedAt: new Date().toISOString() };\n if (patch.status !== undefined && patch.status !== \"cancelled\" && options.reset !== true) {\n const current = (await this.db.updateWhere(\n this.tables.builds,\n id,\n values,\n ne(col(this.tables.builds, \"status\"), \"cancelled\"),\n )) as unknown as Build | null;\n if (!current) throw new Error(\"Build not found\");\n if (current.status === \"cancelled\") {\n this.logger?.debug(\n { buildId: id, refused: patch.status },\n \"build is cancelled; status write refused\",\n );\n }\n return current;\n }\n return (await this.db.update(this.tables.builds, id, values)) as unknown as Build;\n }\n\n async setStatus(id: string, status: BuildStatus): Promise<Build> {\n return await this.update(id, { status });\n }\n\n /**\n * Put a finished build back to `status` for a retry. The one write that leaves `cancelled`\n * (a user chose it), so it skips the terminal guard of {@link BuildModel.update}.\n */\n async resetStatus(id: string, status: BuildStatus): Promise<Build> {\n return await this.update(id, { status }, { reset: true });\n }\n\n /**\n * Record the affected-capture computation for a build. Idempotent: the CLI\n * posts this once after creating the build and before uploading the bundle.\n */\n async setAffected(id: string, input: AffectedComputation): Promise<Build> {\n return (await this.db.update(this.tables.builds, id, {\n baselineSha: input.baselineSha,\n changedFiles: serializePaths(input.changedFiles),\n affectedImportPaths: serializePaths(input.affectedImportPaths),\n updatedAt: new Date().toISOString(),\n })) as unknown as Build;\n }\n\n /**\n * Store the results of the build's `.test()` entries (`TestResult[]`, as JSON), or clear them\n * with null. Written once per capture run, by the run that ran the tests.\n */\n async setTestResults(id: string, results: readonly unknown[] | null): Promise<Build> {\n return (await this.db.update(this.tables.builds, id, {\n testResults: results === null ? null : JSON.stringify(results),\n updatedAt: new Date().toISOString(),\n })) as unknown as Build;\n }\n\n /**\n * Parse a build's affected import paths. Returns `null` for full capture\n * (no computation, or an unreadable payload) — callers render everything.\n */\n static affectedPaths(build: Pick<Build, \"affectedImportPaths\">): string[] | null {\n return parsePaths(build.affectedImportPaths);\n }\n\n async updateCounts(id: string): Promise<Build> {\n const rows = (await this.db.list(this.tables.snapshots, {\n where: eq(col(this.tables.snapshots, \"buildId\"), id),\n })) as unknown as { status: string; inherited: boolean }[];\n const snapshotCount = rows.length;\n const changedCount = rows.filter((s) => s.status === \"changed\" || s.status === \"new\").length;\n const approvedCount = rows.filter(\n (s) => s.status === \"approved\" || s.status === \"unchanged\",\n ).length;\n const rejectedCount = rows.filter((s) => s.status === \"rejected\").length;\n const inheritedCount = rows.filter((s) => s.inherited).length;\n return (await this.db.update(this.tables.builds, id, {\n snapshotCount,\n changedCount,\n approvedCount,\n rejectedCount,\n inheritedCount,\n updatedAt: new Date().toISOString(),\n })) as unknown as Build;\n }\n\n async remove(id: string): Promise<void> {\n await this.db.remove(this.tables.builds, id);\n }\n}\n\n/**\n * Return whether a build is publicly viewable without a session.\n *\n * A build is public iff `builds.public` is set or its branch matches the\n * project's `public_branch_regex` (ADR 0011). Supplying an empty/nonexistent\n * regex makes every build require auth.\n */\nexport function isPublicBuild(\n project: { publicBranchRegex: string | null },\n build: Pick<Build, \"public\" | \"gitBranch\">,\n): boolean {\n if (build.public) {\n return true;\n }\n if (!project.publicBranchRegex) {\n return false;\n }\n return new RegExp(project.publicBranchRegex, \"u\").test(build.gitBranch);\n}\n\n/** Input for creating a build. */\nexport interface BuildCreateInput {\n gitSha: string;\n gitBranch: string;\n isDefault?: boolean;\n authorEmail?: string;\n authorName?: string;\n message?: string;\n public?: boolean;\n affectedOnly?: boolean;\n baselineSha?: string | null;\n changedFiles?: string[] | null;\n affectedImportPaths?: string[] | null;\n}\n\n/** Affected-capture computation posted after build creation. */\nexport interface AffectedComputation {\n baselineSha: string | null;\n changedFiles: string[];\n affectedImportPaths: string[] | null;\n}\n\n/** Serialize an optional path list to its JSON text form (or null). */\nfunction serializePaths(paths: string[] | null | undefined): string | null {\n if (!paths) {\n return null;\n }\n return JSON.stringify(paths);\n}\n\n/** Parse a JSON path list; null (full capture) on missing/invalid input. */\nfunction parsePaths(raw: string | null): string[] | null {\n if (!raw) {\n return null;\n }\n try {\n const parsed: unknown = JSON.parse(raw);\n return Array.isArray(parsed) && parsed.every((entry) => typeof entry === \"string\")\n ? (parsed as string[])\n : null;\n } catch {\n return null;\n }\n}\n\n/** Filter options for listing builds. */\nexport interface BuildListFilter {\n status?: BuildStatus;\n branch?: string;\n labelKey?: string;\n labelValue?: string;\n /** Newest builds to return at most. */\n limit?: number;\n}\n"],"mappings":"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"}
@@ -5,15 +5,15 @@ import { JobStatus } from "../adapter/capture-queue.mjs";
5
5
  import { CaptureAttempt, CaptureLogLevel } from "../schema/capture-attempt.mjs";
6
6
  import { Logger } from "pino";
7
7
  import { Table } from "drizzle-orm";
8
- //#region src/models/capture-attempt.d.ts
9
8
  /** Tables required by {@link CaptureAttemptModel}. */
10
9
  interface CaptureAttemptTables {
11
10
  captureAttempts: Table;
12
11
  }
13
12
  /** Data operations for capture attempts (one row per build run). */
14
13
  declare class CaptureAttemptModel {
15
- private readonly db;
14
+ #private;
16
15
  private readonly tables;
16
+ private readonly db;
17
17
  constructor(db: DatabaseAdapter, tables?: CaptureAttemptTables);
18
18
  listByBuild(buildId: string): Promise<CaptureAttempt[]>;
19
19
  getByNo(buildId: string, attemptNo: number): Promise<CaptureAttempt | null>;
@@ -23,6 +23,11 @@ declare class CaptureAttemptModel {
23
23
  storyCount: number;
24
24
  failedCount: number;
25
25
  }): Promise<CaptureAttempt>;
26
+ /**
27
+ * Close every attempt of a build still `queued` or `running` other than `keepId`, as `cancelled`
28
+ * with `reason`: a new plan of the build supersedes them, and nothing else would ever close them.
29
+ */
30
+ closeOthers(buildId: string, keepId: string | null, reason: string): Promise<number>;
26
31
  }
27
32
  /**
28
33
  * Recorder writing one attempt log line to the `capture_logs` table.
@@ -35,6 +40,5 @@ type AttemptLogRecorder = (level: CaptureLogLevel, message: string, fields?: unk
35
40
  * Log-write failures are reported through pino and never thrown.
36
41
  */
37
42
  declare function emitAttemptLog(record: AttemptLogRecorder | undefined, logger: Logger | undefined, level: CaptureLogLevel, message: string, fields?: unknown): Promise<void>;
38
- //#endregion
39
43
  export { AttemptLogRecorder, CaptureAttemptModel, CaptureAttemptTables, emitAttemptLog };
40
44
  //# sourceMappingURL=capture-attempt.d.mts.map
@@ -1,14 +1,16 @@
1
1
  import "node:path";
2
2
  import "node:url";
3
3
  import.meta.url;
4
+ import { redactSecrets } from "../utils/redact.mjs";
4
5
  import { ulid } from "../utils/ulid.mjs";
5
- import { and, asc, eq, getTableColumns } from "drizzle-orm";
6
- //#region src/models/capture-attempt.ts
6
+ import { col } from "./columns.mjs";
7
+ import { and, asc, eq } from "drizzle-orm";
8
+ // src/models/capture-attempt.ts
7
9
  /** Capture attempts and their log lines: per-run history for a build. */
8
10
  /** Data operations for capture attempts (one row per build run). */
9
11
  var CaptureAttemptModel = class {
10
- db;
11
12
  tables;
13
+ db;
12
14
  constructor(db, tables) {
13
15
  this.db = db;
14
16
  this.tables = tables ?? { captureAttempts: db.tables.captureAttempts };
@@ -45,6 +47,8 @@ var CaptureAttemptModel = class {
45
47
  });
46
48
  }
47
49
  async markRunning(id) {
50
+ const settled = await this.#cancelled(id);
51
+ if (settled) return settled;
48
52
  const now = (/* @__PURE__ */ new Date()).toISOString();
49
53
  return await this.db.update(this.tables.captureAttempts, id, {
50
54
  status: "running",
@@ -53,6 +57,8 @@ var CaptureAttemptModel = class {
53
57
  });
54
58
  }
55
59
  async markFinished(id, status, error, counts) {
60
+ const settled = status === "cancelled" ? null : await this.#cancelled(id);
61
+ if (settled) return settled;
56
62
  const now = (/* @__PURE__ */ new Date()).toISOString();
57
63
  return await this.db.update(this.tables.captureAttempts, id, {
58
64
  status,
@@ -63,9 +69,28 @@ var CaptureAttemptModel = class {
63
69
  updatedAt: now
64
70
  });
65
71
  }
72
+ /**
73
+ * The attempt row when a cancel already closed it (ADR 0006): a run that ends after a user
74
+ * cancelled the build must not rewrite the row as `completed` or `failed`. Null otherwise.
75
+ */
76
+ async #cancelled(id) {
77
+ const row = await this.db.get(this.tables.captureAttempts, id);
78
+ return row?.status === "cancelled" ? row : null;
79
+ }
80
+ /**
81
+ * Close every attempt of a build still `queued` or `running` other than `keepId`, as `cancelled`
82
+ * with `reason`: a new plan of the build supersedes them, and nothing else would ever close them.
83
+ */
84
+ async closeOthers(buildId, keepId, reason) {
85
+ const open = (await this.listByBuild(buildId)).filter((row) => row.id !== keepId && (row.status === "queued" || row.status === "running"));
86
+ await Promise.all(open.map(async (row) => {
87
+ await this.markFinished(row.id, "cancelled", reason);
88
+ }));
89
+ return open.length;
90
+ }
66
91
  };
67
92
  function columnsOf(table, column) {
68
- return getTableColumns(table)[column];
93
+ return col(table, column);
69
94
  }
70
95
  /**
71
96
  * Record one attempt log line without breaking the capture on write failure.
@@ -74,12 +99,11 @@ function columnsOf(table, column) {
74
99
  async function emitAttemptLog(record, logger, level, message, fields) {
75
100
  if (!record) return;
76
101
  try {
77
- await record(level, message, fields);
102
+ await record(level, redactSecrets(message), fields);
78
103
  } catch (error) {
79
104
  logger?.warn({ err: error }, "attempt log write failed");
80
105
  }
81
106
  }
82
- //#endregion
83
107
  export { CaptureAttemptModel, emitAttemptLog };
84
108
 
85
109
  //# sourceMappingURL=capture-attempt.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"capture-attempt.mjs","names":[],"sources":["../../src/models/capture-attempt.ts"],"sourcesContent":["/** Capture attempts and their log lines: per-run history for a build. */\nimport { and, asc, eq, getTableColumns } from \"drizzle-orm\";\nimport type { SQLWrapper, Table } from \"drizzle-orm\";\nimport type { Logger } from \"pino\";\nimport type { JobStatus } from \"../adapters/capture-queue.ts\";\nimport type { DatabaseAdapter } from \"../adapters/database.ts\";\nimport type { CaptureAttempt, CaptureLogLevel } from \"../schema/capture-attempt.ts\";\nimport { ulid } from \"../utils/ulid.ts\";\n\n/** Tables required by {@link CaptureAttemptModel}. */\nexport interface CaptureAttemptTables {\n captureAttempts: Table;\n}\n\n/** Data operations for capture attempts (one row per build run). */\nexport class CaptureAttemptModel {\n private readonly tables: CaptureAttemptTables;\n constructor(\n private readonly db: DatabaseAdapter,\n tables?: CaptureAttemptTables,\n ) {\n this.tables = tables ?? { captureAttempts: db.tables.captureAttempts };\n }\n\n async listByBuild(buildId: string): Promise<CaptureAttempt[]> {\n return (await this.db.list(this.tables.captureAttempts, {\n where: eq(columnsOf(this.tables.captureAttempts, \"buildId\"), buildId),\n orderBy: asc(columnsOf(this.tables.captureAttempts, \"attemptNo\")),\n })) as unknown as CaptureAttempt[];\n }\n\n async getByNo(buildId: string, attemptNo: number): Promise<CaptureAttempt | null> {\n const rows = (await this.db.list(this.tables.captureAttempts, {\n where: and(\n eq(columnsOf(this.tables.captureAttempts, \"buildId\"), buildId),\n eq(columnsOf(this.tables.captureAttempts, \"attemptNo\"), attemptNo),\n ),\n })) as unknown as CaptureAttempt[];\n return rows[0] ?? null;\n }\n\n async startAttempt(projectId: string, buildId: string, reqId?: string): Promise<CaptureAttempt> {\n const existing = await this.listByBuild(buildId);\n let attemptNo = 1;\n for (const row of existing) {\n if (row.attemptNo >= attemptNo) {\n attemptNo = row.attemptNo + 1;\n }\n }\n const now = new Date().toISOString();\n return (await this.db.insert(this.tables.captureAttempts, {\n id: ulid(),\n projectId,\n buildId,\n attemptNo,\n status: \"queued\",\n error: null,\n reqId: reqId ?? null,\n storyCount: 0,\n failedCount: 0,\n queuedAt: now,\n startedAt: null,\n finishedAt: null,\n createdAt: now,\n updatedAt: now,\n })) as unknown as CaptureAttempt;\n }\n\n async markRunning(id: string): Promise<CaptureAttempt> {\n const now = new Date().toISOString();\n return (await this.db.update(this.tables.captureAttempts, id, {\n status: \"running\",\n startedAt: now,\n updatedAt: now,\n })) as unknown as CaptureAttempt;\n }\n\n async markFinished(\n id: string,\n status: Extract<JobStatus, \"completed\" | \"failed\" | \"cancelled\">,\n error?: string,\n counts?: { storyCount: number; failedCount: number },\n ): Promise<CaptureAttempt> {\n const now = new Date().toISOString();\n return (await this.db.update(this.tables.captureAttempts, id, {\n status,\n error: error ?? null,\n storyCount: counts?.storyCount ?? 0,\n failedCount: counts?.failedCount ?? 0,\n finishedAt: now,\n updatedAt: now,\n })) as unknown as CaptureAttempt;\n }\n}\n\nfunction columnsOf(table: Table, column: string): SQLWrapper {\n return getTableColumns(table)[column] as unknown as SQLWrapper;\n}\n\n/**\n * Recorder writing one attempt log line to the `capture_logs` table.\n * Dispatch builds it per attempt; the orchestrator and pipeline call it\n * alongside every pino log so the UI can replay the run later.\n */\nexport type AttemptLogRecorder = (\n level: CaptureLogLevel,\n message: string,\n fields?: unknown,\n) => Promise<void>;\n\n/**\n * Record one attempt log line without breaking the capture on write failure.\n * Log-write failures are reported through pino and never thrown.\n */\nexport async function emitAttemptLog(\n record: AttemptLogRecorder | undefined,\n logger: Logger | undefined,\n level: CaptureLogLevel,\n message: string,\n fields?: unknown,\n): Promise<void> {\n if (!record) {\n return;\n }\n try {\n await record(level, message, fields);\n } catch (error) {\n logger?.warn({ err: error }, \"attempt log write failed\");\n }\n}\n"],"mappings":";;;;;;;;AAeA,IAAa,sBAAb,MAAiC;CAGZ;CAFnB;CACA,YACE,IACA,QACA;EAFiB,KAAA,KAAA;EAGjB,KAAK,SAAS,UAAU,EAAE,iBAAiB,GAAG,OAAO,gBAAgB;CACvE;CAEA,MAAM,YAAY,SAA4C;EAC5D,OAAQ,MAAM,KAAK,GAAG,KAAK,KAAK,OAAO,iBAAiB;GACtD,OAAO,GAAG,UAAU,KAAK,OAAO,iBAAiB,SAAS,GAAG,OAAO;GACpE,SAAS,IAAI,UAAU,KAAK,OAAO,iBAAiB,WAAW,CAAC;EAClE,CAAC;CACH;CAEA,MAAM,QAAQ,SAAiB,WAAmD;EAOhF,QAAO,MANa,KAAK,GAAG,KAAK,KAAK,OAAO,iBAAiB,EAC5D,OAAO,IACL,GAAG,UAAU,KAAK,OAAO,iBAAiB,SAAS,GAAG,OAAO,GAC7D,GAAG,UAAU,KAAK,OAAO,iBAAiB,WAAW,GAAG,SAAS,CACnE,EACF,CAAC,EAAA,CACW,MAAM;CACpB;CAEA,MAAM,aAAa,WAAmB,SAAiB,OAAyC;EAC9F,MAAM,WAAW,MAAM,KAAK,YAAY,OAAO;EAC/C,IAAI,YAAY;EAChB,KAAK,MAAM,OAAO,UAChB,IAAI,IAAI,aAAa,WACnB,YAAY,IAAI,YAAY;EAGhC,MAAM,uBAAM,IAAI,KAAK,EAAA,CAAE,YAAY;EACnC,OAAQ,MAAM,KAAK,GAAG,OAAO,KAAK,OAAO,iBAAiB;GACxD,IAAI,KAAK;GACT;GACA;GACA;GACA,QAAQ;GACR,OAAO;GACP,OAAO,SAAS;GAChB,YAAY;GACZ,aAAa;GACb,UAAU;GACV,WAAW;GACX,YAAY;GACZ,WAAW;GACX,WAAW;EACb,CAAC;CACH;CAEA,MAAM,YAAY,IAAqC;EACrD,MAAM,uBAAM,IAAI,KAAK,EAAA,CAAE,YAAY;EACnC,OAAQ,MAAM,KAAK,GAAG,OAAO,KAAK,OAAO,iBAAiB,IAAI;GAC5D,QAAQ;GACR,WAAW;GACX,WAAW;EACb,CAAC;CACH;CAEA,MAAM,aACJ,IACA,QACA,OACA,QACyB;EACzB,MAAM,uBAAM,IAAI,KAAK,EAAA,CAAE,YAAY;EACnC,OAAQ,MAAM,KAAK,GAAG,OAAO,KAAK,OAAO,iBAAiB,IAAI;GAC5D;GACA,OAAO,SAAS;GAChB,YAAY,QAAQ,cAAc;GAClC,aAAa,QAAQ,eAAe;GACpC,YAAY;GACZ,WAAW;EACb,CAAC;CACH;AACF;AAEA,SAAS,UAAU,OAAc,QAA4B;CAC3D,OAAO,gBAAgB,KAAK,CAAC,CAAC;AAChC;;;;;AAiBA,eAAsB,eACpB,QACA,QACA,OACA,SACA,QACe;CACf,IAAI,CAAC,QACH;CAEF,IAAI;EACF,MAAM,OAAO,OAAO,SAAS,MAAM;CACrC,SAAS,OAAO;EACd,QAAQ,KAAK,EAAE,KAAK,MAAM,GAAG,0BAA0B;CACzD;AACF"}
1
+ {"version":3,"file":"capture-attempt.mjs","names":[],"sources":["../../src/models/capture-attempt.ts"],"sourcesContent":["/** Capture attempts and their log lines: per-run history for a build. */\nimport { and, asc, eq } from \"drizzle-orm\";\nimport type { SQLWrapper, Table } from \"drizzle-orm\";\nimport type { Logger } from \"pino\";\nimport type { JobStatus } from \"../adapters/capture-queue.ts\";\nimport type { DatabaseAdapter } from \"../adapters/database.ts\";\nimport type { CaptureAttempt, CaptureLogLevel } from \"../schema/capture-attempt.ts\";\nimport { redactSecrets } from \"../utils/redact.ts\";\nimport { ulid } from \"../utils/ulid.ts\";\nimport { col } from \"./columns.ts\";\n\n/** Tables required by {@link CaptureAttemptModel}. */\nexport interface CaptureAttemptTables {\n captureAttempts: Table;\n}\n\n/** Data operations for capture attempts (one row per build run). */\nexport class CaptureAttemptModel {\n private readonly tables: CaptureAttemptTables;\n private readonly db: DatabaseAdapter;\n\n constructor(db: DatabaseAdapter, tables?: CaptureAttemptTables) {\n this.db = db;\n this.tables = tables ?? { captureAttempts: db.tables.captureAttempts };\n }\n\n async listByBuild(buildId: string): Promise<CaptureAttempt[]> {\n return (await this.db.list(this.tables.captureAttempts, {\n where: eq(columnsOf(this.tables.captureAttempts, \"buildId\"), buildId),\n orderBy: asc(columnsOf(this.tables.captureAttempts, \"attemptNo\")),\n })) as unknown as CaptureAttempt[];\n }\n\n async getByNo(buildId: string, attemptNo: number): Promise<CaptureAttempt | null> {\n const rows = (await this.db.list(this.tables.captureAttempts, {\n where: and(\n eq(columnsOf(this.tables.captureAttempts, \"buildId\"), buildId),\n eq(columnsOf(this.tables.captureAttempts, \"attemptNo\"), attemptNo),\n ),\n })) as unknown as CaptureAttempt[];\n return rows[0] ?? null;\n }\n\n async startAttempt(projectId: string, buildId: string, reqId?: string): Promise<CaptureAttempt> {\n const existing = await this.listByBuild(buildId);\n let attemptNo = 1;\n for (const row of existing) {\n if (row.attemptNo >= attemptNo) {\n attemptNo = row.attemptNo + 1;\n }\n }\n const now = new Date().toISOString();\n return (await this.db.insert(this.tables.captureAttempts, {\n id: ulid(),\n projectId,\n buildId,\n attemptNo,\n status: \"queued\",\n error: null,\n reqId: reqId ?? null,\n storyCount: 0,\n failedCount: 0,\n queuedAt: now,\n startedAt: null,\n finishedAt: null,\n createdAt: now,\n updatedAt: now,\n })) as unknown as CaptureAttempt;\n }\n\n async markRunning(id: string): Promise<CaptureAttempt> {\n const settled = await this.#cancelled(id);\n if (settled) {\n return settled;\n }\n const now = new Date().toISOString();\n return (await this.db.update(this.tables.captureAttempts, id, {\n status: \"running\",\n startedAt: now,\n updatedAt: now,\n })) as unknown as CaptureAttempt;\n }\n\n async markFinished(\n id: string,\n status: Extract<JobStatus, \"completed\" | \"failed\" | \"cancelled\">,\n error?: string,\n counts?: { storyCount: number; failedCount: number },\n ): Promise<CaptureAttempt> {\n const settled = status === \"cancelled\" ? null : await this.#cancelled(id);\n if (settled) {\n return settled;\n }\n const now = new Date().toISOString();\n return (await this.db.update(this.tables.captureAttempts, id, {\n status,\n error: error ?? null,\n storyCount: counts?.storyCount ?? 0,\n failedCount: counts?.failedCount ?? 0,\n finishedAt: now,\n updatedAt: now,\n })) as unknown as CaptureAttempt;\n }\n\n /**\n * The attempt row when a cancel already closed it (ADR 0006): a run that ends after a user\n * cancelled the build must not rewrite the row as `completed` or `failed`. Null otherwise.\n */\n async #cancelled(id: string): Promise<CaptureAttempt | null> {\n const row = (await this.db.get(\n this.tables.captureAttempts,\n id,\n )) as unknown as CaptureAttempt | null;\n return row?.status === \"cancelled\" ? row : null;\n }\n\n /**\n * Close every attempt of a build still `queued` or `running` other than `keepId`, as `cancelled`\n * with `reason`: a new plan of the build supersedes them, and nothing else would ever close them.\n */\n async closeOthers(buildId: string, keepId: string | null, reason: string): Promise<number> {\n const open = (await this.listByBuild(buildId)).filter(\n (row) => row.id !== keepId && (row.status === \"queued\" || row.status === \"running\"),\n );\n await Promise.all(\n open.map(async (row) => {\n await this.markFinished(row.id, \"cancelled\", reason);\n }),\n );\n return open.length;\n }\n}\n\nfunction columnsOf(table: Table, column: string): SQLWrapper {\n return col(table, column);\n}\n\n/**\n * Recorder writing one attempt log line to the `capture_logs` table.\n * Dispatch builds it per attempt; the orchestrator and pipeline call it\n * alongside every pino log so the UI can replay the run later.\n */\nexport type AttemptLogRecorder = (\n level: CaptureLogLevel,\n message: string,\n fields?: unknown,\n) => Promise<void>;\n\n/**\n * Record one attempt log line without breaking the capture on write failure.\n * Log-write failures are reported through pino and never thrown.\n */\nexport async function emitAttemptLog(\n record: AttemptLogRecorder | undefined,\n logger: Logger | undefined,\n level: CaptureLogLevel,\n message: string,\n fields?: unknown,\n): Promise<void> {\n if (!record) {\n return;\n }\n try {\n await record(level, redactSecrets(message), fields);\n } catch (error) {\n logger?.warn({ err: error }, \"attempt log write failed\");\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,104 @@
1
+ import __tsdown_shims_path from 'node:path';
2
+ import __tsdown_shims_url from 'node:url';
3
+ import { DatabaseAdapter } from "../adapter/database.mjs";
4
+ import { CaptureAttemptStatus, CaptureJobKind, CaptureJobRow, CaptureJobStatus } from "../schema/capture-job.mjs";
5
+ import { Table } from "drizzle-orm";
6
+ /** Tables required by {@link CaptureJobModel}. */
7
+ interface CaptureJobTables {
8
+ captureJobs: Table;
9
+ }
10
+ /** A job to put on the queue. */
11
+ interface NewCaptureJob {
12
+ id: string;
13
+ kind: CaptureJobKind;
14
+ buildId?: string | null;
15
+ shardId?: string | null;
16
+ maxAttempts: number;
17
+ /** JSON payload (request id, traceparent, kind-specific fields). */
18
+ payload?: string | null;
19
+ /** When the job may first be claimed; defaults to `now`. */
20
+ visibleAt?: string;
21
+ }
22
+ /** The claim an instance makes on a job: attempt `attempt`, held by `owner` until `leaseUntil`. */
23
+ interface AttemptClaim {
24
+ job: CaptureJobRow;
25
+ attempt: number;
26
+ owner: string;
27
+ leaseUntil: string;
28
+ }
29
+ /** Job statuses that end a job: nothing claims it again. */
30
+ declare const TERMINAL_JOB_STATUSES: readonly CaptureJobStatus[];
31
+ /**
32
+ * Data operations of the database queue. Every write is an unconditional update by id or an
33
+ * insert; the only atomic primitive is the unique-key insert of an attempt row ({@link claim}),
34
+ * which is what fences concurrent claimers on every supported database.
35
+ */
36
+ declare class CaptureJobModel {
37
+ #private;
38
+ constructor(db: DatabaseAdapter, tables?: CaptureJobTables);
39
+ get(id: string): Promise<CaptureJobRow | null>;
40
+ /** Insert a queued job row. */
41
+ create(input: NewCaptureJob, now: string): Promise<CaptureJobRow>;
42
+ /**
43
+ * The poll probe: the oldest row of `kinds` that may be claimed at `now`. One indexed range query
44
+ * on `(kind, visible_at, status)`, limit 1; only candidates have a `visibleAt`, so an empty queue
45
+ * reads no rows and a busy one reads one.
46
+ */
47
+ nextCandidate(kinds: readonly CaptureJobKind[], now: string): Promise<CaptureJobRow | null>;
48
+ /**
49
+ * Claim attempt `attempt` of a job by inserting its row `<jobId>:<attempt>`. Exactly one caller
50
+ * succeeds on every database; the others hit the primary key, which this reports as `false`
51
+ * (told apart from real failures by re-reading the row, as `CaptureShardModel.claimFinalize`).
52
+ */
53
+ claim(input: AttemptClaim, now: string): Promise<boolean>;
54
+ /** The claim won: the job row records the running attempt and leaves the poll's index range. */
55
+ markRunning(jobId: string, attempt: number, now: string): Promise<void>;
56
+ /** Push an attempt's lease out. Returns the row (null if gone) so the caller sees cancel or expiry. */
57
+ renew(attemptId: string, leaseUntil: string, now: string): Promise<CaptureJobRow | null>;
58
+ /** The attempt row `attemptId` if it is running with a lease that passed by `now`, else null. */
59
+ expiredAttempt(attemptId: string, now: string): Promise<CaptureJobRow | null>;
60
+ /** Move a row's visibility time (and nothing else). */
61
+ defer(id: string, visibleAt: string, now: string): Promise<void>;
62
+ /** Take a row out of the poll's index range without changing its status. */
63
+ clearVisibility(id: string, now: string): Promise<void>;
64
+ /** End an attempt row: `done`, `failed` (with its error) or `expired` (taken over). */
65
+ finishAttempt(attemptId: string, status: Exclude<CaptureAttemptStatus, "running">, now: string, error?: string | null): Promise<void>;
66
+ /** End a job row for good. */
67
+ finishJob(jobId: string, status: Exclude<CaptureJobStatus, "queued" | "running">, now: string, error?: string | null): Promise<void>;
68
+ /** Put a job back on the queue, claimable from `visibleAt`, remembering why the attempt failed. */
69
+ requeue(jobId: string, visibleAt: string, error: string | null, now: string): Promise<void>;
70
+ /** Flag a row as cancel-requested; the flag is never cleared. */
71
+ flagCancel(id: string, now: string): Promise<void>;
72
+ /** Job rows of a build (its own job and its shard jobs), oldest first. */
73
+ listForBuild(buildId: string): Promise<CaptureJobRow[]>;
74
+ /** The build's own (non-shard) job of `kinds` most recently queued, or null. */
75
+ latestForBuild(buildId: string, kinds: readonly CaptureJobKind[]): Promise<CaptureJobRow | null>;
76
+ /** Attempt rows of a job, by attempt number. */
77
+ attempts(jobId: string): Promise<CaptureJobRow[]>;
78
+ /** Job rows of `kinds` that are queued or running, oldest first. */
79
+ listActive(kinds: readonly CaptureJobKind[]): Promise<CaptureJobRow[]>;
80
+ /** The newest job rows of `kinds`, newest first. */
81
+ listRecent(kinds: readonly CaptureJobKind[], limit: number): Promise<CaptureJobRow[]>;
82
+ /**
83
+ * Retention: delete finished jobs created before `before`, with their attempt rows. Returns the
84
+ * number of jobs removed. Running and queued jobs are never touched.
85
+ */
86
+ purgeFinished(before: string): Promise<number>;
87
+ /**
88
+ * A build was removed: delete every `capture_jobs` row that points at it (job and attempt rows,
89
+ * whatever their status). There is no foreign key, so retention does this by hand. Returns the
90
+ * number of rows removed.
91
+ */
92
+ removeForBuild(buildId: string): Promise<number>;
93
+ }
94
+ /** Primary key of attempt `attempt` of a job. */
95
+ declare function attemptRowId(jobId: string, attempt: number): string;
96
+ /** Split an attempt row id back into its job id and number; null when it is not one. */
97
+ declare function parseAttemptRowId(id: string): {
98
+ jobId: string;
99
+ attempt: number;
100
+ } | null;
101
+ /** Whether a job row has ended. */
102
+ declare function isTerminalJob(row: CaptureJobRow): boolean;
103
+ export { AttemptClaim, CaptureJobModel, CaptureJobTables, NewCaptureJob, TERMINAL_JOB_STATUSES, attemptRowId, isTerminalJob, parseAttemptRowId };
104
+ //# sourceMappingURL=capture-job.d.mts.map