@storyshelf/core 0.6.2 → 0.7.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 (628) hide show
  1. package/README.md +49 -32
  2. package/dist/adapter/capture-queue.d.mts +44 -2
  3. package/dist/adapter/capture-runner.d.mts +127 -10
  4. package/dist/adapter/capture-runner.mjs +5 -12
  5. package/dist/adapter/database.d.mts +27 -8
  6. package/dist/adapter/database.mjs.map +1 -1
  7. package/dist/adapter/design.d.mts +85 -0
  8. package/dist/adapter/design.mjs +28 -0
  9. package/dist/adapter/design.mjs.map +1 -0
  10. package/dist/adapter/metadata.d.mts +3 -10
  11. package/dist/adapter/notifier.d.mts +6 -3
  12. package/dist/adapter/notifier.mjs +5 -1
  13. package/dist/adapter/oauth.d.mts +52 -0
  14. package/dist/adapter/setup.d.mts +4 -5
  15. package/dist/adapter/setup.mjs +2 -1
  16. package/dist/adapter/setup.mjs.map +1 -1
  17. package/dist/adapter/webhook-events.mjs +1 -1
  18. package/dist/adapters/capture-target-offers.d.mts +28 -0
  19. package/dist/adapters/capture-target-offers.mjs +58 -0
  20. package/dist/adapters/capture-target-offers.mjs.map +1 -0
  21. package/dist/adapters/capture-target.d.mts +71 -0
  22. package/dist/adapters/capture-target.mjs +76 -0
  23. package/dist/adapters/capture-target.mjs.map +1 -0
  24. package/dist/adapters/notifier/build-comment.d.mts +24 -0
  25. package/dist/adapters/notifier/build-comment.mjs +24 -0
  26. package/dist/adapters/notifier/build-comment.mjs.map +1 -0
  27. package/dist/adapters/notifier/build-state.d.mts +99 -0
  28. package/dist/adapters/notifier/build-state.mjs +218 -0
  29. package/dist/adapters/notifier/build-state.mjs.map +1 -0
  30. package/dist/adapters/notifier/events.d.mts +4 -2
  31. package/dist/adapters/notifier/events.mjs +31 -18
  32. package/dist/adapters/notifier/events.mjs.map +1 -1
  33. package/dist/adapters/notifier/provider.d.mts +53 -7
  34. package/dist/adapters/notifier/provider.mjs +12 -0
  35. package/dist/adapters/notifier/provider.mjs.map +1 -0
  36. package/dist/adapters/notifier/state.d.mts +24 -0
  37. package/dist/adapters/notifier/state.mjs +66 -0
  38. package/dist/adapters/notifier/state.mjs.map +1 -0
  39. package/dist/adapters/notifier/types.d.mts +51 -2
  40. package/dist/adapters/remote-browser.d.mts +87 -0
  41. package/dist/adapters/remote-browser.mjs +103 -0
  42. package/dist/adapters/remote-browser.mjs.map +1 -0
  43. package/dist/adapters/render-runs.d.mts +25 -0
  44. package/dist/adapters/render-runs.mjs +41 -0
  45. package/dist/adapters/render-runs.mjs.map +1 -0
  46. package/dist/adapters/setup-runner.mjs +2 -4
  47. package/dist/adapters/setup-runner.mjs.map +1 -1
  48. package/dist/adapters/sources.d.mts +12 -0
  49. package/dist/adapters/sources.mjs +20 -0
  50. package/dist/adapters/sources.mjs.map +1 -0
  51. package/dist/adapters/validate.mjs +25 -18
  52. package/dist/adapters/validate.mjs.map +1 -1
  53. package/dist/ai.d.mts +105 -0
  54. package/dist/ai.mjs +19 -0
  55. package/dist/ai.mjs.map +1 -0
  56. package/dist/capture/a11y/axe-source.d.mts +18 -0
  57. package/dist/capture/a11y/axe-source.mjs +37 -0
  58. package/dist/capture/a11y/axe-source.mjs.map +1 -0
  59. package/dist/capture/a11y/in-page.d.mts +27 -0
  60. package/dist/capture/a11y/in-page.mjs +60 -0
  61. package/dist/capture/a11y/in-page.mjs.map +1 -0
  62. package/dist/capture/a11y/map.d.mts +24 -0
  63. package/dist/capture/a11y/map.mjs +48 -0
  64. package/dist/capture/a11y/map.mjs.map +1 -0
  65. package/dist/capture/a11y/options.d.mts +23 -0
  66. package/dist/capture/a11y/options.mjs +75 -0
  67. package/dist/capture/a11y/options.mjs.map +1 -0
  68. package/dist/capture/adapter.d.mts +41 -3
  69. package/dist/capture/adapter.mjs.map +1 -1
  70. package/dist/capture/affected.d.mts +2 -0
  71. package/dist/capture/affected.mjs +13 -4
  72. package/dist/capture/affected.mjs.map +1 -1
  73. package/dist/capture/attempt-logger.mjs +84 -0
  74. package/dist/capture/attempt-logger.mjs.map +1 -0
  75. package/dist/capture/browsers.d.mts +35 -0
  76. package/dist/capture/browsers.mjs +67 -0
  77. package/dist/capture/browsers.mjs.map +1 -0
  78. package/dist/capture/build-state/deliver.d.mts +32 -0
  79. package/dist/capture/build-state/deliver.mjs +118 -0
  80. package/dist/capture/build-state/deliver.mjs.map +1 -0
  81. package/dist/capture/build-state/index.d.mts +8 -0
  82. package/dist/capture/build-state/notify-job.d.mts +56 -0
  83. package/dist/capture/build-state/notify-job.mjs +133 -0
  84. package/dist/capture/build-state/notify-job.mjs.map +1 -0
  85. package/dist/capture/build-state/publisher.d.mts +23 -0
  86. package/dist/capture/build-state/publisher.mjs +107 -0
  87. package/dist/capture/build-state/publisher.mjs.map +1 -0
  88. package/dist/capture/build-state/resume.d.mts +9 -0
  89. package/dist/capture/build-state/resume.mjs +47 -0
  90. package/dist/capture/build-state/resume.mjs.map +1 -0
  91. package/dist/capture/build-state/types.d.mts +49 -0
  92. package/dist/capture/catalog/catalogue.d.mts +22 -0
  93. package/dist/capture/catalog/catalogue.mjs +53 -0
  94. package/dist/capture/catalog/catalogue.mjs.map +1 -0
  95. package/dist/capture/catalog/components.d.mts +23 -0
  96. package/dist/capture/catalog/components.mjs +55 -0
  97. package/dist/capture/catalog/components.mjs.map +1 -0
  98. package/dist/capture/catalog/history.d.mts +30 -0
  99. package/dist/capture/catalog/history.mjs +51 -0
  100. package/dist/capture/catalog/history.mjs.map +1 -0
  101. package/dist/capture/catalog/hook.d.mts +14 -0
  102. package/dist/capture/catalog/hook.mjs +19 -0
  103. package/dist/capture/catalog/hook.mjs.map +1 -0
  104. package/dist/capture/catalog/index.d.mts +11 -0
  105. package/dist/capture/catalog/load.d.mts +26 -0
  106. package/dist/capture/catalog/load.mjs +49 -0
  107. package/dist/capture/catalog/load.mjs.map +1 -0
  108. package/dist/capture/catalog/parse.d.mts +15 -0
  109. package/dist/capture/catalog/parse.mjs +56 -0
  110. package/dist/capture/catalog/parse.mjs.map +1 -0
  111. package/dist/capture/catalog/read.d.mts +12 -0
  112. package/dist/capture/catalog/read.mjs +33 -0
  113. package/dist/capture/catalog/read.mjs.map +1 -0
  114. package/dist/capture/catalog/types.d.mts +33 -0
  115. package/dist/capture/consumer/capture-handler.d.mts +58 -0
  116. package/dist/capture/consumer/capture-handler.mjs +118 -0
  117. package/dist/capture/consumer/capture-handler.mjs.map +1 -0
  118. package/dist/capture/consumer/idle.d.mts +17 -0
  119. package/dist/capture/consumer/idle.mjs +25 -0
  120. package/dist/capture/consumer/idle.mjs.map +1 -0
  121. package/dist/capture/consumer/index.d.mts +13 -0
  122. package/dist/capture/consumer/index.mjs +139 -0
  123. package/dist/capture/consumer/index.mjs.map +1 -0
  124. package/dist/capture/consumer/loop.mjs +110 -0
  125. package/dist/capture/consumer/loop.mjs.map +1 -0
  126. package/dist/capture/consumer/metrics.d.mts +41 -0
  127. package/dist/capture/consumer/metrics.mjs +38 -0
  128. package/dist/capture/consumer/metrics.mjs.map +1 -0
  129. package/dist/capture/consumer/retry.d.mts +33 -0
  130. package/dist/capture/consumer/retry.mjs +34 -0
  131. package/dist/capture/consumer/retry.mjs.map +1 -0
  132. package/dist/capture/consumer/run-job.mjs +195 -0
  133. package/dist/capture/consumer/run-job.mjs.map +1 -0
  134. package/dist/capture/consumer/startup.mjs +38 -0
  135. package/dist/capture/consumer/startup.mjs.map +1 -0
  136. package/dist/capture/consumer/types.d.mts +97 -0
  137. package/dist/capture/crash-failure.d.mts +25 -0
  138. package/dist/capture/crash-failure.mjs +70 -0
  139. package/dist/capture/crash-failure.mjs.map +1 -0
  140. package/dist/capture/db-queue/cadence.d.mts +32 -0
  141. package/dist/capture/db-queue/cadence.mjs +38 -0
  142. package/dist/capture/db-queue/cadence.mjs.map +1 -0
  143. package/dist/capture/db-queue/claim.d.mts +9 -0
  144. package/dist/capture/db-queue/claim.mjs +132 -0
  145. package/dist/capture/db-queue/claim.mjs.map +1 -0
  146. package/dist/capture/db-queue/clock.mjs +12 -0
  147. package/dist/capture/db-queue/clock.mjs.map +1 -0
  148. package/dist/capture/db-queue/codec.mjs +76 -0
  149. package/dist/capture/db-queue/codec.mjs.map +1 -0
  150. package/dist/capture/db-queue/index.d.mts +20 -0
  151. package/dist/capture/db-queue/index.mjs +89 -0
  152. package/dist/capture/db-queue/index.mjs.map +1 -0
  153. package/dist/capture/db-queue/keeper.d.mts +36 -0
  154. package/dist/capture/db-queue/keeper.mjs +92 -0
  155. package/dist/capture/db-queue/keeper.mjs.map +1 -0
  156. package/dist/capture/db-queue/lease.mjs +46 -0
  157. package/dist/capture/db-queue/lease.mjs.map +1 -0
  158. package/dist/capture/db-queue/lifecycle.mjs +36 -0
  159. package/dist/capture/db-queue/lifecycle.mjs.map +1 -0
  160. package/dist/capture/db-queue/operations.mjs +94 -0
  161. package/dist/capture/db-queue/operations.mjs.map +1 -0
  162. package/dist/capture/db-queue/poll-loop.d.mts +30 -0
  163. package/dist/capture/db-queue/poll-loop.mjs +89 -0
  164. package/dist/capture/db-queue/poll-loop.mjs.map +1 -0
  165. package/dist/capture/db-queue/poll.d.mts +10 -0
  166. package/dist/capture/db-queue/poll.mjs +69 -0
  167. package/dist/capture/db-queue/poll.mjs.map +1 -0
  168. package/dist/capture/db-queue/timers.d.mts +12 -0
  169. package/dist/capture/db-queue/timers.mjs +37 -0
  170. package/dist/capture/db-queue/timers.mjs.map +1 -0
  171. package/dist/capture/db-queue/types.d.mts +83 -0
  172. package/dist/capture/db-queue/types.mjs +12 -0
  173. package/dist/capture/db-queue/types.mjs.map +1 -0
  174. package/dist/capture/db-queue/wake.mjs +48 -0
  175. package/dist/capture/db-queue/wake.mjs.map +1 -0
  176. package/dist/capture/design.mjs +39 -0
  177. package/dist/capture/design.mjs.map +1 -0
  178. package/dist/capture/diff-record.mjs +23 -3
  179. package/dist/capture/diff-record.mjs.map +1 -1
  180. package/dist/capture/dispatch.d.mts +12 -7
  181. package/dist/capture/dispatch.mjs +86 -107
  182. package/dist/capture/dispatch.mjs.map +1 -1
  183. package/dist/capture/enrich.mjs +60 -0
  184. package/dist/capture/enrich.mjs.map +1 -0
  185. package/dist/capture/failure-sets.mjs +22 -0
  186. package/dist/capture/failure-sets.mjs.map +1 -0
  187. package/dist/capture/fingerprint.d.mts +42 -0
  188. package/dist/capture/fingerprint.mjs +83 -0
  189. package/dist/capture/fingerprint.mjs.map +1 -0
  190. package/dist/capture/forked/channel.d.mts +25 -0
  191. package/dist/capture/forked/child.d.mts +34 -0
  192. package/dist/capture/forked/child.mjs +202 -0
  193. package/dist/capture/forked/child.mjs.map +1 -0
  194. package/dist/capture/forked/crash-tracker.mjs +23 -0
  195. package/dist/capture/forked/crash-tracker.mjs.map +1 -0
  196. package/dist/capture/forked/decision.d.mts +26 -0
  197. package/dist/capture/forked/decision.mjs +27 -0
  198. package/dist/capture/forked/decision.mjs.map +1 -0
  199. package/dist/capture/forked/exit.mjs +12 -0
  200. package/dist/capture/forked/exit.mjs.map +1 -0
  201. package/dist/capture/forked/fork.d.mts +31 -0
  202. package/dist/capture/forked/fork.mjs +93 -0
  203. package/dist/capture/forked/fork.mjs.map +1 -0
  204. package/dist/capture/forked/forked-capture.d.mts +40 -0
  205. package/dist/capture/forked/forked-capture.mjs +240 -0
  206. package/dist/capture/forked/forked-capture.mjs.map +1 -0
  207. package/dist/capture/forked/lease-book.mjs +46 -0
  208. package/dist/capture/forked/lease-book.mjs.map +1 -0
  209. package/dist/capture/forked/protocol.d.mts +38 -0
  210. package/dist/capture/forked/protocol.mjs +25 -0
  211. package/dist/capture/forked/protocol.mjs.map +1 -0
  212. package/dist/capture/forked/runs.mjs +75 -0
  213. package/dist/capture/forked/runs.mjs.map +1 -0
  214. package/dist/capture/isolate.mjs +141 -0
  215. package/dist/capture/isolate.mjs.map +1 -0
  216. package/dist/capture/job-lease.d.mts +28 -0
  217. package/dist/capture/job-lease.mjs +57 -0
  218. package/dist/capture/job-lease.mjs.map +1 -0
  219. package/dist/capture/matrix.mjs +298 -0
  220. package/dist/capture/matrix.mjs.map +1 -0
  221. package/dist/capture/modes.d.mts +25 -0
  222. package/dist/capture/modes.mjs +107 -0
  223. package/dist/capture/modes.mjs.map +1 -0
  224. package/dist/capture/orchestrator.d.mts +37 -5
  225. package/dist/capture/orchestrator.mjs +118 -107
  226. package/dist/capture/orchestrator.mjs.map +1 -1
  227. package/dist/capture/pipeline.d.mts +9 -2
  228. package/dist/capture/pipeline.mjs +250 -45
  229. package/dist/capture/pipeline.mjs.map +1 -1
  230. package/dist/capture/progress.d.mts +11 -0
  231. package/dist/capture/progress.mjs +28 -0
  232. package/dist/capture/progress.mjs.map +1 -0
  233. package/dist/capture/publish/accept.d.mts +39 -0
  234. package/dist/capture/publish/accept.mjs +28 -0
  235. package/dist/capture/publish/accept.mjs.map +1 -0
  236. package/dist/capture/publish/complete.d.mts +46 -0
  237. package/dist/capture/publish/complete.mjs +64 -0
  238. package/dist/capture/publish/complete.mjs.map +1 -0
  239. package/dist/capture/publish/extract.d.mts +20 -0
  240. package/dist/capture/publish/extract.mjs +119 -0
  241. package/dist/capture/publish/extract.mjs.map +1 -0
  242. package/dist/capture/publish/handler.d.mts +28 -0
  243. package/dist/capture/publish/handler.mjs +62 -0
  244. package/dist/capture/publish/handler.mjs.map +1 -0
  245. package/dist/capture/publish/index.d.mts +10 -0
  246. package/dist/capture/publish/limits.d.mts +37 -0
  247. package/dist/capture/publish/limits.mjs +39 -0
  248. package/dist/capture/publish/limits.mjs.map +1 -0
  249. package/dist/capture/publish/published.d.mts +15 -0
  250. package/dist/capture/publish/published.mjs +18 -0
  251. package/dist/capture/publish/published.mjs.map +1 -0
  252. package/dist/capture/publish/run.d.mts +33 -0
  253. package/dist/capture/publish/run.mjs +30 -0
  254. package/dist/capture/publish/run.mjs.map +1 -0
  255. package/dist/capture/rediff.d.mts +1 -1
  256. package/dist/capture/rediff.mjs +4 -3
  257. package/dist/capture/rediff.mjs.map +1 -1
  258. package/dist/capture/render.mjs +53 -0
  259. package/dist/capture/render.mjs.map +1 -0
  260. package/dist/capture/scratch.d.mts +44 -0
  261. package/dist/capture/scratch.mjs +138 -0
  262. package/dist/capture/scratch.mjs.map +1 -0
  263. package/dist/capture/shard-plan.mjs +128 -0
  264. package/dist/capture/shard-plan.mjs.map +1 -0
  265. package/dist/capture/shards/context.mjs +51 -0
  266. package/dist/capture/shards/context.mjs.map +1 -0
  267. package/dist/capture/shards/execute.mjs +174 -0
  268. package/dist/capture/shards/execute.mjs.map +1 -0
  269. package/dist/capture/shards/fan-out.mjs +126 -0
  270. package/dist/capture/shards/fan-out.mjs.map +1 -0
  271. package/dist/capture/shards/history.mjs +35 -0
  272. package/dist/capture/shards/history.mjs.map +1 -0
  273. package/dist/capture/shards/reduce.mjs +97 -0
  274. package/dist/capture/shards/reduce.mjs.map +1 -0
  275. package/dist/capture/sizing.mjs +1 -11
  276. package/dist/capture/sizing.mjs.map +1 -1
  277. package/dist/capture/skip-checks.mjs +2 -65
  278. package/dist/capture/skip-checks.mjs.map +1 -1
  279. package/dist/capture/statics.d.mts +7 -8
  280. package/dist/capture/statics.mjs +25 -71
  281. package/dist/capture/statics.mjs.map +1 -1
  282. package/dist/capture/storybook-urls.d.mts +40 -0
  283. package/dist/capture/storybook-urls.mjs +41 -0
  284. package/dist/capture/storybook-urls.mjs.map +1 -0
  285. package/dist/capture/storybook.d.mts +12 -4
  286. package/dist/capture/storybook.mjs +56 -16
  287. package/dist/capture/storybook.mjs.map +1 -1
  288. package/dist/capture/target.mjs +21 -0
  289. package/dist/capture/target.mjs.map +1 -0
  290. package/dist/capture/targets.d.mts +25 -0
  291. package/dist/capture/targets.mjs +32 -0
  292. package/dist/capture/targets.mjs.map +1 -0
  293. package/dist/capture/tests.d.mts +37 -0
  294. package/dist/capture/tests.mjs +126 -0
  295. package/dist/capture/tests.mjs.map +1 -0
  296. package/dist/capture/token-chunks.mjs +74 -0
  297. package/dist/capture/token-chunks.mjs.map +1 -0
  298. package/dist/capture.d.mts +60 -7
  299. package/dist/capture.mjs +48 -3
  300. package/dist/config.d.mts +121 -12
  301. package/dist/config.mjs +9 -6
  302. package/dist/config.mjs.map +1 -1
  303. package/dist/diff.d.mts +9 -3
  304. package/dist/diff.mjs +16 -3
  305. package/dist/diff.mjs.map +1 -0
  306. package/dist/index.d.mts +1 -1
  307. package/dist/insights/budget.d.mts +20 -0
  308. package/dist/insights/budget.mjs +33 -0
  309. package/dist/insights/budget.mjs.map +1 -0
  310. package/dist/insights/envelope.d.mts +59 -0
  311. package/dist/insights/envelope.mjs +60 -0
  312. package/dist/insights/envelope.mjs.map +1 -0
  313. package/dist/insights/evidence-health.d.mts +32 -0
  314. package/dist/insights/evidence-health.mjs +28 -0
  315. package/dist/insights/evidence-health.mjs.map +1 -0
  316. package/dist/insights/evidence.d.mts +55 -0
  317. package/dist/insights/evidence.mjs +64 -0
  318. package/dist/insights/evidence.mjs.map +1 -0
  319. package/dist/insights/hash.d.mts +18 -0
  320. package/dist/insights/hash.mjs +21 -0
  321. package/dist/insights/hash.mjs.map +1 -0
  322. package/dist/insights/limits.d.mts +24 -0
  323. package/dist/insights/limits.mjs +24 -0
  324. package/dist/insights/limits.mjs.map +1 -0
  325. package/dist/insights/prompts/v1.d.mts +13 -0
  326. package/dist/insights/prompts/v1.mjs +34 -0
  327. package/dist/insights/prompts/v1.mjs.map +1 -0
  328. package/dist/insights/redact.d.mts +9 -0
  329. package/dist/insights/redact.mjs +24 -0
  330. package/dist/insights/redact.mjs.map +1 -0
  331. package/dist/insights/text.mjs +44 -0
  332. package/dist/insights/text.mjs.map +1 -0
  333. package/dist/insights.d.mts +11 -0
  334. package/dist/insights.mjs +12 -0
  335. package/dist/logger.mjs +17 -1
  336. package/dist/logger.mjs.map +1 -1
  337. package/dist/models/ai-budget-alert.d.mts +23 -0
  338. package/dist/models/ai-budget-alert.mjs +38 -0
  339. package/dist/models/ai-budget-alert.mjs.map +1 -0
  340. package/dist/models/ai-usage.d.mts +40 -0
  341. package/dist/models/ai-usage.mjs +61 -0
  342. package/dist/models/ai-usage.mjs.map +1 -0
  343. package/dist/models/baseline-archive.d.mts +84 -0
  344. package/dist/models/baseline-archive.mjs +106 -0
  345. package/dist/models/baseline-archive.mjs.map +1 -0
  346. package/dist/models/baseline-export.d.mts +19 -0
  347. package/dist/models/baseline-export.mjs +136 -0
  348. package/dist/models/baseline-export.mjs.map +1 -0
  349. package/dist/models/baseline-import.mjs +118 -0
  350. package/dist/models/baseline-import.mjs.map +1 -0
  351. package/dist/models/baseline-transfer.d.mts +49 -0
  352. package/dist/models/baseline-transfer.mjs +121 -0
  353. package/dist/models/baseline-transfer.mjs.map +1 -0
  354. package/dist/models/baseline.d.mts +32 -5
  355. package/dist/models/baseline.mjs +65 -20
  356. package/dist/models/baseline.mjs.map +1 -1
  357. package/dist/models/build-component.d.mts +28 -0
  358. package/dist/models/build-component.mjs +55 -0
  359. package/dist/models/build-component.mjs.map +1 -0
  360. package/dist/models/build-review-status.d.mts +29 -0
  361. package/dist/models/build-review-status.mjs +37 -0
  362. package/dist/models/build-review-status.mjs.map +1 -0
  363. package/dist/models/build-review.d.mts +28 -0
  364. package/dist/models/build-review.mjs +54 -0
  365. package/dist/models/build-review.mjs.map +1 -0
  366. package/dist/models/build.d.mts +25 -3
  367. package/dist/models/build.mjs +50 -11
  368. package/dist/models/build.mjs.map +1 -1
  369. package/dist/models/capture-attempt.d.mts +7 -1
  370. package/dist/models/capture-attempt.mjs +29 -4
  371. package/dist/models/capture-attempt.mjs.map +1 -1
  372. package/dist/models/capture-job.d.mts +106 -0
  373. package/dist/models/capture-job.mjs +288 -0
  374. package/dist/models/capture-job.mjs.map +1 -0
  375. package/dist/models/capture-log.d.mts +1 -1
  376. package/dist/models/capture-log.mjs +4 -3
  377. package/dist/models/capture-log.mjs.map +1 -1
  378. package/dist/models/capture-shard.d.mts +62 -0
  379. package/dist/models/capture-shard.mjs +190 -0
  380. package/dist/models/capture-shard.mjs.map +1 -0
  381. package/dist/models/changeset.d.mts +48 -0
  382. package/dist/models/changeset.mjs +81 -0
  383. package/dist/models/changeset.mjs.map +1 -0
  384. package/dist/models/columns.mjs +19 -0
  385. package/dist/models/columns.mjs.map +1 -0
  386. package/dist/models/comment.d.mts +4 -1
  387. package/dist/models/comment.mjs +7 -4
  388. package/dist/models/comment.mjs.map +1 -1
  389. package/dist/models/component.d.mts +43 -0
  390. package/dist/models/component.mjs +60 -0
  391. package/dist/models/component.mjs.map +1 -0
  392. package/dist/models/connections.d.mts +41 -0
  393. package/dist/models/connections.mjs +112 -0
  394. package/dist/models/connections.mjs.map +1 -0
  395. package/dist/models/content-ref.d.mts +1 -1
  396. package/dist/models/content-ref.mjs +1 -1
  397. package/dist/models/content-ref.mjs.map +1 -1
  398. package/dist/models/credentials.mjs +12 -2
  399. package/dist/models/credentials.mjs.map +1 -1
  400. package/dist/models/design-credentials.d.mts +41 -0
  401. package/dist/models/design-credentials.mjs +86 -0
  402. package/dist/models/design-credentials.mjs.map +1 -0
  403. package/dist/models/design-health.d.mts +16 -0
  404. package/dist/models/design-health.mjs +25 -0
  405. package/dist/models/design-health.mjs.map +1 -0
  406. package/dist/models/design-nodes.d.mts +83 -0
  407. package/dist/models/design-nodes.mjs +222 -0
  408. package/dist/models/design-nodes.mjs.map +1 -0
  409. package/dist/models/design-renders.mjs +74 -0
  410. package/dist/models/design-renders.mjs.map +1 -0
  411. package/dist/models/design-resolve.d.mts +29 -0
  412. package/dist/models/design-resolve.mjs +41 -0
  413. package/dist/models/design-resolve.mjs.map +1 -0
  414. package/dist/models/design.d.mts +39 -0
  415. package/dist/models/design.mjs +90 -0
  416. package/dist/models/design.mjs.map +1 -0
  417. package/dist/models/inherited-ratio.d.mts +21 -0
  418. package/dist/models/inherited-ratio.mjs +22 -0
  419. package/dist/models/inherited-ratio.mjs.map +1 -0
  420. package/dist/models/insight.d.mts +70 -0
  421. package/dist/models/insight.mjs +162 -0
  422. package/dist/models/insight.mjs.map +1 -0
  423. package/dist/models/label.d.mts +4 -2
  424. package/dist/models/label.mjs +25 -10
  425. package/dist/models/label.mjs.map +1 -1
  426. package/dist/models/member.d.mts +1 -1
  427. package/dist/models/member.mjs +5 -4
  428. package/dist/models/member.mjs.map +1 -1
  429. package/dist/models/notification-channel.d.mts +37 -3
  430. package/dist/models/notification-channel.mjs +88 -9
  431. package/dist/models/notification-channel.mjs.map +1 -1
  432. package/dist/models/notification-subscription.d.mts +1 -2
  433. package/dist/models/notification-subscription.mjs +7 -9
  434. package/dist/models/notification-subscription.mjs.map +1 -1
  435. package/dist/models/project-group-mapping.d.mts +6 -1
  436. package/dist/models/project-group-mapping.mjs +14 -4
  437. package/dist/models/project-group-mapping.mjs.map +1 -1
  438. package/dist/models/project.d.mts +9 -1
  439. package/dist/models/project.mjs +9 -4
  440. package/dist/models/project.mjs.map +1 -1
  441. package/dist/models/public-origin.d.mts +63 -0
  442. package/dist/models/public-origin.mjs +114 -0
  443. package/dist/models/public-origin.mjs.map +1 -0
  444. package/dist/models/review-switch-off.d.mts +14 -0
  445. package/dist/models/review-switch-off.mjs +26 -0
  446. package/dist/models/review-switch-off.mjs.map +1 -0
  447. package/dist/models/site-settings.d.mts +23 -0
  448. package/dist/models/site-settings.mjs +48 -0
  449. package/dist/models/site-settings.mjs.map +1 -0
  450. package/dist/models/snapshot.d.mts +36 -1
  451. package/dist/models/snapshot.mjs +50 -3
  452. package/dist/models/snapshot.mjs.map +1 -1
  453. package/dist/models/token.d.mts +13 -1
  454. package/dist/models/token.mjs +23 -5
  455. package/dist/models/token.mjs.map +1 -1
  456. package/dist/models/usage.d.mts +76 -0
  457. package/dist/models/usage.mjs +149 -0
  458. package/dist/models/usage.mjs.map +1 -0
  459. package/dist/models/user.d.mts +1 -1
  460. package/dist/models/user.mjs +4 -3
  461. package/dist/models/user.mjs.map +1 -1
  462. package/dist/models/webhook.d.mts +15 -1
  463. package/dist/models/webhook.mjs +33 -4
  464. package/dist/models/webhook.mjs.map +1 -1
  465. package/dist/models.d.mts +26 -3
  466. package/dist/models.mjs +27 -5
  467. package/dist/options.d.mts +18 -0
  468. package/dist/options.mjs +90 -0
  469. package/dist/options.mjs.map +1 -0
  470. package/dist/paths.d.mts +19 -4
  471. package/dist/paths.mjs +51 -10
  472. package/dist/paths.mjs.map +1 -1
  473. package/dist/retention/ai.d.mts +18 -0
  474. package/dist/retention/ai.mjs +23 -0
  475. package/dist/retention/ai.mjs.map +1 -0
  476. package/dist/retention/pending.mjs +47 -0
  477. package/dist/retention/pending.mjs.map +1 -0
  478. package/dist/retention/screenshot-sweep.d.mts +51 -0
  479. package/dist/retention/screenshot-sweep.mjs +128 -0
  480. package/dist/retention/screenshot-sweep.mjs.map +1 -0
  481. package/dist/retention.d.mts +16 -2
  482. package/dist/retention.mjs +71 -14
  483. package/dist/retention.mjs.map +1 -1
  484. package/dist/schema/baseline.d.mts +6 -0
  485. package/dist/schema/build-component.d.mts +25 -0
  486. package/dist/schema/build-review.d.mts +17 -0
  487. package/dist/schema/build.d.mts +7 -0
  488. package/dist/schema/capture-job.d.mts +60 -0
  489. package/dist/schema/capture-shard.d.mts +35 -0
  490. package/dist/schema/comment.d.mts +2 -0
  491. package/dist/schema/connection.d.mts +24 -0
  492. package/dist/schema/design.d.mts +58 -0
  493. package/dist/schema/insight.d.mts +54 -0
  494. package/dist/schema/notification.d.mts +4 -0
  495. package/dist/schema/project.d.mts +21 -0
  496. package/dist/schema/site-setting.d.mts +21 -0
  497. package/dist/schema/snapshot.d.mts +12 -0
  498. package/dist/schema/usage.d.mts +30 -0
  499. package/dist/schema.d.mts +11 -3
  500. package/dist/serverless.d.mts +21 -0
  501. package/dist/serverless.mjs +37 -0
  502. package/dist/serverless.mjs.map +1 -0
  503. package/dist/test-helpers/create-project.d.mts +1 -1
  504. package/dist/test-helpers/deferred.d.mts +39 -0
  505. package/dist/test-helpers/deferred.mjs +61 -0
  506. package/dist/test-helpers/deferred.mjs.map +1 -0
  507. package/dist/test-helpers/design-contract.d.mts +21 -0
  508. package/dist/test-helpers/design-contract.mjs +40 -0
  509. package/dist/test-helpers/design-contract.mjs.map +1 -0
  510. package/dist/test-helpers/fake-database.mjs +14 -2
  511. package/dist/test-helpers/fake-database.mjs.map +1 -1
  512. package/dist/test-helpers/fake-design.d.mts +43 -0
  513. package/dist/test-helpers/fake-design.mjs +114 -0
  514. package/dist/test-helpers/fake-design.mjs.map +1 -0
  515. package/dist/test-helpers/fake-notifier.d.mts +21 -0
  516. package/dist/test-helpers/fake-notifier.mjs +44 -0
  517. package/dist/test-helpers/fake-notifier.mjs.map +1 -0
  518. package/dist/test-helpers/fake-tables.mjs +255 -67
  519. package/dist/test-helpers/fake-tables.mjs.map +1 -1
  520. package/dist/test-helpers/manual-clock.d.mts +27 -0
  521. package/dist/test-helpers/manual-clock.mjs +56 -0
  522. package/dist/test-helpers/manual-clock.mjs.map +1 -0
  523. package/dist/test-helpers/notifier-contract.d.mts +7 -1
  524. package/dist/test-helpers/notifier-contract.mjs +32 -3
  525. package/dist/test-helpers/notifier-contract.mjs.map +1 -1
  526. package/dist/test-helpers/queue-contract.d.mts +16 -2
  527. package/dist/test-helpers/queue-contract.mjs +195 -1
  528. package/dist/test-helpers/queue-contract.mjs.map +1 -1
  529. package/dist/test-helpers/queue-multi-instance.d.mts +20 -0
  530. package/dist/test-helpers/queue-multi-instance.mjs +206 -0
  531. package/dist/test-helpers/queue-multi-instance.mjs.map +1 -0
  532. package/dist/test-helpers/runner-contract.mjs +11 -0
  533. package/dist/test-helpers/runner-contract.mjs.map +1 -1
  534. package/dist/test-helpers/sql-chunks.mjs +23 -0
  535. package/dist/test-helpers/sql-chunks.mjs.map +1 -1
  536. package/dist/test-helpers/storage-contract.mjs +1 -1
  537. package/dist/test-helpers.d.mts +9 -4
  538. package/dist/test-helpers.mjs +8 -3
  539. package/dist/types.d.mts +14 -3
  540. package/dist/types.mjs +21 -4
  541. package/dist/types.mjs.map +1 -1
  542. package/dist/urls.d.mts +4 -0
  543. package/dist/urls.mjs +11 -2
  544. package/dist/urls.mjs.map +1 -1
  545. package/dist/utils/adaptive-concurrency.d.mts +66 -0
  546. package/dist/utils/adaptive-concurrency.mjs +149 -0
  547. package/dist/utils/adaptive-concurrency.mjs.map +1 -0
  548. package/dist/utils/connect-retry.d.mts +26 -0
  549. package/dist/utils/connect-retry.mjs +43 -0
  550. package/dist/utils/connect-retry.mjs.map +1 -0
  551. package/dist/utils/environment.d.mts +19 -0
  552. package/dist/utils/environment.mjs +59 -0
  553. package/dist/utils/environment.mjs.map +1 -0
  554. package/dist/utils/http.d.mts +17 -1
  555. package/dist/utils/http.mjs +22 -3
  556. package/dist/utils/http.mjs.map +1 -1
  557. package/dist/utils/map-adaptive.d.mts +15 -0
  558. package/dist/utils/map-adaptive.mjs +72 -0
  559. package/dist/utils/map-adaptive.mjs.map +1 -0
  560. package/dist/utils/map-limit.d.mts +10 -0
  561. package/dist/utils/map-limit.mjs +32 -0
  562. package/dist/utils/map-limit.mjs.map +1 -0
  563. package/dist/utils/page-limiter.d.mts +37 -0
  564. package/dist/utils/page-limiter.mjs +56 -0
  565. package/dist/utils/page-limiter.mjs.map +1 -0
  566. package/dist/utils/public-origin.d.mts +20 -0
  567. package/dist/utils/public-origin.mjs +75 -0
  568. package/dist/utils/public-origin.mjs.map +1 -0
  569. package/dist/utils/redact.mjs +3 -0
  570. package/dist/utils/redact.mjs.map +1 -1
  571. package/dist/utils/remote-environment.d.mts +39 -0
  572. package/dist/utils/remote-environment.mjs +114 -0
  573. package/dist/utils/remote-environment.mjs.map +1 -0
  574. package/dist/utils/resources.d.mts +64 -0
  575. package/dist/utils/resources.mjs +122 -0
  576. package/dist/utils/resources.mjs.map +1 -0
  577. package/dist/utils/screenshot-store.d.mts +15 -0
  578. package/dist/utils/screenshot-store.mjs +41 -0
  579. package/dist/utils/screenshot-store.mjs.map +1 -0
  580. package/dist/utils/session-gate.d.mts +22 -0
  581. package/dist/utils/session-gate.mjs +53 -0
  582. package/dist/utils/session-gate.mjs.map +1 -0
  583. package/dist/utils/storybook-token.d.mts +81 -0
  584. package/dist/utils/storybook-token.mjs +134 -0
  585. package/dist/utils/storybook-token.mjs.map +1 -0
  586. package/dist/utils/storybook-url.d.mts +15 -0
  587. package/dist/utils/storybook-url.mjs +21 -0
  588. package/dist/utils/storybook-url.mjs.map +1 -0
  589. package/dist/utils/timing.mjs +1 -1
  590. package/dist/utils/timing.mjs.map +1 -1
  591. package/dist/utils/zip-read.mjs +129 -0
  592. package/dist/utils/zip-read.mjs.map +1 -0
  593. package/dist/utils/zip-write.d.mts +18 -0
  594. package/dist/utils/zip-write.mjs +188 -0
  595. package/dist/utils/zip-write.mjs.map +1 -0
  596. package/dist/utils.d.mts +17 -3
  597. package/dist/utils.mjs +18 -4
  598. package/package.json +32 -23
  599. package/dist/adapter/capture-runner.mjs.map +0 -1
  600. package/dist/adapter/git-host/comments.d.mts +0 -41
  601. package/dist/adapter/git-host/comments.mjs +0 -54
  602. package/dist/adapter/git-host/comments.mjs.map +0 -1
  603. package/dist/adapter/git-host/helpers.d.mts +0 -13
  604. package/dist/adapter/git-host/helpers.mjs +0 -30
  605. package/dist/adapter/git-host/helpers.mjs.map +0 -1
  606. package/dist/adapter/git-host.d.mts +0 -42
  607. package/dist/capture/queue.d.mts +0 -38
  608. package/dist/capture/queue.mjs +0 -98
  609. package/dist/capture/queue.mjs.map +0 -1
  610. package/dist/capture/recovery.d.mts +0 -21
  611. package/dist/capture/recovery.mjs +0 -83
  612. package/dist/capture/recovery.mjs.map +0 -1
  613. package/dist/capture/status-fanout.mjs +0 -72
  614. package/dist/capture/status-fanout.mjs.map +0 -1
  615. package/dist/diff/engine.d.mts +0 -16
  616. package/dist/diff/engine.mjs +0 -87
  617. package/dist/diff/engine.mjs.map +0 -1
  618. package/dist/diff/options.d.mts +0 -42
  619. package/dist/diff/options.mjs +0 -15
  620. package/dist/diff/options.mjs.map +0 -1
  621. package/dist/models/status-config.d.mts +0 -38
  622. package/dist/models/status-config.mjs +0 -63
  623. package/dist/models/status-config.mjs.map +0 -1
  624. package/dist/schema/status-config.d.mts +0 -16
  625. package/dist/test-helpers/git-contract.d.mts +0 -11
  626. package/dist/test-helpers/git-contract.mjs +0 -34
  627. package/dist/test-helpers/git-contract.mjs.map +0 -1
  628. /package/dist/adapter/{git-host.mjs → oauth.mjs} +0 -0
@@ -7,27 +7,54 @@ import { WebhookTables } from "./webhook.mjs";
7
7
  import { Baseline } from "../schema/baseline.mjs";
8
8
  import { Table } from "drizzle-orm";
9
9
  //#region src/models/baseline.d.ts
10
+ /** Extra identity/metadata for {@link BaselineModel.upsert}. */
11
+ interface BaselineUpsertOptions {
12
+ /** Engine the baseline belongs to (default chromium). */
13
+ browser?: string;
14
+ /** Resolved mode definition as JSON (display only). */
15
+ mode?: string | null;
16
+ /** JSON `Fingerprint` behind the `infraHash` being written. */
17
+ fingerprint?: string | null;
18
+ }
10
19
  /** Tables required by {@link BaselineModel}. */
11
20
  interface BaselineTables {
12
21
  baselines: Table;
13
22
  }
14
23
  /** Data operations for baseline screenshots. */
15
24
  declare class BaselineModel {
16
- private readonly db;
25
+ #private;
17
26
  private readonly tables;
18
27
  private readonly storage;
19
28
  private readonly secret?;
20
29
  private readonly webhookTables?;
30
+ private readonly db;
21
31
  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>;
32
+ getFor(projectId: string, storyId: string, viewport: string, branch: string, browser?: string): Promise<Baseline | null>;
33
+ resolve(projectId: string, storyId: string, viewport: string, branch: string, defaultBranch: string, browser?: string): Promise<Baseline | null>;
34
+ /** Whether the project has any baseline captured on this engine. */
35
+ hasAny(projectId: string, browser: string): Promise<boolean>;
36
+ /**
37
+ * Record the fingerprint of a baseline that predates fingerprints, without touching its
38
+ * screenshot. Lets the next environment change be detected instead of diffing as a regression.
39
+ */
40
+ backfillFingerprint(baseline: Baseline, infraHash: string, fingerprint: string): Promise<void>;
41
+ /**
42
+ * The content-addressed object a baseline should point at. A source that already is one is
43
+ * reused as is (no copy); a legacy per-build path is stored once by content.
44
+ */
45
+ private contentFor;
24
46
  read(baseline: Baseline): Promise<Buffer>;
25
- upsert(projectId: string, storyId: string, viewport: string, branch: string, snapshotId: string, sourcePath: string, infraHash?: string | null): Promise<Baseline>;
47
+ upsert(projectId: string, storyId: string, viewport: string, branch: string, snapshotId: string, sourcePath: string, infraHash?: string | null, options?: BaselineUpsertOptions): Promise<Baseline>;
26
48
  list(projectId: string): Promise<Baseline[]>;
49
+ /**
50
+ * Delete a baseline's own file. A content-addressed screenshot is shared with snapshots and other
51
+ * baselines, so it is left for the retention sweep instead.
52
+ */
53
+ private discardFile;
27
54
  removeOrphans(projectId: string, validStoryIds: Set<string>): Promise<number>;
28
55
  removeForBranch(projectId: string, branch: string): Promise<number>;
29
56
  removeStaleBranches(projectId: string, staleBranches: ReadonlySet<string>): Promise<number>;
30
57
  }
31
58
  //#endregion
32
- export { BaselineModel, BaselineTables };
59
+ export { BaselineModel, BaselineTables, BaselineUpsertOptions };
33
60
  //# sourceMappingURL=baseline.d.mts.map
@@ -1,19 +1,22 @@
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
+ import { isContentPath } from "../paths.mjs";
9
+ import { storeScreenshot, withContentClaim } from "../utils/screenshot-store.mjs";
10
+ import { and, eq } from "drizzle-orm";
8
11
  //#region src/models/baseline.ts
9
12
  /** Per-branch baseline screenshots with default-branch fallback. */
10
13
  /** Data operations for baseline screenshots. */
11
14
  var BaselineModel = class {
12
- db;
13
15
  tables;
14
16
  storage;
15
17
  secret;
16
18
  webhookTables;
19
+ db;
17
20
  constructor(db, tables, storage, secret, webhookTables) {
18
21
  this.db = db;
19
22
  this.tables = tables ?? { baselines: db.tables.baselines };
@@ -21,39 +24,70 @@ var BaselineModel = class {
21
24
  this.secret = secret;
22
25
  this.webhookTables = webhookTables;
23
26
  }
24
- async getFor(projectId, storyId, viewport, branch) {
27
+ async getFor(projectId, storyId, viewport, branch, browser = DEFAULT_BROWSER) {
25
28
  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)),
29
+ 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
30
  limit: 1
28
31
  }))[0] ?? null;
29
32
  }
30
- async resolve(projectId, storyId, viewport, branch, defaultBranch) {
33
+ async resolve(projectId, storyId, viewport, branch, defaultBranch, browser = DEFAULT_BROWSER) {
31
34
  if (branch !== defaultBranch) {
32
- const own = await this.getFor(projectId, storyId, viewport, branch);
35
+ const own = await this.getFor(projectId, storyId, viewport, branch, browser);
33
36
  if (own) return own;
34
37
  }
35
- return this.getFor(projectId, storyId, viewport, defaultBranch);
38
+ return this.getFor(projectId, storyId, viewport, defaultBranch, browser);
39
+ }
40
+ /** Whether the project has any baseline captured on this engine. */
41
+ async hasAny(projectId, browser) {
42
+ return (await this.db.list(this.tables.baselines, {
43
+ where: and(eq(col(this.tables.baselines, "projectId"), projectId), eq(col(this.tables.baselines, "browser"), browser)),
44
+ limit: 1
45
+ })).length > 0;
46
+ }
47
+ /**
48
+ * Record the fingerprint of a baseline that predates fingerprints, without touching its
49
+ * screenshot. Lets the next environment change be detected instead of diffing as a regression.
50
+ */
51
+ async backfillFingerprint(baseline, infraHash, fingerprint) {
52
+ await this.db.update(this.tables.baselines, baseline.id, {
53
+ infraHash,
54
+ fingerprint
55
+ });
56
+ }
57
+ /**
58
+ * The content-addressed object a baseline should point at. A source that already is one is
59
+ * reused as is (no copy); a legacy per-build path is stored once by content.
60
+ */
61
+ async contentFor(projectId, sourcePath) {
62
+ if (isContentPath(projectId, sourcePath)) return sourcePath;
63
+ return await storeScreenshot(this.storage, projectId, await this.storage.read(sourcePath));
36
64
  }
37
65
  async read(baseline) {
38
66
  return await this.storage.read(baseline.screenshotPath);
39
67
  }
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);
68
+ async upsert(projectId, storyId, viewport, branch, snapshotId, sourcePath, infraHash, options = {}) {
69
+ const write = async () => await this.#upsertAt(projectId, storyId, viewport, branch, snapshotId, await this.contentFor(projectId, sourcePath), infraHash, options);
70
+ return isContentPath(projectId, sourcePath) ? await write() : await withContentClaim(this.storage, projectId, write);
71
+ }
72
+ async #upsertAt(projectId, storyId, viewport, branch, snapshotId, screenshotPath, infraHash, options) {
73
+ const browser = options.browser ?? "chromium";
74
+ const existing = await this.getFor(projectId, storyId, viewport, branch, browser);
45
75
  let baseline;
46
76
  if (existing) {
47
77
  baseline = await this.db.update(this.tables.baselines, existing.id, {
48
78
  snapshotId,
49
79
  screenshotPath,
50
80
  infraHash: infraHash ?? existing.infraHash ?? null,
81
+ fingerprint: options.fingerprint ?? existing.fingerprint ?? null,
82
+ mode: options.mode ?? existing.mode ?? null,
51
83
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
52
84
  });
53
85
  if (this.webhookTables) await emitWebhookEvent(this.db, this.webhookTables, projectId, "baseline:updated", {
54
86
  baselineId: baseline.id,
55
87
  storyId,
56
88
  viewport,
89
+ browser,
90
+ mode: options.mode ?? null,
57
91
  branch,
58
92
  snapshotId
59
93
  }, this.secret);
@@ -68,6 +102,9 @@ var BaselineModel = class {
68
102
  snapshotId,
69
103
  screenshotPath,
70
104
  infraHash: infraHash ?? null,
105
+ fingerprint: options.fingerprint ?? null,
106
+ mode: options.mode ?? null,
107
+ browser,
71
108
  createdAt: now,
72
109
  updatedAt: now
73
110
  });
@@ -75,6 +112,8 @@ var BaselineModel = class {
75
112
  baselineId: baseline.id,
76
113
  storyId,
77
114
  viewport,
115
+ browser,
116
+ mode: options.mode ?? null,
78
117
  branch,
79
118
  snapshotId
80
119
  }, this.secret);
@@ -82,14 +121,22 @@ var BaselineModel = class {
82
121
  return baseline;
83
122
  }
84
123
  async list(projectId) {
85
- return await this.db.list(this.tables.baselines, { where: eq(getTableColumns(this.tables.baselines)["projectId"], projectId) });
124
+ return await this.db.list(this.tables.baselines, { where: eq(col(this.tables.baselines, "projectId"), projectId) });
125
+ }
126
+ /**
127
+ * Delete a baseline's own file. A content-addressed screenshot is shared with snapshots and other
128
+ * baselines, so it is left for the retention sweep instead.
129
+ */
130
+ async discardFile(baseline) {
131
+ if (isContentPath(baseline.projectId, baseline.screenshotPath)) return;
132
+ try {
133
+ await this.storage.delete(baseline.screenshotPath);
134
+ } catch {}
86
135
  }
87
136
  async removeOrphans(projectId, validStoryIds) {
88
137
  const toRemove = (await this.list(projectId)).filter((baseline) => !validStoryIds.has(baseline.storyId));
89
138
  await Promise.all(toRemove.map(async (baseline) => {
90
- try {
91
- await this.storage.delete(baseline.screenshotPath);
92
- } catch {}
139
+ await this.discardFile(baseline);
93
140
  await this.db.remove(this.tables.baselines, baseline.id);
94
141
  }));
95
142
  return toRemove.length;
@@ -97,9 +144,7 @@ var BaselineModel = class {
97
144
  async removeForBranch(projectId, branch) {
98
145
  const toRemove = (await this.list(projectId)).filter((baseline) => baseline.branch === branch);
99
146
  await Promise.all(toRemove.map(async (baseline) => {
100
- try {
101
- await this.storage.delete(baseline.screenshotPath);
102
- } catch {}
147
+ await this.discardFile(baseline);
103
148
  await this.db.remove(this.tables.baselines, baseline.id);
104
149
  }));
105
150
  return toRemove.length;
@@ -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 { 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 { col } from \"./columns.ts\";\nimport type { WebhookTables } from \"./webhook.ts\";\n\n/** Extra identity/metadata for {@link BaselineModel.upsert}. */\nexport interface BaselineUpsertOptions {\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 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 write = async (): Promise<Baseline> =>\n await this.#upsertAt(\n projectId,\n storyId,\n viewport,\n branch,\n snapshotId,\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 async #upsertAt(\n projectId: string,\n storyId: string,\n viewport: string,\n branch: string,\n snapshotId: 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 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 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 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 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 await this.discardFile(baseline);\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 await this.discardFile(baseline);\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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"}
@@ -0,0 +1,28 @@
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
+ //#region src/models/build-component.d.ts
7
+ /** Tables required by {@link BuildComponentModel}. */
8
+ interface BuildComponentTables {
9
+ buildComponents: Table;
10
+ }
11
+ /** What a build records of one component. */
12
+ type BuildComponentInput = Pick<BuildComponent, "importPath" | "title" | "storyCount" | "hash">;
13
+ /** Data operations for the components each published build holds. */
14
+ declare class BuildComponentModel {
15
+ #private;
16
+ constructor(db: DatabaseAdapter, tables?: BuildComponentTables);
17
+ /** Components of one build, ordered by title then file. */
18
+ listByBuild(buildId: string): Promise<BuildComponent[]>;
19
+ /** Rows of one story file across the project's builds, newest first: "which builds contain it". */
20
+ listByImportPath(projectId: string, importPath: string): Promise<BuildComponent[]>;
21
+ /** Replace a build's rows with `components`; repeating a publish never duplicates. */
22
+ replaceForBuild(projectId: string, buildId: string, components: readonly BuildComponentInput[]): Promise<void>;
23
+ /** Forget a build's rows (the foreign key cascades too; stub tables have none). */
24
+ removeForBuild(buildId: string): Promise<number>;
25
+ }
26
+ //#endregion
27
+ export { BuildComponentInput, BuildComponentModel, BuildComponentTables };
28
+ //# sourceMappingURL=build-component.d.mts.map
@@ -0,0 +1,55 @@
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
+ //#region 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
+ //#endregion
53
+ export { BuildComponentModel };
54
+
55
+ //# 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,29 @@
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
+ //#region src/models/build-review-status.d.ts
5
+ /** What {@link deriveReviewStatus} depends on. */
6
+ interface ReviewStatusInput {
7
+ /** `testing` is on: the snapshot rollup decides and verdicts never gate. */
8
+ testing: boolean;
9
+ /** The project's review switch. */
10
+ review: boolean;
11
+ /** `projects.review_approvals`: approvals a build needs (at least 1). */
12
+ approvalsRequired: number;
13
+ /** Every verdict given to the build, in any order; see {@link latestVerdicts}. */
14
+ reviews: readonly Pick<BuildReview, "id" | "userId" | "verdict" | "createdAt">[];
15
+ }
16
+ /**
17
+ * The status the verdicts give the build, or `null` when they do not decide it (`testing` is on,
18
+ * or review is off and the build was accepted when it was published).
19
+ *
20
+ * - `rejected` when any reviewer's latest verdict is `rejected`;
21
+ * - `approved` once the required approvals are present among the latest verdicts;
22
+ * - `pending` otherwise.
23
+ */
24
+ declare function deriveReviewStatus(input: ReviewStatusInput): "approved" | "rejected" | "pending" | null;
25
+ /** Each reviewer's latest verdict (newest `createdAt`, then newest id), oldest reviewer first. */
26
+ declare function latestVerdicts<T extends Pick<BuildReview, "id" | "userId" | "createdAt">>(reviews: readonly T[]): T[];
27
+ //#endregion
28
+ export { ReviewStatusInput, deriveReviewStatus, latestVerdicts };
29
+ //# sourceMappingURL=build-review-status.d.mts.map
@@ -0,0 +1,37 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ //#region 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
+ //#endregion
35
+ export { deriveReviewStatus, latestVerdicts };
36
+
37
+ //# 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,28 @@
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
+ //#region src/models/build-review.d.ts
8
+ /** Tables required by {@link BuildReviewModel}. */
9
+ interface BuildReviewTables {
10
+ buildReviews: Table;
11
+ }
12
+ /** Data operations for the verdicts reviewers give a build. */
13
+ declare class BuildReviewModel {
14
+ #private;
15
+ constructor(db: DatabaseAdapter, tables?: BuildReviewTables);
16
+ /**
17
+ * Record a verdict. A reviewer rewrites theirs by adding another row; the newest row counts.
18
+ * `createdAt` is strictly increasing within a build, so "newest" never ties.
19
+ */
20
+ add(buildId: string, userId: string | null, verdict: ReviewVerdict, body?: string): Promise<BuildReview>;
21
+ /** Every verdict of a build, oldest first. */
22
+ listByBuild(buildId: string): Promise<BuildReview[]>;
23
+ /** Each reviewer's latest verdict on a build. */
24
+ latestByBuild(buildId: string): Promise<BuildReview[]>;
25
+ }
26
+ //#endregion
27
+ export { BuildReviewModel, BuildReviewTables };
28
+ //# sourceMappingURL=build-review.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 { latestVerdicts } from "./build-review-status.mjs";
7
+ import { asc, eq } from "drizzle-orm";
8
+ //#region 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
+ //#endregion
52
+ export { BuildReviewModel };
53
+
54
+ //# 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,6 +1,7 @@
1
1
  import __tsdown_shims_path from 'node:path';
2
2
  import __tsdown_shims_url from 'node:url';
3
3
  import { DatabaseAdapter } from "../adapter/database.mjs";
4
+ import { Logger } from "../logger.mjs";
4
5
  import { BuildStatus } from "../types.mjs";
5
6
  import { Build } from "../schema/build.mjs";
6
7
  import { Table } from "drizzle-orm";
@@ -13,9 +14,11 @@ interface BuildTables {
13
14
  }
14
15
  /** Data operations for build records. */
15
16
  declare class BuildModel {
16
- private readonly db;
17
17
  private readonly tables;
18
- constructor(db: DatabaseAdapter, tables?: BuildTables);
18
+ private readonly db;
19
+ private readonly logger;
20
+ /** `logger` only reports a status write refused by a `cancelled` build, at debug. */
21
+ constructor(db: DatabaseAdapter, tables?: BuildTables, logger?: Logger);
19
22
  create(projectId: string, input: BuildCreateInput): Promise<Build>;
20
23
  get(id: string): Promise<Build | null>;
21
24
  list(projectId: string, filter?: BuildListFilter): Promise<Build[]>;
@@ -30,13 +33,30 @@ declare class BuildModel {
30
33
  id: string;
31
34
  gitDefaultBranch: string;
32
35
  }): Promise<Build | null>;
33
- update(id: string, patch: Partial<Pick<Build, "status" | "public" | "message" | "authorEmail" | "authorName">>): Promise<Build>;
36
+ /**
37
+ * Patch a build. A `cancelled` build is terminal (ADR 0006): a patch that carries another status
38
+ * is refused, the row is returned as it is, so a capture that ends after a cancel cannot move
39
+ * the build. Only {@link BuildModel.resetStatus} leaves `cancelled`.
40
+ */
41
+ update(id: string, patch: Partial<Pick<Build, "status" | "public" | "message" | "authorEmail" | "authorName">>, options?: {
42
+ reset?: boolean;
43
+ }): Promise<Build>;
34
44
  setStatus(id: string, status: BuildStatus): Promise<Build>;
45
+ /**
46
+ * Put a finished build back to `status` for a retry. The one write that leaves `cancelled`
47
+ * (a user chose it), so it skips the terminal guard of {@link BuildModel.update}.
48
+ */
49
+ resetStatus(id: string, status: BuildStatus): Promise<Build>;
35
50
  /**
36
51
  * Record the affected-capture computation for a build. Idempotent: the CLI
37
52
  * posts this once after creating the build and before uploading the bundle.
38
53
  */
39
54
  setAffected(id: string, input: AffectedComputation): Promise<Build>;
55
+ /**
56
+ * Store the results of the build's `.test()` entries (`TestResult[]`, as JSON), or clear them
57
+ * with null. Written once per capture run, by the run that ran the tests.
58
+ */
59
+ setTestResults(id: string, results: readonly unknown[] | null): Promise<Build>;
40
60
  /**
41
61
  * Parse a build's affected import paths. Returns `null` for full capture
42
62
  * (no computation, or an unreadable payload) — callers render everything.
@@ -81,6 +101,8 @@ interface BuildListFilter {
81
101
  branch?: string;
82
102
  labelKey?: string;
83
103
  labelValue?: string;
104
+ /** Newest builds to return at most. */
105
+ limit?: number;
84
106
  }
85
107
  //#endregion
86
108
  export { AffectedComputation, BuildCreateInput, BuildListFilter, BuildModel, BuildTables, isPublicBuild };