@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
@@ -5,29 +5,59 @@ import { StorageAdapter } from "../adapter/storage.mjs";
5
5
  import { SecretInput } from "../utils/encrypt.mjs";
6
6
  import { WebhookTables } from "./webhook.mjs";
7
7
  import { Baseline } from "../schema/baseline.mjs";
8
+ import { AtomicBaselineApproval } from "./baseline-approval.mjs";
8
9
  import { Table } from "drizzle-orm";
9
- //#region src/models/baseline.d.ts
10
+ /** Extra identity/metadata for {@link BaselineModel.upsert}. */
11
+ interface BaselineUpsertOptions {
12
+ approval?: AtomicBaselineApproval;
13
+ /** Engine the baseline belongs to (default chromium). */
14
+ browser?: string;
15
+ /** Resolved mode definition as JSON (display only). */
16
+ mode?: string | null;
17
+ /** JSON `Fingerprint` behind the `infraHash` being written. */
18
+ fingerprint?: string | null;
19
+ }
10
20
  /** Tables required by {@link BaselineModel}. */
11
21
  interface BaselineTables {
12
22
  baselines: Table;
13
23
  }
14
24
  /** Data operations for baseline screenshots. */
15
25
  declare class BaselineModel {
16
- private readonly db;
26
+ #private;
17
27
  private readonly tables;
18
28
  private readonly storage;
19
29
  private readonly secret?;
20
30
  private readonly webhookTables?;
31
+ private readonly db;
21
32
  constructor(db: DatabaseAdapter, tables?: BaselineTables, storage?: StorageAdapter, secret?: SecretInput, webhookTables?: WebhookTables);
22
- getFor(projectId: string, storyId: string, viewport: string, branch: string): Promise<Baseline | null>;
23
- resolve(projectId: string, storyId: string, viewport: string, branch: string, defaultBranch: string): Promise<Baseline | null>;
33
+ getFor(projectId: string, storyId: string, viewport: string, branch: string, browser?: string): Promise<Baseline | null>;
34
+ resolve(projectId: string, storyId: string, viewport: string, branch: string, defaultBranch: string, browser?: string): Promise<Baseline | null>;
35
+ /** Whether the project has any baseline captured on this engine. */
36
+ hasAny(projectId: string, browser: string): Promise<boolean>;
37
+ /**
38
+ * Record the fingerprint of a baseline that predates fingerprints, without touching its
39
+ * screenshot. Lets the next environment change be detected instead of diffing as a regression.
40
+ */
41
+ backfillFingerprint(baseline: Baseline, infraHash: string, fingerprint: string): Promise<void>;
42
+ /**
43
+ * The content-addressed object a baseline should point at. A source that already is one is
44
+ * reused as is (no copy); a legacy per-build path is stored once by content.
45
+ */
46
+ private contentFor;
24
47
  read(baseline: Baseline): Promise<Buffer>;
25
- upsert(projectId: string, storyId: string, viewport: string, branch: string, snapshotId: string, sourcePath: string, infraHash?: string | null): Promise<Baseline>;
48
+ upsert(projectId: string, storyId: string, viewport: string, branch: string, snapshotId: string, sourcePath: string, infraHash?: string | null, options?: BaselineUpsertOptions): Promise<Baseline>;
49
+ private sourceBuildId;
26
50
  list(projectId: string): Promise<Baseline[]>;
51
+ /**
52
+ * Delete a baseline's own file. A content-addressed screenshot is shared with snapshots and other
53
+ * baselines, so it is left for the retention sweep instead.
54
+ */
55
+ private discardFile;
56
+ /** Drop the reference and row together when the adapter supports lifecycle protection. */
57
+ private removeRecord;
27
58
  removeOrphans(projectId: string, validStoryIds: Set<string>): Promise<number>;
28
59
  removeForBranch(projectId: string, branch: string): Promise<number>;
29
60
  removeStaleBranches(projectId: string, staleBranches: ReadonlySet<string>): Promise<number>;
30
61
  }
31
- //#endregion
32
- export { BaselineModel, BaselineTables };
62
+ export { BaselineModel, BaselineTables, BaselineUpsertOptions };
33
63
  //# sourceMappingURL=baseline.d.mts.map
@@ -1,19 +1,25 @@
1
1
  import "node:path";
2
2
  import "node:url";
3
3
  import.meta.url;
4
+ import { DEFAULT_BROWSER } from "../adapters/capture-target.mjs";
4
5
  import { ulid } from "../utils/ulid.mjs";
6
+ import { col } from "./columns.mjs";
5
7
  import { emitWebhookEvent } from "../adapter/webhook-events.mjs";
6
- import { baselinePath } from "../paths.mjs";
7
- import { and, eq, getTableColumns } from "drizzle-orm";
8
- //#region src/models/baseline.ts
8
+ import { isContentPath } from "../paths.mjs";
9
+ import { storeScreenshot, withContentClaim } from "../utils/screenshot-store.mjs";
10
+ import { BaselineWriteConflict } from "./baseline-conflict.mjs";
11
+ import { approveInTransaction } from "./baseline-approval.mjs";
12
+ import { upsertAtomicBaseline } from "./baseline-atomic.mjs";
13
+ import { and, eq } from "drizzle-orm";
14
+ // src/models/baseline.ts
9
15
  /** Per-branch baseline screenshots with default-branch fallback. */
10
16
  /** Data operations for baseline screenshots. */
11
17
  var BaselineModel = class {
12
- db;
13
18
  tables;
14
19
  storage;
15
20
  secret;
16
21
  webhookTables;
22
+ db;
17
23
  constructor(db, tables, storage, secret, webhookTables) {
18
24
  this.db = db;
19
25
  this.tables = tables ?? { baselines: db.tables.baselines };
@@ -21,39 +27,130 @@ var BaselineModel = class {
21
27
  this.secret = secret;
22
28
  this.webhookTables = webhookTables;
23
29
  }
24
- async getFor(projectId, storyId, viewport, branch) {
30
+ async getFor(projectId, storyId, viewport, branch, browser = DEFAULT_BROWSER) {
25
31
  return (await this.db.list(this.tables.baselines, {
26
- where: and(eq(getTableColumns(this.tables.baselines)["projectId"], projectId), eq(getTableColumns(this.tables.baselines)["storyId"], storyId), eq(getTableColumns(this.tables.baselines)["viewportName"], viewport), eq(getTableColumns(this.tables.baselines)["branch"], branch)),
32
+ where: and(eq(col(this.tables.baselines, "projectId"), projectId), eq(col(this.tables.baselines, "storyId"), storyId), eq(col(this.tables.baselines, "viewportName"), viewport), eq(col(this.tables.baselines, "branch"), branch), eq(col(this.tables.baselines, "browser"), browser)),
27
33
  limit: 1
28
34
  }))[0] ?? null;
29
35
  }
30
- async resolve(projectId, storyId, viewport, branch, defaultBranch) {
36
+ async resolve(projectId, storyId, viewport, branch, defaultBranch, browser = DEFAULT_BROWSER) {
31
37
  if (branch !== defaultBranch) {
32
- const own = await this.getFor(projectId, storyId, viewport, branch);
38
+ const own = await this.getFor(projectId, storyId, viewport, branch, browser);
33
39
  if (own) return own;
34
40
  }
35
- return this.getFor(projectId, storyId, viewport, defaultBranch);
41
+ return this.getFor(projectId, storyId, viewport, defaultBranch, browser);
42
+ }
43
+ /** Whether the project has any baseline captured on this engine. */
44
+ async hasAny(projectId, browser) {
45
+ return (await this.db.list(this.tables.baselines, {
46
+ where: and(eq(col(this.tables.baselines, "projectId"), projectId), eq(col(this.tables.baselines, "browser"), browser)),
47
+ limit: 1
48
+ })).length > 0;
49
+ }
50
+ /**
51
+ * Record the fingerprint of a baseline that predates fingerprints, without touching its
52
+ * screenshot. Lets the next environment change be detected instead of diffing as a regression.
53
+ */
54
+ async backfillFingerprint(baseline, infraHash, fingerprint) {
55
+ const lifecycle = this.db.buildReferenceLifecycle;
56
+ if (lifecycle) {
57
+ const owner = {
58
+ type: "baseline",
59
+ id: baseline.id,
60
+ projectId: baseline.projectId
61
+ };
62
+ const generation = await lifecycle.referenceGeneration(owner);
63
+ await lifecycle.replaceFencedReferences(owner, generation, baseline.sourceBuildId ? [baseline.sourceBuildId] : [], async (tx) => {
64
+ const current = await tx.get(this.tables.baselines, baseline.id);
65
+ if (!current || current.projectId !== baseline.projectId || current.updatedAt !== baseline.updatedAt || current.sourceBuildId !== baseline.sourceBuildId || current.screenshotPath !== baseline.screenshotPath || current.fingerprint !== baseline.fingerprint) throw new BaselineWriteConflict("Baseline changed during fingerprint backfill; retry");
66
+ await tx.update(this.tables.baselines, baseline.id, {
67
+ infraHash,
68
+ fingerprint
69
+ });
70
+ });
71
+ return;
72
+ }
73
+ await this.db.update(this.tables.baselines, baseline.id, {
74
+ infraHash,
75
+ fingerprint
76
+ });
77
+ }
78
+ /**
79
+ * The content-addressed object a baseline should point at. A source that already is one is
80
+ * reused as is (no copy); a legacy per-build path is stored once by content.
81
+ */
82
+ async contentFor(projectId, sourcePath) {
83
+ if (isContentPath(projectId, sourcePath)) return sourcePath;
84
+ return await storeScreenshot(this.storage, projectId, await this.storage.read(sourcePath));
36
85
  }
37
86
  async read(baseline) {
38
87
  return await this.storage.read(baseline.screenshotPath);
39
88
  }
40
- async upsert(projectId, storyId, viewport, branch, snapshotId, sourcePath, infraHash) {
41
- const screenshotPath = baselinePath(projectId, branch, storyId, viewport);
42
- const source = await this.storage.read(sourcePath);
43
- await this.storage.write(screenshotPath, source);
44
- const existing = await this.getFor(projectId, storyId, viewport, branch);
89
+ async upsert(projectId, storyId, viewport, branch, snapshotId, sourcePath, infraHash, options = {}) {
90
+ const sourceBuildId = await this.sourceBuildId(projectId, storyId, viewport, snapshotId, sourcePath, options.browser);
91
+ const write = async () => await this.#upsertAt(projectId, storyId, viewport, branch, snapshotId, sourceBuildId, sourcePath, await this.contentFor(projectId, sourcePath), infraHash, options);
92
+ return isContentPath(projectId, sourcePath) ? await write() : await withContentClaim(this.storage, projectId, write);
93
+ }
94
+ async sourceBuildId(projectId, storyId, viewport, snapshotId, sourcePath, browser = DEFAULT_BROWSER) {
95
+ const snapshot = await this.db.get(this.db.tables.snapshots, snapshotId);
96
+ if (!snapshot || snapshot.projectId !== projectId || snapshot.storyId !== storyId || snapshot.viewportName !== viewport || (snapshot.browser ?? "chromium") !== browser || snapshot.screenshotPath !== sourcePath) return null;
97
+ return snapshot.inherited ? snapshot.sourceBuildId ?? null : snapshot.buildId;
98
+ }
99
+ async #upsertAt(projectId, storyId, viewport, branch, snapshotId, sourceBuildId, originalPath, screenshotPath, infraHash, options) {
100
+ const browser = options.browser ?? "chromium";
101
+ const existing = await this.getFor(projectId, storyId, viewport, branch, browser);
102
+ const lifecycle = this.db.buildReferenceLifecycle;
103
+ if (lifecycle) {
104
+ const approval = options.approval;
105
+ const baseline = await upsertAtomicBaseline(lifecycle, {
106
+ baselines: this.tables.baselines,
107
+ snapshots: this.db.tables.snapshots
108
+ }, {
109
+ id: existing?.id ?? ulid(),
110
+ existingId: existing?.id ?? null,
111
+ existingVersion: existing?.updatedAt ?? null,
112
+ projectId,
113
+ storyId,
114
+ viewport,
115
+ branch,
116
+ browser,
117
+ snapshotId,
118
+ sourceBuildId,
119
+ originalPath,
120
+ screenshotPath,
121
+ infraHash,
122
+ fingerprint: options.fingerprint,
123
+ mode: options.mode,
124
+ beforeWrite: approval ? async (tx) => await approveInTransaction(tx, this.db.tables, approval) : void 0
125
+ });
126
+ if (this.webhookTables) await emitWebhookEvent(this.db, this.webhookTables, projectId, existing ? "baseline:updated" : "baseline:created", {
127
+ baselineId: baseline.id,
128
+ storyId,
129
+ viewport,
130
+ browser,
131
+ mode: options.mode ?? null,
132
+ branch,
133
+ snapshotId
134
+ }, this.secret);
135
+ return baseline;
136
+ }
45
137
  let baseline;
46
138
  if (existing) {
47
139
  baseline = await this.db.update(this.tables.baselines, existing.id, {
48
140
  snapshotId,
141
+ sourceBuildId,
49
142
  screenshotPath,
50
143
  infraHash: infraHash ?? existing.infraHash ?? null,
144
+ fingerprint: options.fingerprint ?? existing.fingerprint ?? null,
145
+ mode: options.mode ?? existing.mode ?? null,
51
146
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
52
147
  });
53
148
  if (this.webhookTables) await emitWebhookEvent(this.db, this.webhookTables, projectId, "baseline:updated", {
54
149
  baselineId: baseline.id,
55
150
  storyId,
56
151
  viewport,
152
+ browser,
153
+ mode: options.mode ?? null,
57
154
  branch,
58
155
  snapshotId
59
156
  }, this.secret);
@@ -66,8 +163,12 @@ var BaselineModel = class {
66
163
  viewportName: viewport,
67
164
  branch,
68
165
  snapshotId,
166
+ sourceBuildId,
69
167
  screenshotPath,
70
168
  infraHash: infraHash ?? null,
169
+ fingerprint: options.fingerprint ?? null,
170
+ mode: options.mode ?? null,
171
+ browser,
71
172
  createdAt: now,
72
173
  updatedAt: now
73
174
  });
@@ -75,6 +176,8 @@ var BaselineModel = class {
75
176
  baselineId: baseline.id,
76
177
  storyId,
77
178
  viewport,
179
+ browser,
180
+ mode: options.mode ?? null,
78
181
  branch,
79
182
  snapshotId
80
183
  }, this.secret);
@@ -82,26 +185,48 @@ var BaselineModel = class {
82
185
  return baseline;
83
186
  }
84
187
  async list(projectId) {
85
- return await this.db.list(this.tables.baselines, { where: eq(getTableColumns(this.tables.baselines)["projectId"], projectId) });
188
+ return await this.db.list(this.tables.baselines, { where: eq(col(this.tables.baselines, "projectId"), projectId) });
189
+ }
190
+ /**
191
+ * Delete a baseline's own file. A content-addressed screenshot is shared with snapshots and other
192
+ * baselines, so it is left for the retention sweep instead.
193
+ */
194
+ async discardFile(baseline) {
195
+ if (isContentPath(baseline.projectId, baseline.screenshotPath)) return;
196
+ try {
197
+ await this.storage.delete(baseline.screenshotPath);
198
+ } catch {}
199
+ }
200
+ /** Drop the reference and row together when the adapter supports lifecycle protection. */
201
+ async removeRecord(baseline) {
202
+ const lifecycle = this.db.buildReferenceLifecycle;
203
+ if (lifecycle) {
204
+ const owner = {
205
+ type: "baseline",
206
+ projectId: baseline.projectId,
207
+ id: baseline.id
208
+ };
209
+ const generation = await lifecycle.referenceGeneration(owner);
210
+ await lifecycle.replaceFencedReferences(owner, generation, [], async (tx) => {
211
+ const current = await tx.get(this.tables.baselines, baseline.id);
212
+ if (!current) return;
213
+ if (current.updatedAt !== baseline.updatedAt || current.snapshotId !== baseline.snapshotId || current.sourceBuildId !== baseline.sourceBuildId || current.screenshotPath !== baseline.screenshotPath) throw new BaselineWriteConflict("Baseline changed during removal; retry");
214
+ await tx.remove(this.tables.baselines, baseline.id);
215
+ });
216
+ await this.discardFile(baseline);
217
+ return;
218
+ }
219
+ await this.discardFile(baseline);
220
+ await this.db.remove(this.tables.baselines, baseline.id);
86
221
  }
87
222
  async removeOrphans(projectId, validStoryIds) {
88
223
  const toRemove = (await this.list(projectId)).filter((baseline) => !validStoryIds.has(baseline.storyId));
89
- await Promise.all(toRemove.map(async (baseline) => {
90
- try {
91
- await this.storage.delete(baseline.screenshotPath);
92
- } catch {}
93
- await this.db.remove(this.tables.baselines, baseline.id);
94
- }));
224
+ await Promise.all(toRemove.map(async (baseline) => await this.removeRecord(baseline)));
95
225
  return toRemove.length;
96
226
  }
97
227
  async removeForBranch(projectId, branch) {
98
228
  const toRemove = (await this.list(projectId)).filter((baseline) => baseline.branch === branch);
99
- await Promise.all(toRemove.map(async (baseline) => {
100
- try {
101
- await this.storage.delete(baseline.screenshotPath);
102
- } catch {}
103
- await this.db.remove(this.tables.baselines, baseline.id);
104
- }));
229
+ await Promise.all(toRemove.map(async (baseline) => await this.removeRecord(baseline)));
105
230
  return toRemove.length;
106
231
  }
107
232
  async removeStaleBranches(projectId, staleBranches) {
@@ -109,7 +234,6 @@ var BaselineModel = class {
109
234
  return (await Promise.all([...staleBranches].map((branch) => this.removeForBranch(projectId, branch)))).reduce((sum, count) => sum + count, 0);
110
235
  }
111
236
  };
112
- //#endregion
113
237
  export { BaselineModel };
114
238
 
115
239
  //# sourceMappingURL=baseline.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"baseline.mjs","names":[],"sources":["../../src/models/baseline.ts"],"sourcesContent":["/* oxlint-disable typescript/promise-function-async -- map callbacks return promises from async helpers */\n/** Per-branch baseline screenshots with default-branch fallback. */\nimport { and, eq, getTableColumns } from \"drizzle-orm\";\nimport type { SQLWrapper, Table } from \"drizzle-orm\";\nimport type { DatabaseAdapter } from \"../adapters/database.ts\";\nimport type { StorageAdapter } from \"../adapters/storage.ts\";\nimport { emitWebhookEvent } from \"../adapters/webhook-events.ts\";\nimport type { Baseline } from \"../schema/baseline.ts\";\nimport type { SecretInput } from \"../utils/encrypt.ts\";\nimport { baselinePath } from \"../utils/paths.ts\";\nimport { ulid } from \"../utils/ulid.ts\";\nimport type { WebhookTables } from \"./webhook.ts\";\n\n/** Tables required by {@link BaselineModel}. */\nexport interface BaselineTables {\n baselines: Table;\n}\n\n/** Data operations for baseline screenshots. */\nexport class BaselineModel {\n private readonly tables: BaselineTables;\n private readonly storage: StorageAdapter;\n private readonly secret?: SecretInput;\n private readonly webhookTables?: WebhookTables;\n constructor(\n private readonly db: DatabaseAdapter,\n tables?: BaselineTables,\n storage?: StorageAdapter,\n secret?: SecretInput,\n webhookTables?: WebhookTables,\n ) {\n this.tables = tables ?? { baselines: db.tables.baselines };\n this.storage = storage as StorageAdapter;\n this.secret = secret;\n this.webhookTables = webhookTables;\n }\n\n async getFor(\n projectId: string,\n storyId: string,\n viewport: string,\n branch: string,\n ): Promise<Baseline | null> {\n const rows = (await this.db.list(this.tables.baselines, {\n where: and(\n eq(getTableColumns(this.tables.baselines)[\"projectId\"] as unknown as SQLWrapper, projectId),\n eq(getTableColumns(this.tables.baselines)[\"storyId\"] as unknown as SQLWrapper, storyId),\n eq(\n getTableColumns(this.tables.baselines)[\"viewportName\"] as unknown as SQLWrapper,\n viewport,\n ),\n eq(getTableColumns(this.tables.baselines)[\"branch\"] as unknown as SQLWrapper, branch),\n ),\n limit: 1,\n })) as unknown as Baseline[];\n return rows[0] ?? null;\n }\n\n async resolve(\n projectId: string,\n storyId: string,\n viewport: string,\n branch: string,\n defaultBranch: string,\n ): Promise<Baseline | null> {\n if (branch !== defaultBranch) {\n const own = await this.getFor(projectId, storyId, viewport, branch);\n if (own) {\n return own;\n }\n }\n return this.getFor(projectId, storyId, viewport, defaultBranch);\n }\n\n async read(baseline: Baseline): Promise<Buffer> {\n return await this.storage.read(baseline.screenshotPath);\n }\n\n async upsert(\n projectId: string,\n storyId: string,\n viewport: string,\n branch: string,\n snapshotId: string,\n sourcePath: string,\n infraHash?: string | null,\n ): Promise<Baseline> {\n const screenshotPath = baselinePath(projectId, branch, storyId, viewport);\n const source = await this.storage.read(sourcePath);\n await this.storage.write(screenshotPath, source);\n\n const existing = await this.getFor(projectId, storyId, viewport, branch);\n let baseline: Baseline;\n if (existing) {\n baseline = (await this.db.update(this.tables.baselines, existing.id, {\n snapshotId,\n screenshotPath,\n infraHash: infraHash ?? existing.infraHash ?? null,\n updatedAt: new Date().toISOString(),\n })) as unknown as Baseline;\n if (this.webhookTables) {\n await emitWebhookEvent(\n this.db,\n this.webhookTables,\n projectId,\n \"baseline:updated\",\n {\n baselineId: baseline.id,\n storyId,\n viewport,\n branch,\n snapshotId,\n },\n this.secret,\n );\n }\n } else {\n const now = new Date().toISOString();\n baseline = (await this.db.insert(this.tables.baselines, {\n id: ulid(),\n projectId,\n storyId,\n viewportName: viewport,\n branch,\n snapshotId,\n screenshotPath,\n infraHash: infraHash ?? null,\n createdAt: now,\n updatedAt: now,\n })) as unknown as Baseline;\n if (this.webhookTables) {\n await emitWebhookEvent(\n this.db,\n this.webhookTables,\n projectId,\n \"baseline:created\",\n {\n baselineId: baseline.id,\n storyId,\n viewport,\n branch,\n snapshotId,\n },\n this.secret,\n );\n }\n }\n return baseline;\n }\n\n async list(projectId: string): Promise<Baseline[]> {\n return (await this.db.list(this.tables.baselines, {\n where: eq(\n getTableColumns(this.tables.baselines)[\"projectId\"] as unknown as SQLWrapper,\n projectId,\n ),\n })) as unknown as Baseline[];\n }\n\n async removeOrphans(projectId: string, validStoryIds: Set<string>): Promise<number> {\n const all = await this.list(projectId);\n const toRemove = all.filter((baseline) => !validStoryIds.has(baseline.storyId));\n await Promise.all(\n toRemove.map(async (baseline) => {\n try {\n await this.storage.delete(baseline.screenshotPath);\n } catch {\n // ignore missing file\n }\n await this.db.remove(this.tables.baselines, baseline.id);\n }),\n );\n return toRemove.length;\n }\n\n async removeForBranch(projectId: string, branch: string): Promise<number> {\n const all = await this.list(projectId);\n const toRemove = all.filter((baseline) => baseline.branch === branch);\n await Promise.all(\n toRemove.map(async (baseline) => {\n try {\n await this.storage.delete(baseline.screenshotPath);\n } catch {\n // ignore missing file\n }\n await this.db.remove(this.tables.baselines, baseline.id);\n }),\n );\n return toRemove.length;\n }\n\n // oxlint-disable-next-line eslint/require-await -- delegates to async removeForBranch via Promise.all\n async removeStaleBranches(\n projectId: string,\n staleBranches: ReadonlySet<string>,\n ): Promise<number> {\n if (staleBranches.size === 0) {\n return 0;\n }\n const results = await Promise.all(\n [...staleBranches].map((branch) => this.removeForBranch(projectId, branch)),\n );\n return results.reduce((sum, count) => sum + count, 0);\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"baseline.mjs","names":[],"sources":["../../src/models/baseline.ts"],"sourcesContent":["/* oxlint-disable typescript/promise-function-async -- map callbacks return promises from async helpers */\n/** Per-branch baseline screenshots with default-branch fallback. */\nimport { and, eq } from \"drizzle-orm\";\nimport type { Table } from \"drizzle-orm\";\nimport { DEFAULT_BROWSER } from \"../adapters/capture-runner.ts\";\nimport type { DatabaseAdapter } from \"../adapters/database.ts\";\nimport type { StorageAdapter } from \"../adapters/storage.ts\";\nimport { emitWebhookEvent } from \"../adapters/webhook-events.ts\";\nimport type { Baseline } from \"../schema/baseline.ts\";\nimport type { Snapshot } from \"../schema/snapshot.ts\";\nimport type { SecretInput } from \"../utils/encrypt.ts\";\nimport { isContentPath } from \"../utils/paths.ts\";\nimport { storeScreenshot, withContentClaim } from \"../utils/screenshot-store.ts\";\nimport { ulid } from \"../utils/ulid.ts\";\nimport { approveInTransaction, type AtomicBaselineApproval } from \"./baseline-approval.ts\";\nimport { upsertAtomicBaseline } from \"./baseline-atomic.ts\";\nimport { BaselineWriteConflict } from \"./baseline-conflict.ts\";\nimport { col } from \"./columns.ts\";\nimport type { WebhookTables } from \"./webhook.ts\";\n\n/** Extra identity/metadata for {@link BaselineModel.upsert}. */\nexport interface BaselineUpsertOptions {\n approval?: AtomicBaselineApproval;\n /** Engine the baseline belongs to (default chromium). */\n browser?: string;\n /** Resolved mode definition as JSON (display only). */\n mode?: string | null;\n /** JSON `Fingerprint` behind the `infraHash` being written. */\n fingerprint?: string | null;\n}\n\n/** Tables required by {@link BaselineModel}. */\nexport interface BaselineTables {\n baselines: Table;\n}\n\n/** Data operations for baseline screenshots. */\nexport class BaselineModel {\n private readonly tables: BaselineTables;\n private readonly storage: StorageAdapter;\n private readonly secret?: SecretInput;\n private readonly webhookTables?: WebhookTables;\n private readonly db: DatabaseAdapter;\n\n constructor(\n db: DatabaseAdapter,\n tables?: BaselineTables,\n storage?: StorageAdapter,\n secret?: SecretInput,\n webhookTables?: WebhookTables,\n ) {\n this.db = db;\n this.tables = tables ?? { baselines: db.tables.baselines };\n this.storage = storage as StorageAdapter;\n this.secret = secret;\n this.webhookTables = webhookTables;\n }\n\n async getFor(\n projectId: string,\n storyId: string,\n viewport: string,\n branch: string,\n browser: string = DEFAULT_BROWSER,\n ): Promise<Baseline | null> {\n const rows = (await this.db.list(this.tables.baselines, {\n where: and(\n eq(col(this.tables.baselines, \"projectId\"), projectId),\n eq(col(this.tables.baselines, \"storyId\"), storyId),\n eq(col(this.tables.baselines, \"viewportName\"), viewport),\n eq(col(this.tables.baselines, \"branch\"), branch),\n eq(col(this.tables.baselines, \"browser\"), browser),\n ),\n limit: 1,\n })) as unknown as Baseline[];\n return rows[0] ?? null;\n }\n\n async resolve(\n projectId: string,\n storyId: string,\n viewport: string,\n branch: string,\n defaultBranch: string,\n browser: string = DEFAULT_BROWSER,\n ): Promise<Baseline | null> {\n if (branch !== defaultBranch) {\n const own = await this.getFor(projectId, storyId, viewport, branch, browser);\n if (own) {\n return own;\n }\n }\n return this.getFor(projectId, storyId, viewport, defaultBranch, browser);\n }\n\n /** Whether the project has any baseline captured on this engine. */\n async hasAny(projectId: string, browser: string): Promise<boolean> {\n const rows = (await this.db.list(this.tables.baselines, {\n where: and(\n eq(col(this.tables.baselines, \"projectId\"), projectId),\n eq(col(this.tables.baselines, \"browser\"), browser),\n ),\n limit: 1,\n })) as unknown as Baseline[];\n return rows.length > 0;\n }\n\n /**\n * Record the fingerprint of a baseline that predates fingerprints, without touching its\n * screenshot. Lets the next environment change be detected instead of diffing as a regression.\n */\n async backfillFingerprint(\n baseline: Baseline,\n infraHash: string,\n fingerprint: string,\n ): Promise<void> {\n const lifecycle = this.db.buildReferenceLifecycle;\n if (lifecycle) {\n const owner = { type: \"baseline\" as const, id: baseline.id, projectId: baseline.projectId };\n const generation = await lifecycle.referenceGeneration(owner);\n await lifecycle.replaceFencedReferences(\n owner,\n generation,\n baseline.sourceBuildId ? [baseline.sourceBuildId] : [],\n async (tx) => {\n const current = (await tx.get(\n this.tables.baselines,\n baseline.id,\n )) as unknown as Baseline | null;\n if (\n !current ||\n current.projectId !== baseline.projectId ||\n current.updatedAt !== baseline.updatedAt ||\n current.sourceBuildId !== baseline.sourceBuildId ||\n current.screenshotPath !== baseline.screenshotPath ||\n current.fingerprint !== baseline.fingerprint\n )\n throw new BaselineWriteConflict(\"Baseline changed during fingerprint backfill; retry\");\n await tx.update(this.tables.baselines, baseline.id, { infraHash, fingerprint });\n },\n );\n return;\n }\n await this.db.update(this.tables.baselines, baseline.id, { infraHash, fingerprint });\n }\n\n /**\n * The content-addressed object a baseline should point at. A source that already is one is\n * reused as is (no copy); a legacy per-build path is stored once by content.\n */\n private async contentFor(projectId: string, sourcePath: string): Promise<string> {\n if (isContentPath(projectId, sourcePath)) {\n return sourcePath;\n }\n return await storeScreenshot(this.storage, projectId, await this.storage.read(sourcePath));\n }\n\n async read(baseline: Baseline): Promise<Buffer> {\n return await this.storage.read(baseline.screenshotPath);\n }\n\n async upsert(\n projectId: string,\n storyId: string,\n viewport: string,\n branch: string,\n snapshotId: string,\n sourcePath: string,\n infraHash?: string | null,\n options: BaselineUpsertOptions = {},\n ): Promise<Baseline> {\n const sourceBuildId = await this.sourceBuildId(\n projectId,\n storyId,\n viewport,\n snapshotId,\n sourcePath,\n options.browser,\n );\n const write = async (): Promise<Baseline> =>\n await this.#upsertAt(\n projectId,\n storyId,\n viewport,\n branch,\n snapshotId,\n sourceBuildId,\n sourcePath,\n await this.contentFor(projectId, sourcePath),\n infraHash,\n options,\n );\n // Storing a legacy source may reuse an object the sweep is about to delete: claim it until the\n // row points at it. A content source is already referenced by its snapshot row.\n return isContentPath(projectId, sourcePath)\n ? await write()\n : await withContentClaim(this.storage, projectId, write);\n }\n\n private async sourceBuildId(\n projectId: string,\n storyId: string,\n viewport: string,\n snapshotId: string,\n sourcePath: string,\n browser: string = DEFAULT_BROWSER,\n ): Promise<string | null> {\n const snapshot = (await this.db.get(\n this.db.tables.snapshots,\n snapshotId,\n )) as unknown as Snapshot | null;\n if (\n !snapshot ||\n snapshot.projectId !== projectId ||\n snapshot.storyId !== storyId ||\n snapshot.viewportName !== viewport ||\n (snapshot.browser ?? DEFAULT_BROWSER) !== browser ||\n snapshot.screenshotPath !== sourcePath\n )\n return null;\n return snapshot.inherited ? (snapshot.sourceBuildId ?? null) : snapshot.buildId;\n }\n\n async #upsertAt(\n projectId: string,\n storyId: string,\n viewport: string,\n branch: string,\n snapshotId: string,\n sourceBuildId: string | null,\n originalPath: string,\n screenshotPath: string,\n infraHash: string | null | undefined,\n options: BaselineUpsertOptions,\n ): Promise<Baseline> {\n const browser = options.browser ?? DEFAULT_BROWSER;\n\n const existing = await this.getFor(projectId, storyId, viewport, branch, browser);\n const lifecycle = this.db.buildReferenceLifecycle;\n if (lifecycle) {\n const approval = options.approval;\n const baseline = await upsertAtomicBaseline(\n lifecycle,\n { baselines: this.tables.baselines, snapshots: this.db.tables.snapshots },\n {\n id: existing?.id ?? ulid(),\n existingId: existing?.id ?? null,\n existingVersion: existing?.updatedAt ?? null,\n projectId,\n storyId,\n viewport,\n branch,\n browser,\n snapshotId,\n sourceBuildId,\n originalPath,\n screenshotPath,\n infraHash,\n fingerprint: options.fingerprint,\n mode: options.mode,\n beforeWrite: approval\n ? async (tx): Promise<void> => await approveInTransaction(tx, this.db.tables, approval)\n : undefined,\n },\n );\n if (this.webhookTables) {\n await emitWebhookEvent(\n this.db,\n this.webhookTables,\n projectId,\n existing ? \"baseline:updated\" : \"baseline:created\",\n {\n baselineId: baseline.id,\n storyId,\n viewport,\n browser,\n mode: options.mode ?? null,\n branch,\n snapshotId,\n },\n this.secret,\n );\n }\n return baseline;\n }\n let baseline: Baseline;\n if (existing) {\n baseline = (await this.db.update(this.tables.baselines, existing.id, {\n snapshotId,\n sourceBuildId,\n screenshotPath,\n infraHash: infraHash ?? existing.infraHash ?? null,\n fingerprint: options.fingerprint ?? existing.fingerprint ?? null,\n mode: options.mode ?? existing.mode ?? null,\n updatedAt: new Date().toISOString(),\n })) as unknown as Baseline;\n if (this.webhookTables) {\n await emitWebhookEvent(\n this.db,\n this.webhookTables,\n projectId,\n \"baseline:updated\",\n {\n baselineId: baseline.id,\n storyId,\n viewport,\n browser,\n mode: options.mode ?? null,\n branch,\n snapshotId,\n },\n this.secret,\n );\n }\n } else {\n const now = new Date().toISOString();\n baseline = (await this.db.insert(this.tables.baselines, {\n id: ulid(),\n projectId,\n storyId,\n viewportName: viewport,\n branch,\n snapshotId,\n sourceBuildId,\n screenshotPath,\n infraHash: infraHash ?? null,\n fingerprint: options.fingerprint ?? null,\n mode: options.mode ?? null,\n browser,\n createdAt: now,\n updatedAt: now,\n })) as unknown as Baseline;\n if (this.webhookTables) {\n await emitWebhookEvent(\n this.db,\n this.webhookTables,\n projectId,\n \"baseline:created\",\n {\n baselineId: baseline.id,\n storyId,\n viewport,\n browser,\n mode: options.mode ?? null,\n branch,\n snapshotId,\n },\n this.secret,\n );\n }\n }\n return baseline;\n }\n\n async list(projectId: string): Promise<Baseline[]> {\n return (await this.db.list(this.tables.baselines, {\n where: eq(col(this.tables.baselines, \"projectId\"), projectId),\n })) as unknown as Baseline[];\n }\n\n /**\n * Delete a baseline's own file. A content-addressed screenshot is shared with snapshots and other\n * baselines, so it is left for the retention sweep instead.\n */\n private async discardFile(baseline: Baseline): Promise<void> {\n if (isContentPath(baseline.projectId, baseline.screenshotPath)) {\n return;\n }\n try {\n await this.storage.delete(baseline.screenshotPath);\n } catch {\n // ignore missing file\n }\n }\n\n /** Drop the reference and row together when the adapter supports lifecycle protection. */\n private async removeRecord(baseline: Baseline): Promise<void> {\n const lifecycle = this.db.buildReferenceLifecycle;\n if (lifecycle) {\n const owner = { type: \"baseline\" as const, projectId: baseline.projectId, id: baseline.id };\n const generation = await lifecycle.referenceGeneration(owner);\n await lifecycle.replaceFencedReferences(owner, generation, [], async (tx) => {\n const current = (await tx.get(\n this.tables.baselines,\n baseline.id,\n )) as unknown as Baseline | null;\n if (!current) return;\n if (\n current.updatedAt !== baseline.updatedAt ||\n current.snapshotId !== baseline.snapshotId ||\n current.sourceBuildId !== baseline.sourceBuildId ||\n current.screenshotPath !== baseline.screenshotPath\n ) {\n throw new BaselineWriteConflict(\"Baseline changed during removal; retry\");\n }\n await tx.remove(this.tables.baselines, baseline.id);\n });\n await this.discardFile(baseline);\n return;\n }\n await this.discardFile(baseline);\n await this.db.remove(this.tables.baselines, baseline.id);\n }\n\n async removeOrphans(projectId: string, validStoryIds: Set<string>): Promise<number> {\n const all = await this.list(projectId);\n const toRemove = all.filter((baseline) => !validStoryIds.has(baseline.storyId));\n await Promise.all(toRemove.map(async (baseline) => await this.removeRecord(baseline)));\n return toRemove.length;\n }\n\n async removeForBranch(projectId: string, branch: string): Promise<number> {\n const all = await this.list(projectId);\n const toRemove = all.filter((baseline) => baseline.branch === branch);\n await Promise.all(toRemove.map(async (baseline) => await this.removeRecord(baseline)));\n return toRemove.length;\n }\n\n // oxlint-disable-next-line eslint/require-await -- delegates to async removeForBranch via Promise.all\n async removeStaleBranches(\n projectId: string,\n staleBranches: ReadonlySet<string>,\n ): Promise<number> {\n if (staleBranches.size === 0) {\n return 0;\n }\n const results = await Promise.all(\n [...staleBranches].map((branch) => this.removeForBranch(projectId, branch)),\n );\n return results.reduce((sum, count) => sum + count, 0);\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,26 @@
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 { BuildComponent } from "../schema/build-component.mjs";
5
+ import { Table } from "drizzle-orm";
6
+ /** Tables required by {@link BuildComponentModel}. */
7
+ interface BuildComponentTables {
8
+ buildComponents: Table;
9
+ }
10
+ /** What a build records of one component. */
11
+ type BuildComponentInput = Pick<BuildComponent, "importPath" | "title" | "storyCount" | "hash">;
12
+ /** Data operations for the components each published build holds. */
13
+ declare class BuildComponentModel {
14
+ #private;
15
+ constructor(db: DatabaseAdapter, tables?: BuildComponentTables);
16
+ /** Components of one build, ordered by title then file. */
17
+ listByBuild(buildId: string): Promise<BuildComponent[]>;
18
+ /** Rows of one story file across the project's builds, newest first: "which builds contain it". */
19
+ listByImportPath(projectId: string, importPath: string): Promise<BuildComponent[]>;
20
+ /** Replace a build's rows with `components`; repeating a publish never duplicates. */
21
+ replaceForBuild(projectId: string, buildId: string, components: readonly BuildComponentInput[]): Promise<void>;
22
+ /** Forget a build's rows (the foreign key cascades too; stub tables have none). */
23
+ removeForBuild(buildId: string): Promise<number>;
24
+ }
25
+ export { BuildComponentInput, BuildComponentModel, BuildComponentTables };
26
+ //# sourceMappingURL=build-component.d.mts.map
@@ -0,0 +1,54 @@
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 { and, asc, desc, eq } from "drizzle-orm";
7
+ // src/models/build-component.ts
8
+ /** Component rows of published builds (`build_components`, ADR 0006 decision 9). */
9
+ /** Data operations for the components each published build holds. */
10
+ var BuildComponentModel = class {
11
+ #db;
12
+ #table;
13
+ constructor(db, tables) {
14
+ this.#db = db;
15
+ this.#table = (tables ?? { buildComponents: db.tables.buildComponents }).buildComponents;
16
+ }
17
+ /** Components of one build, ordered by title then file. */
18
+ async listByBuild(buildId) {
19
+ return (await this.#db.list(this.#table, {
20
+ where: eq(col(this.#table, "buildId"), buildId),
21
+ orderBy: asc(col(this.#table, "title"))
22
+ })).toSorted((a, b) => a.title.localeCompare(b.title) || a.importPath.localeCompare(b.importPath));
23
+ }
24
+ /** Rows of one story file across the project's builds, newest first: "which builds contain it". */
25
+ async listByImportPath(projectId, importPath) {
26
+ return await this.#db.list(this.#table, {
27
+ where: and(eq(col(this.#table, "projectId"), projectId), eq(col(this.#table, "importPath"), importPath)),
28
+ orderBy: desc(col(this.#table, "createdAt"))
29
+ });
30
+ }
31
+ /** Replace a build's rows with `components`; repeating a publish never duplicates. */
32
+ async replaceForBuild(projectId, buildId, components) {
33
+ await this.removeForBuild(buildId);
34
+ const createdAt = (/* @__PURE__ */ new Date()).toISOString();
35
+ for (const component of components) await this.#db.insert(this.#table, {
36
+ id: ulid(),
37
+ projectId,
38
+ buildId,
39
+ createdAt,
40
+ ...component
41
+ });
42
+ }
43
+ /** Forget a build's rows (the foreign key cascades too; stub tables have none). */
44
+ async removeForBuild(buildId) {
45
+ const rows = await this.#db.list(this.#table, { where: eq(col(this.#table, "buildId"), buildId) });
46
+ await Promise.all(rows.map(async (row) => {
47
+ await this.#db.remove(this.#table, row.id);
48
+ }));
49
+ return rows.length;
50
+ }
51
+ };
52
+ export { BuildComponentModel };
53
+
54
+ //# sourceMappingURL=build-component.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build-component.mjs","names":[],"sources":["../../src/models/build-component.ts"],"sourcesContent":["/** Component rows of published builds (`build_components`, ADR 0006 decision 9). */\nimport { and, asc, desc, eq } from \"drizzle-orm\";\nimport type { Table } from \"drizzle-orm\";\nimport type { DatabaseAdapter } from \"../adapters/database.ts\";\nimport type { BuildComponent } from \"../schema/build-component.ts\";\nimport { ulid } from \"../utils/ulid.ts\";\nimport { col } from \"./columns.ts\";\n\n/** Tables required by {@link BuildComponentModel}. */\nexport interface BuildComponentTables {\n buildComponents: Table;\n}\n\n/** What a build records of one component. */\nexport type BuildComponentInput = Pick<\n BuildComponent,\n \"importPath\" | \"title\" | \"storyCount\" | \"hash\"\n>;\n\n/** Data operations for the components each published build holds. */\nexport class BuildComponentModel {\n readonly #db: DatabaseAdapter;\n readonly #table: Table;\n\n constructor(db: DatabaseAdapter, tables?: BuildComponentTables) {\n this.#db = db;\n this.#table = (tables ?? { buildComponents: db.tables.buildComponents }).buildComponents;\n }\n\n /** Components of one build, ordered by title then file. */\n async listByBuild(buildId: string): Promise<BuildComponent[]> {\n const rows = (await this.#db.list(this.#table, {\n where: eq(col(this.#table, \"buildId\"), buildId),\n orderBy: asc(col(this.#table, \"title\")),\n })) as unknown as BuildComponent[];\n return rows.toSorted(\n (a, b) => a.title.localeCompare(b.title) || a.importPath.localeCompare(b.importPath),\n );\n }\n\n /** Rows of one story file across the project's builds, newest first: \"which builds contain it\". */\n async listByImportPath(projectId: string, importPath: string): Promise<BuildComponent[]> {\n const rows = (await this.#db.list(this.#table, {\n where: and(\n eq(col(this.#table, \"projectId\"), projectId),\n eq(col(this.#table, \"importPath\"), importPath),\n ),\n orderBy: desc(col(this.#table, \"createdAt\")),\n })) as unknown as BuildComponent[];\n return rows;\n }\n\n /** Replace a build's rows with `components`; repeating a publish never duplicates. */\n async replaceForBuild(\n projectId: string,\n buildId: string,\n components: readonly BuildComponentInput[],\n ): Promise<void> {\n await this.removeForBuild(buildId);\n const createdAt = new Date().toISOString();\n for (const component of components) {\n // eslint-disable-next-line no-await-in-loop -- rows are few; sequential inserts keep ids ordered\n await this.#db.insert(this.#table, {\n id: ulid(),\n projectId,\n buildId,\n createdAt,\n ...component,\n });\n }\n }\n\n /** Forget a build's rows (the foreign key cascades too; stub tables have none). */\n async removeForBuild(buildId: string): Promise<number> {\n const rows = (await this.#db.list(this.#table, {\n where: eq(col(this.#table, \"buildId\"), buildId),\n })) as unknown as BuildComponent[];\n await Promise.all(\n rows.map(async (row) => {\n await this.#db.remove(this.#table, row.id);\n }),\n );\n return rows.length;\n }\n}\n"],"mappings":";;;;;;;;;AAoBA,IAAa,sBAAb,MAAiC;CAC/B;CACA;CAEA,YAAY,IAAqB,QAA+B;EAC9D,KAAK,MAAM;EACX,KAAK,UAAU,UAAU,EAAE,iBAAiB,GAAG,OAAO,gBAAgB,EAAA,CAAG;CAC3E;;CAGA,MAAM,YAAY,SAA4C;EAK5D,QAAO,MAJa,KAAK,IAAI,KAAK,KAAK,QAAQ;GAC7C,OAAO,GAAG,IAAI,KAAK,QAAQ,SAAS,GAAG,OAAO;GAC9C,SAAS,IAAI,IAAI,KAAK,QAAQ,OAAO,CAAC;EACxC,CAAC,EAAA,CACW,UACT,GAAG,MAAM,EAAE,MAAM,cAAc,EAAE,KAAK,KAAK,EAAE,WAAW,cAAc,EAAE,UAAU,CACrF;CACF;;CAGA,MAAM,iBAAiB,WAAmB,YAA+C;EAQvF,OAAO,MAPa,KAAK,IAAI,KAAK,KAAK,QAAQ;GAC7C,OAAO,IACL,GAAG,IAAI,KAAK,QAAQ,WAAW,GAAG,SAAS,GAC3C,GAAG,IAAI,KAAK,QAAQ,YAAY,GAAG,UAAU,CAC/C;GACA,SAAS,KAAK,IAAI,KAAK,QAAQ,WAAW,CAAC;EAC7C,CAAC;CAEH;;CAGA,MAAM,gBACJ,WACA,SACA,YACe;EACf,MAAM,KAAK,eAAe,OAAO;EACjC,MAAM,6BAAY,IAAI,KAAK,EAAA,CAAE,YAAY;EACzC,KAAK,MAAM,aAAa,YAEtB,MAAM,KAAK,IAAI,OAAO,KAAK,QAAQ;GACjC,IAAI,KAAK;GACT;GACA;GACA;GACA,GAAG;EACL,CAAC;CAEL;;CAGA,MAAM,eAAe,SAAkC;EACrD,MAAM,OAAQ,MAAM,KAAK,IAAI,KAAK,KAAK,QAAQ,EAC7C,OAAO,GAAG,IAAI,KAAK,QAAQ,SAAS,GAAG,OAAO,EAChD,CAAC;EACD,MAAM,QAAQ,IACZ,KAAK,IAAI,OAAO,QAAQ;GACtB,MAAM,KAAK,IAAI,OAAO,KAAK,QAAQ,IAAI,EAAE;EAC3C,CAAC,CACH;EACA,OAAO,KAAK;CACd;AACF"}
@@ -0,0 +1,27 @@
1
+ import __tsdown_shims_path from 'node:path';
2
+ import __tsdown_shims_url from 'node:url';
3
+ import { BuildReview } from "../schema/build-review.mjs";
4
+ /** What {@link deriveReviewStatus} depends on. */
5
+ interface ReviewStatusInput {
6
+ /** `testing` is on: the snapshot rollup decides and verdicts never gate. */
7
+ testing: boolean;
8
+ /** The project's review switch. */
9
+ review: boolean;
10
+ /** `projects.review_approvals`: approvals a build needs (at least 1). */
11
+ approvalsRequired: number;
12
+ /** Every verdict given to the build, in any order; see {@link latestVerdicts}. */
13
+ reviews: readonly Pick<BuildReview, "id" | "userId" | "verdict" | "createdAt">[];
14
+ }
15
+ /**
16
+ * The status the verdicts give the build, or `null` when they do not decide it (`testing` is on,
17
+ * or review is off and the build was accepted when it was published).
18
+ *
19
+ * - `rejected` when any reviewer's latest verdict is `rejected`;
20
+ * - `approved` once the required approvals are present among the latest verdicts;
21
+ * - `pending` otherwise.
22
+ */
23
+ declare function deriveReviewStatus(input: ReviewStatusInput): "approved" | "rejected" | "pending" | null;
24
+ /** Each reviewer's latest verdict (newest `createdAt`, then newest id), oldest reviewer first. */
25
+ declare function latestVerdicts<T extends Pick<BuildReview, "id" | "userId" | "createdAt">>(reviews: readonly T[]): T[];
26
+ export { ReviewStatusInput, deriveReviewStatus, latestVerdicts };
27
+ //# sourceMappingURL=build-review-status.d.mts.map
@@ -0,0 +1,36 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ // src/models/build-review-status.ts
5
+ /** Reviewer key standing for verdicts without a user (auth off or an admin token): one reviewer. */
6
+ const ANONYMOUS_REVIEWER = "";
7
+ /**
8
+ * The status the verdicts give the build, or `null` when they do not decide it (`testing` is on,
9
+ * or review is off and the build was accepted when it was published).
10
+ *
11
+ * - `rejected` when any reviewer's latest verdict is `rejected`;
12
+ * - `approved` once the required approvals are present among the latest verdicts;
13
+ * - `pending` otherwise.
14
+ */
15
+ function deriveReviewStatus(input) {
16
+ if (input.testing || !input.review) return null;
17
+ const latest = latestVerdicts(input.reviews);
18
+ if (latest.some((review) => review.verdict === "rejected")) return "rejected";
19
+ return latest.filter((review) => review.verdict === "approved").length >= Math.max(1, input.approvalsRequired) ? "approved" : "pending";
20
+ }
21
+ /** Each reviewer's latest verdict (newest `createdAt`, then newest id), oldest reviewer first. */
22
+ function latestVerdicts(reviews) {
23
+ const latest = /* @__PURE__ */ new Map();
24
+ for (const review of reviews) {
25
+ const key = review.userId ?? ANONYMOUS_REVIEWER;
26
+ const current = latest.get(key);
27
+ if (current === void 0 || isNewer(review, current)) latest.set(key, review);
28
+ }
29
+ return [...latest.values()].toSorted((a, b) => isNewer(b, a) ? -1 : 1);
30
+ }
31
+ function isNewer(a, b) {
32
+ return a.createdAt === b.createdAt ? a.id > b.id : a.createdAt > b.createdAt;
33
+ }
34
+ export { deriveReviewStatus, latestVerdicts };
35
+
36
+ //# sourceMappingURL=build-review-status.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build-review-status.mjs","names":[],"sources":["../../src/models/build-review-status.ts"],"sourcesContent":["/**\n * What the build-level verdicts make of a build (ADR 0013, decision 3). Pure: callers read the\n * reviews and the project, apply the result, and stay free of branching.\n */\nimport type { BuildReview } from \"../schema/build-review.ts\";\n\n/** What {@link deriveReviewStatus} depends on. */\nexport interface ReviewStatusInput {\n /** `testing` is on: the snapshot rollup decides and verdicts never gate. */\n testing: boolean;\n /** The project's review switch. */\n review: boolean;\n /** `projects.review_approvals`: approvals a build needs (at least 1). */\n approvalsRequired: number;\n /** Every verdict given to the build, in any order; see {@link latestVerdicts}. */\n reviews: readonly Pick<BuildReview, \"id\" | \"userId\" | \"verdict\" | \"createdAt\">[];\n}\n\n/** Reviewer key standing for verdicts without a user (auth off or an admin token): one reviewer. */\nconst ANONYMOUS_REVIEWER = \"\";\n\n/**\n * The status the verdicts give the build, or `null` when they do not decide it (`testing` is on,\n * or review is off and the build was accepted when it was published).\n *\n * - `rejected` when any reviewer's latest verdict is `rejected`;\n * - `approved` once the required approvals are present among the latest verdicts;\n * - `pending` otherwise.\n */\nexport function deriveReviewStatus(\n input: ReviewStatusInput,\n): \"approved\" | \"rejected\" | \"pending\" | null {\n if (input.testing || !input.review) {\n return null;\n }\n const latest = latestVerdicts(input.reviews);\n if (latest.some((review) => review.verdict === \"rejected\")) {\n return \"rejected\";\n }\n const approvals = latest.filter((review) => review.verdict === \"approved\").length;\n return approvals >= Math.max(1, input.approvalsRequired) ? \"approved\" : \"pending\";\n}\n\n/** Each reviewer's latest verdict (newest `createdAt`, then newest id), oldest reviewer first. */\nexport function latestVerdicts<T extends Pick<BuildReview, \"id\" | \"userId\" | \"createdAt\">>(\n reviews: readonly T[],\n): T[] {\n const latest = new Map<string, T>();\n for (const review of reviews) {\n const key = review.userId ?? ANONYMOUS_REVIEWER;\n const current = latest.get(key);\n if (current === undefined || isNewer(review, current)) {\n latest.set(key, review);\n }\n }\n return [...latest.values()].toSorted((a, b) => (isNewer(b, a) ? -1 : 1));\n}\n\nfunction isNewer(\n a: Pick<BuildReview, \"id\" | \"createdAt\">,\n b: Pick<BuildReview, \"id\" | \"createdAt\">,\n): boolean {\n return a.createdAt === b.createdAt ? a.id > b.id : a.createdAt > b.createdAt;\n}\n"],"mappings":";;;;;AAmBA,MAAM,qBAAqB;;;;;;;;;AAU3B,SAAgB,mBACd,OAC4C;CAC5C,IAAI,MAAM,WAAW,CAAC,MAAM,QAC1B,OAAO;CAET,MAAM,SAAS,eAAe,MAAM,OAAO;CAC3C,IAAI,OAAO,MAAM,WAAW,OAAO,YAAY,UAAU,GACvD,OAAO;CAGT,OADkB,OAAO,QAAQ,WAAW,OAAO,YAAY,UAAU,CAAC,CAAC,UACvD,KAAK,IAAI,GAAG,MAAM,iBAAiB,IAAI,aAAa;AAC1E;;AAGA,SAAgB,eACd,SACK;CACL,MAAM,yBAAS,IAAI,IAAe;CAClC,KAAK,MAAM,UAAU,SAAS;EAC5B,MAAM,MAAM,OAAO,UAAU;EAC7B,MAAM,UAAU,OAAO,IAAI,GAAG;EAC9B,IAAI,YAAY,KAAA,KAAa,QAAQ,QAAQ,OAAO,GAClD,OAAO,IAAI,KAAK,MAAM;CAE1B;CACA,OAAO,CAAC,GAAG,OAAO,OAAO,CAAC,CAAC,CAAC,UAAU,GAAG,MAAO,QAAQ,GAAG,CAAC,IAAI,KAAK,CAAE;AACzE;AAEA,SAAS,QACP,GACA,GACS;CACT,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,KAAK,EAAE,KAAK,EAAE,YAAY,EAAE;AACrE"}
@@ -0,0 +1,26 @@
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 { ReviewVerdict } from "../types.mjs";
5
+ import { BuildReview } from "../schema/build-review.mjs";
6
+ import { Table } from "drizzle-orm";
7
+ /** Tables required by {@link BuildReviewModel}. */
8
+ interface BuildReviewTables {
9
+ buildReviews: Table;
10
+ }
11
+ /** Data operations for the verdicts reviewers give a build. */
12
+ declare class BuildReviewModel {
13
+ #private;
14
+ constructor(db: DatabaseAdapter, tables?: BuildReviewTables);
15
+ /**
16
+ * Record a verdict. A reviewer rewrites theirs by adding another row; the newest row counts.
17
+ * `createdAt` is strictly increasing within a build, so "newest" never ties.
18
+ */
19
+ add(buildId: string, userId: string | null, verdict: ReviewVerdict, body?: string): Promise<BuildReview>;
20
+ /** Every verdict of a build, oldest first. */
21
+ listByBuild(buildId: string): Promise<BuildReview[]>;
22
+ /** Each reviewer's latest verdict on a build. */
23
+ latestByBuild(buildId: string): Promise<BuildReview[]>;
24
+ }
25
+ export { BuildReviewModel, BuildReviewTables };
26
+ //# sourceMappingURL=build-review.d.mts.map