@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 @@
1
+ {"version":3,"file":"run-job.mjs","names":[],"sources":["../../../src/capture/consumer/run-job.ts"],"sourcesContent":["/**\n * Run one claimed job to its end: pick the handler by kind, keep the lease (heartbeat and progress), then settle\n * the job on the queue (ack, retry with backoff, fail, or leave it to whoever owns it now).\n */\nimport type { PollableCaptureQueue } from \"../../adapters/capture-queue.ts\";\nimport type { Logger } from \"../../logger.ts\";\nimport { roundedTimings, runWithTimings } from \"../../utils/timing.ts\";\nimport { keepJobLease, type JobLeaseKeeper, type LeaseEnd } from \"../db-queue/keeper.ts\";\nimport type { QueueTimers } from \"../db-queue/timers.ts\";\nimport { reportFinished, type ConsumerMetrics, type JobOutcome } from \"./metrics.ts\";\nimport { backoffDelayMs, DEFAULT_BACKOFF_MS, isFinalAttempt } from \"./retry.ts\";\nimport type { ConsumerJob, KindHandler } from \"./types.ts\";\n\n/** What {@link runJob} needs from the consumer. */\nexport interface JobRuntime {\n queue: PollableCaptureQueue;\n /** Normalized registry. */\n handlers: ReadonlyMap<string, KindHandler>;\n maxRetries: number;\n backoffMs?: number;\n logger: Logger;\n metrics?: ConsumerMetrics | undefined;\n onProgress?: ((job: ConsumerJob) => void) | undefined;\n now: () => number;\n timers?: QueueTimers | undefined;\n lease?: { renewEveryMs?: number; checkMs?: number } | undefined;\n}\n\n/** Why a job's handler stopped: it finished (or threw), or the queue took the job away. */\ntype Race = { kind: \"settled\"; error?: unknown } | { kind: \"ended\"; end: LeaseEnd };\n\n/** Run `job` in the slot the caller holds; never rejects. */\nexport async function runJob(rt: JobRuntime, job: ConsumerJob): Promise<void> {\n const kind = job.kind ?? \"capture\";\n const startedAt = rt.now();\n const logger = rt.logger.child({\n buildId: job.buildId,\n jobId: job.jobId ?? job.receipt,\n kind,\n reqId: job.reqId,\n });\n const finish = (outcome: JobOutcome): void => {\n reportFinished(rt.metrics, {\n kind,\n outcome,\n durationMs: rt.now() - startedAt,\n attempt: job.attempt ?? (job.attempts ?? 0) + 1,\n });\n };\n try {\n finish(await execute(rt, job, logger));\n } catch (error) {\n // Settling failed (the database is unreachable): the lease runs out and the queue redelivers.\n logger.error({ err: error }, \"failed to settle job\");\n finish(\"failed\");\n }\n}\n\nasync function execute(rt: JobRuntime, job: ConsumerJob, logger: Logger): Promise<JobOutcome> {\n const handler = rt.handlers.get(job.kind ?? \"capture\");\n const refused = await refuse(rt, job, { handler, logger });\n if (refused !== null || handler === undefined) {\n return refused ?? \"unhandled\";\n }\n return await runHandler(rt, { job, handler, logger });\n}\n\n/** Fail a job nobody can run (no handler for its kind) or that is malformed; null when runnable. */\nasync function refuse(\n rt: JobRuntime,\n job: ConsumerJob,\n held: { handler: KindHandler | undefined; logger: Logger },\n): Promise<JobOutcome | null> {\n const { handler, logger } = held;\n if (handler === undefined) {\n logger.error(\"no handler registered for job kind; failing the job\");\n await failJob(rt, job, `no handler registered for job kind \"${job.kind ?? \"capture\"}\"`, logger);\n return \"unhandled\";\n }\n if (handler.accepts?.(job) === false) {\n logger.warn({ attempts: job.attempts }, \"dropping malformed queue job\");\n await failJob(rt, job, \"malformed job\", logger);\n return \"dropped\";\n }\n return null;\n}\n\nasync function runHandler(\n rt: JobRuntime,\n held: { job: ConsumerJob; handler: KindHandler; logger: Logger },\n): Promise<JobOutcome> {\n const { job, handler, logger } = held;\n const keeper = keepLease(rt, job, logger);\n const touch = (): void => {\n keeper.touch();\n rt.onProgress?.(job);\n };\n logger.info(\"consumer picked up job\");\n const run = invoke(handler, job, { logger, touch });\n try {\n const raced = await Promise.race([run, endOf(keeper)]);\n return raced.kind === \"ended\"\n ? await abandon(rt, held, raced.end)\n : await settle(rt, held, raced);\n } finally {\n keeper.stop();\n run.catch(() => {});\n }\n}\n\nfunction keepLease(rt: JobRuntime, job: ConsumerJob, logger: Logger): JobLeaseKeeper {\n return keepJobLease(rt.queue, job, {\n ...rt.lease,\n now: rt.now,\n ...(rt.timers === undefined ? {} : { timers: rt.timers }),\n logger,\n });\n}\n\n/** Run the handler, timing it; its failure is a value so the race can carry it. */\nasync function invoke(\n handler: KindHandler,\n job: ConsumerJob,\n context: { logger: Logger; touch: () => void },\n): Promise<Race> {\n try {\n const { timings } = await runWithTimings(async () => {\n await handler.run(job, context);\n });\n context.logger.info({ timings: roundedTimings(timings) }, \"consumer job timings\");\n return { kind: \"settled\" };\n } catch (error) {\n return { kind: \"settled\", error };\n }\n}\n\nasync function endOf(keeper: JobLeaseKeeper): Promise<Race> {\n return { kind: \"ended\", end: await keeper.ended };\n}\n\n/** The handler returned: ack, or retry / fail on its error. */\nasync function settle(\n rt: JobRuntime,\n held: { job: ConsumerJob; handler: KindHandler; logger: Logger },\n raced: Race & { kind: \"settled\" },\n): Promise<JobOutcome> {\n if (!(\"error\" in raced)) {\n await rt.queue.ack(held.job);\n held.logger.info(\"consumer completed job\");\n return \"completed\";\n }\n return await retryOrFail(rt, held, raced.error);\n}\n\n/** The handler threw: requeue with backoff, or fail the job on its last attempt. */\nasync function retryOrFail(\n rt: JobRuntime,\n held: { job: ConsumerJob; handler: KindHandler; logger: Logger },\n error: unknown,\n): Promise<JobOutcome> {\n const { job, logger } = held;\n const message = error instanceof Error ? error.message : String(error);\n const attempts = job.attempts ?? 0;\n if (isFinalAttempt(job, rt.maxRetries)) {\n await failJob(rt, job, message, logger);\n logger.error({ err: error, attempts }, \"capture permanently failed\");\n return \"failed\";\n }\n await requeue(rt, held, { error, message, attempts });\n return \"retried\";\n}\n\n/** Put the job back with the next backoff delay. */\nasync function requeue(\n rt: JobRuntime,\n held: { job: ConsumerJob; handler: KindHandler; logger: Logger },\n failure: { error: unknown; message: string; attempts: number },\n): Promise<void> {\n const { backoff } = held.handler;\n const delayMs = backoffDelayMs(\n failure.attempts,\n backoff?.baseMs ?? rt.backoffMs ?? DEFAULT_BACKOFF_MS,\n backoff?.maxMs,\n );\n await rt.queue.nack(held.job, { requeue: true, delayMs, error: failure.message });\n held.logger.warn(\n { err: failure.error, attempts: failure.attempts, delayMs },\n \"job failed, requeued\",\n );\n}\n\n/**\n * The lease is gone or the job was cancelled while the handler ran: stop the work. A lost lease\n * means another consumer owns the job (it settles it); a cancel is settled here as `cancelled`.\n */\nasync function abandon(\n rt: JobRuntime,\n held: { job: ConsumerJob; handler: KindHandler; logger: Logger },\n end: LeaseEnd,\n): Promise<JobOutcome> {\n const { job, handler, logger } = held;\n await handler.cancel?.(job).catch((error: unknown) => {\n logger.warn({ err: error }, \"failed to cancel the running job\");\n });\n if (end === \"lost\") {\n logger.warn(\"queue lease lost, aborting job\");\n return \"lost\";\n }\n logger.info(\"job cancelled, aborting\");\n await rt.queue.nack(job, { requeue: false, error: \"cancelled\" });\n return \"cancelled\";\n}\n\n/**\n * Final failure: nack without requeue so the queue records it failed. A queue that does not count\n * attempts itself falls back to ack when the nack fails, so a poison job never loops; the database\n * queue (it states `maxAttempts`) ends the job by itself, so there a failed nack is left to it.\n */\nasync function failJob(\n rt: JobRuntime,\n job: ConsumerJob,\n message: string,\n logger: Logger,\n): Promise<void> {\n try {\n await rt.queue.nack(job, { requeue: false, error: message });\n } catch (nackError) {\n if (typeof job.maxAttempts === \"number\") {\n throw nackError;\n }\n logger.warn({ err: nackError }, \"final nack failed, acknowledging the job instead\");\n await rt.queue.ack(job);\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,37 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ import { sweepOrphanedScratch } from "../scratch.mjs";
5
+ import { configureImages, verifyImageEngine } from "../../diff.mjs";
6
+ // src/capture/consumer/startup.ts
7
+ /**
8
+ * Only touch the process-wide image settings when asked, so a consumer created next to an app in
9
+ * one process does not undo the app's own `images`/`compare` options.
10
+ */
11
+ function applyImageSettings(images, compare) {
12
+ if (images || compare) configureImages({
13
+ engine: images?.engine,
14
+ threads: compare?.threads
15
+ });
16
+ }
17
+ /** Check that the image engine loads; a consumer that cannot diff must not claim jobs. */
18
+ async function ensureImageEngine(logger) {
19
+ try {
20
+ await verifyImageEngine();
21
+ } catch (error) {
22
+ logger.error({ err: error }, "image engine cannot load; capture consumer stopping");
23
+ throw error;
24
+ }
25
+ }
26
+ /** Sweep scratch left by a killed run; failures are logged, never fatal. */
27
+ async function sweepScratch(scratchDir, logger) {
28
+ if (scratchDir === void 0) return;
29
+ try {
30
+ await sweepOrphanedScratch(scratchDir, { logger });
31
+ } catch (error) {
32
+ logger.warn({ err: error }, "scratch sweep failed");
33
+ }
34
+ }
35
+ export { applyImageSettings, ensureImageEngine, sweepScratch };
36
+
37
+ //# sourceMappingURL=startup.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"startup.mjs","names":[],"sources":["../../../src/capture/consumer/startup.ts"],"sourcesContent":["/** What the consumer does before it polls: process-wide image settings, the engine check, scratch sweep. */\nimport type { ShelfCompareOptions, ShelfImagesOptions } from \"../../config.ts\";\nimport { configureImages, verifyImageEngine } from \"../../diff/index.ts\";\nimport type { Logger } from \"../../logger.ts\";\nimport { sweepOrphanedScratch } from \"../scratch.ts\";\n\n/**\n * Only touch the process-wide image settings when asked, so a consumer created next to an app in\n * one process does not undo the app's own `images`/`compare` options.\n */\nexport function applyImageSettings(\n images?: ShelfImagesOptions,\n compare?: ShelfCompareOptions,\n): void {\n if (images || compare) {\n configureImages({ engine: images?.engine, threads: compare?.threads });\n }\n}\n\n/** Check that the image engine loads; a consumer that cannot diff must not claim jobs. */\nexport async function ensureImageEngine(logger: Logger): Promise<void> {\n try {\n await verifyImageEngine();\n } catch (error) {\n logger.error({ err: error }, \"image engine cannot load; capture consumer stopping\");\n throw error;\n }\n}\n\n/** Sweep scratch left by a killed run; failures are logged, never fatal. */\nexport async function sweepScratch(scratchDir: string | undefined, logger: Logger): Promise<void> {\n if (scratchDir === undefined) {\n return;\n }\n try {\n await sweepOrphanedScratch(scratchDir, { logger });\n } catch (error) {\n logger.warn({ err: error }, \"scratch sweep failed\");\n }\n}\n"],"mappings":";;;;;;;;;;AAUA,SAAgB,mBACd,QACA,SACM;CACN,IAAI,UAAU,SACZ,gBAAgB;EAAE,QAAQ,QAAQ;EAAQ,SAAS,SAAS;CAAQ,CAAC;AAEzE;;AAGA,eAAsB,kBAAkB,QAA+B;CACrE,IAAI;EACF,MAAM,kBAAkB;CAC1B,SAAS,OAAO;EACd,OAAO,MAAM,EAAE,KAAK,MAAM,GAAG,qDAAqD;EAClF,MAAM;CACR;AACF;;AAGA,eAAsB,aAAa,YAAgC,QAA+B;CAChG,IAAI,eAAe,KAAA,GACjB;CAEF,IAAI;EACF,MAAM,qBAAqB,YAAY,EAAE,OAAO,CAAC;CACnD,SAAS,OAAO;EACd,OAAO,KAAK,EAAE,KAAK,MAAM,GAAG,sBAAsB;CACpD;AACF"}
@@ -0,0 +1,95 @@
1
+ import __tsdown_shims_path from 'node:path';
2
+ import __tsdown_shims_url from 'node:url';
3
+ import { Logger } from "../../logger.mjs";
4
+ import { ShelfCompareOptions, ShelfImagesOptions } from "../../config.mjs";
5
+ import { PollableCaptureQueue, PollableJob } from "../../adapter/capture-queue.mjs";
6
+ import { CaptureJobKind } from "../../schema/capture-job.mjs";
7
+ import { PollCadence } from "../db-queue/cadence.mjs";
8
+ import { QueueTimers } from "../db-queue/timers.mjs";
9
+ import { CaptureHandlerOptions } from "./capture-handler.mjs";
10
+ import { ConsumerMetrics } from "./metrics.mjs";
11
+ import { RetryConfig } from "./retry.mjs";
12
+ /** A polled job as the consumer sees it: database jobs carry the extra fields, others omit them. */
13
+ type ConsumerJob = PollableJob & {
14
+ /** Job kind; absent on queues that only carry capture jobs (remote, IPC). */
15
+ kind?: CaptureJobKind;
16
+ /** Id of the job row (database queue). */
17
+ jobId?: string;
18
+ /** Attempt number from 1 (database queue). */
19
+ attempt?: number;
20
+ maxAttempts?: number;
21
+ /** Kind-specific fields given at enqueue. */
22
+ data?: Record<string, unknown>;
23
+ };
24
+ /** What a handler gets besides the job. */
25
+ interface JobRunContext {
26
+ /** Logger scoped to the job (`buildId`, `jobId`, `kind`). */
27
+ logger: Logger;
28
+ /** Report progress: renews the job's lease (throttled) and tells the host (`onProgress`). */
29
+ touch(): void;
30
+ }
31
+ /** The code that runs jobs of one kind. */
32
+ interface KindHandler {
33
+ /** Run the job; resolve to ack it, reject to retry (or fail it on its last attempt). */
34
+ run(job: ConsumerJob, context: JobRunContext): Promise<void>;
35
+ /** Whether the job is well-formed; a job that is not is failed without retries. */
36
+ accepts?(job: ConsumerJob): boolean;
37
+ /** Retry delay of this kind: `baseMs * 2^n` capped at `maxMs`; defaults to the consumer's. */
38
+ backoff?: {
39
+ baseMs: number;
40
+ maxMs: number;
41
+ };
42
+ /** Stop a running job (lease lost or cancelled); must tolerate a job that already ended. */
43
+ cancel?(job: ConsumerJob): Promise<void>;
44
+ }
45
+ /** Handler registry by job kind; a plain function is shorthand for `{ run }`. */
46
+ type JobHandlers = Partial<Record<CaptureJobKind, KindHandler | KindHandler["run"] | undefined>>;
47
+ /** Consumer tuning. */
48
+ interface ConsumerConfig extends RetryConfig {
49
+ /** Jobs run at once; default sized from the CPU and memory the process can use. */
50
+ concurrency?: number;
51
+ /** Long-poll wait for queues that block inside `poll` (default 20, max 20). */
52
+ waitTimeSeconds?: number;
53
+ }
54
+ /** Options of {@link createCaptureConsumer}. */
55
+ interface CaptureConsumerOptions {
56
+ /** Queue to consume: the database queue, or the IPC queue of a forked child. */
57
+ queue: PollableCaptureQueue;
58
+ /** Capture jobs: wires the `capture` kind to the core dispatch (shorthand for `handlers.capture`). */
59
+ capture?: Omit<CaptureHandlerOptions, "enqueue" | "logger"> & Partial<Pick<CaptureHandlerOptions, "enqueue">>;
60
+ /** Handlers by kind; `publish` and `notify` register here. Overrides `capture`'s. */
61
+ handlers?: JobHandlers;
62
+ config?: ConsumerConfig;
63
+ /** Scratch directory swept at start; defaults to `capture.scratchDir`. */
64
+ scratchDir?: string;
65
+ /** Image engine, as the server's `images` option; `start()` fails when it cannot load. */
66
+ images?: ShelfImagesOptions;
67
+ /** Diff pool size, as the server's `compare` option. */
68
+ compare?: ShelfCompareOptions;
69
+ /** Called whenever a running job makes progress (the forked child reports it to its parent). */
70
+ onProgress?: (job: ConsumerJob) => void;
71
+ /** Optional observation hooks; core does not depend on any metrics library. */
72
+ metrics?: ConsumerMetrics;
73
+ logger?: Logger;
74
+ /** Clock and timers (tests inject a manual clock). */
75
+ now?: () => number;
76
+ timers?: QueueTimers;
77
+ /** Poll cadence of the database queue (default ADR 0006's). */
78
+ cadence?: PollCadence;
79
+ random?: () => number;
80
+ /** Lease renewal throttle and state-check interval overrides (tests). */
81
+ lease?: {
82
+ renewEveryMs?: number;
83
+ checkMs?: number;
84
+ };
85
+ }
86
+ /** Handle returned by {@link createCaptureConsumer}. */
87
+ interface CaptureConsumer {
88
+ /** Start consuming. Resolves once stopped and drained; rejects when the image engine cannot load. */
89
+ start(): Promise<void>;
90
+ /** Stop polling and wait for the jobs in flight to finish (SIGTERM drain). */
91
+ stop(): Promise<void>;
92
+ isRunning(): boolean;
93
+ }
94
+ export { CaptureConsumer, CaptureConsumerOptions, ConsumerConfig, ConsumerJob, JobHandlers, JobRunContext, KindHandler };
95
+ //# sourceMappingURL=types.d.mts.map
@@ -0,0 +1,23 @@
1
+ import __tsdown_shims_path from 'node:path';
2
+ import __tsdown_shims_url from 'node:url';
3
+ import { DispatchDeps } from "./dispatch.mjs";
4
+ /** What {@link failCrashedCapture} did. */
5
+ type CrashFailureOutcome =
6
+ /** The build was failed. */
7
+ "failed" |
8
+ /** The shard was failed; other shards are still open, and the last of them finishes the build. */
9
+ "shard-failed" |
10
+ /** The shard was the last one open: the build was finalized (failed, with this shard's stories). */
11
+ "finalized" |
12
+ /** The build had already ended (or is gone): nothing to do. */
13
+ "skipped";
14
+ /**
15
+ * Fail a capture job that crashed its worker past its retries. Safe to call for a build that has
16
+ * since ended: it is left alone.
17
+ */
18
+ declare function failCrashedCapture(deps: DispatchDeps, job: {
19
+ buildId: string;
20
+ shard?: string | undefined;
21
+ }, error: string): Promise<CrashFailureOutcome>;
22
+ export { CrashFailureOutcome, failCrashedCapture };
23
+ //# sourceMappingURL=crash-failure.d.mts.map
@@ -0,0 +1,69 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ import { CaptureAttemptModel } from "../models/capture-attempt.mjs";
5
+ import { BuildModel } from "../models/build.mjs";
6
+ import { CaptureShardModel } from "../models/capture-shard.mjs";
7
+ import { ProjectModel } from "../models/project.mjs";
8
+ import { reduceBuild } from "./shards/reduce.mjs";
9
+ // src/capture/crash-failure.ts
10
+ /**
11
+ * The terminal path for a capture job the forked parent gave up on because the child kept dying on
12
+ * it (ADR 0005 phase 3). The child never got to record the failure, so the parent writes it: the
13
+ * build fails, or, for a shard, the shard row fails and the build is reduced like any other end.
14
+ */
15
+ /**
16
+ * Fail a capture job that crashed its worker past its retries. Safe to call for a build that has
17
+ * since ended: it is left alone.
18
+ */
19
+ async function failCrashedCapture(deps, job, error) {
20
+ const outcome = job.shard ? await failShard(deps, job.buildId, job.shard, error) : await failBuild(deps, job.buildId, error);
21
+ if (outcome !== "skipped") deps.logger.error({
22
+ buildId: job.buildId,
23
+ shard: job.shard,
24
+ outcome,
25
+ error
26
+ }, "capture kept crashing its worker; failed it");
27
+ return outcome;
28
+ }
29
+ async function failBuild(deps, buildId, error) {
30
+ const loaded = await load(deps, buildId);
31
+ if (!loaded || loaded.build.status !== "capturing" && loaded.build.status !== "pending") return "skipped";
32
+ await closeOpenAttempts(deps, buildId, error);
33
+ await new BuildModel(deps.db, deps.tables).setStatus(buildId, "failed");
34
+ await deps.buildState.request(buildId);
35
+ return "failed";
36
+ }
37
+ async function failShard(deps, buildId, shardId, error) {
38
+ await new CaptureShardModel(deps.db).fail(shardId, error);
39
+ const loaded = await load(deps, buildId);
40
+ if (loaded?.build.status !== "capturing") return "skipped";
41
+ const summary = await reduceBuild({
42
+ options: deps.jobOptions,
43
+ ...loaded
44
+ });
45
+ if (!summary) return "shard-failed";
46
+ await closeFinalized(deps, loaded, summary, error);
47
+ return "finalized";
48
+ }
49
+ /** The crashed shard was the last one: close the plan's attempt and report the build's end. */
50
+ async function closeFinalized(deps, loaded, summary, error) {
51
+ if (summary.attemptId) await new CaptureAttemptModel(deps.db, deps.tables).markFinished(summary.attemptId, "failed", error, summary);
52
+ await deps.buildState.request(loaded.build.id);
53
+ }
54
+ async function load(deps, buildId) {
55
+ const build = await new BuildModel(deps.db, deps.tables).get(buildId);
56
+ const project = build ? await new ProjectModel(deps.db, deps.tables).get(build.projectId) : null;
57
+ return build && project ? {
58
+ build,
59
+ project
60
+ } : null;
61
+ }
62
+ async function closeOpenAttempts(deps, buildId, error) {
63
+ const attempts = new CaptureAttemptModel(deps.db, deps.tables);
64
+ const open = (await attempts.listByBuild(buildId)).filter((attempt) => attempt.status === "queued" || attempt.status === "running");
65
+ await Promise.all(open.map(async (attempt) => await attempts.markFinished(attempt.id, "failed", error)));
66
+ }
67
+ export { failCrashedCapture };
68
+
69
+ //# sourceMappingURL=crash-failure.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crash-failure.mjs","names":[],"sources":["../../src/capture/crash-failure.ts"],"sourcesContent":["/**\n * The terminal path for a capture job the forked parent gave up on because the child kept dying on\n * it (ADR 0005 phase 3). The child never got to record the failure, so the parent writes it: the\n * build fails, or, for a shard, the shard row fails and the build is reduced like any other end.\n */\nimport { BuildModel } from \"../models/build.ts\";\nimport { CaptureAttemptModel } from \"../models/capture-attempt.ts\";\nimport { CaptureShardModel } from \"../models/capture-shard.ts\";\nimport { ProjectModel } from \"../models/project.ts\";\nimport type { Build } from \"../schema/build.ts\";\nimport type { Project } from \"../schema/project.ts\";\nimport type { DispatchDeps } from \"./dispatch.ts\";\nimport { reduceBuild, type FinalSummary } from \"./shards/reduce.ts\";\n\n/** What {@link failCrashedCapture} did. */\nexport type CrashFailureOutcome =\n /** The build was failed. */\n | \"failed\"\n /** The shard was failed; other shards are still open, and the last of them finishes the build. */\n | \"shard-failed\"\n /** The shard was the last one open: the build was finalized (failed, with this shard's stories). */\n | \"finalized\"\n /** The build had already ended (or is gone): nothing to do. */\n | \"skipped\";\n\n/**\n * Fail a capture job that crashed its worker past its retries. Safe to call for a build that has\n * since ended: it is left alone.\n */\nexport async function failCrashedCapture(\n deps: DispatchDeps,\n job: { buildId: string; shard?: string | undefined },\n error: string,\n): Promise<CrashFailureOutcome> {\n const outcome = job.shard\n ? await failShard(deps, job.buildId, job.shard, error)\n : await failBuild(deps, job.buildId, error);\n if (outcome !== \"skipped\") {\n deps.logger.error(\n { buildId: job.buildId, shard: job.shard, outcome, error },\n \"capture kept crashing its worker; failed it\",\n );\n }\n return outcome;\n}\n\nasync function failBuild(\n deps: DispatchDeps,\n buildId: string,\n error: string,\n): Promise<CrashFailureOutcome> {\n const loaded = await load(deps, buildId);\n if (!loaded || (loaded.build.status !== \"capturing\" && loaded.build.status !== \"pending\")) {\n return \"skipped\";\n }\n await closeOpenAttempts(deps, buildId, error);\n await new BuildModel(deps.db, deps.tables).setStatus(buildId, \"failed\");\n await deps.buildState.request(buildId);\n return \"failed\";\n}\n\nasync function failShard(\n deps: DispatchDeps,\n buildId: string,\n shardId: string,\n error: string,\n): Promise<CrashFailureOutcome> {\n await new CaptureShardModel(deps.db).fail(shardId, error);\n const loaded = await load(deps, buildId);\n if (loaded?.build.status !== \"capturing\") {\n return \"skipped\";\n }\n const summary = await reduceBuild({ options: deps.jobOptions, ...loaded });\n if (!summary) {\n return \"shard-failed\";\n }\n await closeFinalized(deps, loaded, summary, error);\n return \"finalized\";\n}\n\n/** The crashed shard was the last one: close the plan's attempt and report the build's end. */\nasync function closeFinalized(\n deps: DispatchDeps,\n loaded: { build: Build; project: Project },\n summary: FinalSummary,\n error: string,\n): Promise<void> {\n if (summary.attemptId) {\n await new CaptureAttemptModel(deps.db, deps.tables).markFinished(\n summary.attemptId,\n \"failed\",\n error,\n summary,\n );\n }\n await deps.buildState.request(loaded.build.id);\n}\n\nasync function load(\n deps: DispatchDeps,\n buildId: string,\n): Promise<{ build: Build; project: Project } | null> {\n const build = await new BuildModel(deps.db, deps.tables).get(buildId);\n const project = build ? await new ProjectModel(deps.db, deps.tables).get(build.projectId) : null;\n return build && project ? { build, project } : null;\n}\n\nasync function closeOpenAttempts(\n deps: DispatchDeps,\n buildId: string,\n error: string,\n): Promise<void> {\n const attempts = new CaptureAttemptModel(deps.db, deps.tables);\n const open = (await attempts.listByBuild(buildId)).filter(\n (attempt) => attempt.status === \"queued\" || attempt.status === \"running\",\n );\n await Promise.all(\n open.map(async (attempt) => await attempts.markFinished(attempt.id, \"failed\", error)),\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AA6BA,eAAsB,mBACpB,MACA,KACA,OAC8B;CAC9B,MAAM,UAAU,IAAI,QAChB,MAAM,UAAU,MAAM,IAAI,SAAS,IAAI,OAAO,KAAK,IACnD,MAAM,UAAU,MAAM,IAAI,SAAS,KAAK;CAC5C,IAAI,YAAY,WACd,KAAK,OAAO,MACV;EAAE,SAAS,IAAI;EAAS,OAAO,IAAI;EAAO;EAAS;CAAM,GACzD,6CACF;CAEF,OAAO;AACT;AAEA,eAAe,UACb,MACA,SACA,OAC8B;CAC9B,MAAM,SAAS,MAAM,KAAK,MAAM,OAAO;CACvC,IAAI,CAAC,UAAW,OAAO,MAAM,WAAW,eAAe,OAAO,MAAM,WAAW,WAC7E,OAAO;CAET,MAAM,kBAAkB,MAAM,SAAS,KAAK;CAC5C,MAAM,IAAI,WAAW,KAAK,IAAI,KAAK,MAAM,CAAC,CAAC,UAAU,SAAS,QAAQ;CACtE,MAAM,KAAK,WAAW,QAAQ,OAAO;CACrC,OAAO;AACT;AAEA,eAAe,UACb,MACA,SACA,SACA,OAC8B;CAC9B,MAAM,IAAI,kBAAkB,KAAK,EAAE,CAAC,CAAC,KAAK,SAAS,KAAK;CACxD,MAAM,SAAS,MAAM,KAAK,MAAM,OAAO;CACvC,IAAI,QAAQ,MAAM,WAAW,aAC3B,OAAO;CAET,MAAM,UAAU,MAAM,YAAY;EAAE,SAAS,KAAK;EAAY,GAAG;CAAO,CAAC;CACzE,IAAI,CAAC,SACH,OAAO;CAET,MAAM,eAAe,MAAM,QAAQ,SAAS,KAAK;CACjD,OAAO;AACT;;AAGA,eAAe,eACb,MACA,QACA,SACA,OACe;CACf,IAAI,QAAQ,WACV,MAAM,IAAI,oBAAoB,KAAK,IAAI,KAAK,MAAM,CAAC,CAAC,aAClD,QAAQ,WACR,UACA,OACA,OACF;CAEF,MAAM,KAAK,WAAW,QAAQ,OAAO,MAAM,EAAE;AAC/C;AAEA,eAAe,KACb,MACA,SACoD;CACpD,MAAM,QAAQ,MAAM,IAAI,WAAW,KAAK,IAAI,KAAK,MAAM,CAAC,CAAC,IAAI,OAAO;CACpE,MAAM,UAAU,QAAQ,MAAM,IAAI,aAAa,KAAK,IAAI,KAAK,MAAM,CAAC,CAAC,IAAI,MAAM,SAAS,IAAI;CAC5F,OAAO,SAAS,UAAU;EAAE;EAAO;CAAQ,IAAI;AACjD;AAEA,eAAe,kBACb,MACA,SACA,OACe;CACf,MAAM,WAAW,IAAI,oBAAoB,KAAK,IAAI,KAAK,MAAM;CAC7D,MAAM,QAAQ,MAAM,SAAS,YAAY,OAAO,EAAA,CAAG,QAChD,YAAY,QAAQ,WAAW,YAAY,QAAQ,WAAW,SACjE;CACA,MAAM,QAAQ,IACZ,KAAK,IAAI,OAAO,YAAY,MAAM,SAAS,aAAa,QAAQ,IAAI,UAAU,KAAK,CAAC,CACtF;AACF"}
@@ -0,0 +1,30 @@
1
+ import __tsdown_shims_path from 'node:path';
2
+ import __tsdown_shims_url from 'node:url';
3
+ /** Poll and cancel-check cadence of the database queue (ADR 0006, decision 11). */
4
+ /** Poll interval of an idle instance. */
5
+ declare const IDLE_POLL_MS = 15000;
6
+ /** Poll interval while jobs were recently queued or found. */
7
+ declare const ACTIVE_POLL_MS = 2000;
8
+ /** How long after the last job seen an instance keeps polling at the active rate. */
9
+ declare const ACTIVE_WINDOW_MS = 60000;
10
+ /** Each delay is stretched by up to this fraction (never shortened, so idle stays ≥ 15 s). */
11
+ declare const POLL_JITTER = 0.2;
12
+ /** How often a running job reads its own row for cancel. */
13
+ declare const CANCEL_CHECK_MS = 5000;
14
+ /** Tunable cadence; tests shrink it, production uses the defaults above. */
15
+ interface PollCadence {
16
+ idleMs: number;
17
+ activeMs: number;
18
+ activeWindowMs: number;
19
+ jitter: number;
20
+ }
21
+ /** The cadence ADR 0006 fixes. */
22
+ declare const DEFAULT_CADENCE: PollCadence;
23
+ /**
24
+ * Delay before the next poll: the active interval while the last job was seen (found by a poll or
25
+ * queued locally) within the active window, otherwise the idle interval; plus jitter in
26
+ * `[0, jitter)` of it, so instances started together drift apart instead of probing in lockstep.
27
+ */
28
+ declare function nextPollDelay(lastWorkAt: number | null, now: number, cadence?: PollCadence, random?: () => number): number;
29
+ export { ACTIVE_POLL_MS, ACTIVE_WINDOW_MS, CANCEL_CHECK_MS, DEFAULT_CADENCE, IDLE_POLL_MS, POLL_JITTER, PollCadence, nextPollDelay };
30
+ //# sourceMappingURL=cadence.d.mts.map
@@ -0,0 +1,37 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ // src/capture/db-queue/cadence.ts
5
+ /** Poll and cancel-check cadence of the database queue (ADR 0006, decision 11). */
6
+ /** Poll interval of an idle instance. */
7
+ const IDLE_POLL_MS = 15e3;
8
+ /** Poll interval while jobs were recently queued or found. */
9
+ const ACTIVE_POLL_MS = 2e3;
10
+ /** How long after the last job seen an instance keeps polling at the active rate. */
11
+ const ACTIVE_WINDOW_MS = 6e4;
12
+ /** Each delay is stretched by up to this fraction (never shortened, so idle stays ≥ 15 s). */
13
+ const POLL_JITTER = .2;
14
+ /** How often a running job reads its own row for cancel. */
15
+ const CANCEL_CHECK_MS = 5e3;
16
+ /** The cadence ADR 0006 fixes. */
17
+ const DEFAULT_CADENCE = {
18
+ idleMs: IDLE_POLL_MS,
19
+ activeMs: ACTIVE_POLL_MS,
20
+ activeWindowMs: ACTIVE_WINDOW_MS,
21
+ jitter: POLL_JITTER
22
+ };
23
+ /**
24
+ * Delay before the next poll: the active interval while the last job was seen (found by a poll or
25
+ * queued locally) within the active window, otherwise the idle interval; plus jitter in
26
+ * `[0, jitter)` of it, so instances started together drift apart instead of probing in lockstep.
27
+ */
28
+ function nextPollDelay(lastWorkAt, now, cadence = DEFAULT_CADENCE, random = Math.random) {
29
+ const base = lastWorkAt !== null && now - lastWorkAt < cadence.activeWindowMs ? cadence.activeMs : cadence.idleMs;
30
+ return Math.round(base * (1 + cadence.jitter * clampUnit(random())));
31
+ }
32
+ function clampUnit(value) {
33
+ return Math.min(Math.max(value, 0), .999999);
34
+ }
35
+ export { ACTIVE_POLL_MS, ACTIVE_WINDOW_MS, CANCEL_CHECK_MS, DEFAULT_CADENCE, IDLE_POLL_MS, POLL_JITTER, nextPollDelay };
36
+
37
+ //# sourceMappingURL=cadence.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cadence.mjs","names":[],"sources":["../../../src/capture/db-queue/cadence.ts"],"sourcesContent":["/** Poll and cancel-check cadence of the database queue (ADR 0006, decision 11). */\n\n/** Poll interval of an idle instance. */\nexport const IDLE_POLL_MS = 15_000;\n/** Poll interval while jobs were recently queued or found. */\nexport const ACTIVE_POLL_MS = 2000;\n/** How long after the last job seen an instance keeps polling at the active rate. */\nexport const ACTIVE_WINDOW_MS = 60_000;\n/** Each delay is stretched by up to this fraction (never shortened, so idle stays ≥ 15 s). */\nexport const POLL_JITTER = 0.2;\n/** How often a running job reads its own row for cancel. */\nexport const CANCEL_CHECK_MS = 5000;\n\n/** Tunable cadence; tests shrink it, production uses the defaults above. */\nexport interface PollCadence {\n idleMs: number;\n activeMs: number;\n activeWindowMs: number;\n jitter: number;\n}\n\n/** The cadence ADR 0006 fixes. */\nexport const DEFAULT_CADENCE: PollCadence = {\n idleMs: IDLE_POLL_MS,\n activeMs: ACTIVE_POLL_MS,\n activeWindowMs: ACTIVE_WINDOW_MS,\n jitter: POLL_JITTER,\n};\n\n/**\n * Delay before the next poll: the active interval while the last job was seen (found by a poll or\n * queued locally) within the active window, otherwise the idle interval; plus jitter in\n * `[0, jitter)` of it, so instances started together drift apart instead of probing in lockstep.\n */\nexport function nextPollDelay(\n lastWorkAt: number | null,\n now: number,\n cadence: PollCadence = DEFAULT_CADENCE,\n random: () => number = Math.random,\n): number {\n const active = lastWorkAt !== null && now - lastWorkAt < cadence.activeWindowMs;\n const base = active ? cadence.activeMs : cadence.idleMs;\n return Math.round(base * (1 + cadence.jitter * clampUnit(random())));\n}\n\nfunction clampUnit(value: number): number {\n return Math.min(Math.max(value, 0), 0.999999);\n}\n"],"mappings":";;;;;;AAGA,MAAa,eAAe;;AAE5B,MAAa,iBAAiB;;AAE9B,MAAa,mBAAmB;;AAEhC,MAAa,cAAc;;AAE3B,MAAa,kBAAkB;;AAW/B,MAAa,kBAA+B;CAC1C,QAAQ;CACR,UAAU;CACV,gBAAgB;CAChB,QAAQ;AACV;;;;;;AAOA,SAAgB,cACd,YACA,KACA,UAAuB,iBACvB,SAAuB,KAAK,QACpB;CAER,MAAM,OADS,eAAe,QAAQ,MAAM,aAAa,QAAQ,iBAC3C,QAAQ,WAAW,QAAQ;CACjD,OAAO,KAAK,MAAM,QAAQ,IAAI,QAAQ,SAAS,UAAU,OAAO,CAAC,EAAE;AACrE;AAEA,SAAS,UAAU,OAAuB;CACxC,OAAO,KAAK,IAAI,KAAK,IAAI,OAAO,CAAC,GAAG,OAAQ;AAC9C"}
@@ -0,0 +1,7 @@
1
+ import __tsdown_shims_path from 'node:path';
2
+ import __tsdown_shims_url from 'node:url';
3
+ import "./types.mjs";
4
+ /** Error recorded on a job whose last allowed attempt lost its lease. */
5
+ declare const LEASE_EXHAUSTED_ERROR = "lease expired on the final attempt";
6
+ export { LEASE_EXHAUSTED_ERROR };
7
+ //# sourceMappingURL=claim.d.mts.map
@@ -0,0 +1,148 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ import { attemptRowId, isTerminalJob } from "../../models/capture-job.mjs";
5
+ import { toDatabaseJob } from "./codec.mjs";
6
+ import { isoAt } from "./clock.mjs";
7
+ // src/capture/db-queue/claim.ts
8
+ /**
9
+ * The claim algorithm of the database queue. A poll probes for the oldest candidate row (a queued
10
+ * job whose `visibleAt` passed, or a running attempt whose lease ran out), then claims the job's
11
+ * next attempt by inserting its row: the unique key lets exactly one instance win. The winner
12
+ * retires the expired attempt it replaced and marks the job row running.
13
+ */
14
+ /** Candidates tried per poll when other instances win the race for them. */
15
+ const MAX_CLAIM_TRIES = 3;
16
+ /** Error recorded on a job whose last allowed attempt lost its lease. */
17
+ const LEASE_EXHAUSTED_ERROR = "lease expired on the final attempt";
18
+ /** Probe once and claim the job found; on a lost race, try the next candidate (a few times). */
19
+ async function claimNext(rt) {
20
+ for (let tries = 0; tries < MAX_CLAIM_TRIES; tries += 1) {
21
+ const candidate = await rt.model.nextCandidate(rt.kinds, isoAt(rt));
22
+ if (candidate === null) return null;
23
+ const job = await claimCandidate(rt, candidate);
24
+ if (job !== null) return job;
25
+ }
26
+ return null;
27
+ }
28
+ /** Claim the job behind a candidate row; null when it is gone, over, exhausted or lost to a race. */
29
+ async function claimCandidate(rt, candidate) {
30
+ if (candidate.entry === "job" && candidate.status !== "queued") {
31
+ await rt.model.clearVisibility(candidate.id, isoAt(rt));
32
+ return null;
33
+ }
34
+ const stale = candidate.entry === "attempt" ? candidate : null;
35
+ const job = stale === null ? candidate : await rt.model.get(candidate.jobId);
36
+ if (job === null || isTerminalJob(job) || stale !== null && stale.attempt < job.attempt) {
37
+ await retire(rt, stale);
38
+ return null;
39
+ }
40
+ return await claimNextAttempt(rt, job, stale);
41
+ }
42
+ /** Claim the attempt after the last one made, or fail the job when none is left. */
43
+ async function claimNextAttempt(rt, job, stale) {
44
+ const made = Math.max(job.attempt, stale?.attempt ?? 0);
45
+ if (made >= job.maxAttempts) {
46
+ await exhaust(rt, job, stale);
47
+ return null;
48
+ }
49
+ return await claimAttempt(rt, job, made + 1, stale);
50
+ }
51
+ async function claimAttempt(rt, job, attempt, stale) {
52
+ const leaseUntil = isoAt(rt, rt.leaseMs);
53
+ if (!await rt.model.claim({
54
+ job,
55
+ attempt,
56
+ owner: rt.owner,
57
+ leaseUntil
58
+ }, isoAt(rt))) return stale === null ? await afterLostRace(rt, job, attempt) : null;
59
+ const fresh = await rt.model.get(job.jobId);
60
+ if (fresh === null || isTerminalJob(fresh)) {
61
+ await rt.model.finishAttempt(attemptRowId(job.jobId, attempt), "expired", isoAt(rt));
62
+ return null;
63
+ }
64
+ return await takeOver(rt, fresh, attempt, stale);
65
+ }
66
+ /**
67
+ * Another instance holds attempt `attempt` of a job whose row still reads queued. Usually that
68
+ * winner is about to mark the job running. If it died before doing so, its attempt's lease decides:
69
+ * once expired, the attempt is claimed like any expired one; until then the job row is moved
70
+ * behind that lease, so it does not sit at the head of the queue losing every claim. Only
71
+ * `visibleAt` is written, never the status, so a winner that finished meanwhile keeps its outcome.
72
+ */
73
+ async function afterLostRace(rt, job, attempt) {
74
+ const id = attemptRowId(job.jobId, attempt);
75
+ const expired = await rt.model.expiredAttempt(id, isoAt(rt));
76
+ if (expired !== null) return await claimCandidate(rt, expired);
77
+ await deferBehind(rt, job, await rt.model.get(id));
78
+ return null;
79
+ }
80
+ async function deferBehind(rt, job, holder) {
81
+ const current = await rt.model.get(job.jobId);
82
+ if (holder?.visibleAt && current?.status === "queued" && current.attempt === job.attempt) await rt.model.defer(job.jobId, holder.visibleAt, isoAt(rt));
83
+ }
84
+ /** The claim won: retire the attempt it replaced and record the new one on the job row. */
85
+ async function takeOver(rt, job, attempt, stale) {
86
+ await retire(rt, stale);
87
+ await rt.model.markRunning(job.jobId, attempt, isoAt(rt));
88
+ rt.logger?.debug({
89
+ jobId: job.jobId,
90
+ attempt
91
+ }, "job claimed");
92
+ return toDatabaseJob(job, attempt, rt.leaseMs);
93
+ }
94
+ /** Mark an expired attempt row `expired`: it leaves the poll's range, and its holder sees `lost`. */
95
+ async function retire(rt, stale) {
96
+ if (stale !== null) await rt.model.finishAttempt(stale.id, "expired", isoAt(rt));
97
+ }
98
+ /**
99
+ * The final attempt lost its lease: the job fails (one limit covers failed attempts and lost
100
+ * leases). Idempotent, so two instances finding the same row write the same outcome. If the late
101
+ * holder finished after all, its `done` wins.
102
+ */
103
+ async function exhaust(rt, job, stale) {
104
+ const now = isoAt(rt);
105
+ if (stale !== null && (await rt.model.get(stale.id))?.status === "done") {
106
+ await rt.model.finishJob(job.jobId, "done", now);
107
+ return;
108
+ }
109
+ await retire(rt, stale);
110
+ await rt.model.finishJob(job.jobId, "failed", now, LEASE_EXHAUSTED_ERROR);
111
+ rt.logger?.warn({
112
+ jobId: job.jobId,
113
+ attempts: job.attempt
114
+ }, "job failed: lease exhausted");
115
+ await reportExhausted(rt, job);
116
+ }
117
+ /** Sleep for `ms`, using injected timers when available. */
118
+ async function sleep(rt, ms) {
119
+ if (rt.timers === void 0) {
120
+ await new Promise((resolve) => {
121
+ setTimeout(resolve, ms);
122
+ });
123
+ return;
124
+ }
125
+ await new Promise((resolve) => {
126
+ rt.timers?.setTimeout(resolve, ms);
127
+ });
128
+ }
129
+ /** Tell the host a job ended without any holder to record it; its failure never blocks the poll. */
130
+ async function reportExhausted(rt, job) {
131
+ if (rt.onExhausted === void 0) return;
132
+ const attempts = 3;
133
+ for (let attempt = 1; attempt <= attempts; attempt += 1) try {
134
+ await rt.onExhausted(toDatabaseJob(job, job.attempt, rt.leaseMs), LEASE_EXHAUSTED_ERROR);
135
+ return;
136
+ } catch (error) {
137
+ rt.logger?.error({
138
+ err: error,
139
+ jobId: job.jobId,
140
+ attempt,
141
+ attempts
142
+ }, "exhausted-job hook failed");
143
+ if (attempt < attempts) await sleep(rt, 100 * attempt);
144
+ }
145
+ }
146
+ export { LEASE_EXHAUSTED_ERROR, claimCandidate, claimNext };
147
+
148
+ //# sourceMappingURL=claim.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claim.mjs","names":[],"sources":["../../../src/capture/db-queue/claim.ts"],"sourcesContent":["/**\n * The claim algorithm of the database queue. A poll probes for the oldest candidate row (a queued\n * job whose `visibleAt` passed, or a running attempt whose lease ran out), then claims the job's\n * next attempt by inserting its row: the unique key lets exactly one instance win. The winner\n * retires the expired attempt it replaced and marks the job row running.\n */\nimport { attemptRowId, isTerminalJob } from \"../../models/capture-job.ts\";\nimport type { CaptureJobRow } from \"../../schema/capture-job.ts\";\nimport { isoAt } from \"./clock.ts\";\nimport { toDatabaseJob } from \"./codec.ts\";\nimport type { DatabaseJob, QueueRuntime } from \"./types.ts\";\n\n/** Candidates tried per poll when other instances win the race for them. */\nconst MAX_CLAIM_TRIES = 3;\n\n/** Error recorded on a job whose last allowed attempt lost its lease. */\nexport const LEASE_EXHAUSTED_ERROR = \"lease expired on the final attempt\";\n\n/** Probe once and claim the job found; on a lost race, try the next candidate (a few times). */\nexport async function claimNext(rt: QueueRuntime): Promise<DatabaseJob | null> {\n for (let tries = 0; tries < MAX_CLAIM_TRIES; tries += 1) {\n // oxlint-disable-next-line no-await-in-loop -- one candidate at a time, in order\n const candidate = await rt.model.nextCandidate(rt.kinds, isoAt(rt));\n if (candidate === null) {\n return null;\n }\n // oxlint-disable-next-line no-await-in-loop -- see above\n const job = await claimCandidate(rt, candidate);\n if (job !== null) {\n return job;\n }\n }\n return null;\n}\n\n/** Claim the job behind a candidate row; null when it is gone, over, exhausted or lost to a race. */\nexport async function claimCandidate(\n rt: QueueRuntime,\n candidate: CaptureJobRow,\n): Promise<DatabaseJob | null> {\n if (candidate.entry === \"job\" && candidate.status !== \"queued\") {\n // A running job row put back in range by a late deferral (see `deferBehind`): not claimable.\n await rt.model.clearVisibility(candidate.id, isoAt(rt));\n return null;\n }\n const stale = candidate.entry === \"attempt\" ? candidate : null;\n const job = stale === null ? candidate : await rt.model.get(candidate.jobId);\n if (job === null || isTerminalJob(job) || (stale !== null && stale.attempt < job.attempt)) {\n // Nothing left to claim: the expired attempt is just cleaned out of the poll's range.\n await retire(rt, stale);\n return null;\n }\n return await claimNextAttempt(rt, job, stale);\n}\n\n/** Claim the attempt after the last one made, or fail the job when none is left. */\nasync function claimNextAttempt(\n rt: QueueRuntime,\n job: CaptureJobRow,\n stale: CaptureJobRow | null,\n): Promise<DatabaseJob | null> {\n // An attempt row ahead of its job row belongs to a claimer that died before marking the job.\n const made = Math.max(job.attempt, stale?.attempt ?? 0);\n if (made >= job.maxAttempts) {\n await exhaust(rt, job, stale);\n return null;\n }\n return await claimAttempt(rt, job, made + 1, stale);\n}\n\nasync function claimAttempt(\n rt: QueueRuntime,\n job: CaptureJobRow,\n attempt: number,\n stale: CaptureJobRow | null,\n): Promise<DatabaseJob | null> {\n const leaseUntil = isoAt(rt, rt.leaseMs);\n const won = await rt.model.claim({ job, attempt, owner: rt.owner, leaseUntil }, isoAt(rt));\n if (!won) {\n return stale === null ? await afterLostRace(rt, job, attempt) : null;\n }\n const fresh = await rt.model.get(job.jobId);\n if (fresh === null || isTerminalJob(fresh)) {\n // Cancelled (or finished by a late holder) between the probe and the insert.\n await rt.model.finishAttempt(attemptRowId(job.jobId, attempt), \"expired\", isoAt(rt));\n return null;\n }\n return await takeOver(rt, fresh, attempt, stale);\n}\n\n/**\n * Another instance holds attempt `attempt` of a job whose row still reads queued. Usually that\n * winner is about to mark the job running. If it died before doing so, its attempt's lease decides:\n * once expired, the attempt is claimed like any expired one; until then the job row is moved\n * behind that lease, so it does not sit at the head of the queue losing every claim. Only\n * `visibleAt` is written, never the status, so a winner that finished meanwhile keeps its outcome.\n */\nasync function afterLostRace(\n rt: QueueRuntime,\n job: CaptureJobRow,\n attempt: number,\n): Promise<DatabaseJob | null> {\n const id = attemptRowId(job.jobId, attempt);\n const expired = await rt.model.expiredAttempt(id, isoAt(rt));\n if (expired !== null) {\n return await claimCandidate(rt, expired);\n }\n await deferBehind(rt, job, await rt.model.get(id));\n return null;\n}\n\nasync function deferBehind(\n rt: QueueRuntime,\n job: CaptureJobRow,\n holder: CaptureJobRow | null,\n): Promise<void> {\n const current = await rt.model.get(job.jobId);\n if (holder?.visibleAt && current?.status === \"queued\" && current.attempt === job.attempt) {\n await rt.model.defer(job.jobId, holder.visibleAt, isoAt(rt));\n }\n}\n\n/** The claim won: retire the attempt it replaced and record the new one on the job row. */\nasync function takeOver(\n rt: QueueRuntime,\n job: CaptureJobRow,\n attempt: number,\n stale: CaptureJobRow | null,\n): Promise<DatabaseJob> {\n await retire(rt, stale);\n await rt.model.markRunning(job.jobId, attempt, isoAt(rt));\n rt.logger?.debug({ jobId: job.jobId, attempt }, \"job claimed\");\n return toDatabaseJob(job, attempt, rt.leaseMs);\n}\n\n/** Mark an expired attempt row `expired`: it leaves the poll's range, and its holder sees `lost`. */\nasync function retire(rt: QueueRuntime, stale: CaptureJobRow | null): Promise<void> {\n if (stale !== null) {\n await rt.model.finishAttempt(stale.id, \"expired\", isoAt(rt));\n }\n}\n\n/**\n * The final attempt lost its lease: the job fails (one limit covers failed attempts and lost\n * leases). Idempotent, so two instances finding the same row write the same outcome. If the late\n * holder finished after all, its `done` wins.\n */\nasync function exhaust(\n rt: QueueRuntime,\n job: CaptureJobRow,\n stale: CaptureJobRow | null,\n): Promise<void> {\n const now = isoAt(rt);\n if (stale !== null && (await rt.model.get(stale.id))?.status === \"done\") {\n await rt.model.finishJob(job.jobId, \"done\", now);\n return;\n }\n await retire(rt, stale);\n await rt.model.finishJob(job.jobId, \"failed\", now, LEASE_EXHAUSTED_ERROR);\n rt.logger?.warn({ jobId: job.jobId, attempts: job.attempt }, \"job failed: lease exhausted\");\n await reportExhausted(rt, job);\n}\n\n/** Sleep for `ms`, using injected timers when available. */\nasync function sleep(rt: QueueRuntime, ms: number): Promise<void> {\n if (rt.timers === undefined) {\n await new Promise<void>((resolve) => {\n setTimeout(resolve, ms);\n });\n return;\n }\n await new Promise<void>((resolve) => {\n rt.timers?.setTimeout(resolve, ms);\n });\n}\n\n/** Tell the host a job ended without any holder to record it; its failure never blocks the poll. */\nasync function reportExhausted(rt: QueueRuntime, job: CaptureJobRow): Promise<void> {\n if (rt.onExhausted === undefined) {\n return;\n }\n const attempts = 3;\n for (let attempt = 1; attempt <= attempts; attempt += 1) {\n try {\n // oxlint-disable-next-line eslint/no-await-in-loop -- sequential retries\n await rt.onExhausted(toDatabaseJob(job, job.attempt, rt.leaseMs), LEASE_EXHAUSTED_ERROR);\n return;\n } catch (error) {\n rt.logger?.error(\n { err: error, jobId: job.jobId, attempt, attempts },\n \"exhausted-job hook failed\",\n );\n if (attempt < attempts) {\n // oxlint-disable-next-line eslint/no-await-in-loop -- sequential retries\n await sleep(rt, 100 * attempt);\n }\n }\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ // src/capture/db-queue/clock.ts
5
+ /** ISO-8601 time `plusMs` from now on the queue's clock. */
6
+ function isoAt(rt, plusMs = 0) {
7
+ return new Date(rt.now() + plusMs).toISOString();
8
+ }
9
+ export { isoAt };
10
+
11
+ //# sourceMappingURL=clock.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clock.mjs","names":[],"sources":["../../../src/capture/db-queue/clock.ts"],"sourcesContent":["/** Time of the database queue: every timestamp it writes or compares comes from the runtime clock. */\nimport type { QueueRuntime } from \"./types.ts\";\n\n/** ISO-8601 time `plusMs` from now on the queue's clock. */\nexport function isoAt(rt: Pick<QueueRuntime, \"now\">, plusMs = 0): string {\n return new Date(rt.now() + plusMs).toISOString();\n}\n"],"mappings":";;;;;AAIA,SAAgB,MAAM,IAA+B,SAAS,GAAW;CACvE,OAAO,IAAI,KAAK,GAAG,IAAI,IAAI,MAAM,CAAC,CAAC,YAAY;AACjD"}
@@ -0,0 +1,75 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ import { attemptRowId } from "../../models/capture-job.mjs";
5
+ // src/capture/db-queue/codec.ts
6
+ /** Serialize the request id, trace context and kind-specific fields of a job; null when empty. */
7
+ function encodePayload(job) {
8
+ const stored = {};
9
+ if (job.reqId !== void 0) stored.reqId = job.reqId;
10
+ if (job.traceparent !== void 0) stored.traceparent = job.traceparent;
11
+ if (job.data !== void 0 && Object.keys(job.data).length > 0) stored.data = job.data;
12
+ return Object.keys(stored).length === 0 ? null : JSON.stringify(stored);
13
+ }
14
+ /** Parse a stored payload. A malformed one (a poison row) decodes as empty rather than throwing. */
15
+ function decodePayload(raw) {
16
+ if (raw === null || raw === "") return {};
17
+ try {
18
+ const parsed = JSON.parse(raw);
19
+ return isRecord(parsed) ? pickPayload(parsed) : {};
20
+ } catch {
21
+ return {};
22
+ }
23
+ }
24
+ /** The job handed to a consumer for attempt `attempt` of a job row. */
25
+ function toDatabaseJob(row, attempt, leaseMs) {
26
+ const payload = decodePayload(row.payload);
27
+ return withOptional({
28
+ buildId: row.buildId ?? "",
29
+ jobId: row.jobId,
30
+ kind: row.kind,
31
+ attempt,
32
+ attempts: attempt - 1,
33
+ maxAttempts: row.maxAttempts,
34
+ receipt: attemptRowId(row.jobId, attempt),
35
+ leaseMs,
36
+ data: payload.data ?? {}
37
+ }, row, payload);
38
+ }
39
+ /** The queue-view entry of a job row. */
40
+ function toQueueEntry(row) {
41
+ const entry = {
42
+ buildId: row.buildId ?? "",
43
+ status: entryStatus(row.status),
44
+ queuedAt: row.createdAt
45
+ };
46
+ if (row.shardId !== null) entry.shard = row.shardId;
47
+ if (row.startedAt !== null) entry.startedAt = row.startedAt;
48
+ if (row.finishedAt !== null) entry.finishedAt = row.finishedAt;
49
+ if (row.error !== null) entry.error = row.error;
50
+ return entry;
51
+ }
52
+ function withOptional(job, row, payload) {
53
+ if (row.shardId !== null) job.shard = row.shardId;
54
+ if (payload.reqId !== void 0) job.reqId = payload.reqId;
55
+ if (payload.traceparent !== void 0) job.traceparent = payload.traceparent;
56
+ return job;
57
+ }
58
+ function entryStatus(status) {
59
+ if (status === "done") return "completed";
60
+ if (status === "expired") return "failed";
61
+ return status;
62
+ }
63
+ function pickPayload(parsed) {
64
+ const stored = {};
65
+ if (typeof parsed["reqId"] === "string") stored.reqId = parsed["reqId"];
66
+ if (typeof parsed["traceparent"] === "string") stored.traceparent = parsed["traceparent"];
67
+ if (isRecord(parsed["data"])) stored.data = parsed["data"];
68
+ return stored;
69
+ }
70
+ function isRecord(value) {
71
+ return typeof value === "object" && value !== null && !Array.isArray(value);
72
+ }
73
+ export { decodePayload, encodePayload, toDatabaseJob, toQueueEntry };
74
+
75
+ //# sourceMappingURL=codec.mjs.map