@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,175 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ import { TargetLaunchError } from "./capture-target.mjs";
5
+ import { hasSchemeFor, schemesOf } from "./remote-browser.mjs";
6
+ import { retryConnect } from "../utils/connect-retry.mjs";
7
+ import { endAttempt, startAttempt } from "./remote-lease-attempt.mjs";
8
+ import { RemoteConnectError, classify, connectErrorOf, stoppedError } from "./remote-lease-error.mjs";
9
+ import { holdLease, noLease, withDeadline } from "./remote-lease-held.mjs";
10
+ import { shutdownEpoch, takeSlot } from "./remote-limiter.mjs";
11
+ import { onRemoteShutdown, shutdownRemote } from "./remote-lease-shutdown.mjs";
12
+ // src/adapters/remote-lease.ts
13
+ /**
14
+ * Connect (calling `connect` when it is a function) and open the connection, as one retried
15
+ * attempt: a failed handshake releases its lease as `failed` before the next attempt connects
16
+ * again, so at most one lease is held per connection. On success the caller owns the returned
17
+ * lease and must call {@link HeldConnection.end}; on failure nothing is held and a
18
+ * {@link TargetLaunchError} is thrown.
19
+ */
20
+ async function connectLeased(entry, context, options) {
21
+ const { remote } = entry;
22
+ let current = typeof remote.connect === "function" ? { endpoint: "" } : remote.connect;
23
+ let attemptNo = 0;
24
+ try {
25
+ return await retryConnect(async () => {
26
+ attemptNo += 1;
27
+ const lease = await acquire(entry, context, attemptNo, options.logger);
28
+ current = lease.connection;
29
+ try {
30
+ return {
31
+ value: await options.open(lease.connection),
32
+ lease
33
+ };
34
+ } catch (error) {
35
+ await lease.end("failed", error);
36
+ throw error;
37
+ }
38
+ }, {
39
+ retries: remote.retry.attempts,
40
+ backoffMs: remote.retry.backoffMs,
41
+ retryable: (error) => classify(error).retry,
42
+ onRetry: (error, retry, delayMs) => {
43
+ options.logger?.warn({
44
+ target: entry.target,
45
+ retry,
46
+ delayMs,
47
+ reason: options.reasonOf(error, current)
48
+ }, "remote browser connect failed, retrying");
49
+ }
50
+ });
51
+ } catch (error) {
52
+ const reason = options.reasonOf(error, current);
53
+ throw new TargetLaunchError(entry.target, reason, {
54
+ cause: Object.assign(new Error(reason), { name: error instanceof Error ? error.name : "Error" }),
55
+ transient: !classify(error).unavailable
56
+ });
57
+ }
58
+ }
59
+ /**
60
+ * One connection, counted under the service's `maxConnections` when it has one: the slot is taken
61
+ * (waiting, cancellably) before `connect` runs and given back when the lease ends, or at once when
62
+ * the connection never materialises. Without a cap this is just {@link open}.
63
+ */
64
+ async function acquire(entry, context, attemptNo, logger) {
65
+ const epoch = shutdownEpoch();
66
+ const taking = takeSlot(entry, context.signal, logger);
67
+ const slot = taking === void 0 ? void 0 : await taking;
68
+ stopIfShutDown(slot, epoch);
69
+ try {
70
+ const held = await open(entry, context, attemptNo, logger);
71
+ return slot === void 0 ? held : withSlot(held, slot);
72
+ } catch (error) {
73
+ slot?.release();
74
+ throw error;
75
+ }
76
+ }
77
+ /**
78
+ * A slot granted across a shutdown must not open a connection: the shutdown has already stopped the
79
+ * attempts and collected the leases, so nothing would release it. The slot is given back and the
80
+ * connection stops. Called right before `open`, with nothing awaited in between, so a shutdown cannot
81
+ * slip into the gap (`open` registers its attempt synchronously).
82
+ */
83
+ function stopIfShutDown(slot, epoch) {
84
+ if (slot !== void 0 && shutdownEpoch() !== epoch) {
85
+ slot.release();
86
+ throw stoppedError();
87
+ }
88
+ }
89
+ /** `held` that gives its slot back, once, after the lease is released. */
90
+ function withSlot(held, slot) {
91
+ let ending;
92
+ return {
93
+ connection: held.connection,
94
+ end: async (outcome, error) => {
95
+ ending ??= endAndFree(held, slot, outcome, error);
96
+ await ending;
97
+ }
98
+ };
99
+ }
100
+ async function endAndFree(held, slot, outcome, error) {
101
+ try {
102
+ await held.end(outcome, error);
103
+ } finally {
104
+ slot.release();
105
+ }
106
+ }
107
+ async function open(entry, context, attemptNo, logger) {
108
+ const { remote } = entry;
109
+ if (typeof remote.connect !== "function") return noLease(remote.connect);
110
+ const attempt = startAttempt(context.signal);
111
+ try {
112
+ return await adopt(entry, await called(entry, context, attemptNo, attempt, logger), logger);
113
+ } finally {
114
+ endAttempt(attempt);
115
+ }
116
+ }
117
+ /** The function `connect` under its deadline; a lease that arrives after it is released at once. */
118
+ async function called(entry, context, attemptNo, attempt, logger) {
119
+ if (attempt.stopped) throw stoppedError();
120
+ const pending = invoke(entry.remote.connect, {
121
+ engine: entry.engine,
122
+ buildId: context.buildId,
123
+ runId: context.runId,
124
+ shardIndex: context.shardIndex,
125
+ purpose: context.purpose,
126
+ attempt: attemptNo,
127
+ signal: attempt.controller.signal
128
+ });
129
+ const { acquireMs } = entry.remote.timeouts;
130
+ try {
131
+ return await withDeadline(pending, acquireMs, () => {
132
+ attempt.controller.abort();
133
+ releaseLate(entry, pending, logger).catch(() => {});
134
+ return new RemoteConnectError(`acquire timed out after ${acquireMs} ms`);
135
+ });
136
+ } catch (error) {
137
+ throw await failedConnect(entry, error, attempt, logger);
138
+ }
139
+ }
140
+ /** Call a sync or async `connect`, so a sync throw is a rejection like any other. */
141
+ async function invoke(connect, context) {
142
+ return await connect(context);
143
+ }
144
+ /** A function `connect`'s failure as the error to throw, releasing a lease it carried. */
145
+ async function failedConnect(entry, error, attempt, logger) {
146
+ const converted = error instanceof RemoteConnectError ? error : connectErrorOf(error);
147
+ if (converted.lease !== void 0) await holdLease(entry, converted.lease, { endpoint: "" }, logger).end("failed", converted);
148
+ return attempt.stopped ? stoppedError() : converted;
149
+ }
150
+ /** Check what a function `connect` returned against what the service declared, then hold it. */
151
+ async function adopt(entry, result, logger) {
152
+ const problem = problemWith(entry, result);
153
+ if (problem === null) return holdLease(entry, result.lease, result, logger);
154
+ const lease = result?.lease;
155
+ if (lease !== void 0) await holdLease(entry, lease, { endpoint: "" }, logger).end("failed");
156
+ throw new RemoteConnectError(problem, {
157
+ transient: false,
158
+ scope: "job"
159
+ });
160
+ }
161
+ function problemWith(entry, result) {
162
+ if (typeof result !== "object" || result === null || typeof result.endpoint !== "string") return "connect must return an object with an endpoint";
163
+ const extra = Object.keys(result).filter((key) => key !== "endpoint" && key !== "headers" && key !== "lease");
164
+ if (extra.length > 0) return `connect returned ${extra.join(", ")}: protocol and reach are declared on remote(), not returned`;
165
+ const { protocol } = entry.remote;
166
+ return hasSchemeFor(protocol, result.endpoint) ? null : `connect returned an endpoint that is not a ${schemesOf(protocol)} URL`;
167
+ }
168
+ /** A connection that arrives after its deadline is released at once. */
169
+ async function releaseLate(entry, pending, logger) {
170
+ const late = await pending.catch(() => null);
171
+ if (late?.lease !== void 0) await holdLease(entry, late.lease, { endpoint: "" }, logger).end("failed");
172
+ }
173
+ export { RemoteConnectError, connectLeased, onRemoteShutdown, shutdownRemote };
174
+
175
+ //# sourceMappingURL=remote-lease.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"remote-lease.mjs","names":[],"sources":["../../src/adapters/remote-lease.ts"],"sourcesContent":["/**\n * Connecting to a remote browser over a lease (ADR 0007, decision 4): a function `connect` is\n * called per connection, and `release` once the render that used it has ended. The runners own the\n * handshake itself; this owns the pairing, so a lease is released exactly once on every path\n * (finish, failure, cancel, shutdown) and never logged.\n */\nimport type { Logger } from \"../logger.ts\";\nimport { retryConnect } from \"../utils/connect-retry.ts\";\nimport { TargetLaunchError } from \"./capture-target.ts\";\nimport {\n hasSchemeFor,\n schemesOf,\n type RemoteConnectContext,\n type RemoteConnection,\n type RemoteLeaseOutcome,\n type RemoteLeasedConnection,\n type RemoteTarget,\n} from \"./remote-browser.ts\";\nimport { endAttempt, startAttempt, type Attempt } from \"./remote-lease-attempt.ts\";\nimport {\n RemoteConnectError,\n classify,\n connectErrorOf,\n stoppedError,\n} from \"./remote-lease-error.ts\";\nimport { holdLease, noLease, withDeadline, type HeldConnection } from \"./remote-lease-held.ts\";\nimport { shutdownEpoch, takeSlot, type ConnectionSlot } from \"./remote-limiter.ts\";\n\nexport { RemoteConnectError, type RemoteConnectErrorOptions } from \"./remote-lease-error.ts\";\nexport type { HeldConnection } from \"./remote-lease-held.ts\";\nexport { onRemoteShutdown, shutdownRemote } from \"./remote-lease-shutdown.ts\";\n\n/** Which run a connection is for. */\nexport interface ConnectLeasedContext {\n buildId: string;\n runId: string;\n shardIndex?: number | undefined;\n purpose: RemoteConnectContext[\"purpose\"];\n /** Aborts a pending `connect` when the run is cancelled. */\n signal?: AbortSignal | undefined;\n}\n\n/** Options of {@link connectLeased}. */\nexport interface ConnectLeasedOptions<T> {\n logger?: Logger | undefined;\n /** The handshake over one connection; its failures are retried when connection-level. */\n open: (connection: RemoteConnection) => Promise<T>;\n /** One line saying what failed, with the connection's credentials removed. */\n reasonOf: (error: unknown, connection: RemoteConnection & { lease?: unknown }) => string;\n}\n\ntype ConnectFn = (\n context: RemoteConnectContext,\n) => RemoteLeasedConnection | Promise<RemoteLeasedConnection>;\n\n/**\n * Connect (calling `connect` when it is a function) and open the connection, as one retried\n * attempt: a failed handshake releases its lease as `failed` before the next attempt connects\n * again, so at most one lease is held per connection. On success the caller owns the returned\n * lease and must call {@link HeldConnection.end}; on failure nothing is held and a\n * {@link TargetLaunchError} is thrown.\n */\nexport async function connectLeased<T>(\n entry: RemoteTarget,\n context: ConnectLeasedContext,\n options: ConnectLeasedOptions<T>,\n): Promise<{ value: T; lease: HeldConnection }> {\n const { remote } = entry;\n let current: RemoteConnection & { lease?: unknown } =\n typeof remote.connect === \"function\" ? { endpoint: \"\" } : remote.connect;\n let attemptNo = 0;\n try {\n return await retryConnect(\n async () => {\n attemptNo += 1;\n const lease = await acquire(entry, context, attemptNo, options.logger);\n current = lease.connection;\n try {\n return { value: await options.open(lease.connection), lease };\n } catch (error) {\n await lease.end(\"failed\", error);\n throw error;\n }\n },\n {\n retries: remote.retry.attempts,\n backoffMs: remote.retry.backoffMs,\n retryable: (error) => classify(error).retry,\n onRetry: (error, retry, delayMs) => {\n options.logger?.warn(\n { target: entry.target, retry, delayMs, reason: options.reasonOf(error, current) },\n \"remote browser connect failed, retrying\",\n );\n },\n },\n );\n } catch (error) {\n const reason = options.reasonOf(error, current);\n throw new TargetLaunchError(entry.target, reason, {\n cause: Object.assign(new Error(reason), {\n name: error instanceof Error ? error.name : \"Error\",\n }),\n transient: !classify(error).unavailable,\n });\n }\n}\n\n/**\n * One connection, counted under the service's `maxConnections` when it has one: the slot is taken\n * (waiting, cancellably) before `connect` runs and given back when the lease ends, or at once when\n * the connection never materialises. Without a cap this is just {@link open}.\n */\nasync function acquire(\n entry: RemoteTarget,\n context: ConnectLeasedContext,\n attemptNo: number,\n logger: Logger | undefined,\n): Promise<HeldConnection> {\n // No `await` without a cap: an uncapped connection starts exactly as it did before.\n const epoch = shutdownEpoch();\n const taking = takeSlot(entry, context.signal, logger);\n const slot = taking === undefined ? undefined : await taking;\n stopIfShutDown(slot, epoch);\n try {\n const held = await open(entry, context, attemptNo, logger);\n return slot === undefined ? held : withSlot(held, slot);\n } catch (error) {\n slot?.release();\n throw error;\n }\n}\n\n/**\n * A slot granted across a shutdown must not open a connection: the shutdown has already stopped the\n * attempts and collected the leases, so nothing would release it. The slot is given back and the\n * connection stops. Called right before `open`, with nothing awaited in between, so a shutdown cannot\n * slip into the gap (`open` registers its attempt synchronously).\n */\nfunction stopIfShutDown(slot: ConnectionSlot | undefined, epoch: number): void {\n if (slot !== undefined && shutdownEpoch() !== epoch) {\n slot.release();\n throw stoppedError();\n }\n}\n\n/** `held` that gives its slot back, once, after the lease is released. */\nfunction withSlot(held: HeldConnection, slot: ConnectionSlot): HeldConnection {\n let ending: Promise<void> | undefined;\n return {\n connection: held.connection,\n // Every caller gets the first call's promise: the inner `end` returns at once for a second call,\n // which would free the slot while the first release is still running.\n end: async (outcome, error) => {\n ending ??= endAndFree(held, slot, outcome, error);\n await ending;\n },\n };\n}\n\nasync function endAndFree(\n held: HeldConnection,\n slot: ConnectionSlot,\n outcome: RemoteLeaseOutcome,\n error: unknown,\n): Promise<void> {\n try {\n await held.end(outcome, error);\n } finally {\n slot.release();\n }\n}\n\nasync function open(\n entry: RemoteTarget,\n context: ConnectLeasedContext,\n attemptNo: number,\n logger: Logger | undefined,\n): Promise<HeldConnection> {\n const { remote } = entry;\n if (typeof remote.connect !== \"function\") {\n return noLease(remote.connect);\n }\n const attempt = startAttempt(context.signal);\n try {\n const result = await called(entry, context, attemptNo, attempt, logger);\n return await adopt(entry, result, logger);\n } finally {\n endAttempt(attempt);\n }\n}\n\n/** The function `connect` under its deadline; a lease that arrives after it is released at once. */\nasync function called(\n entry: RemoteTarget,\n context: ConnectLeasedContext,\n attemptNo: number,\n attempt: Attempt,\n logger: Logger | undefined,\n): Promise<RemoteLeasedConnection> {\n if (attempt.stopped) {\n // Cancelled during a backoff: never call the provider with a signal that is already aborted.\n throw stoppedError();\n }\n const pending = invoke(entry.remote.connect as ConnectFn, {\n engine: entry.engine,\n buildId: context.buildId,\n runId: context.runId,\n shardIndex: context.shardIndex,\n purpose: context.purpose,\n attempt: attemptNo,\n signal: attempt.controller.signal,\n });\n const { acquireMs } = entry.remote.timeouts;\n try {\n return await withDeadline(pending, acquireMs, () => {\n attempt.controller.abort();\n releaseLate(entry, pending, logger).catch(() => {});\n return new RemoteConnectError(`acquire timed out after ${acquireMs} ms`);\n });\n } catch (error) {\n throw await failedConnect(entry, error, attempt, logger);\n }\n}\n\n/** Call a sync or async `connect`, so a sync throw is a rejection like any other. */\nasync function invoke(\n connect: ConnectFn,\n context: RemoteConnectContext,\n): Promise<RemoteLeasedConnection> {\n return await connect(context);\n}\n\n/** A function `connect`'s failure as the error to throw, releasing a lease it carried. */\nasync function failedConnect(\n entry: RemoteTarget,\n error: unknown,\n attempt: Attempt,\n logger: Logger | undefined,\n): Promise<Error> {\n const converted = error instanceof RemoteConnectError ? error : connectErrorOf(error);\n if (converted.lease !== undefined) {\n await holdLease(entry, converted.lease, { endpoint: \"\" }, logger).end(\"failed\", converted);\n }\n return attempt.stopped ? stoppedError() : converted;\n}\n\n/** Check what a function `connect` returned against what the service declared, then hold it. */\nasync function adopt(\n entry: RemoteTarget,\n result: RemoteLeasedConnection,\n logger: Logger | undefined,\n): Promise<HeldConnection> {\n const problem = problemWith(entry, result);\n if (problem === null) {\n return holdLease(entry, result.lease, result, logger);\n }\n const lease = (result as { lease?: unknown } | null)?.lease;\n if (lease !== undefined) {\n await holdLease(entry, lease, { endpoint: \"\" }, logger).end(\"failed\");\n }\n throw new RemoteConnectError(problem, { transient: false, scope: \"job\" });\n}\n\nfunction problemWith(entry: RemoteTarget, result: RemoteLeasedConnection): string | null {\n if (typeof result !== \"object\" || result === null || typeof result.endpoint !== \"string\") {\n return \"connect must return an object with an endpoint\";\n }\n const extra = Object.keys(result).filter(\n (key) => key !== \"endpoint\" && key !== \"headers\" && key !== \"lease\",\n );\n if (extra.length > 0) {\n return `connect returned ${extra.join(\", \")}: protocol and reach are declared on remote(), not returned`;\n }\n const { protocol } = entry.remote;\n return hasSchemeFor(protocol, result.endpoint)\n ? null\n : `connect returned an endpoint that is not a ${schemesOf(protocol)} URL`;\n}\n\n/** A connection that arrives after its deadline is released at once. */\nasync function releaseLate(\n entry: RemoteTarget,\n pending: Promise<RemoteLeasedConnection>,\n logger: Logger | undefined,\n): Promise<void> {\n const late = await pending.catch(() => null);\n if (late?.lease !== undefined) {\n await holdLease(entry, late.lease, { endpoint: \"\" }, logger).end(\"failed\");\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,121 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ import { stoppedError } from "./remote-lease-error.mjs";
5
+ // src/adapters/remote-limiter.ts
6
+ /** A counting semaphore with a FIFO queue whose waiters can be cancelled. */
7
+ var ConnectionLimiter = class {
8
+ limit;
9
+ #held = 0;
10
+ #queue = [];
11
+ constructor(limit) {
12
+ this.limit = limit;
13
+ }
14
+ /** Slots in use. */
15
+ get held() {
16
+ return this.#held;
17
+ }
18
+ /** Callers waiting for a slot. */
19
+ get waiting() {
20
+ return this.#queue.length;
21
+ }
22
+ /**
23
+ * Take a slot, waiting for one when all are in use. Rejects with the stopped error when `signal`
24
+ * aborts (or the process shuts down) while waiting, without having taken anything.
25
+ * `onLongWait` runs once if the wait outlasts `longWaitMs`.
26
+ */
27
+ async acquire(signal, onLongWait, longWaitMs = 0) {
28
+ if (signal?.aborted === true) throw stoppedError();
29
+ if (this.#held < this.limit && this.#queue.length === 0) {
30
+ this.#held += 1;
31
+ return this.#slot();
32
+ }
33
+ return await new Promise((resolve, reject) => {
34
+ const timer = onLongWait === void 0 ? void 0 : setTimeout(onLongWait, longWaitMs).unref();
35
+ const settle = () => {
36
+ clearTimeout(timer);
37
+ signal?.removeEventListener("abort", onAbort);
38
+ };
39
+ const waiter = {
40
+ grant: () => {
41
+ settle();
42
+ this.#held += 1;
43
+ resolve(this.#slot());
44
+ },
45
+ stop: () => {
46
+ settle();
47
+ const index = this.#queue.indexOf(waiter);
48
+ if (index >= 0) this.#queue.splice(index, 1);
49
+ reject(stoppedError());
50
+ }
51
+ };
52
+ const onAbort = () => {
53
+ waiter.stop();
54
+ };
55
+ signal?.addEventListener("abort", onAbort, { once: true });
56
+ this.#queue.push(waiter);
57
+ });
58
+ }
59
+ /** Reject every waiter (shutdown). Slots already taken are released by their leases. */
60
+ stopWaiting() {
61
+ for (const waiter of this.#queue.splice(0)) waiter.stop();
62
+ }
63
+ #slot() {
64
+ let released = false;
65
+ return { release: () => {
66
+ if (released) return;
67
+ released = true;
68
+ this.#held -= 1;
69
+ this.#queue.shift()?.grant();
70
+ } };
71
+ }
72
+ };
73
+ const limiters = /* @__PURE__ */ new Map();
74
+ let epoch = 0;
75
+ /** The limiter of a service, created on first use; undefined when it has no `maxConnections`. */
76
+ function limiterOf(remote) {
77
+ if (remote.maxConnections === void 0) return;
78
+ let limiter = limiters.get(remote);
79
+ if (limiter === void 0) {
80
+ limiter = new ConnectionLimiter(remote.maxConnections);
81
+ limiters.set(remote, limiter);
82
+ }
83
+ return limiter;
84
+ }
85
+ /**
86
+ * Take the connection slot of `entry`'s service, or undefined when it has no cap. Not `async`: without
87
+ * a cap the caller must not wait even one tick before it connects. A wait longer than
88
+ * `timeouts.acquireMs` is logged once, with the target and the limit and nothing about the endpoint.
89
+ */
90
+ function takeSlot(entry, signal, logger) {
91
+ const limiter = limiterOf(entry.remote);
92
+ if (limiter === void 0) return;
93
+ return limiter.acquire(signal, () => {
94
+ logger?.warn({
95
+ target: entry.target,
96
+ limit: limiter.limit,
97
+ waiting: limiter.waiting
98
+ }, "waiting for a remote browser connection slot");
99
+ }, entry.remote.timeouts.acquireMs);
100
+ }
101
+ /**
102
+ * Shutdown: reject every caller still waiting for a slot and forget every limiter, so a service used
103
+ * again starts with none held. A lease that is released later gives its slot back to the limiter it
104
+ * took it from, which nothing uses any more.
105
+ */
106
+ function resetAllLimiters() {
107
+ epoch += 1;
108
+ for (const limiter of limiters.values()) limiter.stopWaiting();
109
+ limiters.clear();
110
+ }
111
+ /**
112
+ * Counts shutdowns. A caller that took a slot compares it with the value from before it waited: a
113
+ * slot granted just before a shutdown resumes a tick later, after the attempts were stopped, and
114
+ * must not open a connection nobody will release.
115
+ */
116
+ function shutdownEpoch() {
117
+ return epoch;
118
+ }
119
+ export { ConnectionLimiter, limiterOf, resetAllLimiters, shutdownEpoch, takeSlot };
120
+
121
+ //# sourceMappingURL=remote-limiter.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"remote-limiter.mjs","names":[],"sources":["../../src/adapters/remote-limiter.ts"],"sourcesContent":["/**\n * The per-process cap on a remote service's connections (ADR 0007, decision 4): a separate limiter,\n * never the page gate. A slot is taken before `connect` is called and given back with the lease,\n * exactly once, so a connection counts from the moment it is requested until its render has ended.\n * Waiting for a slot ends on the run's cancel and at shutdown, and frees nothing it did not take.\n */\nimport type { Logger } from \"../logger.ts\";\nimport type { RemoteBrowser, RemoteTarget } from \"./remote-browser.ts\";\nimport { stoppedError } from \"./remote-lease-error.ts\";\n\n/** One connection's place under the cap; `release` is safe to call more than once. */\nexport interface ConnectionSlot {\n release(): void;\n}\n\ninterface Waiter {\n grant(): void;\n stop(): void;\n}\n\n/** A counting semaphore with a FIFO queue whose waiters can be cancelled. */\nexport class ConnectionLimiter {\n readonly limit: number;\n #held = 0;\n readonly #queue: Waiter[] = [];\n\n constructor(limit: number) {\n this.limit = limit;\n }\n\n /** Slots in use. */\n get held(): number {\n return this.#held;\n }\n\n /** Callers waiting for a slot. */\n get waiting(): number {\n return this.#queue.length;\n }\n\n /**\n * Take a slot, waiting for one when all are in use. Rejects with the stopped error when `signal`\n * aborts (or the process shuts down) while waiting, without having taken anything.\n * `onLongWait` runs once if the wait outlasts `longWaitMs`.\n */\n async acquire(\n signal?: AbortSignal,\n onLongWait?: () => void,\n longWaitMs = 0,\n ): Promise<ConnectionSlot> {\n if (signal?.aborted === true) {\n throw stoppedError();\n }\n if (this.#held < this.limit && this.#queue.length === 0) {\n this.#held += 1;\n return this.#slot();\n }\n return await new Promise<ConnectionSlot>((resolve, reject) => {\n const timer =\n onLongWait === undefined ? undefined : setTimeout(onLongWait, longWaitMs).unref();\n const settle = (): void => {\n clearTimeout(timer);\n signal?.removeEventListener(\"abort\", onAbort);\n };\n const waiter: Waiter = {\n grant: () => {\n settle();\n this.#held += 1;\n resolve(this.#slot());\n },\n stop: () => {\n settle();\n const index = this.#queue.indexOf(waiter);\n if (index >= 0) {\n this.#queue.splice(index, 1);\n }\n reject(stoppedError());\n },\n };\n const onAbort = (): void => {\n waiter.stop();\n };\n signal?.addEventListener(\"abort\", onAbort, { once: true });\n this.#queue.push(waiter);\n });\n }\n\n /** Reject every waiter (shutdown). Slots already taken are released by their leases. */\n stopWaiting(): void {\n for (const waiter of this.#queue.splice(0)) {\n waiter.stop();\n }\n }\n\n #slot(): ConnectionSlot {\n let released = false;\n return {\n release: () => {\n if (released) {\n return;\n }\n released = true;\n this.#held -= 1;\n this.#queue.shift()?.grant();\n },\n };\n }\n}\n\nconst limiters = new Map<RemoteBrowser, ConnectionLimiter>();\nlet epoch = 0;\n\n/** The limiter of a service, created on first use; undefined when it has no `maxConnections`. */\nexport function limiterOf(remote: RemoteBrowser): ConnectionLimiter | undefined {\n if (remote.maxConnections === undefined) {\n return undefined;\n }\n let limiter = limiters.get(remote);\n if (limiter === undefined) {\n limiter = new ConnectionLimiter(remote.maxConnections);\n limiters.set(remote, limiter);\n }\n return limiter;\n}\n\n/**\n * Take the connection slot of `entry`'s service, or undefined when it has no cap. Not `async`: without\n * a cap the caller must not wait even one tick before it connects. A wait longer than\n * `timeouts.acquireMs` is logged once, with the target and the limit and nothing about the endpoint.\n */\nexport function takeSlot(\n entry: RemoteTarget,\n signal: AbortSignal | undefined,\n logger: Logger | undefined,\n): Promise<ConnectionSlot> | undefined {\n const limiter = limiterOf(entry.remote);\n if (limiter === undefined) {\n return undefined;\n }\n return limiter.acquire(\n signal,\n () => {\n logger?.warn(\n { target: entry.target, limit: limiter.limit, waiting: limiter.waiting },\n \"waiting for a remote browser connection slot\",\n );\n },\n entry.remote.timeouts.acquireMs,\n );\n}\n\n/**\n * Shutdown: reject every caller still waiting for a slot and forget every limiter, so a service used\n * again starts with none held. A lease that is released later gives its slot back to the limiter it\n * took it from, which nothing uses any more.\n */\nexport function resetAllLimiters(): void {\n epoch += 1;\n for (const limiter of limiters.values()) {\n limiter.stopWaiting();\n }\n limiters.clear();\n}\n\n/**\n * Counts shutdowns. A caller that took a slot compares it with the value from before it waited: a\n * slot granted just before a shutdown resumes a tick later, after the attempts were stopped, and\n * must not open a connection nobody will release.\n */\nexport function shutdownEpoch(): number {\n return epoch;\n}\n"],"mappings":";;;;;;AAqBA,IAAa,oBAAb,MAA+B;CAC7B;CACA,QAAQ;CACR,SAA4B,CAAC;CAE7B,YAAY,OAAe;EACzB,KAAK,QAAQ;CACf;;CAGA,IAAI,OAAe;EACjB,OAAO,KAAK;CACd;;CAGA,IAAI,UAAkB;EACpB,OAAO,KAAK,OAAO;CACrB;;;;;;CAOA,MAAM,QACJ,QACA,YACA,aAAa,GACY;EACzB,IAAI,QAAQ,YAAY,MACtB,MAAM,aAAa;EAErB,IAAI,KAAK,QAAQ,KAAK,SAAS,KAAK,OAAO,WAAW,GAAG;GACvD,KAAK,SAAS;GACd,OAAO,KAAK,MAAM;EACpB;EACA,OAAO,MAAM,IAAI,SAAyB,SAAS,WAAW;GAC5D,MAAM,QACJ,eAAe,KAAA,IAAY,KAAA,IAAY,WAAW,YAAY,UAAU,CAAC,CAAC,MAAM;GAClF,MAAM,eAAqB;IACzB,aAAa,KAAK;IAClB,QAAQ,oBAAoB,SAAS,OAAO;GAC9C;GACA,MAAM,SAAiB;IACrB,aAAa;KACX,OAAO;KACP,KAAK,SAAS;KACd,QAAQ,KAAK,MAAM,CAAC;IACtB;IACA,YAAY;KACV,OAAO;KACP,MAAM,QAAQ,KAAK,OAAO,QAAQ,MAAM;KACxC,IAAI,SAAS,GACX,KAAK,OAAO,OAAO,OAAO,CAAC;KAE7B,OAAO,aAAa,CAAC;IACvB;GACF;GACA,MAAM,gBAAsB;IAC1B,OAAO,KAAK;GACd;GACA,QAAQ,iBAAiB,SAAS,SAAS,EAAE,MAAM,KAAK,CAAC;GACzD,KAAK,OAAO,KAAK,MAAM;EACzB,CAAC;CACH;;CAGA,cAAoB;EAClB,KAAK,MAAM,UAAU,KAAK,OAAO,OAAO,CAAC,GACvC,OAAO,KAAK;CAEhB;CAEA,QAAwB;EACtB,IAAI,WAAW;EACf,OAAO,EACL,eAAe;GACb,IAAI,UACF;GAEF,WAAW;GACX,KAAK,SAAS;GACd,KAAK,OAAO,MAAM,CAAC,EAAE,MAAM;EAC7B,EACF;CACF;AACF;AAEA,MAAM,2BAAW,IAAI,IAAsC;AAC3D,IAAI,QAAQ;;AAGZ,SAAgB,UAAU,QAAsD;CAC9E,IAAI,OAAO,mBAAmB,KAAA,GAC5B;CAEF,IAAI,UAAU,SAAS,IAAI,MAAM;CACjC,IAAI,YAAY,KAAA,GAAW;EACzB,UAAU,IAAI,kBAAkB,OAAO,cAAc;EACrD,SAAS,IAAI,QAAQ,OAAO;CAC9B;CACA,OAAO;AACT;;;;;;AAOA,SAAgB,SACd,OACA,QACA,QACqC;CACrC,MAAM,UAAU,UAAU,MAAM,MAAM;CACtC,IAAI,YAAY,KAAA,GACd;CAEF,OAAO,QAAQ,QACb,cACM;EACJ,QAAQ,KACN;GAAE,QAAQ,MAAM;GAAQ,OAAO,QAAQ;GAAO,SAAS,QAAQ;EAAQ,GACvE,8CACF;CACF,GACA,MAAM,OAAO,SAAS,SACxB;AACF;;;;;;AAOA,SAAgB,mBAAyB;CACvC,SAAS;CACT,KAAK,MAAM,WAAW,SAAS,OAAO,GACpC,QAAQ,YAAY;CAEtB,SAAS,MAAM;AACjB;;;;;;AAOA,SAAgB,gBAAwB;CACtC,OAAO;AACT"}
@@ -0,0 +1,56 @@
1
+ import __tsdown_shims_path from 'node:path';
2
+ import __tsdown_shims_url from 'node:url';
3
+ import { RemoteConnectContext, RemoteConnection, RemoteLeasedConnection, RemoteReleaseInfo } from "./remote-browser.mjs";
4
+ import { Logger } from "../logger.mjs";
5
+ /** Options of {@link refCounted}. */
6
+ interface RefCountedOptions<TResource> {
7
+ /**
8
+ * Start the resource (wake the computer) and return what connections need of it. Called by the
9
+ * first connection; concurrent first connections share the call. Must be idempotent: it runs
10
+ * again after a failed `close`, and may overlap a `close` that timed out. `signal` aborts when
11
+ * every waiting connection was cancelled, and at shutdown: clean up after yourself when it does,
12
+ * or throw an error with a `resource` property so the pool closes it.
13
+ */
14
+ open: (context: RemoteConnectContext, signal: AbortSignal) => Promise<TResource>;
15
+ /** Stop the resource (suspend the computer). */
16
+ close: (resource: TResource) => Promise<void> | void;
17
+ /**
18
+ * How long the resource stays open with no connection, in ms. Required: set it longer than the
19
+ * gap between a build's passes (the extract and tests pre-passes and its shards connect close
20
+ * together), for example 120 000.
21
+ */
22
+ idleMs: number;
23
+ /** How long `close` may take in ms before it counts as failed (default 30 000). */
24
+ closeTimeoutMs?: number | undefined;
25
+ /** Where close failures go (error class only, never the message). */
26
+ logger?: Logger | undefined;
27
+ /**
28
+ * Runs `run` after `ms` and returns what cancels it (default: an unref'd `setTimeout`). A seam
29
+ * for tests to drive the idle timer without real time.
30
+ */
31
+ schedule?: ((run: () => void, ms: number) => () => void) | undefined;
32
+ }
33
+ /** What the pool hands to `release`: an opaque token for one connection. */
34
+ interface PoolLease {
35
+ readonly pool: "refCounted";
36
+ }
37
+ /** A reference-counted resource, wired into a service's `connect` and `release`. */
38
+ interface RefCounted<TResource> {
39
+ /** Build the service's `connect`: `build` gets the shared resource and returns the connection. */
40
+ connect(build: (resource: TResource, context: RemoteConnectContext) => RemoteConnection | Promise<RemoteConnection>): (context: RemoteConnectContext) => Promise<RemoteLeasedConnection<PoolLease>>;
41
+ /** The service's `release`. */
42
+ release(lease: PoolLease, info: RemoteReleaseInfo): Promise<void>;
43
+ /** Close now: abort a pending `open`, then close the resource. Runs at shutdown. */
44
+ flush(): Promise<void>;
45
+ /** Unregister from shutdown and drop the idle timer, without closing (for tests). */
46
+ dispose(): void;
47
+ }
48
+ /**
49
+ * Share one resource between connections: opened by the first, closed `idleMs` after the last
50
+ * was released (or at once on shutdown). A connection arriving while it closes waits, then opens it
51
+ * again. After a failed `close` the resource is `unknown`: the next connection opens it again and
52
+ * the idle timer retries the close.
53
+ */
54
+ declare function refCounted<TResource>(options: RefCountedOptions<TResource>): RefCounted<TResource>;
55
+ export { PoolLease, RefCounted, RefCountedOptions, refCounted };
56
+ //# sourceMappingURL=remote-pool.d.mts.map
@@ -0,0 +1,195 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ import { onRemoteShutdown } from "./remote-lease-shutdown.mjs";
5
+ // src/adapters/remote-pool.ts
6
+ /**
7
+ * Share one resource between connections: opened by the first, closed `idleMs` after the last
8
+ * was released (or at once on shutdown). A connection arriving while it closes waits, then opens it
9
+ * again. After a failed `close` the resource is `unknown`: the next connection opens it again and
10
+ * the idle timer retries the close.
11
+ */
12
+ function refCounted(options) {
13
+ const pool = new Pool(options);
14
+ const unregister = onRemoteShutdown(async () => {
15
+ await pool.flush();
16
+ });
17
+ return {
18
+ connect: (build) => async (context) => {
19
+ const slot = await pool.take(context);
20
+ try {
21
+ return {
22
+ ...await build(slot.resource, context),
23
+ lease: slot
24
+ };
25
+ } catch (error) {
26
+ pool.give(slot);
27
+ throw error;
28
+ }
29
+ },
30
+ release: async (lease) => {
31
+ pool.give(lease);
32
+ await Promise.resolve();
33
+ },
34
+ flush: async () => {
35
+ await pool.flush();
36
+ },
37
+ dispose: () => {
38
+ unregister();
39
+ pool.dispose();
40
+ }
41
+ };
42
+ }
43
+ var Pool = class {
44
+ #state = "idle";
45
+ #slot;
46
+ #stale;
47
+ #hasStale = false;
48
+ #cancelIdle;
49
+ #opening;
50
+ #openController;
51
+ #waiters = /* @__PURE__ */ new Set();
52
+ #closing;
53
+ #options;
54
+ constructor(options) {
55
+ this.#options = options;
56
+ }
57
+ /** A connection's share of the resource, opening it first when nobody has. */
58
+ async take(context) {
59
+ for (;;) if (this.#state === "closing") await this.#closing?.catch(() => {});
60
+ else if (this.#state === "opening") await this.#waitOpen(context.signal);
61
+ else if (this.#state === "open" && this.#slot !== void 0) {
62
+ this.#clearIdle();
63
+ this.#slot.count += 1;
64
+ return this.#slot;
65
+ } else this.#startOpen(context);
66
+ }
67
+ /** A connection is done with its slot; the last one starts the idle timer. */
68
+ give(slot) {
69
+ slot.count = Math.max(0, slot.count - 1);
70
+ if (slot === this.#slot && slot.count === 0 && this.#state === "open") this.#armIdle();
71
+ }
72
+ /** Close now, aborting a pending open first. */
73
+ async flush() {
74
+ this.#clearIdle();
75
+ this.#openController?.abort();
76
+ await this.#opening?.catch(() => {});
77
+ if (this.#state === "closing") await this.#closing?.catch(() => {});
78
+ if (this.#state === "open" || this.#state === "unknown") await this.#close();
79
+ }
80
+ dispose() {
81
+ this.#clearIdle();
82
+ }
83
+ #startOpen(context) {
84
+ this.#clearIdle();
85
+ const controller = new AbortController();
86
+ this.#openController = controller;
87
+ this.#state = "opening";
88
+ this.#opening = this.#options.open(context, controller.signal).then((resource) => {
89
+ this.#slot = {
90
+ resource,
91
+ count: 0
92
+ };
93
+ this.#hasStale = false;
94
+ this.#stale = void 0;
95
+ this.#state = "open";
96
+ }, async (error) => {
97
+ this.#state = this.#hasStale ? "unknown" : "idle";
98
+ if (this.#hasStale) this.#armIdle();
99
+ await this.#closeLeftover(error);
100
+ throw error;
101
+ }).finally(() => {
102
+ this.#openController = void 0;
103
+ this.#waiters.clear();
104
+ });
105
+ this.#opening.catch(() => {});
106
+ this.#waitOpen(context.signal).catch(() => {});
107
+ }
108
+ /** Wait for the open in flight; it is aborted once every waiter has given up. */
109
+ async #waitOpen(signal) {
110
+ this.#waiters.add(signal);
111
+ const onAbort = () => {
112
+ this.#waiters.delete(signal);
113
+ if (this.#waiters.size === 0) this.#openController?.abort();
114
+ };
115
+ signal.addEventListener("abort", onAbort, { once: true });
116
+ try {
117
+ await this.#opening;
118
+ } finally {
119
+ signal.removeEventListener("abort", onAbort);
120
+ }
121
+ }
122
+ #armIdle() {
123
+ this.#clearIdle();
124
+ const schedule = this.#options.schedule ?? scheduleTimeout;
125
+ this.#cancelIdle = schedule(() => {
126
+ if ((this.#slot?.count ?? 0) === 0) this.#close().catch(() => {});
127
+ }, this.#options.idleMs);
128
+ }
129
+ #clearIdle() {
130
+ this.#cancelIdle?.();
131
+ this.#cancelIdle = void 0;
132
+ }
133
+ async #close() {
134
+ const resource = this.#slot?.resource ?? this.#stale;
135
+ if (resource === void 0 || this.#state === "closing") return;
136
+ this.#state = "closing";
137
+ this.#closing = this.#runClose(resource);
138
+ await this.#closing;
139
+ }
140
+ async #runClose(resource) {
141
+ try {
142
+ await timed(async () => {
143
+ await this.#options.close(resource);
144
+ }, this.#options.closeTimeoutMs ?? 3e4);
145
+ this.#settle("idle");
146
+ } catch (error) {
147
+ this.#settle("unknown", resource);
148
+ this.#options.logger?.warn({ err: error instanceof Error ? error.name : "Error" }, "remote pool close failed, will retry after the idle period");
149
+ this.#armIdle();
150
+ }
151
+ }
152
+ /** After a close: `idle` with nothing, or `unknown` holding the resource to close again. */
153
+ #settle(state, stale) {
154
+ this.#slot = void 0;
155
+ this.#stale = stale;
156
+ this.#hasStale = state === "unknown";
157
+ this.#state = state;
158
+ }
159
+ /** An `open` that failed after starting something may name it, so it is not left running. */
160
+ async #closeLeftover(error) {
161
+ const leftover = error?.resource;
162
+ if (leftover === void 0) return;
163
+ try {
164
+ await timed(async () => {
165
+ await this.#options.close(leftover);
166
+ }, this.#options.closeTimeoutMs ?? 3e4);
167
+ } catch (closeError) {
168
+ this.#options.logger?.warn({ err: closeError instanceof Error ? closeError.name : "Error" }, "remote pool close failed after a failed open");
169
+ }
170
+ }
171
+ };
172
+ /** The default scheduler: an unref'd timer, so an idle pool never keeps the process alive. */
173
+ function scheduleTimeout(run, ms) {
174
+ const timer = setTimeout(run, ms);
175
+ timer.unref();
176
+ return () => {
177
+ clearTimeout(timer);
178
+ };
179
+ }
180
+ async function timed(run, ms) {
181
+ let timer;
182
+ const deadline = new Promise((_, reject) => {
183
+ timer = setTimeout(() => {
184
+ reject(Object.assign(/* @__PURE__ */ new Error(`close timed out after ${ms} ms`), { name: "CloseTimeout" }));
185
+ }, ms);
186
+ });
187
+ try {
188
+ await Promise.race([run(), deadline]);
189
+ } finally {
190
+ clearTimeout(timer);
191
+ }
192
+ }
193
+ export { refCounted };
194
+
195
+ //# sourceMappingURL=remote-pool.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"remote-pool.mjs","names":[],"sources":["../../src/adapters/remote-pool.ts"],"sourcesContent":["/**\n * A shared resource behind a remote service, opened by the first connection and closed once the\n * last one has been idle for a while: a computer woken for the builds' shards and suspended after\n * (ADR 0007, decision 4). One pool per service; its connections share the resource.\n */\nimport type { Logger } from \"../logger.ts\";\nimport type {\n RemoteConnectContext,\n RemoteConnection,\n RemoteLeasedConnection,\n RemoteReleaseInfo,\n} from \"./remote-browser.ts\";\nimport { onRemoteShutdown } from \"./remote-lease.ts\";\n\n/** Options of {@link refCounted}. */\nexport interface RefCountedOptions<TResource> {\n /**\n * Start the resource (wake the computer) and return what connections need of it. Called by the\n * first connection; concurrent first connections share the call. Must be idempotent: it runs\n * again after a failed `close`, and may overlap a `close` that timed out. `signal` aborts when\n * every waiting connection was cancelled, and at shutdown: clean up after yourself when it does,\n * or throw an error with a `resource` property so the pool closes it.\n */\n open: (context: RemoteConnectContext, signal: AbortSignal) => Promise<TResource>;\n /** Stop the resource (suspend the computer). */\n close: (resource: TResource) => Promise<void> | void;\n /**\n * How long the resource stays open with no connection, in ms. Required: set it longer than the\n * gap between a build's passes (the extract and tests pre-passes and its shards connect close\n * together), for example 120 000.\n */\n idleMs: number;\n /** How long `close` may take in ms before it counts as failed (default 30 000). */\n closeTimeoutMs?: number | undefined;\n /** Where close failures go (error class only, never the message). */\n logger?: Logger | undefined;\n /**\n * Runs `run` after `ms` and returns what cancels it (default: an unref'd `setTimeout`). A seam\n * for tests to drive the idle timer without real time.\n */\n schedule?: ((run: () => void, ms: number) => () => void) | undefined;\n}\n\n/** What the pool hands to `release`: an opaque token for one connection. */\nexport interface PoolLease {\n readonly pool: \"refCounted\";\n}\n\n/** A reference-counted resource, wired into a service's `connect` and `release`. */\nexport interface RefCounted<TResource> {\n /** Build the service's `connect`: `build` gets the shared resource and returns the connection. */\n connect(\n build: (\n resource: TResource,\n context: RemoteConnectContext,\n ) => RemoteConnection | Promise<RemoteConnection>,\n ): (context: RemoteConnectContext) => Promise<RemoteLeasedConnection<PoolLease>>;\n /** The service's `release`. */\n release(lease: PoolLease, info: RemoteReleaseInfo): Promise<void>;\n /** Close now: abort a pending `open`, then close the resource. Runs at shutdown. */\n flush(): Promise<void>;\n /** Unregister from shutdown and drop the idle timer, without closing (for tests). */\n dispose(): void;\n}\n\ntype State = \"idle\" | \"opening\" | \"open\" | \"closing\" | \"unknown\";\n\ninterface Slot<TResource> {\n resource: TResource;\n count: number;\n}\n\n/**\n * Share one resource between connections: opened by the first, closed `idleMs` after the last\n * was released (or at once on shutdown). A connection arriving while it closes waits, then opens it\n * again. After a failed `close` the resource is `unknown`: the next connection opens it again and\n * the idle timer retries the close.\n */\nexport function refCounted<TResource>(\n options: RefCountedOptions<TResource>,\n): RefCounted<TResource> {\n const pool = new Pool(options);\n const unregister = onRemoteShutdown(async () => {\n await pool.flush();\n });\n return {\n connect: (build) => async (context) => {\n const slot = await pool.take(context);\n try {\n const connection = await build(slot.resource, context);\n return { ...connection, lease: slot as unknown as PoolLease };\n } catch (error) {\n pool.give(slot);\n throw error;\n }\n },\n release: async (lease) => {\n pool.give(lease as unknown as Slot<TResource>);\n await Promise.resolve();\n },\n flush: async () => {\n await pool.flush();\n },\n dispose: () => {\n unregister();\n pool.dispose();\n },\n };\n}\n\nclass Pool<TResource> {\n #state: State = \"idle\";\n #slot: Slot<TResource> | undefined;\n #stale: TResource | undefined;\n #hasStale = false;\n #cancelIdle: (() => void) | undefined;\n #opening: Promise<void> | undefined;\n #openController: AbortController | undefined;\n #waiters = new Set<AbortSignal>();\n #closing: Promise<void> | undefined;\n readonly #options: RefCountedOptions<TResource>;\n\n constructor(options: RefCountedOptions<TResource>) {\n this.#options = options;\n }\n\n /** A connection's share of the resource, opening it first when nobody has. */\n async take(context: RemoteConnectContext): Promise<Slot<TResource>> {\n for (;;) {\n if (this.#state === \"closing\") {\n // oxlint-disable-next-line eslint/no-await-in-loop -- a connection waits for the close to end\n await this.#closing?.catch(() => {});\n } else if (this.#state === \"opening\") {\n // oxlint-disable-next-line eslint/no-await-in-loop -- waiters share the one open\n await this.#waitOpen(context.signal);\n } else if (this.#state === \"open\" && this.#slot !== undefined) {\n this.#clearIdle();\n this.#slot.count += 1;\n return this.#slot;\n } else {\n this.#startOpen(context);\n }\n }\n }\n\n /** A connection is done with its slot; the last one starts the idle timer. */\n give(slot: Slot<TResource>): void {\n slot.count = Math.max(0, slot.count - 1);\n if (slot === this.#slot && slot.count === 0 && this.#state === \"open\") {\n this.#armIdle();\n }\n }\n\n /** Close now, aborting a pending open first. */\n async flush(): Promise<void> {\n this.#clearIdle();\n this.#openController?.abort();\n await this.#opening?.catch(() => {});\n if (this.#state === \"closing\") {\n await this.#closing?.catch(() => {});\n }\n if (this.#state === \"open\" || this.#state === \"unknown\") {\n await this.#close();\n }\n }\n\n dispose(): void {\n this.#clearIdle();\n }\n\n #startOpen(context: RemoteConnectContext): void {\n this.#clearIdle();\n const controller = new AbortController();\n this.#openController = controller;\n this.#state = \"opening\";\n this.#opening = this.#options\n .open(context, controller.signal)\n .then(\n (resource) => {\n this.#slot = { resource, count: 0 };\n this.#hasStale = false;\n this.#stale = undefined;\n this.#state = \"open\";\n },\n async (error: unknown) => {\n this.#state = this.#hasStale ? \"unknown\" : \"idle\";\n if (this.#hasStale) {\n // Reopening cancelled the retry of the failed close; it is still owed.\n this.#armIdle();\n }\n await this.#closeLeftover(error);\n throw error;\n },\n )\n .finally(() => {\n this.#openController = undefined;\n this.#waiters.clear();\n });\n // The first waiter is the caller that started it.\n this.#opening.catch(() => {});\n this.#waitOpen(context.signal).catch(() => {});\n }\n\n /** Wait for the open in flight; it is aborted once every waiter has given up. */\n async #waitOpen(signal: AbortSignal): Promise<void> {\n this.#waiters.add(signal);\n const onAbort = (): void => {\n this.#waiters.delete(signal);\n if (this.#waiters.size === 0) {\n this.#openController?.abort();\n }\n };\n signal.addEventListener(\"abort\", onAbort, { once: true });\n try {\n await this.#opening;\n } finally {\n signal.removeEventListener(\"abort\", onAbort);\n }\n }\n\n #armIdle(): void {\n this.#clearIdle();\n const schedule = this.#options.schedule ?? scheduleTimeout;\n this.#cancelIdle = schedule(() => {\n if ((this.#slot?.count ?? 0) === 0) {\n this.#close().catch(() => {});\n }\n }, this.#options.idleMs);\n }\n\n #clearIdle(): void {\n this.#cancelIdle?.();\n this.#cancelIdle = undefined;\n }\n\n async #close(): Promise<void> {\n const resource = this.#slot?.resource ?? this.#stale;\n if (resource === undefined || this.#state === \"closing\") {\n return;\n }\n this.#state = \"closing\";\n this.#closing = this.#runClose(resource);\n await this.#closing;\n }\n\n async #runClose(resource: TResource): Promise<void> {\n try {\n await timed(async () => {\n await this.#options.close(resource);\n }, this.#options.closeTimeoutMs ?? 30_000);\n this.#settle(\"idle\");\n } catch (error) {\n // The suspend may have worked on the server: keep the reference, but trust nothing about it.\n this.#settle(\"unknown\", resource);\n this.#options.logger?.warn(\n { err: error instanceof Error ? error.name : \"Error\" },\n \"remote pool close failed, will retry after the idle period\",\n );\n this.#armIdle();\n }\n }\n\n /** After a close: `idle` with nothing, or `unknown` holding the resource to close again. */\n #settle(state: \"idle\" | \"unknown\", stale?: TResource): void {\n this.#slot = undefined;\n this.#stale = stale;\n this.#hasStale = state === \"unknown\";\n this.#state = state;\n }\n\n /** An `open` that failed after starting something may name it, so it is not left running. */\n async #closeLeftover(error: unknown): Promise<void> {\n const leftover = (error as { resource?: TResource } | null)?.resource;\n if (leftover === undefined) {\n return;\n }\n try {\n await timed(async () => {\n await this.#options.close(leftover);\n }, this.#options.closeTimeoutMs ?? 30_000);\n } catch (closeError) {\n this.#options.logger?.warn(\n { err: closeError instanceof Error ? closeError.name : \"Error\" },\n \"remote pool close failed after a failed open\",\n );\n }\n }\n}\n\n/** The default scheduler: an unref'd timer, so an idle pool never keeps the process alive. */\nfunction scheduleTimeout(run: () => void, ms: number): () => void {\n const timer = setTimeout(run, ms);\n timer.unref();\n return () => {\n clearTimeout(timer);\n };\n}\n\nasync function timed(run: () => Promise<void>, ms: number): Promise<void> {\n let timer: NodeJS.Timeout | undefined;\n const deadline = new Promise<never>((_, reject) => {\n timer = setTimeout(() => {\n reject(Object.assign(new Error(`close timed out after ${ms} ms`), { name: \"CloseTimeout\" }));\n }, ms);\n });\n try {\n await Promise.race([run(), deadline]);\n } finally {\n clearTimeout(timer);\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ import __tsdown_shims_path from 'node:path';
2
+ import __tsdown_shims_url from 'node:url';
3
+ /** In-flight render calls of a capture runner, so `cancel` reaches exactly the runs it names. */
4
+ /**
5
+ * Every render call a runner has in flight, by build and run id.
6
+ *
7
+ * Several renders of one build can run at once in one process (the shards of a sharded build), and
8
+ * even two with the same run id (a redelivered shard). Each call registers its own entry and
9
+ * removes only that entry when it ends, so no render ever drops another's handle.
10
+ */
11
+ declare class RenderRuns<T> {
12
+ #private;
13
+ /**
14
+ * Register a render call. Returns the function that removes it again (call it in `finally`).
15
+ *
16
+ * @param runId - The request's run id; a render without one is keyed by its build id.
17
+ */
18
+ add(buildId: string, runId: string | undefined, run: T): () => void;
19
+ /** The runs `cancel(buildId, runId)` should stop: every run of the build without a run id. */
20
+ matching(buildId: string, runId?: string): T[];
21
+ }
22
+ export { RenderRuns };
23
+ //# sourceMappingURL=render-runs.d.mts.map