@storyshelf/core 0.6.2 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (727) hide show
  1. package/README.md +49 -32
  2. package/dist/adapter/capture-queue.d.mts +44 -4
  3. package/dist/adapter/capture-runner.d.mts +134 -12
  4. package/dist/adapter/capture-runner.mjs +9 -12
  5. package/dist/adapter/database.d.mts +89 -10
  6. package/dist/adapter/database.mjs +4 -3
  7. package/dist/adapter/database.mjs.map +1 -1
  8. package/dist/adapter/design.d.mts +83 -0
  9. package/dist/adapter/design.mjs +27 -0
  10. package/dist/adapter/design.mjs.map +1 -0
  11. package/dist/adapter/email-sender.d.mts +0 -2
  12. package/dist/adapter/metadata.d.mts +3 -12
  13. package/dist/adapter/notifier.d.mts +6 -3
  14. package/dist/adapter/notifier.mjs +5 -1
  15. package/dist/adapter/oauth.d.mts +50 -0
  16. package/dist/adapter/setup.d.mts +4 -7
  17. package/dist/adapter/setup.mjs +3 -3
  18. package/dist/adapter/setup.mjs.map +1 -1
  19. package/dist/adapter/storage.d.mts +1 -3
  20. package/dist/adapter/webhook-events.d.mts +0 -2
  21. package/dist/adapter/webhook-events.mjs +13 -14
  22. package/dist/adapter/webhook-events.mjs.map +1 -1
  23. package/dist/adapters/capture-target-offers.d.mts +26 -0
  24. package/dist/adapters/capture-target-offers.mjs +57 -0
  25. package/dist/adapters/capture-target-offers.mjs.map +1 -0
  26. package/dist/adapters/capture-target.d.mts +69 -0
  27. package/dist/adapters/capture-target.mjs +75 -0
  28. package/dist/adapters/capture-target.mjs.map +1 -0
  29. package/dist/adapters/notifier/build-comment.d.mts +22 -0
  30. package/dist/adapters/notifier/build-comment.mjs +23 -0
  31. package/dist/adapters/notifier/build-comment.mjs.map +1 -0
  32. package/dist/adapters/notifier/build-state.d.mts +97 -0
  33. package/dist/adapters/notifier/build-state.mjs +217 -0
  34. package/dist/adapters/notifier/build-state.mjs.map +1 -0
  35. package/dist/adapters/notifier/events.d.mts +4 -4
  36. package/dist/adapters/notifier/events.mjs +32 -20
  37. package/dist/adapters/notifier/events.mjs.map +1 -1
  38. package/dist/adapters/notifier/format.d.mts +0 -2
  39. package/dist/adapters/notifier/format.mjs +1 -2
  40. package/dist/adapters/notifier/provider.d.mts +53 -9
  41. package/dist/adapters/notifier/provider.mjs +11 -0
  42. package/dist/adapters/notifier/provider.mjs.map +1 -0
  43. package/dist/adapters/notifier/state.d.mts +22 -0
  44. package/dist/adapters/notifier/state.mjs +65 -0
  45. package/dist/adapters/notifier/state.mjs.map +1 -0
  46. package/dist/adapters/notifier/types.d.mts +51 -4
  47. package/dist/adapters/remote-browser.d.mts +168 -0
  48. package/dist/adapters/remote-browser.mjs +201 -0
  49. package/dist/adapters/remote-browser.mjs.map +1 -0
  50. package/dist/adapters/remote-lease-attempt.mjs +39 -0
  51. package/dist/adapters/remote-lease-attempt.mjs.map +1 -0
  52. package/dist/adapters/remote-lease-error.d.mts +25 -0
  53. package/dist/adapters/remote-lease-error.mjs +54 -0
  54. package/dist/adapters/remote-lease-error.mjs.map +1 -0
  55. package/dist/adapters/remote-lease-held.d.mts +15 -0
  56. package/dist/adapters/remote-lease-held.mjs +86 -0
  57. package/dist/adapters/remote-lease-held.mjs.map +1 -0
  58. package/dist/adapters/remote-lease-shutdown.d.mts +12 -0
  59. package/dist/adapters/remote-lease-shutdown.mjs +32 -0
  60. package/dist/adapters/remote-lease-shutdown.mjs.map +1 -0
  61. package/dist/adapters/remote-lease.d.mts +39 -0
  62. package/dist/adapters/remote-lease.mjs +175 -0
  63. package/dist/adapters/remote-lease.mjs.map +1 -0
  64. package/dist/adapters/remote-limiter.mjs +121 -0
  65. package/dist/adapters/remote-limiter.mjs.map +1 -0
  66. package/dist/adapters/remote-pool.d.mts +56 -0
  67. package/dist/adapters/remote-pool.mjs +195 -0
  68. package/dist/adapters/remote-pool.mjs.map +1 -0
  69. package/dist/adapters/render-runs.d.mts +23 -0
  70. package/dist/adapters/render-runs.mjs +40 -0
  71. package/dist/adapters/render-runs.mjs.map +1 -0
  72. package/dist/adapters/setup-runner.d.mts +0 -2
  73. package/dist/adapters/setup-runner.mjs +3 -6
  74. package/dist/adapters/setup-runner.mjs.map +1 -1
  75. package/dist/adapters/sources.d.mts +10 -0
  76. package/dist/adapters/sources.mjs +19 -0
  77. package/dist/adapters/sources.mjs.map +1 -0
  78. package/dist/adapters/validate.d.mts +0 -2
  79. package/dist/adapters/validate.mjs +27 -20
  80. package/dist/adapters/validate.mjs.map +1 -1
  81. package/dist/ai.d.mts +103 -0
  82. package/dist/ai.mjs +18 -0
  83. package/dist/ai.mjs.map +1 -0
  84. package/dist/auth.d.mts +0 -2
  85. package/dist/auth.mjs +1 -2
  86. package/dist/brand-packs.d.mts +0 -2
  87. package/dist/brand-packs.mjs +0 -1
  88. package/dist/capture/a11y/axe-source.d.mts +16 -0
  89. package/dist/capture/a11y/axe-source.mjs +36 -0
  90. package/dist/capture/a11y/axe-source.mjs.map +1 -0
  91. package/dist/capture/a11y/in-page.d.mts +25 -0
  92. package/dist/capture/a11y/in-page.mjs +59 -0
  93. package/dist/capture/a11y/in-page.mjs.map +1 -0
  94. package/dist/capture/a11y/map.d.mts +22 -0
  95. package/dist/capture/a11y/map.mjs +47 -0
  96. package/dist/capture/a11y/map.mjs.map +1 -0
  97. package/dist/capture/a11y/options.d.mts +21 -0
  98. package/dist/capture/a11y/options.mjs +74 -0
  99. package/dist/capture/a11y/options.mjs.map +1 -0
  100. package/dist/capture/adapter.d.mts +46 -5
  101. package/dist/capture/adapter.mjs +1 -2
  102. package/dist/capture/adapter.mjs.map +1 -1
  103. package/dist/capture/affected.d.mts +2 -2
  104. package/dist/capture/affected.mjs +14 -6
  105. package/dist/capture/affected.mjs.map +1 -1
  106. package/dist/capture/attempt-logger.mjs +83 -0
  107. package/dist/capture/attempt-logger.mjs.map +1 -0
  108. package/dist/capture/browsers.d.mts +33 -0
  109. package/dist/capture/browsers.mjs +66 -0
  110. package/dist/capture/browsers.mjs.map +1 -0
  111. package/dist/capture/build-state/deliver.d.mts +30 -0
  112. package/dist/capture/build-state/deliver.mjs +117 -0
  113. package/dist/capture/build-state/deliver.mjs.map +1 -0
  114. package/dist/capture/build-state/index.d.mts +8 -0
  115. package/dist/capture/build-state/notify-job.d.mts +54 -0
  116. package/dist/capture/build-state/notify-job.mjs +132 -0
  117. package/dist/capture/build-state/notify-job.mjs.map +1 -0
  118. package/dist/capture/build-state/publisher.d.mts +21 -0
  119. package/dist/capture/build-state/publisher.mjs +106 -0
  120. package/dist/capture/build-state/publisher.mjs.map +1 -0
  121. package/dist/capture/build-state/resume.d.mts +7 -0
  122. package/dist/capture/build-state/resume.mjs +46 -0
  123. package/dist/capture/build-state/resume.mjs.map +1 -0
  124. package/dist/capture/build-state/types.d.mts +47 -0
  125. package/dist/capture/catalog/catalogue.d.mts +20 -0
  126. package/dist/capture/catalog/catalogue.mjs +52 -0
  127. package/dist/capture/catalog/catalogue.mjs.map +1 -0
  128. package/dist/capture/catalog/components.d.mts +21 -0
  129. package/dist/capture/catalog/components.mjs +54 -0
  130. package/dist/capture/catalog/components.mjs.map +1 -0
  131. package/dist/capture/catalog/history.d.mts +28 -0
  132. package/dist/capture/catalog/history.mjs +50 -0
  133. package/dist/capture/catalog/history.mjs.map +1 -0
  134. package/dist/capture/catalog/hook.d.mts +12 -0
  135. package/dist/capture/catalog/hook.mjs +18 -0
  136. package/dist/capture/catalog/hook.mjs.map +1 -0
  137. package/dist/capture/catalog/index.d.mts +11 -0
  138. package/dist/capture/catalog/load.d.mts +24 -0
  139. package/dist/capture/catalog/load.mjs +48 -0
  140. package/dist/capture/catalog/load.mjs.map +1 -0
  141. package/dist/capture/catalog/parse.d.mts +13 -0
  142. package/dist/capture/catalog/parse.mjs +55 -0
  143. package/dist/capture/catalog/parse.mjs.map +1 -0
  144. package/dist/capture/catalog/read.d.mts +15 -0
  145. package/dist/capture/catalog/read.mjs +39 -0
  146. package/dist/capture/catalog/read.mjs.map +1 -0
  147. package/dist/capture/catalog/types.d.mts +31 -0
  148. package/dist/capture/comparison-reference.d.mts +27 -0
  149. package/dist/capture/comparison-reference.mjs +75 -0
  150. package/dist/capture/comparison-reference.mjs.map +1 -0
  151. package/dist/capture/consumer/capture-handler.d.mts +56 -0
  152. package/dist/capture/consumer/capture-handler.mjs +117 -0
  153. package/dist/capture/consumer/capture-handler.mjs.map +1 -0
  154. package/dist/capture/consumer/idle.d.mts +15 -0
  155. package/dist/capture/consumer/idle.mjs +24 -0
  156. package/dist/capture/consumer/idle.mjs.map +1 -0
  157. package/dist/capture/consumer/index.d.mts +11 -0
  158. package/dist/capture/consumer/index.mjs +138 -0
  159. package/dist/capture/consumer/index.mjs.map +1 -0
  160. package/dist/capture/consumer/loop.mjs +109 -0
  161. package/dist/capture/consumer/loop.mjs.map +1 -0
  162. package/dist/capture/consumer/metrics.d.mts +39 -0
  163. package/dist/capture/consumer/metrics.mjs +37 -0
  164. package/dist/capture/consumer/metrics.mjs.map +1 -0
  165. package/dist/capture/consumer/retry.d.mts +31 -0
  166. package/dist/capture/consumer/retry.mjs +33 -0
  167. package/dist/capture/consumer/retry.mjs.map +1 -0
  168. package/dist/capture/consumer/run-job.mjs +194 -0
  169. package/dist/capture/consumer/run-job.mjs.map +1 -0
  170. package/dist/capture/consumer/startup.mjs +37 -0
  171. package/dist/capture/consumer/startup.mjs.map +1 -0
  172. package/dist/capture/consumer/types.d.mts +95 -0
  173. package/dist/capture/crash-failure.d.mts +23 -0
  174. package/dist/capture/crash-failure.mjs +69 -0
  175. package/dist/capture/crash-failure.mjs.map +1 -0
  176. package/dist/capture/db-queue/cadence.d.mts +30 -0
  177. package/dist/capture/db-queue/cadence.mjs +37 -0
  178. package/dist/capture/db-queue/cadence.mjs.map +1 -0
  179. package/dist/capture/db-queue/claim.d.mts +7 -0
  180. package/dist/capture/db-queue/claim.mjs +148 -0
  181. package/dist/capture/db-queue/claim.mjs.map +1 -0
  182. package/dist/capture/db-queue/clock.mjs +11 -0
  183. package/dist/capture/db-queue/clock.mjs.map +1 -0
  184. package/dist/capture/db-queue/codec.mjs +75 -0
  185. package/dist/capture/db-queue/codec.mjs.map +1 -0
  186. package/dist/capture/db-queue/index.d.mts +18 -0
  187. package/dist/capture/db-queue/index.mjs +89 -0
  188. package/dist/capture/db-queue/index.mjs.map +1 -0
  189. package/dist/capture/db-queue/keeper.d.mts +34 -0
  190. package/dist/capture/db-queue/keeper.mjs +91 -0
  191. package/dist/capture/db-queue/keeper.mjs.map +1 -0
  192. package/dist/capture/db-queue/lease.mjs +45 -0
  193. package/dist/capture/db-queue/lease.mjs.map +1 -0
  194. package/dist/capture/db-queue/lifecycle.mjs +35 -0
  195. package/dist/capture/db-queue/lifecycle.mjs.map +1 -0
  196. package/dist/capture/db-queue/operations.mjs +93 -0
  197. package/dist/capture/db-queue/operations.mjs.map +1 -0
  198. package/dist/capture/db-queue/poll-loop.d.mts +28 -0
  199. package/dist/capture/db-queue/poll-loop.mjs +88 -0
  200. package/dist/capture/db-queue/poll-loop.mjs.map +1 -0
  201. package/dist/capture/db-queue/poll.d.mts +8 -0
  202. package/dist/capture/db-queue/poll.mjs +68 -0
  203. package/dist/capture/db-queue/poll.mjs.map +1 -0
  204. package/dist/capture/db-queue/timers.d.mts +10 -0
  205. package/dist/capture/db-queue/timers.mjs +36 -0
  206. package/dist/capture/db-queue/timers.mjs.map +1 -0
  207. package/dist/capture/db-queue/types.d.mts +81 -0
  208. package/dist/capture/db-queue/types.mjs +11 -0
  209. package/dist/capture/db-queue/types.mjs.map +1 -0
  210. package/dist/capture/db-queue/wake.mjs +47 -0
  211. package/dist/capture/db-queue/wake.mjs.map +1 -0
  212. package/dist/capture/design.mjs +38 -0
  213. package/dist/capture/design.mjs.map +1 -0
  214. package/dist/capture/diff-record.mjs +24 -5
  215. package/dist/capture/diff-record.mjs.map +1 -1
  216. package/dist/capture/dispatch.d.mts +13 -10
  217. package/dist/capture/dispatch.mjs +102 -111
  218. package/dist/capture/dispatch.mjs.map +1 -1
  219. package/dist/capture/enrich.mjs +59 -0
  220. package/dist/capture/enrich.mjs.map +1 -0
  221. package/dist/capture/failure-sets.mjs +21 -0
  222. package/dist/capture/failure-sets.mjs.map +1 -0
  223. package/dist/capture/fingerprint.d.mts +40 -0
  224. package/dist/capture/fingerprint.mjs +82 -0
  225. package/dist/capture/fingerprint.mjs.map +1 -0
  226. package/dist/capture/forked/channel.d.mts +23 -0
  227. package/dist/capture/forked/child.d.mts +32 -0
  228. package/dist/capture/forked/child.mjs +201 -0
  229. package/dist/capture/forked/child.mjs.map +1 -0
  230. package/dist/capture/forked/crash-tracker.mjs +22 -0
  231. package/dist/capture/forked/crash-tracker.mjs.map +1 -0
  232. package/dist/capture/forked/decision.d.mts +24 -0
  233. package/dist/capture/forked/decision.mjs +26 -0
  234. package/dist/capture/forked/decision.mjs.map +1 -0
  235. package/dist/capture/forked/exit.mjs +11 -0
  236. package/dist/capture/forked/exit.mjs.map +1 -0
  237. package/dist/capture/forked/fork.d.mts +29 -0
  238. package/dist/capture/forked/fork.mjs +92 -0
  239. package/dist/capture/forked/fork.mjs.map +1 -0
  240. package/dist/capture/forked/forked-capture.d.mts +38 -0
  241. package/dist/capture/forked/forked-capture.mjs +239 -0
  242. package/dist/capture/forked/forked-capture.mjs.map +1 -0
  243. package/dist/capture/forked/lease-book.mjs +45 -0
  244. package/dist/capture/forked/lease-book.mjs.map +1 -0
  245. package/dist/capture/forked/protocol.d.mts +36 -0
  246. package/dist/capture/forked/protocol.mjs +24 -0
  247. package/dist/capture/forked/protocol.mjs.map +1 -0
  248. package/dist/capture/forked/runs.mjs +74 -0
  249. package/dist/capture/forked/runs.mjs.map +1 -0
  250. package/dist/capture/guards.mjs +1 -2
  251. package/dist/capture/ignore-regions.d.mts +36 -0
  252. package/dist/capture/ignore-regions.mjs +65 -0
  253. package/dist/capture/ignore-regions.mjs.map +1 -0
  254. package/dist/capture/isolate.mjs +140 -0
  255. package/dist/capture/isolate.mjs.map +1 -0
  256. package/dist/capture/job-lease.d.mts +26 -0
  257. package/dist/capture/job-lease.mjs +56 -0
  258. package/dist/capture/job-lease.mjs.map +1 -0
  259. package/dist/capture/matrix.mjs +298 -0
  260. package/dist/capture/matrix.mjs.map +1 -0
  261. package/dist/capture/modes.d.mts +23 -0
  262. package/dist/capture/modes.mjs +106 -0
  263. package/dist/capture/modes.mjs.map +1 -0
  264. package/dist/capture/orchestrator.d.mts +37 -7
  265. package/dist/capture/orchestrator.mjs +120 -110
  266. package/dist/capture/orchestrator.mjs.map +1 -1
  267. package/dist/capture/pipeline.d.mts +9 -4
  268. package/dist/capture/pipeline.mjs +284 -48
  269. package/dist/capture/pipeline.mjs.map +1 -1
  270. package/dist/capture/progress.d.mts +9 -0
  271. package/dist/capture/progress.mjs +27 -0
  272. package/dist/capture/progress.mjs.map +1 -0
  273. package/dist/capture/publish/accept.d.mts +37 -0
  274. package/dist/capture/publish/accept.mjs +27 -0
  275. package/dist/capture/publish/accept.mjs.map +1 -0
  276. package/dist/capture/publish/complete.d.mts +44 -0
  277. package/dist/capture/publish/complete.mjs +63 -0
  278. package/dist/capture/publish/complete.mjs.map +1 -0
  279. package/dist/capture/publish/extract.d.mts +18 -0
  280. package/dist/capture/publish/extract.mjs +120 -0
  281. package/dist/capture/publish/extract.mjs.map +1 -0
  282. package/dist/capture/publish/handler.d.mts +26 -0
  283. package/dist/capture/publish/handler.mjs +61 -0
  284. package/dist/capture/publish/handler.mjs.map +1 -0
  285. package/dist/capture/publish/index.d.mts +11 -0
  286. package/dist/capture/publish/invalid-zip.d.mts +8 -0
  287. package/dist/capture/publish/invalid-zip.mjs +11 -0
  288. package/dist/capture/publish/invalid-zip.mjs.map +1 -0
  289. package/dist/capture/publish/limits.d.mts +35 -0
  290. package/dist/capture/publish/limits.mjs +38 -0
  291. package/dist/capture/publish/limits.mjs.map +1 -0
  292. package/dist/capture/publish/published.d.mts +13 -0
  293. package/dist/capture/publish/published.mjs +17 -0
  294. package/dist/capture/publish/published.mjs.map +1 -0
  295. package/dist/capture/publish/run.d.mts +31 -0
  296. package/dist/capture/publish/run.mjs +29 -0
  297. package/dist/capture/publish/run.mjs.map +1 -0
  298. package/dist/capture/rediff.d.mts +1 -3
  299. package/dist/capture/rediff.mjs +23 -9
  300. package/dist/capture/rediff.mjs.map +1 -1
  301. package/dist/capture/render.mjs +52 -0
  302. package/dist/capture/render.mjs.map +1 -0
  303. package/dist/capture/scratch.d.mts +42 -0
  304. package/dist/capture/scratch.mjs +136 -0
  305. package/dist/capture/scratch.mjs.map +1 -0
  306. package/dist/capture/shard-plan.mjs +127 -0
  307. package/dist/capture/shard-plan.mjs.map +1 -0
  308. package/dist/capture/shards/context.mjs +51 -0
  309. package/dist/capture/shards/context.mjs.map +1 -0
  310. package/dist/capture/shards/execute.mjs +175 -0
  311. package/dist/capture/shards/execute.mjs.map +1 -0
  312. package/dist/capture/shards/fan-out.mjs +125 -0
  313. package/dist/capture/shards/fan-out.mjs.map +1 -0
  314. package/dist/capture/shards/history.mjs +34 -0
  315. package/dist/capture/shards/history.mjs.map +1 -0
  316. package/dist/capture/shards/reduce.mjs +96 -0
  317. package/dist/capture/shards/reduce.mjs.map +1 -0
  318. package/dist/capture/sizing.d.mts +0 -2
  319. package/dist/capture/sizing.mjs +2 -13
  320. package/dist/capture/sizing.mjs.map +1 -1
  321. package/dist/capture/skip-checks.mjs +3 -67
  322. package/dist/capture/skip-checks.mjs.map +1 -1
  323. package/dist/capture/statics.d.mts +7 -10
  324. package/dist/capture/statics.mjs +26 -74
  325. package/dist/capture/statics.mjs.map +1 -1
  326. package/dist/capture/storybook-urls.d.mts +38 -0
  327. package/dist/capture/storybook-urls.mjs +41 -0
  328. package/dist/capture/storybook-urls.mjs.map +1 -0
  329. package/dist/capture/storybook.d.mts +12 -6
  330. package/dist/capture/storybook.mjs +63 -19
  331. package/dist/capture/storybook.mjs.map +1 -1
  332. package/dist/capture/target.mjs +20 -0
  333. package/dist/capture/target.mjs.map +1 -0
  334. package/dist/capture/targets.d.mts +23 -0
  335. package/dist/capture/targets.mjs +31 -0
  336. package/dist/capture/targets.mjs.map +1 -0
  337. package/dist/capture/tests.d.mts +35 -0
  338. package/dist/capture/tests.mjs +136 -0
  339. package/dist/capture/tests.mjs.map +1 -0
  340. package/dist/capture/token-chunks.mjs +91 -0
  341. package/dist/capture/token-chunks.mjs.map +1 -0
  342. package/dist/capture.d.mts +63 -7
  343. package/dist/capture.mjs +51 -3
  344. package/dist/config.d.mts +136 -14
  345. package/dist/config.mjs +14 -8
  346. package/dist/config.mjs.map +1 -1
  347. package/dist/diff.d.mts +7 -3
  348. package/dist/diff.mjs +15 -3
  349. package/dist/diff.mjs.map +1 -0
  350. package/dist/index.d.mts +3 -3
  351. package/dist/index.mjs +2 -2
  352. package/dist/insights/budget.d.mts +18 -0
  353. package/dist/insights/budget.mjs +32 -0
  354. package/dist/insights/budget.mjs.map +1 -0
  355. package/dist/insights/envelope.d.mts +57 -0
  356. package/dist/insights/envelope.mjs +59 -0
  357. package/dist/insights/envelope.mjs.map +1 -0
  358. package/dist/insights/evidence-health.d.mts +30 -0
  359. package/dist/insights/evidence-health.mjs +27 -0
  360. package/dist/insights/evidence-health.mjs.map +1 -0
  361. package/dist/insights/evidence.d.mts +53 -0
  362. package/dist/insights/evidence.mjs +63 -0
  363. package/dist/insights/evidence.mjs.map +1 -0
  364. package/dist/insights/hash.d.mts +16 -0
  365. package/dist/insights/hash.mjs +20 -0
  366. package/dist/insights/hash.mjs.map +1 -0
  367. package/dist/insights/limits.d.mts +22 -0
  368. package/dist/insights/limits.mjs +23 -0
  369. package/dist/insights/limits.mjs.map +1 -0
  370. package/dist/insights/prompts/v1.d.mts +11 -0
  371. package/dist/insights/prompts/v1.mjs +33 -0
  372. package/dist/insights/prompts/v1.mjs.map +1 -0
  373. package/dist/insights/redact.d.mts +7 -0
  374. package/dist/insights/redact.mjs +23 -0
  375. package/dist/insights/redact.mjs.map +1 -0
  376. package/dist/insights/text.mjs +43 -0
  377. package/dist/insights/text.mjs.map +1 -0
  378. package/dist/insights.d.mts +11 -0
  379. package/dist/insights.mjs +12 -0
  380. package/dist/logger.d.mts +0 -2
  381. package/dist/logger.mjs +18 -3
  382. package/dist/logger.mjs.map +1 -1
  383. package/dist/models/ai-budget-alert.d.mts +21 -0
  384. package/dist/models/ai-budget-alert.mjs +37 -0
  385. package/dist/models/ai-budget-alert.mjs.map +1 -0
  386. package/dist/models/ai-usage.d.mts +38 -0
  387. package/dist/models/ai-usage.mjs +60 -0
  388. package/dist/models/ai-usage.mjs.map +1 -0
  389. package/dist/models/baseline-approval.d.mts +18 -0
  390. package/dist/models/baseline-approval.mjs +41 -0
  391. package/dist/models/baseline-approval.mjs.map +1 -0
  392. package/dist/models/baseline-archive.d.mts +82 -0
  393. package/dist/models/baseline-archive.mjs +105 -0
  394. package/dist/models/baseline-archive.mjs.map +1 -0
  395. package/dist/models/baseline-atomic.mjs +66 -0
  396. package/dist/models/baseline-atomic.mjs.map +1 -0
  397. package/dist/models/baseline-conflict.d.mts +6 -0
  398. package/dist/models/baseline-conflict.mjs +9 -0
  399. package/dist/models/baseline-conflict.mjs.map +1 -0
  400. package/dist/models/baseline-export.d.mts +17 -0
  401. package/dist/models/baseline-export.mjs +135 -0
  402. package/dist/models/baseline-export.mjs.map +1 -0
  403. package/dist/models/baseline-import.mjs +117 -0
  404. package/dist/models/baseline-import.mjs.map +1 -0
  405. package/dist/models/baseline-origin.d.mts +16 -0
  406. package/dist/models/baseline-origin.mjs +35 -0
  407. package/dist/models/baseline-origin.mjs.map +1 -0
  408. package/dist/models/baseline-status.d.mts +0 -2
  409. package/dist/models/baseline-status.mjs +1 -2
  410. package/dist/models/baseline-transfer.d.mts +48 -0
  411. package/dist/models/baseline-transfer.mjs +149 -0
  412. package/dist/models/baseline-transfer.mjs.map +1 -0
  413. package/dist/models/baseline.d.mts +37 -7
  414. package/dist/models/baseline.mjs +152 -28
  415. package/dist/models/baseline.mjs.map +1 -1
  416. package/dist/models/build-component.d.mts +26 -0
  417. package/dist/models/build-component.mjs +54 -0
  418. package/dist/models/build-component.mjs.map +1 -0
  419. package/dist/models/build-review-status.d.mts +27 -0
  420. package/dist/models/build-review-status.mjs +36 -0
  421. package/dist/models/build-review-status.mjs.map +1 -0
  422. package/dist/models/build-review.d.mts +26 -0
  423. package/dist/models/build-review.mjs +53 -0
  424. package/dist/models/build-review.mjs.map +1 -0
  425. package/dist/models/build.d.mts +25 -5
  426. package/dist/models/build.mjs +53 -15
  427. package/dist/models/build.mjs.map +1 -1
  428. package/dist/models/capture-attempt.d.mts +7 -3
  429. package/dist/models/capture-attempt.mjs +30 -6
  430. package/dist/models/capture-attempt.mjs.map +1 -1
  431. package/dist/models/capture-job.d.mts +104 -0
  432. package/dist/models/capture-job.mjs +287 -0
  433. package/dist/models/capture-job.mjs.map +1 -0
  434. package/dist/models/capture-log.d.mts +1 -3
  435. package/dist/models/capture-log.mjs +5 -5
  436. package/dist/models/capture-log.mjs.map +1 -1
  437. package/dist/models/capture-shard.d.mts +60 -0
  438. package/dist/models/capture-shard.mjs +189 -0
  439. package/dist/models/capture-shard.mjs.map +1 -0
  440. package/dist/models/changeset.d.mts +46 -0
  441. package/dist/models/changeset.mjs +80 -0
  442. package/dist/models/changeset.mjs.map +1 -0
  443. package/dist/models/columns.mjs +18 -0
  444. package/dist/models/columns.mjs.map +1 -0
  445. package/dist/models/comment.d.mts +4 -3
  446. package/dist/models/comment.mjs +8 -6
  447. package/dist/models/comment.mjs.map +1 -1
  448. package/dist/models/component.d.mts +41 -0
  449. package/dist/models/component.mjs +59 -0
  450. package/dist/models/component.mjs.map +1 -0
  451. package/dist/models/connections.d.mts +39 -0
  452. package/dist/models/connections.mjs +111 -0
  453. package/dist/models/connections.mjs.map +1 -0
  454. package/dist/models/content-ref.d.mts +1 -3
  455. package/dist/models/content-ref.mjs +2 -3
  456. package/dist/models/content-ref.mjs.map +1 -1
  457. package/dist/models/credentials.d.mts +0 -2
  458. package/dist/models/credentials.mjs +13 -4
  459. package/dist/models/credentials.mjs.map +1 -1
  460. package/dist/models/design-credentials.d.mts +39 -0
  461. package/dist/models/design-credentials.mjs +85 -0
  462. package/dist/models/design-credentials.mjs.map +1 -0
  463. package/dist/models/design-health.d.mts +14 -0
  464. package/dist/models/design-health.mjs +24 -0
  465. package/dist/models/design-health.mjs.map +1 -0
  466. package/dist/models/design-nodes.d.mts +81 -0
  467. package/dist/models/design-nodes.mjs +221 -0
  468. package/dist/models/design-nodes.mjs.map +1 -0
  469. package/dist/models/design-renders.mjs +73 -0
  470. package/dist/models/design-renders.mjs.map +1 -0
  471. package/dist/models/design-resolve.d.mts +27 -0
  472. package/dist/models/design-resolve.mjs +40 -0
  473. package/dist/models/design-resolve.mjs.map +1 -0
  474. package/dist/models/design.d.mts +37 -0
  475. package/dist/models/design.mjs +89 -0
  476. package/dist/models/design.mjs.map +1 -0
  477. package/dist/models/inherited-ratio.d.mts +19 -0
  478. package/dist/models/inherited-ratio.mjs +21 -0
  479. package/dist/models/inherited-ratio.mjs.map +1 -0
  480. package/dist/models/insight.d.mts +68 -0
  481. package/dist/models/insight.mjs +161 -0
  482. package/dist/models/insight.mjs.map +1 -0
  483. package/dist/models/label.d.mts +4 -4
  484. package/dist/models/label.mjs +26 -12
  485. package/dist/models/label.mjs.map +1 -1
  486. package/dist/models/member.d.mts +1 -3
  487. package/dist/models/member.mjs +6 -6
  488. package/dist/models/member.mjs.map +1 -1
  489. package/dist/models/notification-channel.d.mts +37 -5
  490. package/dist/models/notification-channel.mjs +89 -11
  491. package/dist/models/notification-channel.mjs.map +1 -1
  492. package/dist/models/notification-subscription.d.mts +1 -4
  493. package/dist/models/notification-subscription.mjs +8 -11
  494. package/dist/models/notification-subscription.mjs.map +1 -1
  495. package/dist/models/project-group-mapping.d.mts +6 -3
  496. package/dist/models/project-group-mapping.mjs +15 -6
  497. package/dist/models/project-group-mapping.mjs.map +1 -1
  498. package/dist/models/project.d.mts +9 -3
  499. package/dist/models/project.mjs +10 -6
  500. package/dist/models/project.mjs.map +1 -1
  501. package/dist/models/public-origin.d.mts +61 -0
  502. package/dist/models/public-origin.mjs +113 -0
  503. package/dist/models/public-origin.mjs.map +1 -0
  504. package/dist/models/review-switch-off.d.mts +12 -0
  505. package/dist/models/review-switch-off.mjs +25 -0
  506. package/dist/models/review-switch-off.mjs.map +1 -0
  507. package/dist/models/site-settings.d.mts +21 -0
  508. package/dist/models/site-settings.mjs +47 -0
  509. package/dist/models/site-settings.mjs.map +1 -0
  510. package/dist/models/snapshot.d.mts +39 -3
  511. package/dist/models/snapshot.mjs +54 -5
  512. package/dist/models/snapshot.mjs.map +1 -1
  513. package/dist/models/token.d.mts +13 -3
  514. package/dist/models/token.mjs +24 -7
  515. package/dist/models/token.mjs.map +1 -1
  516. package/dist/models/usage.d.mts +74 -0
  517. package/dist/models/usage.mjs +148 -0
  518. package/dist/models/usage.mjs.map +1 -0
  519. package/dist/models/user.d.mts +1 -3
  520. package/dist/models/user.mjs +5 -5
  521. package/dist/models/user.mjs.map +1 -1
  522. package/dist/models/webhook.d.mts +15 -3
  523. package/dist/models/webhook.mjs +34 -6
  524. package/dist/models/webhook.mjs.map +1 -1
  525. package/dist/models.d.mts +30 -4
  526. package/dist/models.mjs +31 -6
  527. package/dist/options.d.mts +16 -0
  528. package/dist/options.mjs +88 -0
  529. package/dist/options.mjs.map +1 -0
  530. package/dist/paths.d.mts +21 -6
  531. package/dist/paths.mjs +57 -12
  532. package/dist/paths.mjs.map +1 -1
  533. package/dist/retention/ai.d.mts +16 -0
  534. package/dist/retention/ai.mjs +22 -0
  535. package/dist/retention/ai.mjs.map +1 -0
  536. package/dist/retention/pending.mjs +46 -0
  537. package/dist/retention/pending.mjs.map +1 -0
  538. package/dist/retention/published-gzip-sweep.mjs +28 -0
  539. package/dist/retention/published-gzip-sweep.mjs.map +1 -0
  540. package/dist/retention/screenshot-sweep.d.mts +49 -0
  541. package/dist/retention/screenshot-sweep.mjs +127 -0
  542. package/dist/retention/screenshot-sweep.mjs.map +1 -0
  543. package/dist/retention.d.mts +16 -4
  544. package/dist/retention.mjs +76 -16
  545. package/dist/retention.mjs.map +1 -1
  546. package/dist/schema/baseline.d.mts +8 -2
  547. package/dist/schema/build-component.d.mts +23 -0
  548. package/dist/schema/build-review.d.mts +15 -0
  549. package/dist/schema/build.d.mts +7 -2
  550. package/dist/schema/capture-attempt.d.mts +0 -2
  551. package/dist/schema/capture-job.d.mts +58 -0
  552. package/dist/schema/capture-shard.d.mts +33 -0
  553. package/dist/schema/comment.d.mts +2 -2
  554. package/dist/schema/connection.d.mts +22 -0
  555. package/dist/schema/content-ref.d.mts +0 -2
  556. package/dist/schema/design.d.mts +56 -0
  557. package/dist/schema/insight.d.mts +52 -0
  558. package/dist/schema/label.d.mts +0 -2
  559. package/dist/schema/member.d.mts +0 -2
  560. package/dist/schema/notification.d.mts +4 -2
  561. package/dist/schema/project-group-mapping.d.mts +0 -2
  562. package/dist/schema/project.d.mts +21 -2
  563. package/dist/schema/site-setting.d.mts +19 -0
  564. package/dist/schema/snapshot.d.mts +17 -2
  565. package/dist/schema/token.d.mts +0 -2
  566. package/dist/schema/usage.d.mts +28 -0
  567. package/dist/schema/user.d.mts +0 -2
  568. package/dist/schema/webhook.d.mts +0 -2
  569. package/dist/schema.d.mts +12 -6
  570. package/dist/serverless.d.mts +19 -0
  571. package/dist/serverless.mjs +36 -0
  572. package/dist/serverless.mjs.map +1 -0
  573. package/dist/test-helpers/build-reference-contract.d.mts +14 -0
  574. package/dist/test-helpers/build-reference-contract.mjs +66 -0
  575. package/dist/test-helpers/build-reference-contract.mjs.map +1 -0
  576. package/dist/test-helpers/create-project.d.mts +1 -3
  577. package/dist/test-helpers/create-project.mjs +1 -2
  578. package/dist/test-helpers/database-contract.d.mts +0 -2
  579. package/dist/test-helpers/database-contract.mjs +37 -2
  580. package/dist/test-helpers/database-contract.mjs.map +1 -1
  581. package/dist/test-helpers/deferred.d.mts +37 -0
  582. package/dist/test-helpers/deferred.mjs +60 -0
  583. package/dist/test-helpers/deferred.mjs.map +1 -0
  584. package/dist/test-helpers/design-contract.d.mts +19 -0
  585. package/dist/test-helpers/design-contract.mjs +39 -0
  586. package/dist/test-helpers/design-contract.mjs.map +1 -0
  587. package/dist/test-helpers/fake-adapters.d.mts +0 -2
  588. package/dist/test-helpers/fake-adapters.mjs +1 -2
  589. package/dist/test-helpers/fake-database.d.mts +0 -2
  590. package/dist/test-helpers/fake-database.mjs +21 -4
  591. package/dist/test-helpers/fake-database.mjs.map +1 -1
  592. package/dist/test-helpers/fake-design.d.mts +41 -0
  593. package/dist/test-helpers/fake-design.mjs +113 -0
  594. package/dist/test-helpers/fake-design.mjs.map +1 -0
  595. package/dist/test-helpers/fake-notifier.d.mts +19 -0
  596. package/dist/test-helpers/fake-notifier.mjs +43 -0
  597. package/dist/test-helpers/fake-notifier.mjs.map +1 -0
  598. package/dist/test-helpers/fake-storage.d.mts +0 -2
  599. package/dist/test-helpers/fake-storage.mjs +1 -2
  600. package/dist/test-helpers/fake-tables.mjs +260 -69
  601. package/dist/test-helpers/fake-tables.mjs.map +1 -1
  602. package/dist/test-helpers/manual-clock.d.mts +25 -0
  603. package/dist/test-helpers/manual-clock.mjs +55 -0
  604. package/dist/test-helpers/manual-clock.mjs.map +1 -0
  605. package/dist/test-helpers/notifier-contract.d.mts +7 -3
  606. package/dist/test-helpers/notifier-contract.mjs +33 -5
  607. package/dist/test-helpers/notifier-contract.mjs.map +1 -1
  608. package/dist/test-helpers/queue-contract.d.mts +16 -4
  609. package/dist/test-helpers/queue-contract.mjs +196 -3
  610. package/dist/test-helpers/queue-contract.mjs.map +1 -1
  611. package/dist/test-helpers/queue-multi-instance.d.mts +18 -0
  612. package/dist/test-helpers/queue-multi-instance.mjs +205 -0
  613. package/dist/test-helpers/queue-multi-instance.mjs.map +1 -0
  614. package/dist/test-helpers/runner-contract.d.mts +0 -2
  615. package/dist/test-helpers/runner-contract.mjs +12 -2
  616. package/dist/test-helpers/runner-contract.mjs.map +1 -1
  617. package/dist/test-helpers/sql-chunks.mjs +25 -2
  618. package/dist/test-helpers/sql-chunks.mjs.map +1 -1
  619. package/dist/test-helpers/storage-contract.d.mts +0 -2
  620. package/dist/test-helpers/storage-contract.mjs +2 -3
  621. package/dist/test-helpers.d.mts +10 -4
  622. package/dist/test-helpers.mjs +9 -3
  623. package/dist/tracing.mjs +36 -6
  624. package/dist/tracing.mjs.map +1 -1
  625. package/dist/types.d.mts +14 -5
  626. package/dist/types.mjs +22 -6
  627. package/dist/types.mjs.map +1 -1
  628. package/dist/urls.d.mts +4 -2
  629. package/dist/urls.mjs +12 -4
  630. package/dist/urls.mjs.map +1 -1
  631. package/dist/utils/adaptive-concurrency.d.mts +64 -0
  632. package/dist/utils/adaptive-concurrency.mjs +148 -0
  633. package/dist/utils/adaptive-concurrency.mjs.map +1 -0
  634. package/dist/utils/connect-retry.d.mts +26 -0
  635. package/dist/utils/connect-retry.mjs +42 -0
  636. package/dist/utils/connect-retry.mjs.map +1 -0
  637. package/dist/utils/encrypt.d.mts +0 -2
  638. package/dist/utils/encrypt.mjs +1 -2
  639. package/dist/utils/environment.d.mts +17 -0
  640. package/dist/utils/environment.mjs +58 -0
  641. package/dist/utils/environment.mjs.map +1 -0
  642. package/dist/utils/hash.d.mts +0 -2
  643. package/dist/utils/hash.mjs +1 -2
  644. package/dist/utils/http.d.mts +29 -3
  645. package/dist/utils/http.mjs +100 -19
  646. package/dist/utils/http.mjs.map +1 -1
  647. package/dist/utils/map-adaptive.d.mts +13 -0
  648. package/dist/utils/map-adaptive.mjs +71 -0
  649. package/dist/utils/map-adaptive.mjs.map +1 -0
  650. package/dist/utils/map-limit.d.mts +8 -0
  651. package/dist/utils/map-limit.mjs +31 -0
  652. package/dist/utils/map-limit.mjs.map +1 -0
  653. package/dist/utils/mime.d.mts +0 -2
  654. package/dist/utils/mime.mjs +1 -2
  655. package/dist/utils/page-limiter.d.mts +35 -0
  656. package/dist/utils/page-limiter.mjs +55 -0
  657. package/dist/utils/page-limiter.mjs.map +1 -0
  658. package/dist/utils/path-security.d.mts +0 -2
  659. package/dist/utils/path-security.mjs +2 -4
  660. package/dist/utils/path-security.mjs.map +1 -1
  661. package/dist/utils/public-origin.d.mts +18 -0
  662. package/dist/utils/public-origin.mjs +74 -0
  663. package/dist/utils/public-origin.mjs.map +1 -0
  664. package/dist/utils/redact.d.mts +0 -2
  665. package/dist/utils/redact.mjs +4 -2
  666. package/dist/utils/redact.mjs.map +1 -1
  667. package/dist/utils/remote-environment.d.mts +76 -0
  668. package/dist/utils/remote-environment.mjs +163 -0
  669. package/dist/utils/remote-environment.mjs.map +1 -0
  670. package/dist/utils/resources.d.mts +62 -0
  671. package/dist/utils/resources.mjs +121 -0
  672. package/dist/utils/resources.mjs.map +1 -0
  673. package/dist/utils/screenshot-store.d.mts +13 -0
  674. package/dist/utils/screenshot-store.mjs +40 -0
  675. package/dist/utils/screenshot-store.mjs.map +1 -0
  676. package/dist/utils/session-gate.d.mts +23 -0
  677. package/dist/utils/session-gate.mjs +69 -0
  678. package/dist/utils/session-gate.mjs.map +1 -0
  679. package/dist/utils/storybook-token.d.mts +89 -0
  680. package/dist/utils/storybook-token.mjs +135 -0
  681. package/dist/utils/storybook-token.mjs.map +1 -0
  682. package/dist/utils/storybook-url.d.mts +13 -0
  683. package/dist/utils/storybook-url.mjs +20 -0
  684. package/dist/utils/storybook-url.mjs.map +1 -0
  685. package/dist/utils/timing.d.mts +0 -2
  686. package/dist/utils/timing.mjs +2 -3
  687. package/dist/utils/timing.mjs.map +1 -1
  688. package/dist/utils/ulid.d.mts +0 -2
  689. package/dist/utils/ulid.mjs +1 -2
  690. package/dist/utils/zip-read.mjs +128 -0
  691. package/dist/utils/zip-read.mjs.map +1 -0
  692. package/dist/utils/zip-write.d.mts +16 -0
  693. package/dist/utils/zip-write.mjs +187 -0
  694. package/dist/utils/zip-write.mjs.map +1 -0
  695. package/dist/utils.d.mts +17 -3
  696. package/dist/utils.mjs +18 -4
  697. package/package.json +35 -26
  698. package/dist/adapter/capture-runner.mjs.map +0 -1
  699. package/dist/adapter/git-host/comments.d.mts +0 -41
  700. package/dist/adapter/git-host/comments.mjs +0 -54
  701. package/dist/adapter/git-host/comments.mjs.map +0 -1
  702. package/dist/adapter/git-host/helpers.d.mts +0 -13
  703. package/dist/adapter/git-host/helpers.mjs +0 -30
  704. package/dist/adapter/git-host/helpers.mjs.map +0 -1
  705. package/dist/adapter/git-host.d.mts +0 -42
  706. package/dist/capture/queue.d.mts +0 -38
  707. package/dist/capture/queue.mjs +0 -98
  708. package/dist/capture/queue.mjs.map +0 -1
  709. package/dist/capture/recovery.d.mts +0 -21
  710. package/dist/capture/recovery.mjs +0 -83
  711. package/dist/capture/recovery.mjs.map +0 -1
  712. package/dist/capture/status-fanout.mjs +0 -72
  713. package/dist/capture/status-fanout.mjs.map +0 -1
  714. package/dist/diff/engine.d.mts +0 -16
  715. package/dist/diff/engine.mjs +0 -87
  716. package/dist/diff/engine.mjs.map +0 -1
  717. package/dist/diff/options.d.mts +0 -42
  718. package/dist/diff/options.mjs +0 -15
  719. package/dist/diff/options.mjs.map +0 -1
  720. package/dist/models/status-config.d.mts +0 -38
  721. package/dist/models/status-config.mjs +0 -63
  722. package/dist/models/status-config.mjs.map +0 -1
  723. package/dist/schema/status-config.d.mts +0 -16
  724. package/dist/test-helpers/git-contract.d.mts +0 -11
  725. package/dist/test-helpers/git-contract.mjs +0 -34
  726. package/dist/test-helpers/git-contract.mjs.map +0 -1
  727. /package/dist/adapter/{git-host.mjs → oauth.mjs} +0 -0
@@ -0,0 +1,83 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ import { redactSecrets } from "../utils/redact.mjs";
5
+ import { emitAttemptLog } from "../models/capture-attempt.mjs";
6
+ // src/capture/attempt-logger.ts
7
+ /** Longest error text recorded on an attempt-log line. */
8
+ const ERROR_TEXT_MAX = 500;
9
+ /** Mirrored levels; `debug` stays in pino only so attempt logs keep the signal. */
10
+ const MIRRORED_LEVELS = [
11
+ "info",
12
+ "warn",
13
+ "error"
14
+ ];
15
+ /**
16
+ * Mirror a logger's `info`/`warn`/`error` lines onto the attempt log.
17
+ *
18
+ * Writes go through one promise chain: `capture_logs` numbers lines with a
19
+ * read-then-insert, so concurrent appends (parallel pages failing at once)
20
+ * would collide on `seq`. With no recorder the logger is returned unchanged.
21
+ */
22
+ function mirrorAttemptLog(logger, record, levelFor = (level) => level) {
23
+ let chain = Promise.resolve();
24
+ const enqueue = (level, message, fields) => {
25
+ if (!record) return;
26
+ const recorded = levelFor(level, fields);
27
+ chain = chain.then(async () => {
28
+ await emitAttemptLog(record, logger, recorded, message, fields);
29
+ });
30
+ };
31
+ const base = logger ?? silentLogger();
32
+ return {
33
+ logger: new Proxy(base, { get(target, property, receiver) {
34
+ const level = MIRRORED_LEVELS.find((name) => name === property);
35
+ if (!level) return Reflect.get(target, property, receiver);
36
+ return (first, second, ...rest) => {
37
+ const args = [
38
+ first,
39
+ second,
40
+ ...rest
41
+ ];
42
+ while (args.length > 1 && args.at(-1) === void 0) args.pop();
43
+ Reflect.apply(target[level], target, args);
44
+ const [fields, message] = parseArgs(first, second);
45
+ enqueue(level, message, fields);
46
+ };
47
+ } }),
48
+ log: (level, message, fields) => {
49
+ base[level](fields ?? {}, message);
50
+ enqueue(level, message, fields === void 0 ? void 0 : plainFields(fields));
51
+ },
52
+ flush: async () => {
53
+ await chain;
54
+ }
55
+ };
56
+ }
57
+ /** pino call shapes: `(fields, message)` or `(message)`. */
58
+ function parseArgs(first, second) {
59
+ if (typeof first === "string") return [void 0, first];
60
+ return [first && typeof first === "object" ? plainFields(first) : void 0, typeof second === "string" ? second : ""];
61
+ }
62
+ /** JSON-safe copy: an `Error` under `err` becomes its capped message. */
63
+ function plainFields(fields) {
64
+ const copy = { ...fields };
65
+ const err = copy["err"];
66
+ if (err instanceof Error) copy["err"] = redactSecrets(err.message).slice(0, ERROR_TEXT_MAX);
67
+ return copy;
68
+ }
69
+ function noop() {}
70
+ /** Stand-in when no logger was configured, so callers can log unconditionally. */
71
+ function silentLogger() {
72
+ const stub = {
73
+ info: noop,
74
+ warn: noop,
75
+ error: noop,
76
+ debug: noop
77
+ };
78
+ stub["child"] = () => stub;
79
+ return stub;
80
+ }
81
+ export { mirrorAttemptLog };
82
+
83
+ //# sourceMappingURL=attempt-logger.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attempt-logger.mjs","names":[],"sources":["../../src/capture/attempt-logger.ts"],"sourcesContent":["import type { Logger } from \"pino\";\nimport { emitAttemptLog, type AttemptLogRecorder } from \"../models/capture-attempt.ts\";\nimport type { CaptureLogLevel } from \"../schema/capture-attempt.ts\";\nimport { redactSecrets } from \"../utils/redact.ts\";\n\n/** Longest error text recorded on an attempt-log line. */\nconst ERROR_TEXT_MAX = 500;\n\n/** Mirrored levels; `debug` stays in pino only so attempt logs keep the signal. */\nconst MIRRORED_LEVELS = [\"info\", \"warn\", \"error\"] as const;\n\n/** A logger that also records `info`/`warn`/`error` lines on the build attempt. */\nexport interface AttemptMirror {\n /** Logger to hand to a runner: same as the original, plus attempt-log recording. */\n logger: Logger;\n /** Record one line on the attempt (and in pino); serialized with the mirrored lines. */\n log(level: CaptureLogLevel, message: string, fields?: Record<string, unknown>): void;\n /** Wait for every recorded line to be written. */\n flush(): Promise<void>;\n}\n\n/**\n * Mirror a logger's `info`/`warn`/`error` lines onto the attempt log.\n *\n * Writes go through one promise chain: `capture_logs` numbers lines with a\n * read-then-insert, so concurrent appends (parallel pages failing at once)\n * would collide on `seq`. With no recorder the logger is returned unchanged.\n */\nexport function mirrorAttemptLog(\n logger: Logger | undefined,\n record: AttemptLogRecorder | undefined,\n /** Optionally re-level a recorded line (pino keeps the original level). */\n levelFor: (level: CaptureLogLevel, fields?: Record<string, unknown>) => CaptureLogLevel = (\n level,\n ) => level,\n): AttemptMirror {\n let chain: Promise<void> = Promise.resolve();\n const enqueue = (level: CaptureLogLevel, message: string, fields?: unknown): void => {\n if (!record) return;\n const recorded = levelFor(level, fields as Record<string, unknown> | undefined);\n chain = chain.then(async () => {\n await emitAttemptLog(record, logger, recorded, message, fields);\n });\n };\n const base = logger ?? silentLogger();\n const mirrored = new Proxy(base, {\n get(target, property, receiver): unknown {\n const level = MIRRORED_LEVELS.find((name) => name === property);\n if (!level) return Reflect.get(target, property, receiver);\n return (first: unknown, second?: unknown, ...rest: unknown[]): void => {\n const args = [first, second, ...rest];\n while (args.length > 1 && args.at(-1) === undefined) args.pop();\n Reflect.apply(target[level], target, args);\n const [fields, message] = parseArgs(first, second);\n enqueue(level, message, fields);\n };\n },\n });\n return {\n logger: mirrored,\n log: (level, message, fields) => {\n base[level](fields ?? {}, message);\n enqueue(level, message, fields === undefined ? undefined : plainFields(fields));\n },\n flush: async () => {\n await chain;\n },\n };\n}\n\n/** pino call shapes: `(fields, message)` or `(message)`. */\nfunction parseArgs(first: unknown, second: unknown): [Record<string, unknown> | undefined, string] {\n if (typeof first === \"string\") return [undefined, first];\n const fields = first && typeof first === \"object\" ? plainFields(first) : undefined;\n return [fields, typeof second === \"string\" ? second : \"\"];\n}\n\n/** JSON-safe copy: an `Error` under `err` becomes its capped message. */\nfunction plainFields(fields: object): Record<string, unknown> {\n const copy: Record<string, unknown> = { ...fields };\n const err = copy[\"err\"];\n if (err instanceof Error) copy[\"err\"] = redactSecrets(err.message).slice(0, ERROR_TEXT_MAX);\n return copy;\n}\n\nfunction noop(): void {}\n\n/** Stand-in when no logger was configured, so callers can log unconditionally. */\nfunction silentLogger(): Logger {\n const stub: Record<string, unknown> = { info: noop, warn: noop, error: noop, debug: noop };\n stub[\"child\"] = (): unknown => stub;\n return stub as unknown as Logger;\n}\n"],"mappings":";;;;;;;AAMA,MAAM,iBAAiB;;AAGvB,MAAM,kBAAkB;CAAC;CAAQ;CAAQ;AAAO;;;;;;;;AAmBhD,SAAgB,iBACd,QACA,QAEA,YACE,UACG,OACU;CACf,IAAI,QAAuB,QAAQ,QAAQ;CAC3C,MAAM,WAAW,OAAwB,SAAiB,WAA2B;EACnF,IAAI,CAAC,QAAQ;EACb,MAAM,WAAW,SAAS,OAAO,MAA6C;EAC9E,QAAQ,MAAM,KAAK,YAAY;GAC7B,MAAM,eAAe,QAAQ,QAAQ,UAAU,SAAS,MAAM;EAChE,CAAC;CACH;CACA,MAAM,OAAO,UAAU,aAAa;CAcpC,OAAO;EACL,QAAQ,IAdW,MAAM,MAAM,EAC/B,IAAI,QAAQ,UAAU,UAAmB;GACvC,MAAM,QAAQ,gBAAgB,MAAM,SAAS,SAAS,QAAQ;GAC9D,IAAI,CAAC,OAAO,OAAO,QAAQ,IAAI,QAAQ,UAAU,QAAQ;GACzD,QAAQ,OAAgB,QAAkB,GAAG,SAA0B;IACrE,MAAM,OAAO;KAAC;KAAO;KAAQ,GAAG;IAAI;IACpC,OAAO,KAAK,SAAS,KAAK,KAAK,GAAG,EAAE,MAAM,KAAA,GAAW,KAAK,IAAI;IAC9D,QAAQ,MAAM,OAAO,QAAQ,QAAQ,IAAI;IACzC,MAAM,CAAC,QAAQ,WAAW,UAAU,OAAO,MAAM;IACjD,QAAQ,OAAO,SAAS,MAAM;GAChC;EACF,EACF,CAEiB;EACf,MAAM,OAAO,SAAS,WAAW;GAC/B,KAAK,MAAM,CAAC,UAAU,CAAC,GAAG,OAAO;GACjC,QAAQ,OAAO,SAAS,WAAW,KAAA,IAAY,KAAA,IAAY,YAAY,MAAM,CAAC;EAChF;EACA,OAAO,YAAY;GACjB,MAAM;EACR;CACF;AACF;;AAGA,SAAS,UAAU,OAAgB,QAAgE;CACjG,IAAI,OAAO,UAAU,UAAU,OAAO,CAAC,KAAA,GAAW,KAAK;CAEvD,OAAO,CADQ,SAAS,OAAO,UAAU,WAAW,YAAY,KAAK,IAAI,KAAA,GACzD,OAAO,WAAW,WAAW,SAAS,EAAE;AAC1D;;AAGA,SAAS,YAAY,QAAyC;CAC5D,MAAM,OAAgC,EAAE,GAAG,OAAO;CAClD,MAAM,MAAM,KAAK;CACjB,IAAI,eAAe,OAAO,KAAK,SAAS,cAAc,IAAI,OAAO,CAAC,CAAC,MAAM,GAAG,cAAc;CAC1F,OAAO;AACT;AAEA,SAAS,OAAa,CAAC;;AAGvB,SAAS,eAAuB;CAC9B,MAAM,OAAgC;EAAE,MAAM;EAAM,MAAM;EAAM,OAAO;EAAM,OAAO;CAAK;CACzF,KAAK,iBAA0B;CAC/B,OAAO;AACT"}
@@ -0,0 +1,33 @@
1
+ import __tsdown_shims_path from 'node:path';
2
+ import __tsdown_shims_url from 'node:url';
3
+ import { CaptureTarget, RunnerTarget } from "../adapters/capture-target.mjs";
4
+ /** Upper bound on targets per project (each multiplies render time). */
5
+ declare const MAX_BROWSERS = 3;
6
+ /** Parse the project's JSON `browsers` column; `null` when unset or malformed. */
7
+ declare function parseBrowsers(raw: string | null | undefined): CaptureTarget[] | null;
8
+ /**
9
+ * Targets to capture, primary first: the project's `browsers` list, else its single `browser`.
10
+ * Throws when the list exceeds {@link MAX_BROWSERS}.
11
+ */
12
+ declare function resolveBrowsers(project: {
13
+ browser?: string | undefined;
14
+ browsers?: string | null | undefined;
15
+ }): CaptureTarget[];
16
+ /**
17
+ * The primary target of a resolved target list: its first entry. The single owner of "primary":
18
+ * axe runs only here (ADR 0004, decision 6), so one story is not checked once per target.
19
+ */
20
+ declare function primaryTargetOf(targets: readonly CaptureTarget[]): CaptureTarget | undefined;
21
+ /** Throw when more than {@link MAX_BROWSERS} targets are requested. */
22
+ declare function assertBrowserCount(browsers: readonly string[]): void;
23
+ /**
24
+ * Why `target` cannot render on a runner offering `offered`: it is not on the list, or it is and is
25
+ * unavailable. `null` when it can, and always when the runner publishes no list.
26
+ */
27
+ declare function refusalFor(target: CaptureTarget, offered?: readonly RunnerTarget[]): string | null;
28
+ /** The targets of `offered`, for messages; `none` for an empty list. */
29
+ declare function listTargets(offered: readonly RunnerTarget[]): string;
30
+ /** Throw, naming every refusal, when the runner cannot render one of `targets`. */
31
+ declare function assertSupportedBrowsers(targets: readonly CaptureTarget[], offered?: readonly RunnerTarget[]): void;
32
+ export { MAX_BROWSERS, assertBrowserCount, assertSupportedBrowsers, listTargets, parseBrowsers, primaryTargetOf, refusalFor, resolveBrowsers };
33
+ //# sourceMappingURL=browsers.d.mts.map
@@ -0,0 +1,66 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ import { DEFAULT_BROWSER, isCaptureTarget, unavailableReason } from "../adapters/capture-target.mjs";
5
+ // src/capture/browsers.ts
6
+ /** Which capture targets a project renders on, and the guards around that list (ADR 0007). */
7
+ /** Upper bound on targets per project (each multiplies render time). */
8
+ const MAX_BROWSERS = 3;
9
+ /** Parse the project's JSON `browsers` column; `null` when unset or malformed. */
10
+ function parseBrowsers(raw) {
11
+ if (!raw) return null;
12
+ try {
13
+ const parsed = JSON.parse(raw);
14
+ return Array.isArray(parsed) && parsed.every((entry) => isCaptureTarget(entry)) ? dedupe(parsed) : null;
15
+ } catch {
16
+ return null;
17
+ }
18
+ }
19
+ /**
20
+ * Targets to capture, primary first: the project's `browsers` list, else its single `browser`.
21
+ * Throws when the list exceeds {@link MAX_BROWSERS}.
22
+ */
23
+ function resolveBrowsers(project) {
24
+ const listed = parseBrowsers(project.browsers);
25
+ const single = isCaptureTarget(project.browser) ? project.browser : DEFAULT_BROWSER;
26
+ const browsers = listed && listed.length > 0 ? listed : [single];
27
+ assertBrowserCount(browsers);
28
+ return browsers;
29
+ }
30
+ /**
31
+ * The primary target of a resolved target list: its first entry. The single owner of "primary":
32
+ * axe runs only here (ADR 0004, decision 6), so one story is not checked once per target.
33
+ */
34
+ function primaryTargetOf(targets) {
35
+ return targets[0];
36
+ }
37
+ /** Throw when more than {@link MAX_BROWSERS} targets are requested. */
38
+ function assertBrowserCount(browsers) {
39
+ if (browsers.length > 3) throw new Error(`At most 3 browsers are supported (got ${browsers.length})`);
40
+ }
41
+ /**
42
+ * Why `target` cannot render on a runner offering `offered`: it is not on the list, or it is and is
43
+ * unavailable. `null` when it can, and always when the runner publishes no list.
44
+ */
45
+ function refusalFor(target, offered) {
46
+ if (offered === void 0) return null;
47
+ const entry = offered.find((candidate) => candidate.target === target);
48
+ if (entry === void 0) return `${target} is not offered by this capture runner (offered: ${listTargets(offered)})`;
49
+ const reason = unavailableReason(entry.status);
50
+ return reason === null ? null : `${target} is unavailable: ${reason}`;
51
+ }
52
+ /** The targets of `offered`, for messages; `none` for an empty list. */
53
+ function listTargets(offered) {
54
+ return offered.length > 0 ? offered.map((entry) => entry.target).join(", ") : "none";
55
+ }
56
+ /** Throw, naming every refusal, when the runner cannot render one of `targets`. */
57
+ function assertSupportedBrowsers(targets, offered) {
58
+ const refusals = targets.flatMap((target) => refusalFor(target, offered) ?? []);
59
+ if (refusals.length > 0) throw new Error(`Capture runner cannot render: ${refusals.join("; ")}`);
60
+ }
61
+ function dedupe(browsers) {
62
+ return [...new Set(browsers)];
63
+ }
64
+ export { MAX_BROWSERS, assertBrowserCount, assertSupportedBrowsers, listTargets, parseBrowsers, primaryTargetOf, refusalFor, resolveBrowsers };
65
+
66
+ //# sourceMappingURL=browsers.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browsers.mjs","names":[],"sources":["../../src/capture/browsers.ts"],"sourcesContent":["/** Which capture targets a project renders on, and the guards around that list (ADR 0007). */\nimport {\n DEFAULT_BROWSER,\n isCaptureTarget,\n unavailableReason,\n type CaptureTarget,\n type RunnerTarget,\n} from \"../adapters/capture-target.ts\";\n\n/** Upper bound on targets per project (each multiplies render time). */\nexport const MAX_BROWSERS = 3;\n\n/** Parse the project's JSON `browsers` column; `null` when unset or malformed. */\nexport function parseBrowsers(raw: string | null | undefined): CaptureTarget[] | null {\n if (!raw) return null;\n try {\n const parsed: unknown = JSON.parse(raw);\n return Array.isArray(parsed) && parsed.every((entry) => isCaptureTarget(entry))\n ? dedupe(parsed)\n : null;\n } catch {\n return null;\n }\n}\n\n/**\n * Targets to capture, primary first: the project's `browsers` list, else its single `browser`.\n * Throws when the list exceeds {@link MAX_BROWSERS}.\n */\nexport function resolveBrowsers(project: {\n browser?: string | undefined;\n browsers?: string | null | undefined;\n}): CaptureTarget[] {\n const listed = parseBrowsers(project.browsers);\n const single = isCaptureTarget(project.browser) ? project.browser : DEFAULT_BROWSER;\n const browsers = listed && listed.length > 0 ? listed : [single];\n assertBrowserCount(browsers);\n return browsers;\n}\n\n/**\n * The primary target of a resolved target list: its first entry. The single owner of \"primary\":\n * axe runs only here (ADR 0004, decision 6), so one story is not checked once per target.\n */\nexport function primaryTargetOf(targets: readonly CaptureTarget[]): CaptureTarget | undefined {\n return targets[0];\n}\n\n/** Throw when more than {@link MAX_BROWSERS} targets are requested. */\nexport function assertBrowserCount(browsers: readonly string[]): void {\n if (browsers.length > MAX_BROWSERS) {\n throw new Error(`At most ${MAX_BROWSERS} browsers are supported (got ${browsers.length})`);\n }\n}\n\n/**\n * Why `target` cannot render on a runner offering `offered`: it is not on the list, or it is and is\n * unavailable. `null` when it can, and always when the runner publishes no list.\n */\nexport function refusalFor(\n target: CaptureTarget,\n offered?: readonly RunnerTarget[],\n): string | null {\n if (offered === undefined) {\n return null;\n }\n const entry = offered.find((candidate) => candidate.target === target);\n if (entry === undefined) {\n return `${target} is not offered by this capture runner (offered: ${listTargets(offered)})`;\n }\n const reason = unavailableReason(entry.status);\n return reason === null ? null : `${target} is unavailable: ${reason}`;\n}\n\n/** The targets of `offered`, for messages; `none` for an empty list. */\nexport function listTargets(offered: readonly RunnerTarget[]): string {\n return offered.length > 0 ? offered.map((entry) => entry.target).join(\", \") : \"none\";\n}\n\n/** Throw, naming every refusal, when the runner cannot render one of `targets`. */\nexport function assertSupportedBrowsers(\n targets: readonly CaptureTarget[],\n offered?: readonly RunnerTarget[],\n): void {\n const refusals = targets.flatMap((target) => refusalFor(target, offered) ?? []);\n if (refusals.length > 0) {\n throw new Error(`Capture runner cannot render: ${refusals.join(\"; \")}`);\n }\n}\n\nfunction dedupe(browsers: CaptureTarget[]): CaptureTarget[] {\n return [...new Set(browsers)];\n}\n"],"mappings":";;;;;;;AAUA,MAAa,eAAe;;AAG5B,SAAgB,cAAc,KAAwD;CACpF,IAAI,CAAC,KAAK,OAAO;CACjB,IAAI;EACF,MAAM,SAAkB,KAAK,MAAM,GAAG;EACtC,OAAO,MAAM,QAAQ,MAAM,KAAK,OAAO,OAAO,UAAU,gBAAgB,KAAK,CAAC,IAC1E,OAAO,MAAM,IACb;CACN,QAAQ;EACN,OAAO;CACT;AACF;;;;;AAMA,SAAgB,gBAAgB,SAGZ;CAClB,MAAM,SAAS,cAAc,QAAQ,QAAQ;CAC7C,MAAM,SAAS,gBAAgB,QAAQ,OAAO,IAAI,QAAQ,UAAU;CACpE,MAAM,WAAW,UAAU,OAAO,SAAS,IAAI,SAAS,CAAC,MAAM;CAC/D,mBAAmB,QAAQ;CAC3B,OAAO;AACT;;;;;AAMA,SAAgB,gBAAgB,SAA8D;CAC5F,OAAO,QAAQ;AACjB;;AAGA,SAAgB,mBAAmB,UAAmC;CACpE,IAAI,SAAS,SAAA,GACX,MAAM,IAAI,MAAM,yCAAuD,SAAS,OAAO,EAAE;AAE7F;;;;;AAMA,SAAgB,WACd,QACA,SACe;CACf,IAAI,YAAY,KAAA,GACd,OAAO;CAET,MAAM,QAAQ,QAAQ,MAAM,cAAc,UAAU,WAAW,MAAM;CACrE,IAAI,UAAU,KAAA,GACZ,OAAO,GAAG,OAAO,mDAAmD,YAAY,OAAO,EAAE;CAE3F,MAAM,SAAS,kBAAkB,MAAM,MAAM;CAC7C,OAAO,WAAW,OAAO,OAAO,GAAG,OAAO,mBAAmB;AAC/D;;AAGA,SAAgB,YAAY,SAA0C;CACpE,OAAO,QAAQ,SAAS,IAAI,QAAQ,KAAK,UAAU,MAAM,MAAM,CAAC,CAAC,KAAK,IAAI,IAAI;AAChF;;AAGA,SAAgB,wBACd,SACA,SACM;CACN,MAAM,WAAW,QAAQ,SAAS,WAAW,WAAW,QAAQ,OAAO,KAAK,CAAC,CAAC;CAC9E,IAAI,SAAS,SAAS,GACpB,MAAM,IAAI,MAAM,iCAAiC,SAAS,KAAK,IAAI,GAAG;AAE1E;AAEA,SAAS,OAAO,UAA4C;CAC1D,OAAO,CAAC,GAAG,IAAI,IAAI,QAAQ,CAAC;AAC9B"}
@@ -0,0 +1,30 @@
1
+ import __tsdown_shims_path from 'node:path';
2
+ import __tsdown_shims_url from 'node:url';
3
+ import { Build } from "../../schema/build.mjs";
4
+ import { Project } from "../../schema/project.mjs";
5
+ import { BuildStateDeps, SkipReason, StateTarget } from "./types.mjs";
6
+ /**
7
+ * Show the build's current state on one channel. Throws when the host refuses a status, so the
8
+ * caller can retry; resolves to why nothing was written when the build or channel is gone.
9
+ */
10
+ declare function deliverBuildState(deps: BuildStateDeps, ids: {
11
+ buildId: string;
12
+ channelId: string;
13
+ }): Promise<"delivered" | SkipReason>;
14
+ /** The build and its project, or null when either is gone. */
15
+ declare function loadBuild(deps: BuildStateDeps, buildId: string): Promise<{
16
+ build: Build;
17
+ project: Project;
18
+ } | null>;
19
+ /** Write `target`'s view of the build, linking to `origin`; throws on a refused status. */
20
+ declare function writeTo(deps: BuildStateDeps, loaded: {
21
+ build: Build;
22
+ project: Project;
23
+ }, where: {
24
+ target: StateTarget;
25
+ origin: string;
26
+ }): Promise<void>;
27
+ /** The project's enabled channels with a wired `state` provider; unreadable secrets are logged and skipped. */
28
+ declare function stateTargets(deps: BuildStateDeps, projectId: string): Promise<StateTarget[]>;
29
+ export { deliverBuildState, loadBuild, stateTargets, writeTo };
30
+ //# sourceMappingURL=deliver.d.mts.map
@@ -0,0 +1,117 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ import { isStateProvider } from "../../adapters/notifier/provider.mjs";
5
+ import { statusesForBuild } from "../../adapters/notifier/build-state.mjs";
6
+ import { channelPrefix, writeBuildState } from "../../adapters/notifier/state.mjs";
7
+ import { BuildModel } from "../../models/build.mjs";
8
+ import { SnapshotModel } from "../../models/snapshot.mjs";
9
+ import { ProjectModel } from "../../models/project.mjs";
10
+ import { latestVerdicts } from "../../models/build-review-status.mjs";
11
+ import { BuildReviewModel } from "../../models/build-review.mjs";
12
+ import { NotificationChannelModel } from "../../models/notification-channel.mjs";
13
+ import { createUrlBuilder } from "../../urls.mjs";
14
+ import { publishingOf } from "../publish/published.mjs";
15
+ // src/capture/build-state/deliver.ts
16
+ /**
17
+ * Delivering a build's state to git channels: read the build now, compute what each channel shows
18
+ * ({@link statusesForBuild}), write it. Reading at delivery time is what makes a retried, delayed
19
+ * or reordered request harmless (ADR 0014, decision 7).
20
+ */
21
+ /**
22
+ * Show the build's current state on one channel. Throws when the host refuses a status, so the
23
+ * caller can retry; resolves to why nothing was written when the build or channel is gone.
24
+ */
25
+ async function deliverBuildState(deps, ids) {
26
+ const loaded = await loadBuild(deps, ids.buildId);
27
+ const origin = loaded === null ? void 0 : await deps.publicOrigin();
28
+ if (loaded === null || !origin) return loaded === null ? "no-build" : "no-origin";
29
+ const target = await loadTarget(deps, ids.channelId, loaded.project.id);
30
+ if (typeof target === "string") return target;
31
+ await writeTo(deps, loaded, {
32
+ target,
33
+ origin
34
+ });
35
+ return "delivered";
36
+ }
37
+ /** The build and its project, or null when either is gone. */
38
+ async function loadBuild(deps, buildId) {
39
+ const build = await new BuildModel(deps.db).get(buildId);
40
+ const project = build ? await new ProjectModel(deps.db).get(build.projectId) : null;
41
+ return build && project ? {
42
+ build,
43
+ project
44
+ } : null;
45
+ }
46
+ /** Write `target`'s view of the build, linking to `origin`; throws on a refused status. */
47
+ async function writeTo(deps, loaded, where) {
48
+ const { build, project } = loaded;
49
+ const { target, origin } = where;
50
+ const states = statusesForBuild({
51
+ build,
52
+ projectSlug: project.slug,
53
+ prefix: channelPrefix(target.channel),
54
+ urls: createUrlBuilder(origin, deps.config.publishedBaseDomain),
55
+ flags: deps.flags(project),
56
+ publishing: await publishingOf(deps.storage, build),
57
+ approvals: await approvalsOf(deps, build, project),
58
+ changes: await changesOf(deps, build, project)
59
+ });
60
+ if (states.statuses.length === 0) {
61
+ deps.logger.info({ buildId: build.id }, "no commit to show a state on");
62
+ return;
63
+ }
64
+ await writeBuildState(target.provider, target.channel, {
65
+ sha: build.gitSha,
66
+ states,
67
+ logger: deps.logger
68
+ });
69
+ }
70
+ /** The project's enabled channels with a wired `state` provider; unreadable secrets are logged and skipped. */
71
+ async function stateTargets(deps, projectId) {
72
+ const model = channelModel(deps);
73
+ return (await model.list(projectId)).flatMap((row) => {
74
+ const view = row.enabled ? model.toView(row) : null;
75
+ if (row.enabled && view === null) deps.logger.warn({
76
+ channelId: row.id,
77
+ provider: row.provider
78
+ }, "failed to decrypt channel secret");
79
+ return view === null ? [] : targetOf(deps, view);
80
+ });
81
+ }
82
+ /** One channel by id, or why it cannot take part. */
83
+ async function loadTarget(deps, channelId, projectId) {
84
+ const model = channelModel(deps);
85
+ const row = await model.get(channelId);
86
+ if (row === null || row.projectId !== projectId) return "no-channel";
87
+ if (!row.enabled) return "disabled";
88
+ const view = model.toView(row);
89
+ if (view === null) throw new Error("channel secret cannot be decrypted with the configured keys");
90
+ return targetOf(deps, view)[0] ?? "no-provider";
91
+ }
92
+ function targetOf(deps, channel) {
93
+ const provider = deps.notifiers.find((candidate) => candidate.metadata.kind === channel.provider);
94
+ return provider !== void 0 && isStateProvider(provider) ? [{
95
+ channel,
96
+ provider
97
+ }] : [];
98
+ }
99
+ function channelModel(deps) {
100
+ return new NotificationChannelModel(deps.db, { notificationChannels: deps.db.tables.notificationChannels }, deps.secret);
101
+ }
102
+ /** Approval progress for `<prefix>/review`; only read when that status is the gate. */
103
+ async function approvalsOf(deps, build, project) {
104
+ const flags = deps.flags(project);
105
+ if (flags.testing || !flags.review) return;
106
+ return {
107
+ given: latestVerdicts(await new BuildReviewModel(deps.db).listByBuild(build.id)).filter((review) => review.verdict === "approved").length,
108
+ required: Math.max(1, project.reviewApprovals ?? 1)
109
+ };
110
+ }
111
+ /** The build's snapshots for the annotations; read only when `testing` is on and something changed. */
112
+ async function changesOf(deps, build, project) {
113
+ return deps.flags(project).testing && build.changedCount > 0 ? await new SnapshotModel(deps.db).listByBuild(build.id) : void 0;
114
+ }
115
+ export { deliverBuildState, loadBuild, stateTargets, writeTo };
116
+
117
+ //# sourceMappingURL=deliver.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deliver.mjs","names":[],"sources":["../../../src/capture/build-state/deliver.ts"],"sourcesContent":["/**\n * Delivering a build's state to git channels: read the build now, compute what each channel shows\n * ({@link statusesForBuild}), write it. Reading at delivery time is what makes a retried, delayed\n * or reordered request harmless (ADR 0014, decision 7).\n */\nimport { channelPrefix, statusesForBuild, writeBuildState } from \"../../adapters/notifier/index.ts\";\nimport { isStateProvider } from \"../../adapters/notifier/provider.ts\";\nimport { latestVerdicts } from \"../../models/build-review-status.ts\";\nimport { BuildReviewModel } from \"../../models/build-review.ts\";\nimport { BuildModel } from \"../../models/build.ts\";\nimport { NotificationChannelModel } from \"../../models/notification-channel.ts\";\nimport { ProjectModel } from \"../../models/project.ts\";\nimport { SnapshotModel } from \"../../models/snapshot.ts\";\nimport type { Build } from \"../../schema/build.ts\";\nimport type { Project } from \"../../schema/project.ts\";\nimport type { Snapshot } from \"../../schema/snapshot.ts\";\nimport { createUrlBuilder } from \"../../urls.ts\";\nimport { publishingOf } from \"../publish/published.ts\";\nimport type { BuildStateDeps, SkipReason, StateTarget } from \"./types.ts\";\n\n/**\n * Show the build's current state on one channel. Throws when the host refuses a status, so the\n * caller can retry; resolves to why nothing was written when the build or channel is gone.\n */\nexport async function deliverBuildState(\n deps: BuildStateDeps,\n ids: { buildId: string; channelId: string },\n): Promise<\"delivered\" | SkipReason> {\n const loaded = await loadBuild(deps, ids.buildId);\n // The origin can disappear between queueing and running (a cleared setting): nothing is shown.\n const origin = loaded === null ? undefined : await deps.publicOrigin();\n if (loaded === null || !origin) {\n return loaded === null ? \"no-build\" : \"no-origin\";\n }\n const target = await loadTarget(deps, ids.channelId, loaded.project.id);\n if (typeof target === \"string\") {\n return target;\n }\n await writeTo(deps, loaded, { target, origin });\n return \"delivered\";\n}\n\n/** The build and its project, or null when either is gone. */\nexport async function loadBuild(\n deps: BuildStateDeps,\n buildId: string,\n): Promise<{ build: Build; project: Project } | null> {\n const build = await new BuildModel(deps.db).get(buildId);\n const project = build ? await new ProjectModel(deps.db).get(build.projectId) : null;\n return build && project ? { build, project } : null;\n}\n\n/** Write `target`'s view of the build, linking to `origin`; throws on a refused status. */\nexport async function writeTo(\n deps: BuildStateDeps,\n loaded: { build: Build; project: Project },\n where: { target: StateTarget; origin: string },\n): Promise<void> {\n const { build, project } = loaded;\n const { target, origin } = where;\n const states = statusesForBuild({\n build,\n projectSlug: project.slug,\n prefix: channelPrefix(target.channel),\n urls: createUrlBuilder(origin, deps.config.publishedBaseDomain),\n flags: deps.flags(project),\n publishing: await publishingOf(deps.storage, build),\n approvals: await approvalsOf(deps, build, project),\n changes: await changesOf(deps, build, project),\n });\n if (states.statuses.length === 0) {\n deps.logger.info({ buildId: build.id }, \"no commit to show a state on\");\n return;\n }\n await writeBuildState(target.provider, target.channel, {\n sha: build.gitSha,\n states,\n logger: deps.logger,\n });\n}\n\n/** The project's enabled channels with a wired `state` provider; unreadable secrets are logged and skipped. */\nexport async function stateTargets(\n deps: BuildStateDeps,\n projectId: string,\n): Promise<StateTarget[]> {\n const model = channelModel(deps);\n const rows = await model.list(projectId);\n return rows.flatMap((row) => {\n const view = row.enabled ? model.toView(row) : null;\n if (row.enabled && view === null) {\n deps.logger.warn(\n { channelId: row.id, provider: row.provider },\n \"failed to decrypt channel secret\",\n );\n }\n return view === null ? [] : targetOf(deps, view);\n });\n}\n\n/** One channel by id, or why it cannot take part. */\nasync function loadTarget(\n deps: BuildStateDeps,\n channelId: string,\n projectId: string,\n): Promise<StateTarget | Extract<SkipReason, \"no-channel\" | \"disabled\" | \"no-provider\">> {\n const model = channelModel(deps);\n const row = await model.get(channelId);\n if (row === null || row.projectId !== projectId) {\n return \"no-channel\";\n }\n if (!row.enabled) {\n return \"disabled\";\n }\n const view = model.toView(row);\n if (view === null) {\n throw new Error(\"channel secret cannot be decrypted with the configured keys\");\n }\n return targetOf(deps, view)[0] ?? \"no-provider\";\n}\n\nfunction targetOf(deps: BuildStateDeps, channel: StateTarget[\"channel\"]): StateTarget[] {\n const provider = deps.notifiers.find((candidate) => candidate.metadata.kind === channel.provider);\n return provider !== undefined && isStateProvider(provider) ? [{ channel, provider }] : [];\n}\n\nfunction channelModel(deps: BuildStateDeps): NotificationChannelModel {\n return new NotificationChannelModel(\n deps.db,\n { notificationChannels: deps.db.tables.notificationChannels },\n deps.secret,\n );\n}\n\n/** Approval progress for `<prefix>/review`; only read when that status is the gate. */\nasync function approvalsOf(\n deps: BuildStateDeps,\n build: Build,\n project: Project,\n): Promise<{ given: number; required: number } | undefined> {\n const flags = deps.flags(project);\n if (flags.testing || !flags.review) {\n return undefined;\n }\n const reviews = latestVerdicts(await new BuildReviewModel(deps.db).listByBuild(build.id));\n return {\n given: reviews.filter((review) => review.verdict === \"approved\").length,\n required: Math.max(1, project.reviewApprovals ?? 1),\n };\n}\n\n/** The build's snapshots for the annotations; read only when `testing` is on and something changed. */\nasync function changesOf(\n deps: BuildStateDeps,\n build: Build,\n project: Project,\n): Promise<Snapshot[] | undefined> {\n const wanted = deps.flags(project).testing && build.changedCount > 0;\n return wanted ? await new SnapshotModel(deps.db).listByBuild(build.id) : undefined;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAwBA,eAAsB,kBACpB,MACA,KACmC;CACnC,MAAM,SAAS,MAAM,UAAU,MAAM,IAAI,OAAO;CAEhD,MAAM,SAAS,WAAW,OAAO,KAAA,IAAY,MAAM,KAAK,aAAa;CACrE,IAAI,WAAW,QAAQ,CAAC,QACtB,OAAO,WAAW,OAAO,aAAa;CAExC,MAAM,SAAS,MAAM,WAAW,MAAM,IAAI,WAAW,OAAO,QAAQ,EAAE;CACtE,IAAI,OAAO,WAAW,UACpB,OAAO;CAET,MAAM,QAAQ,MAAM,QAAQ;EAAE;EAAQ;CAAO,CAAC;CAC9C,OAAO;AACT;;AAGA,eAAsB,UACpB,MACA,SACoD;CACpD,MAAM,QAAQ,MAAM,IAAI,WAAW,KAAK,EAAE,CAAC,CAAC,IAAI,OAAO;CACvD,MAAM,UAAU,QAAQ,MAAM,IAAI,aAAa,KAAK,EAAE,CAAC,CAAC,IAAI,MAAM,SAAS,IAAI;CAC/E,OAAO,SAAS,UAAU;EAAE;EAAO;CAAQ,IAAI;AACjD;;AAGA,eAAsB,QACpB,MACA,QACA,OACe;CACf,MAAM,EAAE,OAAO,YAAY;CAC3B,MAAM,EAAE,QAAQ,WAAW;CAC3B,MAAM,SAAS,iBAAiB;EAC9B;EACA,aAAa,QAAQ;EACrB,QAAQ,cAAc,OAAO,OAAO;EACpC,MAAM,iBAAiB,QAAQ,KAAK,OAAO,mBAAmB;EAC9D,OAAO,KAAK,MAAM,OAAO;EACzB,YAAY,MAAM,aAAa,KAAK,SAAS,KAAK;EAClD,WAAW,MAAM,YAAY,MAAM,OAAO,OAAO;EACjD,SAAS,MAAM,UAAU,MAAM,OAAO,OAAO;CAC/C,CAAC;CACD,IAAI,OAAO,SAAS,WAAW,GAAG;EAChC,KAAK,OAAO,KAAK,EAAE,SAAS,MAAM,GAAG,GAAG,8BAA8B;EACtE;CACF;CACA,MAAM,gBAAgB,OAAO,UAAU,OAAO,SAAS;EACrD,KAAK,MAAM;EACX;EACA,QAAQ,KAAK;CACf,CAAC;AACH;;AAGA,eAAsB,aACpB,MACA,WACwB;CACxB,MAAM,QAAQ,aAAa,IAAI;CAE/B,QAAO,MADY,MAAM,KAAK,SAAS,EAAA,CAC3B,SAAS,QAAQ;EAC3B,MAAM,OAAO,IAAI,UAAU,MAAM,OAAO,GAAG,IAAI;EAC/C,IAAI,IAAI,WAAW,SAAS,MAC1B,KAAK,OAAO,KACV;GAAE,WAAW,IAAI;GAAI,UAAU,IAAI;EAAS,GAC5C,kCACF;EAEF,OAAO,SAAS,OAAO,CAAC,IAAI,SAAS,MAAM,IAAI;CACjD,CAAC;AACH;;AAGA,eAAe,WACb,MACA,WACA,WACuF;CACvF,MAAM,QAAQ,aAAa,IAAI;CAC/B,MAAM,MAAM,MAAM,MAAM,IAAI,SAAS;CACrC,IAAI,QAAQ,QAAQ,IAAI,cAAc,WACpC,OAAO;CAET,IAAI,CAAC,IAAI,SACP,OAAO;CAET,MAAM,OAAO,MAAM,OAAO,GAAG;CAC7B,IAAI,SAAS,MACX,MAAM,IAAI,MAAM,6DAA6D;CAE/E,OAAO,SAAS,MAAM,IAAI,CAAC,CAAC,MAAM;AACpC;AAEA,SAAS,SAAS,MAAsB,SAAgD;CACtF,MAAM,WAAW,KAAK,UAAU,MAAM,cAAc,UAAU,SAAS,SAAS,QAAQ,QAAQ;CAChG,OAAO,aAAa,KAAA,KAAa,gBAAgB,QAAQ,IAAI,CAAC;EAAE;EAAS;CAAS,CAAC,IAAI,CAAC;AAC1F;AAEA,SAAS,aAAa,MAAgD;CACpE,OAAO,IAAI,yBACT,KAAK,IACL,EAAE,sBAAsB,KAAK,GAAG,OAAO,qBAAqB,GAC5D,KAAK,MACP;AACF;;AAGA,eAAe,YACb,MACA,OACA,SAC0D;CAC1D,MAAM,QAAQ,KAAK,MAAM,OAAO;CAChC,IAAI,MAAM,WAAW,CAAC,MAAM,QAC1B;CAGF,OAAO;EACL,OAFc,eAAe,MAAM,IAAI,iBAAiB,KAAK,EAAE,CAAC,CAAC,YAAY,MAAM,EAAE,CAExE,CAAC,CAAC,QAAQ,WAAW,OAAO,YAAY,UAAU,CAAC,CAAC;EACjE,UAAU,KAAK,IAAI,GAAG,QAAQ,mBAAmB,CAAC;CACpD;AACF;;AAGA,eAAe,UACb,MACA,OACA,SACiC;CAEjC,OADe,KAAK,MAAM,OAAO,CAAC,CAAC,WAAW,MAAM,eAAe,IACnD,MAAM,IAAI,cAAc,KAAK,EAAE,CAAC,CAAC,YAAY,MAAM,EAAE,IAAI,KAAA;AAC3E"}
@@ -0,0 +1,8 @@
1
+ import __tsdown_shims_path from 'node:path';
2
+ import __tsdown_shims_url from 'node:url';
3
+ import { BuildStateDeps, BuildStatePublisher, SkipReason, StateTarget } from "./types.mjs";
4
+ import { deliverBuildState, loadBuild, stateTargets, writeTo } from "./deliver.mjs";
5
+ import { NOTIFY_BACKOFF, NOTIFY_MAX_ATTEMPTS, NotifyFailure, NotifyHandlerOptions, NotifyJobQueue, createNotifyEnqueuer, createNotifyHandler, notifyExhausted } from "./notify-job.mjs";
6
+ import { BuildStatePublisherOptions, PAUSED_MESSAGE, createBuildStatePublisher } from "./publisher.mjs";
7
+ import { requestOpenBuildStates } from "./resume.mjs";
8
+ export { type BuildStateDeps, type BuildStatePublisher, type BuildStatePublisherOptions, NOTIFY_BACKOFF, NOTIFY_MAX_ATTEMPTS, type NotifyFailure, type NotifyHandlerOptions, type NotifyJobQueue, PAUSED_MESSAGE, type SkipReason, type StateTarget, createBuildStatePublisher, createNotifyEnqueuer, createNotifyHandler, deliverBuildState, loadBuild, notifyExhausted, requestOpenBuildStates, stateTargets, writeTo };
@@ -0,0 +1,54 @@
1
+ import __tsdown_shims_path from 'node:path';
2
+ import __tsdown_shims_url from 'node:url';
3
+ import { BuildStateDeps } from "./types.mjs";
4
+ import { ConsumerJob, KindHandler } from "../consumer/types.mjs";
5
+ import { QueueJobInput } from "../db-queue/types.mjs";
6
+ /** Attempts a delivery gets before it is given up (the first try and four retries). */
7
+ declare const NOTIFY_MAX_ATTEMPTS = 5;
8
+ /** Retry delay of a delivery: 10 s, then doubling to at most five minutes. */
9
+ declare const NOTIFY_BACKOFF: {
10
+ readonly baseMs: 10000;
11
+ readonly maxMs: number;
12
+ };
13
+ /** A delivery that was given up: the channel shows the error and the admin event fires. */
14
+ interface NotifyFailure {
15
+ channelId: string;
16
+ projectId: string | null;
17
+ provider: string;
18
+ buildId: string;
19
+ /** Sanitized error text. */
20
+ error: string;
21
+ }
22
+ /** What {@link createNotifyHandler} needs. */
23
+ interface NotifyHandlerOptions {
24
+ deps: BuildStateDeps;
25
+ /** Called once when a delivery is given up, after the channel row recorded the error. Failures are logged. */
26
+ onFailed?: (failure: NotifyFailure) => Promise<void>;
27
+ }
28
+ /**
29
+ * The handler. Resolving acks the job (also when the build or channel is gone: nothing is left to
30
+ * show). A refused status rejects, so the consumer requeues with {@link NOTIFY_BACKOFF}; the last
31
+ * attempt records the error on the channel and raises `onFailed`.
32
+ */
33
+ declare function createNotifyHandler(options: NotifyHandlerOptions): KindHandler;
34
+ /**
35
+ * For the queue's `onExhausted` hook: a last attempt that lost its lease never ran its handler to
36
+ * the end, so the failure is surfaced here (the hook may run on several instances; recording the
37
+ * same error twice is harmless, the admin event may repeat).
38
+ */
39
+ declare function notifyExhausted(options: NotifyHandlerOptions): (job: ConsumerJob, error: string) => Promise<void>;
40
+ /** The slice of the database queue the enqueuer uses. */
41
+ interface NotifyJobQueue {
42
+ enqueueJob(job: QueueJobInput): Promise<string>;
43
+ }
44
+ /**
45
+ * Enqueue a `notify` job for a channel and build, unless one is already waiting: it will read the
46
+ * build's state when it runs, so a second would deliver the same thing. A job that is already
47
+ * running may have read an older state, so a new one is queued behind it.
48
+ */
49
+ declare function createNotifyEnqueuer(deps: Pick<BuildStateDeps, "db">, queue: NotifyJobQueue): (job: {
50
+ channelId: string;
51
+ buildId: string;
52
+ }) => Promise<void>;
53
+ export { NOTIFY_BACKOFF, NOTIFY_MAX_ATTEMPTS, NotifyFailure, NotifyHandlerOptions, NotifyJobQueue, createNotifyEnqueuer, createNotifyHandler, notifyExhausted };
54
+ //# sourceMappingURL=notify-job.d.mts.map
@@ -0,0 +1,132 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ import { sanitizeErrorText } from "../../utils/redact.mjs";
5
+ import { NotificationChannelModel } from "../../models/notification-channel.mjs";
6
+ import { deliverBuildState } from "./deliver.mjs";
7
+ import { CaptureJobModel } from "../../models/capture-job.mjs";
8
+ import { isFinalAttempt } from "../consumer/retry.mjs";
9
+ import { decodePayload } from "../db-queue/codec.mjs";
10
+ // src/capture/build-state/notify-job.ts
11
+ /**
12
+ * The `notify` job kind (ADR 0014, decision 7; ADR 0006, decision 4): one job per `(channel,
13
+ * build)` state change. The job names a channel and a build, never a status: the handler reads the
14
+ * build when it runs, so a retried or reordered job cannot post a stale state, and a queued job
15
+ * for the same pair already covers a newer request.
16
+ */
17
+ /** Attempts a delivery gets before it is given up (the first try and four retries). */
18
+ const NOTIFY_MAX_ATTEMPTS = 5;
19
+ /** Retry delay of a delivery: 10 s, then doubling to at most five minutes. */
20
+ const NOTIFY_BACKOFF = {
21
+ baseMs: 1e4,
22
+ maxMs: 3e5
23
+ };
24
+ /** Longest error text kept on a channel row. */
25
+ const MAX_ERROR_LENGTH = 500;
26
+ /** The channel a `notify` job delivers to. */
27
+ function channelIdOf(job) {
28
+ const id = job.data?.["channelId"];
29
+ return typeof id === "string" ? id : "";
30
+ }
31
+ /**
32
+ * The handler. Resolving acks the job (also when the build or channel is gone: nothing is left to
33
+ * show). A refused status rejects, so the consumer requeues with {@link NOTIFY_BACKOFF}; the last
34
+ * attempt records the error on the channel and raises `onFailed`.
35
+ */
36
+ function createNotifyHandler(options) {
37
+ const { deps } = options;
38
+ return {
39
+ accepts: (job) => job.buildId.length > 0 && channelIdOf(job).length > 0,
40
+ backoff: NOTIFY_BACKOFF,
41
+ run: async (job, context) => {
42
+ const channelId = channelIdOf(job);
43
+ try {
44
+ const result = await deliverBuildState(deps, {
45
+ buildId: job.buildId,
46
+ channelId
47
+ });
48
+ if (result === "delivered") await channelModel(deps).recordOutcome(channelId);
49
+ else context.logger.info({ result }, "nothing to deliver");
50
+ } catch (error) {
51
+ if (isFinalAttempt(job)) await surfaceFailure(options, {
52
+ channelId,
53
+ buildId: job.buildId,
54
+ error
55
+ });
56
+ throw error;
57
+ }
58
+ }
59
+ };
60
+ }
61
+ /**
62
+ * For the queue's `onExhausted` hook: a last attempt that lost its lease never ran its handler to
63
+ * the end, so the failure is surfaced here (the hook may run on several instances; recording the
64
+ * same error twice is harmless, the admin event may repeat).
65
+ */
66
+ function notifyExhausted(options) {
67
+ return async (job, error) => {
68
+ await surfaceFailure(options, {
69
+ channelId: channelIdOf(job),
70
+ buildId: job.buildId,
71
+ error
72
+ });
73
+ };
74
+ }
75
+ async function surfaceFailure(options, failure) {
76
+ const { deps } = options;
77
+ const text = sanitizeErrorText(errorText(failure.error), MAX_ERROR_LENGTH);
78
+ deps.logger.error({
79
+ channelId: failure.channelId,
80
+ buildId: failure.buildId,
81
+ error: text
82
+ }, "state delivery given up");
83
+ try {
84
+ await recordAndAlert(options, {
85
+ ...failure,
86
+ error: text
87
+ });
88
+ } catch (error) {
89
+ deps.logger.warn({
90
+ err: error,
91
+ channelId: failure.channelId
92
+ }, "failed to record the failure");
93
+ }
94
+ }
95
+ /** Put the error on the channel row, then raise `onFailed`; a channel that is gone needs neither. */
96
+ async function recordAndAlert(options, failure) {
97
+ const model = channelModel(options.deps);
98
+ const row = await model.get(failure.channelId);
99
+ if (row === null) return;
100
+ await model.recordOutcome(row.id, failure.error);
101
+ await options.onFailed?.({
102
+ channelId: row.id,
103
+ projectId: row.projectId,
104
+ provider: row.provider,
105
+ buildId: failure.buildId,
106
+ error: failure.error
107
+ });
108
+ }
109
+ function errorText(error) {
110
+ return error instanceof Error ? error.message : String(error);
111
+ }
112
+ function channelModel(deps) {
113
+ return new NotificationChannelModel(deps.db, { notificationChannels: deps.db.tables.notificationChannels }, deps.secret);
114
+ }
115
+ /**
116
+ * Enqueue a `notify` job for a channel and build, unless one is already waiting: it will read the
117
+ * build's state when it runs, so a second would deliver the same thing. A job that is already
118
+ * running may have read an older state, so a new one is queued behind it.
119
+ */
120
+ function createNotifyEnqueuer(deps, queue) {
121
+ return async ({ channelId, buildId }) => {
122
+ if (!(await new CaptureJobModel(deps.db).listForBuild(buildId)).some((row) => row.kind === "notify" && row.status === "queued" && decodePayload(row.payload).data?.["channelId"] === channelId)) await queue.enqueueJob({
123
+ kind: "notify",
124
+ buildId,
125
+ data: { channelId },
126
+ maxAttempts: 5
127
+ });
128
+ };
129
+ }
130
+ export { NOTIFY_BACKOFF, NOTIFY_MAX_ATTEMPTS, createNotifyEnqueuer, createNotifyHandler, notifyExhausted };
131
+
132
+ //# sourceMappingURL=notify-job.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notify-job.mjs","names":[],"sources":["../../../src/capture/build-state/notify-job.ts"],"sourcesContent":["/**\n * The `notify` job kind (ADR 0014, decision 7; ADR 0006, decision 4): one job per `(channel,\n * build)` state change. The job names a channel and a build, never a status: the handler reads the\n * build when it runs, so a retried or reordered job cannot post a stale state, and a queued job\n * for the same pair already covers a newer request.\n */\nimport { CaptureJobModel } from \"../../models/capture-job.ts\";\nimport { NotificationChannelModel } from \"../../models/notification-channel.ts\";\nimport { sanitizeErrorText } from \"../../utils/redact.ts\";\nimport { isFinalAttempt } from \"../consumer/retry.ts\";\nimport type { ConsumerJob, KindHandler } from \"../consumer/types.ts\";\nimport { decodePayload } from \"../db-queue/codec.ts\";\nimport type { QueueJobInput } from \"../db-queue/types.ts\";\nimport { deliverBuildState } from \"./deliver.ts\";\nimport type { BuildStateDeps } from \"./types.ts\";\n\n/** Attempts a delivery gets before it is given up (the first try and four retries). */\nexport const NOTIFY_MAX_ATTEMPTS = 5;\n\n/** Retry delay of a delivery: 10 s, then doubling to at most five minutes. */\nexport const NOTIFY_BACKOFF = { baseMs: 10_000, maxMs: 5 * 60_000 } as const;\n\n/** Longest error text kept on a channel row. */\nconst MAX_ERROR_LENGTH = 500;\n\n/** A delivery that was given up: the channel shows the error and the admin event fires. */\nexport interface NotifyFailure {\n channelId: string;\n projectId: string | null;\n provider: string;\n buildId: string;\n /** Sanitized error text. */\n error: string;\n}\n\n/** What {@link createNotifyHandler} needs. */\nexport interface NotifyHandlerOptions {\n deps: BuildStateDeps;\n /** Called once when a delivery is given up, after the channel row recorded the error. Failures are logged. */\n onFailed?: (failure: NotifyFailure) => Promise<void>;\n}\n\n/** The channel a `notify` job delivers to. */\nfunction channelIdOf(job: Pick<ConsumerJob, \"data\">): string {\n const id = job.data?.[\"channelId\"];\n return typeof id === \"string\" ? id : \"\";\n}\n\n/**\n * The handler. Resolving acks the job (also when the build or channel is gone: nothing is left to\n * show). A refused status rejects, so the consumer requeues with {@link NOTIFY_BACKOFF}; the last\n * attempt records the error on the channel and raises `onFailed`.\n */\nexport function createNotifyHandler(options: NotifyHandlerOptions): KindHandler {\n const { deps } = options;\n return {\n accepts: (job) => job.buildId.length > 0 && channelIdOf(job).length > 0,\n backoff: NOTIFY_BACKOFF,\n run: async (job, context) => {\n const channelId = channelIdOf(job);\n try {\n const result = await deliverBuildState(deps, { buildId: job.buildId, channelId });\n if (result === \"delivered\") {\n await channelModel(deps).recordOutcome(channelId);\n } else {\n context.logger.info({ result }, \"nothing to deliver\");\n }\n } catch (error) {\n if (isFinalAttempt(job)) {\n await surfaceFailure(options, { channelId, buildId: job.buildId, error });\n }\n throw error;\n }\n },\n };\n}\n\n/**\n * For the queue's `onExhausted` hook: a last attempt that lost its lease never ran its handler to\n * the end, so the failure is surfaced here (the hook may run on several instances; recording the\n * same error twice is harmless, the admin event may repeat).\n */\nexport function notifyExhausted(\n options: NotifyHandlerOptions,\n): (job: ConsumerJob, error: string) => Promise<void> {\n return async (job, error) => {\n await surfaceFailure(options, { channelId: channelIdOf(job), buildId: job.buildId, error });\n };\n}\n\nasync function surfaceFailure(\n options: NotifyHandlerOptions,\n failure: { channelId: string; buildId: string; error: unknown },\n): Promise<void> {\n const { deps } = options;\n const text = sanitizeErrorText(errorText(failure.error), MAX_ERROR_LENGTH);\n deps.logger.error(\n { channelId: failure.channelId, buildId: failure.buildId, error: text },\n \"state delivery given up\",\n );\n try {\n await recordAndAlert(options, { ...failure, error: text });\n } catch (error) {\n deps.logger.warn({ err: error, channelId: failure.channelId }, \"failed to record the failure\");\n }\n}\n\n/** Put the error on the channel row, then raise `onFailed`; a channel that is gone needs neither. */\nasync function recordAndAlert(\n options: NotifyHandlerOptions,\n failure: { channelId: string; buildId: string; error: string },\n): Promise<void> {\n const model = channelModel(options.deps);\n const row = await model.get(failure.channelId);\n if (row === null) {\n return;\n }\n await model.recordOutcome(row.id, failure.error);\n await options.onFailed?.({\n channelId: row.id,\n projectId: row.projectId,\n provider: row.provider,\n buildId: failure.buildId,\n error: failure.error,\n });\n}\n\nfunction errorText(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\nfunction channelModel(deps: BuildStateDeps): NotificationChannelModel {\n return new NotificationChannelModel(\n deps.db,\n { notificationChannels: deps.db.tables.notificationChannels },\n deps.secret,\n );\n}\n\n/** The slice of the database queue the enqueuer uses. */\nexport interface NotifyJobQueue {\n enqueueJob(job: QueueJobInput): Promise<string>;\n}\n\n/**\n * Enqueue a `notify` job for a channel and build, unless one is already waiting: it will read the\n * build's state when it runs, so a second would deliver the same thing. A job that is already\n * running may have read an older state, so a new one is queued behind it.\n */\nexport function createNotifyEnqueuer(\n deps: Pick<BuildStateDeps, \"db\">,\n queue: NotifyJobQueue,\n): (job: { channelId: string; buildId: string }) => Promise<void> {\n return async ({ channelId, buildId }) => {\n // Built per call: a host that never enqueues must not touch `capture_jobs`.\n const rows = await new CaptureJobModel(deps.db).listForBuild(buildId);\n const waiting = rows.some(\n (row) =>\n row.kind === \"notify\" &&\n row.status === \"queued\" &&\n decodePayload(row.payload).data?.[\"channelId\"] === channelId,\n );\n if (!waiting) {\n await queue.enqueueJob({\n kind: \"notify\",\n buildId,\n data: { channelId },\n maxAttempts: NOTIFY_MAX_ATTEMPTS,\n });\n }\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAiBA,MAAa,sBAAsB;;AAGnC,MAAa,iBAAiB;CAAE,QAAQ;CAAQ,OAAO;AAAW;;AAGlE,MAAM,mBAAmB;;AAoBzB,SAAS,YAAY,KAAwC;CAC3D,MAAM,KAAK,IAAI,OAAO;CACtB,OAAO,OAAO,OAAO,WAAW,KAAK;AACvC;;;;;;AAOA,SAAgB,oBAAoB,SAA4C;CAC9E,MAAM,EAAE,SAAS;CACjB,OAAO;EACL,UAAU,QAAQ,IAAI,QAAQ,SAAS,KAAK,YAAY,GAAG,CAAC,CAAC,SAAS;EACtE,SAAS;EACT,KAAK,OAAO,KAAK,YAAY;GAC3B,MAAM,YAAY,YAAY,GAAG;GACjC,IAAI;IACF,MAAM,SAAS,MAAM,kBAAkB,MAAM;KAAE,SAAS,IAAI;KAAS;IAAU,CAAC;IAChF,IAAI,WAAW,aACb,MAAM,aAAa,IAAI,CAAC,CAAC,cAAc,SAAS;SAEhD,QAAQ,OAAO,KAAK,EAAE,OAAO,GAAG,oBAAoB;GAExD,SAAS,OAAO;IACd,IAAI,eAAe,GAAG,GACpB,MAAM,eAAe,SAAS;KAAE;KAAW,SAAS,IAAI;KAAS;IAAM,CAAC;IAE1E,MAAM;GACR;EACF;CACF;AACF;;;;;;AAOA,SAAgB,gBACd,SACoD;CACpD,OAAO,OAAO,KAAK,UAAU;EAC3B,MAAM,eAAe,SAAS;GAAE,WAAW,YAAY,GAAG;GAAG,SAAS,IAAI;GAAS;EAAM,CAAC;CAC5F;AACF;AAEA,eAAe,eACb,SACA,SACe;CACf,MAAM,EAAE,SAAS;CACjB,MAAM,OAAO,kBAAkB,UAAU,QAAQ,KAAK,GAAG,gBAAgB;CACzE,KAAK,OAAO,MACV;EAAE,WAAW,QAAQ;EAAW,SAAS,QAAQ;EAAS,OAAO;CAAK,GACtE,yBACF;CACA,IAAI;EACF,MAAM,eAAe,SAAS;GAAE,GAAG;GAAS,OAAO;EAAK,CAAC;CAC3D,SAAS,OAAO;EACd,KAAK,OAAO,KAAK;GAAE,KAAK;GAAO,WAAW,QAAQ;EAAU,GAAG,8BAA8B;CAC/F;AACF;;AAGA,eAAe,eACb,SACA,SACe;CACf,MAAM,QAAQ,aAAa,QAAQ,IAAI;CACvC,MAAM,MAAM,MAAM,MAAM,IAAI,QAAQ,SAAS;CAC7C,IAAI,QAAQ,MACV;CAEF,MAAM,MAAM,cAAc,IAAI,IAAI,QAAQ,KAAK;CAC/C,MAAM,QAAQ,WAAW;EACvB,WAAW,IAAI;EACf,WAAW,IAAI;EACf,UAAU,IAAI;EACd,SAAS,QAAQ;EACjB,OAAO,QAAQ;CACjB,CAAC;AACH;AAEA,SAAS,UAAU,OAAwB;CACzC,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAC9D;AAEA,SAAS,aAAa,MAAgD;CACpE,OAAO,IAAI,yBACT,KAAK,IACL,EAAE,sBAAsB,KAAK,GAAG,OAAO,qBAAqB,GAC5D,KAAK,MACP;AACF;;;;;;AAYA,SAAgB,qBACd,MACA,OACgE;CAChE,OAAO,OAAO,EAAE,WAAW,cAAc;EASvC,IAAI,EANY,MADG,IAAI,gBAAgB,KAAK,EAAE,CAAC,CAAC,aAAa,OAAO,EAAA,CAC/C,MAClB,QACC,IAAI,SAAS,YACb,IAAI,WAAW,YACf,cAAc,IAAI,OAAO,CAAC,CAAC,OAAO,iBAAiB,SAE5C,GACT,MAAM,MAAM,WAAW;GACrB,MAAM;GACN;GACA,MAAM,EAAE,UAAU;GAClB,aAAA;EACF,CAAC;CAEL;AACF"}
@@ -0,0 +1,21 @@
1
+ import __tsdown_shims_path from 'node:path';
2
+ import __tsdown_shims_url from 'node:url';
3
+ import { BuildStateDeps, BuildStatePublisher } from "./types.mjs";
4
+ /** What {@link createBuildStatePublisher} needs. */
5
+ interface BuildStatePublisherOptions {
6
+ deps: BuildStateDeps;
7
+ /**
8
+ * Queues a `notify` job. Set when this instance (or another one) consumes the queue; absent, the
9
+ * state is delivered inline in the caller's request, without durability.
10
+ */
11
+ enqueue?: ((job: {
12
+ channelId: string;
13
+ buildId: string;
14
+ }) => Promise<void>) | undefined;
15
+ }
16
+ /** Logged once when the first request finds no public origin, and again after one came back and went. */
17
+ declare const PAUSED_MESSAGE = "notifications are paused until a public URL is set (PUBLIC_BASE_URL or a confirmed detected origin)";
18
+ /** A publisher over `options.deps`. */
19
+ declare function createBuildStatePublisher(options: BuildStatePublisherOptions): BuildStatePublisher;
20
+ export { BuildStatePublisherOptions, PAUSED_MESSAGE, createBuildStatePublisher };
21
+ //# sourceMappingURL=publisher.d.mts.map