@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.
- package/README.md +49 -32
- package/dist/adapter/capture-queue.d.mts +44 -4
- package/dist/adapter/capture-runner.d.mts +134 -12
- package/dist/adapter/capture-runner.mjs +9 -12
- package/dist/adapter/database.d.mts +89 -10
- package/dist/adapter/database.mjs +4 -3
- package/dist/adapter/database.mjs.map +1 -1
- package/dist/adapter/design.d.mts +83 -0
- package/dist/adapter/design.mjs +27 -0
- package/dist/adapter/design.mjs.map +1 -0
- package/dist/adapter/email-sender.d.mts +0 -2
- package/dist/adapter/metadata.d.mts +3 -12
- package/dist/adapter/notifier.d.mts +6 -3
- package/dist/adapter/notifier.mjs +5 -1
- package/dist/adapter/oauth.d.mts +50 -0
- package/dist/adapter/setup.d.mts +4 -7
- package/dist/adapter/setup.mjs +3 -3
- package/dist/adapter/setup.mjs.map +1 -1
- package/dist/adapter/storage.d.mts +1 -3
- package/dist/adapter/webhook-events.d.mts +0 -2
- package/dist/adapter/webhook-events.mjs +13 -14
- package/dist/adapter/webhook-events.mjs.map +1 -1
- package/dist/adapters/capture-target-offers.d.mts +26 -0
- package/dist/adapters/capture-target-offers.mjs +57 -0
- package/dist/adapters/capture-target-offers.mjs.map +1 -0
- package/dist/adapters/capture-target.d.mts +69 -0
- package/dist/adapters/capture-target.mjs +75 -0
- package/dist/adapters/capture-target.mjs.map +1 -0
- package/dist/adapters/notifier/build-comment.d.mts +22 -0
- package/dist/adapters/notifier/build-comment.mjs +23 -0
- package/dist/adapters/notifier/build-comment.mjs.map +1 -0
- package/dist/adapters/notifier/build-state.d.mts +97 -0
- package/dist/adapters/notifier/build-state.mjs +217 -0
- package/dist/adapters/notifier/build-state.mjs.map +1 -0
- package/dist/adapters/notifier/events.d.mts +4 -4
- package/dist/adapters/notifier/events.mjs +32 -20
- package/dist/adapters/notifier/events.mjs.map +1 -1
- package/dist/adapters/notifier/format.d.mts +0 -2
- package/dist/adapters/notifier/format.mjs +1 -2
- package/dist/adapters/notifier/provider.d.mts +53 -9
- package/dist/adapters/notifier/provider.mjs +11 -0
- package/dist/adapters/notifier/provider.mjs.map +1 -0
- package/dist/adapters/notifier/state.d.mts +22 -0
- package/dist/adapters/notifier/state.mjs +65 -0
- package/dist/adapters/notifier/state.mjs.map +1 -0
- package/dist/adapters/notifier/types.d.mts +51 -4
- package/dist/adapters/remote-browser.d.mts +168 -0
- package/dist/adapters/remote-browser.mjs +201 -0
- package/dist/adapters/remote-browser.mjs.map +1 -0
- package/dist/adapters/remote-lease-attempt.mjs +39 -0
- package/dist/adapters/remote-lease-attempt.mjs.map +1 -0
- package/dist/adapters/remote-lease-error.d.mts +25 -0
- package/dist/adapters/remote-lease-error.mjs +54 -0
- package/dist/adapters/remote-lease-error.mjs.map +1 -0
- package/dist/adapters/remote-lease-held.d.mts +15 -0
- package/dist/adapters/remote-lease-held.mjs +86 -0
- package/dist/adapters/remote-lease-held.mjs.map +1 -0
- package/dist/adapters/remote-lease-shutdown.d.mts +12 -0
- package/dist/adapters/remote-lease-shutdown.mjs +32 -0
- package/dist/adapters/remote-lease-shutdown.mjs.map +1 -0
- package/dist/adapters/remote-lease.d.mts +39 -0
- package/dist/adapters/remote-lease.mjs +175 -0
- package/dist/adapters/remote-lease.mjs.map +1 -0
- package/dist/adapters/remote-limiter.mjs +121 -0
- package/dist/adapters/remote-limiter.mjs.map +1 -0
- package/dist/adapters/remote-pool.d.mts +56 -0
- package/dist/adapters/remote-pool.mjs +195 -0
- package/dist/adapters/remote-pool.mjs.map +1 -0
- package/dist/adapters/render-runs.d.mts +23 -0
- package/dist/adapters/render-runs.mjs +40 -0
- package/dist/adapters/render-runs.mjs.map +1 -0
- package/dist/adapters/setup-runner.d.mts +0 -2
- package/dist/adapters/setup-runner.mjs +3 -6
- package/dist/adapters/setup-runner.mjs.map +1 -1
- package/dist/adapters/sources.d.mts +10 -0
- package/dist/adapters/sources.mjs +19 -0
- package/dist/adapters/sources.mjs.map +1 -0
- package/dist/adapters/validate.d.mts +0 -2
- package/dist/adapters/validate.mjs +27 -20
- package/dist/adapters/validate.mjs.map +1 -1
- package/dist/ai.d.mts +103 -0
- package/dist/ai.mjs +18 -0
- package/dist/ai.mjs.map +1 -0
- package/dist/auth.d.mts +0 -2
- package/dist/auth.mjs +1 -2
- package/dist/brand-packs.d.mts +0 -2
- package/dist/brand-packs.mjs +0 -1
- package/dist/capture/a11y/axe-source.d.mts +16 -0
- package/dist/capture/a11y/axe-source.mjs +36 -0
- package/dist/capture/a11y/axe-source.mjs.map +1 -0
- package/dist/capture/a11y/in-page.d.mts +25 -0
- package/dist/capture/a11y/in-page.mjs +59 -0
- package/dist/capture/a11y/in-page.mjs.map +1 -0
- package/dist/capture/a11y/map.d.mts +22 -0
- package/dist/capture/a11y/map.mjs +47 -0
- package/dist/capture/a11y/map.mjs.map +1 -0
- package/dist/capture/a11y/options.d.mts +21 -0
- package/dist/capture/a11y/options.mjs +74 -0
- package/dist/capture/a11y/options.mjs.map +1 -0
- package/dist/capture/adapter.d.mts +46 -5
- package/dist/capture/adapter.mjs +1 -2
- package/dist/capture/adapter.mjs.map +1 -1
- package/dist/capture/affected.d.mts +2 -2
- package/dist/capture/affected.mjs +14 -6
- package/dist/capture/affected.mjs.map +1 -1
- package/dist/capture/attempt-logger.mjs +83 -0
- package/dist/capture/attempt-logger.mjs.map +1 -0
- package/dist/capture/browsers.d.mts +33 -0
- package/dist/capture/browsers.mjs +66 -0
- package/dist/capture/browsers.mjs.map +1 -0
- package/dist/capture/build-state/deliver.d.mts +30 -0
- package/dist/capture/build-state/deliver.mjs +117 -0
- package/dist/capture/build-state/deliver.mjs.map +1 -0
- package/dist/capture/build-state/index.d.mts +8 -0
- package/dist/capture/build-state/notify-job.d.mts +54 -0
- package/dist/capture/build-state/notify-job.mjs +132 -0
- package/dist/capture/build-state/notify-job.mjs.map +1 -0
- package/dist/capture/build-state/publisher.d.mts +21 -0
- package/dist/capture/build-state/publisher.mjs +106 -0
- package/dist/capture/build-state/publisher.mjs.map +1 -0
- package/dist/capture/build-state/resume.d.mts +7 -0
- package/dist/capture/build-state/resume.mjs +46 -0
- package/dist/capture/build-state/resume.mjs.map +1 -0
- package/dist/capture/build-state/types.d.mts +47 -0
- package/dist/capture/catalog/catalogue.d.mts +20 -0
- package/dist/capture/catalog/catalogue.mjs +52 -0
- package/dist/capture/catalog/catalogue.mjs.map +1 -0
- package/dist/capture/catalog/components.d.mts +21 -0
- package/dist/capture/catalog/components.mjs +54 -0
- package/dist/capture/catalog/components.mjs.map +1 -0
- package/dist/capture/catalog/history.d.mts +28 -0
- package/dist/capture/catalog/history.mjs +50 -0
- package/dist/capture/catalog/history.mjs.map +1 -0
- package/dist/capture/catalog/hook.d.mts +12 -0
- package/dist/capture/catalog/hook.mjs +18 -0
- package/dist/capture/catalog/hook.mjs.map +1 -0
- package/dist/capture/catalog/index.d.mts +11 -0
- package/dist/capture/catalog/load.d.mts +24 -0
- package/dist/capture/catalog/load.mjs +48 -0
- package/dist/capture/catalog/load.mjs.map +1 -0
- package/dist/capture/catalog/parse.d.mts +13 -0
- package/dist/capture/catalog/parse.mjs +55 -0
- package/dist/capture/catalog/parse.mjs.map +1 -0
- package/dist/capture/catalog/read.d.mts +15 -0
- package/dist/capture/catalog/read.mjs +39 -0
- package/dist/capture/catalog/read.mjs.map +1 -0
- package/dist/capture/catalog/types.d.mts +31 -0
- package/dist/capture/comparison-reference.d.mts +27 -0
- package/dist/capture/comparison-reference.mjs +75 -0
- package/dist/capture/comparison-reference.mjs.map +1 -0
- package/dist/capture/consumer/capture-handler.d.mts +56 -0
- package/dist/capture/consumer/capture-handler.mjs +117 -0
- package/dist/capture/consumer/capture-handler.mjs.map +1 -0
- package/dist/capture/consumer/idle.d.mts +15 -0
- package/dist/capture/consumer/idle.mjs +24 -0
- package/dist/capture/consumer/idle.mjs.map +1 -0
- package/dist/capture/consumer/index.d.mts +11 -0
- package/dist/capture/consumer/index.mjs +138 -0
- package/dist/capture/consumer/index.mjs.map +1 -0
- package/dist/capture/consumer/loop.mjs +109 -0
- package/dist/capture/consumer/loop.mjs.map +1 -0
- package/dist/capture/consumer/metrics.d.mts +39 -0
- package/dist/capture/consumer/metrics.mjs +37 -0
- package/dist/capture/consumer/metrics.mjs.map +1 -0
- package/dist/capture/consumer/retry.d.mts +31 -0
- package/dist/capture/consumer/retry.mjs +33 -0
- package/dist/capture/consumer/retry.mjs.map +1 -0
- package/dist/capture/consumer/run-job.mjs +194 -0
- package/dist/capture/consumer/run-job.mjs.map +1 -0
- package/dist/capture/consumer/startup.mjs +37 -0
- package/dist/capture/consumer/startup.mjs.map +1 -0
- package/dist/capture/consumer/types.d.mts +95 -0
- package/dist/capture/crash-failure.d.mts +23 -0
- package/dist/capture/crash-failure.mjs +69 -0
- package/dist/capture/crash-failure.mjs.map +1 -0
- package/dist/capture/db-queue/cadence.d.mts +30 -0
- package/dist/capture/db-queue/cadence.mjs +37 -0
- package/dist/capture/db-queue/cadence.mjs.map +1 -0
- package/dist/capture/db-queue/claim.d.mts +7 -0
- package/dist/capture/db-queue/claim.mjs +148 -0
- package/dist/capture/db-queue/claim.mjs.map +1 -0
- package/dist/capture/db-queue/clock.mjs +11 -0
- package/dist/capture/db-queue/clock.mjs.map +1 -0
- package/dist/capture/db-queue/codec.mjs +75 -0
- package/dist/capture/db-queue/codec.mjs.map +1 -0
- package/dist/capture/db-queue/index.d.mts +18 -0
- package/dist/capture/db-queue/index.mjs +89 -0
- package/dist/capture/db-queue/index.mjs.map +1 -0
- package/dist/capture/db-queue/keeper.d.mts +34 -0
- package/dist/capture/db-queue/keeper.mjs +91 -0
- package/dist/capture/db-queue/keeper.mjs.map +1 -0
- package/dist/capture/db-queue/lease.mjs +45 -0
- package/dist/capture/db-queue/lease.mjs.map +1 -0
- package/dist/capture/db-queue/lifecycle.mjs +35 -0
- package/dist/capture/db-queue/lifecycle.mjs.map +1 -0
- package/dist/capture/db-queue/operations.mjs +93 -0
- package/dist/capture/db-queue/operations.mjs.map +1 -0
- package/dist/capture/db-queue/poll-loop.d.mts +28 -0
- package/dist/capture/db-queue/poll-loop.mjs +88 -0
- package/dist/capture/db-queue/poll-loop.mjs.map +1 -0
- package/dist/capture/db-queue/poll.d.mts +8 -0
- package/dist/capture/db-queue/poll.mjs +68 -0
- package/dist/capture/db-queue/poll.mjs.map +1 -0
- package/dist/capture/db-queue/timers.d.mts +10 -0
- package/dist/capture/db-queue/timers.mjs +36 -0
- package/dist/capture/db-queue/timers.mjs.map +1 -0
- package/dist/capture/db-queue/types.d.mts +81 -0
- package/dist/capture/db-queue/types.mjs +11 -0
- package/dist/capture/db-queue/types.mjs.map +1 -0
- package/dist/capture/db-queue/wake.mjs +47 -0
- package/dist/capture/db-queue/wake.mjs.map +1 -0
- package/dist/capture/design.mjs +38 -0
- package/dist/capture/design.mjs.map +1 -0
- package/dist/capture/diff-record.mjs +24 -5
- package/dist/capture/diff-record.mjs.map +1 -1
- package/dist/capture/dispatch.d.mts +13 -10
- package/dist/capture/dispatch.mjs +102 -111
- package/dist/capture/dispatch.mjs.map +1 -1
- package/dist/capture/enrich.mjs +59 -0
- package/dist/capture/enrich.mjs.map +1 -0
- package/dist/capture/failure-sets.mjs +21 -0
- package/dist/capture/failure-sets.mjs.map +1 -0
- package/dist/capture/fingerprint.d.mts +40 -0
- package/dist/capture/fingerprint.mjs +82 -0
- package/dist/capture/fingerprint.mjs.map +1 -0
- package/dist/capture/forked/channel.d.mts +23 -0
- package/dist/capture/forked/child.d.mts +32 -0
- package/dist/capture/forked/child.mjs +201 -0
- package/dist/capture/forked/child.mjs.map +1 -0
- package/dist/capture/forked/crash-tracker.mjs +22 -0
- package/dist/capture/forked/crash-tracker.mjs.map +1 -0
- package/dist/capture/forked/decision.d.mts +24 -0
- package/dist/capture/forked/decision.mjs +26 -0
- package/dist/capture/forked/decision.mjs.map +1 -0
- package/dist/capture/forked/exit.mjs +11 -0
- package/dist/capture/forked/exit.mjs.map +1 -0
- package/dist/capture/forked/fork.d.mts +29 -0
- package/dist/capture/forked/fork.mjs +92 -0
- package/dist/capture/forked/fork.mjs.map +1 -0
- package/dist/capture/forked/forked-capture.d.mts +38 -0
- package/dist/capture/forked/forked-capture.mjs +239 -0
- package/dist/capture/forked/forked-capture.mjs.map +1 -0
- package/dist/capture/forked/lease-book.mjs +45 -0
- package/dist/capture/forked/lease-book.mjs.map +1 -0
- package/dist/capture/forked/protocol.d.mts +36 -0
- package/dist/capture/forked/protocol.mjs +24 -0
- package/dist/capture/forked/protocol.mjs.map +1 -0
- package/dist/capture/forked/runs.mjs +74 -0
- package/dist/capture/forked/runs.mjs.map +1 -0
- package/dist/capture/guards.mjs +1 -2
- package/dist/capture/ignore-regions.d.mts +36 -0
- package/dist/capture/ignore-regions.mjs +65 -0
- package/dist/capture/ignore-regions.mjs.map +1 -0
- package/dist/capture/isolate.mjs +140 -0
- package/dist/capture/isolate.mjs.map +1 -0
- package/dist/capture/job-lease.d.mts +26 -0
- package/dist/capture/job-lease.mjs +56 -0
- package/dist/capture/job-lease.mjs.map +1 -0
- package/dist/capture/matrix.mjs +298 -0
- package/dist/capture/matrix.mjs.map +1 -0
- package/dist/capture/modes.d.mts +23 -0
- package/dist/capture/modes.mjs +106 -0
- package/dist/capture/modes.mjs.map +1 -0
- package/dist/capture/orchestrator.d.mts +37 -7
- package/dist/capture/orchestrator.mjs +120 -110
- package/dist/capture/orchestrator.mjs.map +1 -1
- package/dist/capture/pipeline.d.mts +9 -4
- package/dist/capture/pipeline.mjs +284 -48
- package/dist/capture/pipeline.mjs.map +1 -1
- package/dist/capture/progress.d.mts +9 -0
- package/dist/capture/progress.mjs +27 -0
- package/dist/capture/progress.mjs.map +1 -0
- package/dist/capture/publish/accept.d.mts +37 -0
- package/dist/capture/publish/accept.mjs +27 -0
- package/dist/capture/publish/accept.mjs.map +1 -0
- package/dist/capture/publish/complete.d.mts +44 -0
- package/dist/capture/publish/complete.mjs +63 -0
- package/dist/capture/publish/complete.mjs.map +1 -0
- package/dist/capture/publish/extract.d.mts +18 -0
- package/dist/capture/publish/extract.mjs +120 -0
- package/dist/capture/publish/extract.mjs.map +1 -0
- package/dist/capture/publish/handler.d.mts +26 -0
- package/dist/capture/publish/handler.mjs +61 -0
- package/dist/capture/publish/handler.mjs.map +1 -0
- package/dist/capture/publish/index.d.mts +11 -0
- package/dist/capture/publish/invalid-zip.d.mts +8 -0
- package/dist/capture/publish/invalid-zip.mjs +11 -0
- package/dist/capture/publish/invalid-zip.mjs.map +1 -0
- package/dist/capture/publish/limits.d.mts +35 -0
- package/dist/capture/publish/limits.mjs +38 -0
- package/dist/capture/publish/limits.mjs.map +1 -0
- package/dist/capture/publish/published.d.mts +13 -0
- package/dist/capture/publish/published.mjs +17 -0
- package/dist/capture/publish/published.mjs.map +1 -0
- package/dist/capture/publish/run.d.mts +31 -0
- package/dist/capture/publish/run.mjs +29 -0
- package/dist/capture/publish/run.mjs.map +1 -0
- package/dist/capture/rediff.d.mts +1 -3
- package/dist/capture/rediff.mjs +23 -9
- package/dist/capture/rediff.mjs.map +1 -1
- package/dist/capture/render.mjs +52 -0
- package/dist/capture/render.mjs.map +1 -0
- package/dist/capture/scratch.d.mts +42 -0
- package/dist/capture/scratch.mjs +136 -0
- package/dist/capture/scratch.mjs.map +1 -0
- package/dist/capture/shard-plan.mjs +127 -0
- package/dist/capture/shard-plan.mjs.map +1 -0
- package/dist/capture/shards/context.mjs +51 -0
- package/dist/capture/shards/context.mjs.map +1 -0
- package/dist/capture/shards/execute.mjs +175 -0
- package/dist/capture/shards/execute.mjs.map +1 -0
- package/dist/capture/shards/fan-out.mjs +125 -0
- package/dist/capture/shards/fan-out.mjs.map +1 -0
- package/dist/capture/shards/history.mjs +34 -0
- package/dist/capture/shards/history.mjs.map +1 -0
- package/dist/capture/shards/reduce.mjs +96 -0
- package/dist/capture/shards/reduce.mjs.map +1 -0
- package/dist/capture/sizing.d.mts +0 -2
- package/dist/capture/sizing.mjs +2 -13
- package/dist/capture/sizing.mjs.map +1 -1
- package/dist/capture/skip-checks.mjs +3 -67
- package/dist/capture/skip-checks.mjs.map +1 -1
- package/dist/capture/statics.d.mts +7 -10
- package/dist/capture/statics.mjs +26 -74
- package/dist/capture/statics.mjs.map +1 -1
- package/dist/capture/storybook-urls.d.mts +38 -0
- package/dist/capture/storybook-urls.mjs +41 -0
- package/dist/capture/storybook-urls.mjs.map +1 -0
- package/dist/capture/storybook.d.mts +12 -6
- package/dist/capture/storybook.mjs +63 -19
- package/dist/capture/storybook.mjs.map +1 -1
- package/dist/capture/target.mjs +20 -0
- package/dist/capture/target.mjs.map +1 -0
- package/dist/capture/targets.d.mts +23 -0
- package/dist/capture/targets.mjs +31 -0
- package/dist/capture/targets.mjs.map +1 -0
- package/dist/capture/tests.d.mts +35 -0
- package/dist/capture/tests.mjs +136 -0
- package/dist/capture/tests.mjs.map +1 -0
- package/dist/capture/token-chunks.mjs +91 -0
- package/dist/capture/token-chunks.mjs.map +1 -0
- package/dist/capture.d.mts +63 -7
- package/dist/capture.mjs +51 -3
- package/dist/config.d.mts +136 -14
- package/dist/config.mjs +14 -8
- package/dist/config.mjs.map +1 -1
- package/dist/diff.d.mts +7 -3
- package/dist/diff.mjs +15 -3
- package/dist/diff.mjs.map +1 -0
- package/dist/index.d.mts +3 -3
- package/dist/index.mjs +2 -2
- package/dist/insights/budget.d.mts +18 -0
- package/dist/insights/budget.mjs +32 -0
- package/dist/insights/budget.mjs.map +1 -0
- package/dist/insights/envelope.d.mts +57 -0
- package/dist/insights/envelope.mjs +59 -0
- package/dist/insights/envelope.mjs.map +1 -0
- package/dist/insights/evidence-health.d.mts +30 -0
- package/dist/insights/evidence-health.mjs +27 -0
- package/dist/insights/evidence-health.mjs.map +1 -0
- package/dist/insights/evidence.d.mts +53 -0
- package/dist/insights/evidence.mjs +63 -0
- package/dist/insights/evidence.mjs.map +1 -0
- package/dist/insights/hash.d.mts +16 -0
- package/dist/insights/hash.mjs +20 -0
- package/dist/insights/hash.mjs.map +1 -0
- package/dist/insights/limits.d.mts +22 -0
- package/dist/insights/limits.mjs +23 -0
- package/dist/insights/limits.mjs.map +1 -0
- package/dist/insights/prompts/v1.d.mts +11 -0
- package/dist/insights/prompts/v1.mjs +33 -0
- package/dist/insights/prompts/v1.mjs.map +1 -0
- package/dist/insights/redact.d.mts +7 -0
- package/dist/insights/redact.mjs +23 -0
- package/dist/insights/redact.mjs.map +1 -0
- package/dist/insights/text.mjs +43 -0
- package/dist/insights/text.mjs.map +1 -0
- package/dist/insights.d.mts +11 -0
- package/dist/insights.mjs +12 -0
- package/dist/logger.d.mts +0 -2
- package/dist/logger.mjs +18 -3
- package/dist/logger.mjs.map +1 -1
- package/dist/models/ai-budget-alert.d.mts +21 -0
- package/dist/models/ai-budget-alert.mjs +37 -0
- package/dist/models/ai-budget-alert.mjs.map +1 -0
- package/dist/models/ai-usage.d.mts +38 -0
- package/dist/models/ai-usage.mjs +60 -0
- package/dist/models/ai-usage.mjs.map +1 -0
- package/dist/models/baseline-approval.d.mts +18 -0
- package/dist/models/baseline-approval.mjs +41 -0
- package/dist/models/baseline-approval.mjs.map +1 -0
- package/dist/models/baseline-archive.d.mts +82 -0
- package/dist/models/baseline-archive.mjs +105 -0
- package/dist/models/baseline-archive.mjs.map +1 -0
- package/dist/models/baseline-atomic.mjs +66 -0
- package/dist/models/baseline-atomic.mjs.map +1 -0
- package/dist/models/baseline-conflict.d.mts +6 -0
- package/dist/models/baseline-conflict.mjs +9 -0
- package/dist/models/baseline-conflict.mjs.map +1 -0
- package/dist/models/baseline-export.d.mts +17 -0
- package/dist/models/baseline-export.mjs +135 -0
- package/dist/models/baseline-export.mjs.map +1 -0
- package/dist/models/baseline-import.mjs +117 -0
- package/dist/models/baseline-import.mjs.map +1 -0
- package/dist/models/baseline-origin.d.mts +16 -0
- package/dist/models/baseline-origin.mjs +35 -0
- package/dist/models/baseline-origin.mjs.map +1 -0
- package/dist/models/baseline-status.d.mts +0 -2
- package/dist/models/baseline-status.mjs +1 -2
- package/dist/models/baseline-transfer.d.mts +48 -0
- package/dist/models/baseline-transfer.mjs +149 -0
- package/dist/models/baseline-transfer.mjs.map +1 -0
- package/dist/models/baseline.d.mts +37 -7
- package/dist/models/baseline.mjs +152 -28
- package/dist/models/baseline.mjs.map +1 -1
- package/dist/models/build-component.d.mts +26 -0
- package/dist/models/build-component.mjs +54 -0
- package/dist/models/build-component.mjs.map +1 -0
- package/dist/models/build-review-status.d.mts +27 -0
- package/dist/models/build-review-status.mjs +36 -0
- package/dist/models/build-review-status.mjs.map +1 -0
- package/dist/models/build-review.d.mts +26 -0
- package/dist/models/build-review.mjs +53 -0
- package/dist/models/build-review.mjs.map +1 -0
- package/dist/models/build.d.mts +25 -5
- package/dist/models/build.mjs +53 -15
- package/dist/models/build.mjs.map +1 -1
- package/dist/models/capture-attempt.d.mts +7 -3
- package/dist/models/capture-attempt.mjs +30 -6
- package/dist/models/capture-attempt.mjs.map +1 -1
- package/dist/models/capture-job.d.mts +104 -0
- package/dist/models/capture-job.mjs +287 -0
- package/dist/models/capture-job.mjs.map +1 -0
- package/dist/models/capture-log.d.mts +1 -3
- package/dist/models/capture-log.mjs +5 -5
- package/dist/models/capture-log.mjs.map +1 -1
- package/dist/models/capture-shard.d.mts +60 -0
- package/dist/models/capture-shard.mjs +189 -0
- package/dist/models/capture-shard.mjs.map +1 -0
- package/dist/models/changeset.d.mts +46 -0
- package/dist/models/changeset.mjs +80 -0
- package/dist/models/changeset.mjs.map +1 -0
- package/dist/models/columns.mjs +18 -0
- package/dist/models/columns.mjs.map +1 -0
- package/dist/models/comment.d.mts +4 -3
- package/dist/models/comment.mjs +8 -6
- package/dist/models/comment.mjs.map +1 -1
- package/dist/models/component.d.mts +41 -0
- package/dist/models/component.mjs +59 -0
- package/dist/models/component.mjs.map +1 -0
- package/dist/models/connections.d.mts +39 -0
- package/dist/models/connections.mjs +111 -0
- package/dist/models/connections.mjs.map +1 -0
- package/dist/models/content-ref.d.mts +1 -3
- package/dist/models/content-ref.mjs +2 -3
- package/dist/models/content-ref.mjs.map +1 -1
- package/dist/models/credentials.d.mts +0 -2
- package/dist/models/credentials.mjs +13 -4
- package/dist/models/credentials.mjs.map +1 -1
- package/dist/models/design-credentials.d.mts +39 -0
- package/dist/models/design-credentials.mjs +85 -0
- package/dist/models/design-credentials.mjs.map +1 -0
- package/dist/models/design-health.d.mts +14 -0
- package/dist/models/design-health.mjs +24 -0
- package/dist/models/design-health.mjs.map +1 -0
- package/dist/models/design-nodes.d.mts +81 -0
- package/dist/models/design-nodes.mjs +221 -0
- package/dist/models/design-nodes.mjs.map +1 -0
- package/dist/models/design-renders.mjs +73 -0
- package/dist/models/design-renders.mjs.map +1 -0
- package/dist/models/design-resolve.d.mts +27 -0
- package/dist/models/design-resolve.mjs +40 -0
- package/dist/models/design-resolve.mjs.map +1 -0
- package/dist/models/design.d.mts +37 -0
- package/dist/models/design.mjs +89 -0
- package/dist/models/design.mjs.map +1 -0
- package/dist/models/inherited-ratio.d.mts +19 -0
- package/dist/models/inherited-ratio.mjs +21 -0
- package/dist/models/inherited-ratio.mjs.map +1 -0
- package/dist/models/insight.d.mts +68 -0
- package/dist/models/insight.mjs +161 -0
- package/dist/models/insight.mjs.map +1 -0
- package/dist/models/label.d.mts +4 -4
- package/dist/models/label.mjs +26 -12
- package/dist/models/label.mjs.map +1 -1
- package/dist/models/member.d.mts +1 -3
- package/dist/models/member.mjs +6 -6
- package/dist/models/member.mjs.map +1 -1
- package/dist/models/notification-channel.d.mts +37 -5
- package/dist/models/notification-channel.mjs +89 -11
- package/dist/models/notification-channel.mjs.map +1 -1
- package/dist/models/notification-subscription.d.mts +1 -4
- package/dist/models/notification-subscription.mjs +8 -11
- package/dist/models/notification-subscription.mjs.map +1 -1
- package/dist/models/project-group-mapping.d.mts +6 -3
- package/dist/models/project-group-mapping.mjs +15 -6
- package/dist/models/project-group-mapping.mjs.map +1 -1
- package/dist/models/project.d.mts +9 -3
- package/dist/models/project.mjs +10 -6
- package/dist/models/project.mjs.map +1 -1
- package/dist/models/public-origin.d.mts +61 -0
- package/dist/models/public-origin.mjs +113 -0
- package/dist/models/public-origin.mjs.map +1 -0
- package/dist/models/review-switch-off.d.mts +12 -0
- package/dist/models/review-switch-off.mjs +25 -0
- package/dist/models/review-switch-off.mjs.map +1 -0
- package/dist/models/site-settings.d.mts +21 -0
- package/dist/models/site-settings.mjs +47 -0
- package/dist/models/site-settings.mjs.map +1 -0
- package/dist/models/snapshot.d.mts +39 -3
- package/dist/models/snapshot.mjs +54 -5
- package/dist/models/snapshot.mjs.map +1 -1
- package/dist/models/token.d.mts +13 -3
- package/dist/models/token.mjs +24 -7
- package/dist/models/token.mjs.map +1 -1
- package/dist/models/usage.d.mts +74 -0
- package/dist/models/usage.mjs +148 -0
- package/dist/models/usage.mjs.map +1 -0
- package/dist/models/user.d.mts +1 -3
- package/dist/models/user.mjs +5 -5
- package/dist/models/user.mjs.map +1 -1
- package/dist/models/webhook.d.mts +15 -3
- package/dist/models/webhook.mjs +34 -6
- package/dist/models/webhook.mjs.map +1 -1
- package/dist/models.d.mts +30 -4
- package/dist/models.mjs +31 -6
- package/dist/options.d.mts +16 -0
- package/dist/options.mjs +88 -0
- package/dist/options.mjs.map +1 -0
- package/dist/paths.d.mts +21 -6
- package/dist/paths.mjs +57 -12
- package/dist/paths.mjs.map +1 -1
- package/dist/retention/ai.d.mts +16 -0
- package/dist/retention/ai.mjs +22 -0
- package/dist/retention/ai.mjs.map +1 -0
- package/dist/retention/pending.mjs +46 -0
- package/dist/retention/pending.mjs.map +1 -0
- package/dist/retention/published-gzip-sweep.mjs +28 -0
- package/dist/retention/published-gzip-sweep.mjs.map +1 -0
- package/dist/retention/screenshot-sweep.d.mts +49 -0
- package/dist/retention/screenshot-sweep.mjs +127 -0
- package/dist/retention/screenshot-sweep.mjs.map +1 -0
- package/dist/retention.d.mts +16 -4
- package/dist/retention.mjs +76 -16
- package/dist/retention.mjs.map +1 -1
- package/dist/schema/baseline.d.mts +8 -2
- package/dist/schema/build-component.d.mts +23 -0
- package/dist/schema/build-review.d.mts +15 -0
- package/dist/schema/build.d.mts +7 -2
- package/dist/schema/capture-attempt.d.mts +0 -2
- package/dist/schema/capture-job.d.mts +58 -0
- package/dist/schema/capture-shard.d.mts +33 -0
- package/dist/schema/comment.d.mts +2 -2
- package/dist/schema/connection.d.mts +22 -0
- package/dist/schema/content-ref.d.mts +0 -2
- package/dist/schema/design.d.mts +56 -0
- package/dist/schema/insight.d.mts +52 -0
- package/dist/schema/label.d.mts +0 -2
- package/dist/schema/member.d.mts +0 -2
- package/dist/schema/notification.d.mts +4 -2
- package/dist/schema/project-group-mapping.d.mts +0 -2
- package/dist/schema/project.d.mts +21 -2
- package/dist/schema/site-setting.d.mts +19 -0
- package/dist/schema/snapshot.d.mts +17 -2
- package/dist/schema/token.d.mts +0 -2
- package/dist/schema/usage.d.mts +28 -0
- package/dist/schema/user.d.mts +0 -2
- package/dist/schema/webhook.d.mts +0 -2
- package/dist/schema.d.mts +12 -6
- package/dist/serverless.d.mts +19 -0
- package/dist/serverless.mjs +36 -0
- package/dist/serverless.mjs.map +1 -0
- package/dist/test-helpers/build-reference-contract.d.mts +14 -0
- package/dist/test-helpers/build-reference-contract.mjs +66 -0
- package/dist/test-helpers/build-reference-contract.mjs.map +1 -0
- package/dist/test-helpers/create-project.d.mts +1 -3
- package/dist/test-helpers/create-project.mjs +1 -2
- package/dist/test-helpers/database-contract.d.mts +0 -2
- package/dist/test-helpers/database-contract.mjs +37 -2
- package/dist/test-helpers/database-contract.mjs.map +1 -1
- package/dist/test-helpers/deferred.d.mts +37 -0
- package/dist/test-helpers/deferred.mjs +60 -0
- package/dist/test-helpers/deferred.mjs.map +1 -0
- package/dist/test-helpers/design-contract.d.mts +19 -0
- package/dist/test-helpers/design-contract.mjs +39 -0
- package/dist/test-helpers/design-contract.mjs.map +1 -0
- package/dist/test-helpers/fake-adapters.d.mts +0 -2
- package/dist/test-helpers/fake-adapters.mjs +1 -2
- package/dist/test-helpers/fake-database.d.mts +0 -2
- package/dist/test-helpers/fake-database.mjs +21 -4
- package/dist/test-helpers/fake-database.mjs.map +1 -1
- package/dist/test-helpers/fake-design.d.mts +41 -0
- package/dist/test-helpers/fake-design.mjs +113 -0
- package/dist/test-helpers/fake-design.mjs.map +1 -0
- package/dist/test-helpers/fake-notifier.d.mts +19 -0
- package/dist/test-helpers/fake-notifier.mjs +43 -0
- package/dist/test-helpers/fake-notifier.mjs.map +1 -0
- package/dist/test-helpers/fake-storage.d.mts +0 -2
- package/dist/test-helpers/fake-storage.mjs +1 -2
- package/dist/test-helpers/fake-tables.mjs +260 -69
- package/dist/test-helpers/fake-tables.mjs.map +1 -1
- package/dist/test-helpers/manual-clock.d.mts +25 -0
- package/dist/test-helpers/manual-clock.mjs +55 -0
- package/dist/test-helpers/manual-clock.mjs.map +1 -0
- package/dist/test-helpers/notifier-contract.d.mts +7 -3
- package/dist/test-helpers/notifier-contract.mjs +33 -5
- package/dist/test-helpers/notifier-contract.mjs.map +1 -1
- package/dist/test-helpers/queue-contract.d.mts +16 -4
- package/dist/test-helpers/queue-contract.mjs +196 -3
- package/dist/test-helpers/queue-contract.mjs.map +1 -1
- package/dist/test-helpers/queue-multi-instance.d.mts +18 -0
- package/dist/test-helpers/queue-multi-instance.mjs +205 -0
- package/dist/test-helpers/queue-multi-instance.mjs.map +1 -0
- package/dist/test-helpers/runner-contract.d.mts +0 -2
- package/dist/test-helpers/runner-contract.mjs +12 -2
- package/dist/test-helpers/runner-contract.mjs.map +1 -1
- package/dist/test-helpers/sql-chunks.mjs +25 -2
- package/dist/test-helpers/sql-chunks.mjs.map +1 -1
- package/dist/test-helpers/storage-contract.d.mts +0 -2
- package/dist/test-helpers/storage-contract.mjs +2 -3
- package/dist/test-helpers.d.mts +10 -4
- package/dist/test-helpers.mjs +9 -3
- package/dist/tracing.mjs +36 -6
- package/dist/tracing.mjs.map +1 -1
- package/dist/types.d.mts +14 -5
- package/dist/types.mjs +22 -6
- package/dist/types.mjs.map +1 -1
- package/dist/urls.d.mts +4 -2
- package/dist/urls.mjs +12 -4
- package/dist/urls.mjs.map +1 -1
- package/dist/utils/adaptive-concurrency.d.mts +64 -0
- package/dist/utils/adaptive-concurrency.mjs +148 -0
- package/dist/utils/adaptive-concurrency.mjs.map +1 -0
- package/dist/utils/connect-retry.d.mts +26 -0
- package/dist/utils/connect-retry.mjs +42 -0
- package/dist/utils/connect-retry.mjs.map +1 -0
- package/dist/utils/encrypt.d.mts +0 -2
- package/dist/utils/encrypt.mjs +1 -2
- package/dist/utils/environment.d.mts +17 -0
- package/dist/utils/environment.mjs +58 -0
- package/dist/utils/environment.mjs.map +1 -0
- package/dist/utils/hash.d.mts +0 -2
- package/dist/utils/hash.mjs +1 -2
- package/dist/utils/http.d.mts +29 -3
- package/dist/utils/http.mjs +100 -19
- package/dist/utils/http.mjs.map +1 -1
- package/dist/utils/map-adaptive.d.mts +13 -0
- package/dist/utils/map-adaptive.mjs +71 -0
- package/dist/utils/map-adaptive.mjs.map +1 -0
- package/dist/utils/map-limit.d.mts +8 -0
- package/dist/utils/map-limit.mjs +31 -0
- package/dist/utils/map-limit.mjs.map +1 -0
- package/dist/utils/mime.d.mts +0 -2
- package/dist/utils/mime.mjs +1 -2
- package/dist/utils/page-limiter.d.mts +35 -0
- package/dist/utils/page-limiter.mjs +55 -0
- package/dist/utils/page-limiter.mjs.map +1 -0
- package/dist/utils/path-security.d.mts +0 -2
- package/dist/utils/path-security.mjs +2 -4
- package/dist/utils/path-security.mjs.map +1 -1
- package/dist/utils/public-origin.d.mts +18 -0
- package/dist/utils/public-origin.mjs +74 -0
- package/dist/utils/public-origin.mjs.map +1 -0
- package/dist/utils/redact.d.mts +0 -2
- package/dist/utils/redact.mjs +4 -2
- package/dist/utils/redact.mjs.map +1 -1
- package/dist/utils/remote-environment.d.mts +76 -0
- package/dist/utils/remote-environment.mjs +163 -0
- package/dist/utils/remote-environment.mjs.map +1 -0
- package/dist/utils/resources.d.mts +62 -0
- package/dist/utils/resources.mjs +121 -0
- package/dist/utils/resources.mjs.map +1 -0
- package/dist/utils/screenshot-store.d.mts +13 -0
- package/dist/utils/screenshot-store.mjs +40 -0
- package/dist/utils/screenshot-store.mjs.map +1 -0
- package/dist/utils/session-gate.d.mts +23 -0
- package/dist/utils/session-gate.mjs +69 -0
- package/dist/utils/session-gate.mjs.map +1 -0
- package/dist/utils/storybook-token.d.mts +89 -0
- package/dist/utils/storybook-token.mjs +135 -0
- package/dist/utils/storybook-token.mjs.map +1 -0
- package/dist/utils/storybook-url.d.mts +13 -0
- package/dist/utils/storybook-url.mjs +20 -0
- package/dist/utils/storybook-url.mjs.map +1 -0
- package/dist/utils/timing.d.mts +0 -2
- package/dist/utils/timing.mjs +2 -3
- package/dist/utils/timing.mjs.map +1 -1
- package/dist/utils/ulid.d.mts +0 -2
- package/dist/utils/ulid.mjs +1 -2
- package/dist/utils/zip-read.mjs +128 -0
- package/dist/utils/zip-read.mjs.map +1 -0
- package/dist/utils/zip-write.d.mts +16 -0
- package/dist/utils/zip-write.mjs +187 -0
- package/dist/utils/zip-write.mjs.map +1 -0
- package/dist/utils.d.mts +17 -3
- package/dist/utils.mjs +18 -4
- package/package.json +35 -26
- package/dist/adapter/capture-runner.mjs.map +0 -1
- package/dist/adapter/git-host/comments.d.mts +0 -41
- package/dist/adapter/git-host/comments.mjs +0 -54
- package/dist/adapter/git-host/comments.mjs.map +0 -1
- package/dist/adapter/git-host/helpers.d.mts +0 -13
- package/dist/adapter/git-host/helpers.mjs +0 -30
- package/dist/adapter/git-host/helpers.mjs.map +0 -1
- package/dist/adapter/git-host.d.mts +0 -42
- package/dist/capture/queue.d.mts +0 -38
- package/dist/capture/queue.mjs +0 -98
- package/dist/capture/queue.mjs.map +0 -1
- package/dist/capture/recovery.d.mts +0 -21
- package/dist/capture/recovery.mjs +0 -83
- package/dist/capture/recovery.mjs.map +0 -1
- package/dist/capture/status-fanout.mjs +0 -72
- package/dist/capture/status-fanout.mjs.map +0 -1
- package/dist/diff/engine.d.mts +0 -16
- package/dist/diff/engine.mjs +0 -87
- package/dist/diff/engine.mjs.map +0 -1
- package/dist/diff/options.d.mts +0 -42
- package/dist/diff/options.mjs +0 -15
- package/dist/diff/options.mjs.map +0 -1
- package/dist/models/status-config.d.mts +0 -38
- package/dist/models/status-config.mjs +0 -63
- package/dist/models/status-config.mjs.map +0 -1
- package/dist/schema/status-config.d.mts +0 -16
- package/dist/test-helpers/git-contract.d.mts +0 -11
- package/dist/test-helpers/git-contract.mjs +0 -34
- package/dist/test-helpers/git-contract.mjs.map +0 -1
- /package/dist/adapter/{git-host.mjs → oauth.mjs} +0 -0
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import "node:path";
|
|
2
|
+
import "node:url";
|
|
3
|
+
import.meta.url;
|
|
4
|
+
import { createShelfLogger } from "../../logger.mjs";
|
|
5
|
+
import { DEFAULT_BACKOFF_MS, DEFAULT_MAX_RETRIES, DEFAULT_WAIT_TIME_SECONDS, MAX_BACKOFF_MS, backoffDelayMs, isFinalAttempt } from "./retry.mjs";
|
|
6
|
+
import { REAL_TIMERS } from "../db-queue/timers.mjs";
|
|
7
|
+
import { startPollLoop } from "../db-queue/poll-loop.mjs";
|
|
8
|
+
import { recommendedBuildConcurrency } from "../../utils/resources.mjs";
|
|
9
|
+
import { captureDispatchDeps, createCaptureHandler } from "./capture-handler.mjs";
|
|
10
|
+
import { MIN_BLOCKING_POLL_MS, idleDelayMs, isFastEmptyPoll } from "./idle.mjs";
|
|
11
|
+
import { startSlotLoop } from "./loop.mjs";
|
|
12
|
+
import { DEFAULT_SAMPLE_MS, sampleQueue, startQueueSampling } from "./metrics.mjs";
|
|
13
|
+
import { runJob } from "./run-job.mjs";
|
|
14
|
+
import { applyImageSettings, ensureImageEngine, sweepScratch } from "./startup.mjs";
|
|
15
|
+
// src/capture/consumer/index.ts
|
|
16
|
+
/** Create a consumer of `options.queue`; nothing runs until `start()`. */
|
|
17
|
+
function createCaptureConsumer(options) {
|
|
18
|
+
applyImageSettings(options.images, options.compare);
|
|
19
|
+
const logger = options.logger ?? createShelfLogger();
|
|
20
|
+
options.queue.setLogger?.(logger.child({ component: options.queue.metadata.kind }));
|
|
21
|
+
const rt = buildRuntime(options, logger);
|
|
22
|
+
const slots = options.config?.concurrency ?? recommendedBuildConcurrency();
|
|
23
|
+
let session = null;
|
|
24
|
+
let requestStop = null;
|
|
25
|
+
let stopRequested = false;
|
|
26
|
+
return {
|
|
27
|
+
isRunning: () => session !== null,
|
|
28
|
+
start: async () => {
|
|
29
|
+
if (session !== null) return;
|
|
30
|
+
const stopped = Promise.withResolvers();
|
|
31
|
+
stopRequested = false;
|
|
32
|
+
requestStop = () => {
|
|
33
|
+
stopRequested = true;
|
|
34
|
+
stopped.resolve(null);
|
|
35
|
+
};
|
|
36
|
+
session = consume({
|
|
37
|
+
options,
|
|
38
|
+
rt,
|
|
39
|
+
slots,
|
|
40
|
+
stopped: stopped.promise,
|
|
41
|
+
isStopped: () => stopRequested
|
|
42
|
+
});
|
|
43
|
+
await session.finally(() => {
|
|
44
|
+
session = null;
|
|
45
|
+
});
|
|
46
|
+
},
|
|
47
|
+
stop: async () => {
|
|
48
|
+
requestStop?.();
|
|
49
|
+
await session?.catch(() => {});
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
/** One start-to-drain session: sweep, check the engine, poll until stopped, drain. */
|
|
54
|
+
async function consume(c) {
|
|
55
|
+
const { options, rt } = c;
|
|
56
|
+
const sweep = sweepScratch(options.scratchDir ?? options.capture?.scratchDir, rt.logger);
|
|
57
|
+
try {
|
|
58
|
+
await ensureImageEngine(rt.logger);
|
|
59
|
+
if (!c.isStopped()) await pollUntilStopped(c);
|
|
60
|
+
} finally {
|
|
61
|
+
await sweep;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/** Poll (and sample the queue) until `stop()`, then drain the jobs in flight. */
|
|
65
|
+
async function pollUntilStopped(c) {
|
|
66
|
+
const { options, rt } = c;
|
|
67
|
+
const loop = startLoop(c);
|
|
68
|
+
const stopSampling = startQueueSampling(options.queue, options.metrics, {
|
|
69
|
+
now: rt.now,
|
|
70
|
+
timers: rt.timers ?? REAL_TIMERS,
|
|
71
|
+
logger: rt.logger
|
|
72
|
+
});
|
|
73
|
+
await c.stopped;
|
|
74
|
+
stopSampling();
|
|
75
|
+
await loop.stop();
|
|
76
|
+
}
|
|
77
|
+
/** The database queue sets its own cadence; any other queue is polled in a slot loop. */
|
|
78
|
+
function startLoop(c) {
|
|
79
|
+
const { options, rt } = c;
|
|
80
|
+
const run = async (job) => {
|
|
81
|
+
await runJob(rt, job);
|
|
82
|
+
};
|
|
83
|
+
if (isDatabaseQueue(options.queue)) return startPollLoop({
|
|
84
|
+
queue: options.queue,
|
|
85
|
+
slots: c.slots,
|
|
86
|
+
run,
|
|
87
|
+
logger: rt.logger,
|
|
88
|
+
now: rt.now,
|
|
89
|
+
...options.cadence === void 0 ? {} : { cadence: options.cadence },
|
|
90
|
+
...options.random === void 0 ? {} : { random: options.random }
|
|
91
|
+
});
|
|
92
|
+
const waitSeconds = options.config?.waitTimeSeconds ?? 20;
|
|
93
|
+
return startSlotLoop({
|
|
94
|
+
queue: options.queue,
|
|
95
|
+
slots: c.slots,
|
|
96
|
+
waitMs: waitSeconds * 1e3,
|
|
97
|
+
run,
|
|
98
|
+
logger: rt.logger,
|
|
99
|
+
now: rt.now,
|
|
100
|
+
timers: options.timers
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
function isDatabaseQueue(queue) {
|
|
104
|
+
return typeof queue.waitForWork === "function";
|
|
105
|
+
}
|
|
106
|
+
function buildRuntime(options, logger) {
|
|
107
|
+
return {
|
|
108
|
+
queue: options.queue,
|
|
109
|
+
handlers: registry(options, logger),
|
|
110
|
+
maxRetries: options.config?.maxRetries ?? 2,
|
|
111
|
+
backoffMs: options.config?.backoffMs ?? 1e3,
|
|
112
|
+
logger,
|
|
113
|
+
metrics: options.metrics,
|
|
114
|
+
onProgress: options.onProgress,
|
|
115
|
+
now: options.now ?? Date.now,
|
|
116
|
+
timers: options.timers,
|
|
117
|
+
lease: options.lease
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
/** `capture` wired to the core dispatch, then the explicit handlers on top. */
|
|
121
|
+
function registry(options, logger) {
|
|
122
|
+
const all = {};
|
|
123
|
+
if (options.capture !== void 0) all.capture = createCaptureHandler({
|
|
124
|
+
enqueue: async (job) => {
|
|
125
|
+
await options.queue.enqueue(job);
|
|
126
|
+
},
|
|
127
|
+
maxRetries: options.config?.maxRetries,
|
|
128
|
+
...options.capture,
|
|
129
|
+
logger
|
|
130
|
+
});
|
|
131
|
+
Object.assign(all, options.handlers);
|
|
132
|
+
const map = /* @__PURE__ */ new Map();
|
|
133
|
+
for (const [kind, handler] of Object.entries(all)) if (handler !== void 0) map.set(kind, typeof handler === "function" ? { run: handler } : handler);
|
|
134
|
+
return map;
|
|
135
|
+
}
|
|
136
|
+
export { DEFAULT_BACKOFF_MS, DEFAULT_MAX_RETRIES, DEFAULT_SAMPLE_MS, DEFAULT_WAIT_TIME_SECONDS, MAX_BACKOFF_MS, MIN_BLOCKING_POLL_MS, backoffDelayMs, captureDispatchDeps, createCaptureConsumer, createCaptureHandler, idleDelayMs, isFastEmptyPoll, isFinalAttempt, sampleQueue };
|
|
137
|
+
|
|
138
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../../src/capture/consumer/index.ts"],"sourcesContent":["/**\n * The capture consumer (ADR 0006, decisions 4 and 11): consumes any `PollableCaptureQueue` (the\n * database queue, the IPC queue of a forked capture child) and runs a handler per\n * job `kind`. A slot is taken before polling, a failing job is retried with exponential backoff\n * until its last attempt, the lease is kept by the job's progress, a lost lease or a cancel stops\n * the run, and `stop()` drains the jobs in flight.\n *\n * ```ts\n * const consumer = createCaptureConsumer({\n * queue: createDatabaseCaptureQueue(db),\n * capture: { db, storage, runner, scratchDir },\n * config: { concurrency: 2 },\n * });\n * consumer.start(); // resolves after stop()\n * // on SIGTERM: await consumer.stop();\n * ```\n */\nimport type { PollableCaptureQueue } from \"../../adapters/capture-queue.ts\";\nimport { createShelfLogger, type Logger } from \"../../logger.ts\";\nimport { recommendedBuildConcurrency } from \"../../utils/resources.ts\";\nimport { startPollLoop, type PollLoop } from \"../db-queue/poll-loop.ts\";\nimport { REAL_TIMERS } from \"../db-queue/timers.ts\";\nimport type { DatabaseCaptureQueue } from \"../db-queue/types.ts\";\nimport { createCaptureHandler } from \"./capture-handler.ts\";\nimport { startSlotLoop } from \"./loop.ts\";\nimport { startQueueSampling } from \"./metrics.ts\";\nimport { DEFAULT_BACKOFF_MS, DEFAULT_MAX_RETRIES, DEFAULT_WAIT_TIME_SECONDS } from \"./retry.ts\";\nimport { runJob, type JobRuntime } from \"./run-job.ts\";\nimport { applyImageSettings, ensureImageEngine, sweepScratch } from \"./startup.ts\";\nimport type {\n CaptureConsumer,\n CaptureConsumerOptions,\n ConsumerJob,\n JobHandlers,\n KindHandler,\n} from \"./types.ts\";\n\n/** Create a consumer of `options.queue`; nothing runs until `start()`. */\nexport function createCaptureConsumer(options: CaptureConsumerOptions): CaptureConsumer {\n applyImageSettings(options.images, options.compare);\n const logger = options.logger ?? createShelfLogger();\n options.queue.setLogger?.(logger.child({ component: options.queue.metadata.kind }));\n const rt = buildRuntime(options, logger);\n const slots = options.config?.concurrency ?? recommendedBuildConcurrency();\n let session: Promise<void> | null = null;\n let requestStop: (() => void) | null = null;\n let stopRequested = false;\n return {\n isRunning: () => session !== null,\n start: async () => {\n if (session !== null) {\n return;\n }\n const stopped = Promise.withResolvers<null>();\n stopRequested = false;\n requestStop = (): void => {\n stopRequested = true;\n stopped.resolve(null);\n };\n session = consume({\n options,\n rt,\n slots,\n stopped: stopped.promise,\n isStopped: () => stopRequested,\n });\n await session.finally(() => {\n session = null;\n });\n },\n stop: async () => {\n requestStop?.();\n await session?.catch(() => {});\n },\n };\n}\n\ninterface Consuming {\n options: CaptureConsumerOptions;\n rt: JobRuntime;\n slots: number;\n /** Resolves when `stop()` was called. */\n stopped: Promise<null>;\n isStopped: () => boolean;\n}\n\n/** One start-to-drain session: sweep, check the engine, poll until stopped, drain. */\nasync function consume(c: Consuming): Promise<void> {\n const { options, rt } = c;\n const sweep = sweepScratch(options.scratchDir ?? options.capture?.scratchDir, rt.logger);\n try {\n await ensureImageEngine(rt.logger);\n if (!c.isStopped()) {\n await pollUntilStopped(c);\n }\n } finally {\n await sweep;\n }\n}\n\n/** Poll (and sample the queue) until `stop()`, then drain the jobs in flight. */\nasync function pollUntilStopped(c: Consuming): Promise<void> {\n const { options, rt } = c;\n const loop = startLoop(c);\n const stopSampling = startQueueSampling(options.queue, options.metrics, {\n now: rt.now,\n timers: rt.timers ?? REAL_TIMERS,\n logger: rt.logger,\n });\n await c.stopped;\n stopSampling();\n await loop.stop();\n}\n\n/** The database queue sets its own cadence; any other queue is polled in a slot loop. */\nfunction startLoop(c: Consuming): PollLoop {\n const { options, rt } = c;\n const run = async (job: ConsumerJob): Promise<void> => {\n await runJob(rt, job);\n };\n if (isDatabaseQueue(options.queue)) {\n return startPollLoop({\n queue: options.queue,\n slots: c.slots,\n run,\n logger: rt.logger,\n now: rt.now,\n ...(options.cadence === undefined ? {} : { cadence: options.cadence }),\n ...(options.random === undefined ? {} : { random: options.random }),\n });\n }\n const waitSeconds = options.config?.waitTimeSeconds ?? DEFAULT_WAIT_TIME_SECONDS;\n return startSlotLoop({\n queue: options.queue,\n slots: c.slots,\n waitMs: waitSeconds * 1000,\n run,\n logger: rt.logger,\n now: rt.now,\n timers: options.timers,\n });\n}\n\nfunction isDatabaseQueue(queue: PollableCaptureQueue): queue is DatabaseCaptureQueue {\n return typeof (queue as Partial<DatabaseCaptureQueue>).waitForWork === \"function\";\n}\n\nfunction buildRuntime(options: CaptureConsumerOptions, logger: Logger): JobRuntime {\n return {\n queue: options.queue,\n handlers: registry(options, logger),\n maxRetries: options.config?.maxRetries ?? DEFAULT_MAX_RETRIES,\n backoffMs: options.config?.backoffMs ?? DEFAULT_BACKOFF_MS,\n logger,\n metrics: options.metrics,\n onProgress: options.onProgress,\n now: options.now ?? Date.now,\n timers: options.timers,\n lease: options.lease,\n };\n}\n\n/** `capture` wired to the core dispatch, then the explicit handlers on top. */\nfunction registry(options: CaptureConsumerOptions, logger: Logger): Map<string, KindHandler> {\n const all: JobHandlers = {};\n if (options.capture !== undefined) {\n all.capture = createCaptureHandler({\n enqueue: async (job) => {\n await options.queue.enqueue(job);\n },\n maxRetries: options.config?.maxRetries,\n ...options.capture,\n logger,\n });\n }\n Object.assign(all, options.handlers);\n const map = new Map<string, KindHandler>();\n for (const [kind, handler] of Object.entries(all)) {\n if (handler !== undefined) {\n map.set(kind, typeof handler === \"function\" ? { run: handler } : handler);\n }\n }\n return map;\n}\n\nexport {\n captureDispatchDeps,\n createCaptureHandler,\n type CaptureHandlerOptions,\n} from \"./capture-handler.ts\";\nexport { idleDelayMs, isFastEmptyPoll, MIN_BLOCKING_POLL_MS } from \"./idle.ts\";\nexport {\n DEFAULT_SAMPLE_MS,\n sampleQueue,\n type ConsumerMetrics,\n type JobFinishedEvent,\n type JobOutcome,\n type QueueSample,\n} from \"./metrics.ts\";\nexport {\n backoffDelayMs,\n DEFAULT_BACKOFF_MS,\n DEFAULT_MAX_RETRIES,\n DEFAULT_WAIT_TIME_SECONDS,\n isFinalAttempt,\n MAX_BACKOFF_MS,\n type RetryConfig,\n} from \"./retry.ts\";\nexport type {\n CaptureConsumer,\n CaptureConsumerOptions,\n ConsumerConfig,\n ConsumerJob,\n JobHandlers,\n JobRunContext,\n KindHandler,\n} from \"./types.ts\";\n"],"mappings":";;;;;;;;;;;;;;;;AAsCA,SAAgB,sBAAsB,SAAkD;CACtF,mBAAmB,QAAQ,QAAQ,QAAQ,OAAO;CAClD,MAAM,SAAS,QAAQ,UAAU,kBAAkB;CACnD,QAAQ,MAAM,YAAY,OAAO,MAAM,EAAE,WAAW,QAAQ,MAAM,SAAS,KAAK,CAAC,CAAC;CAClF,MAAM,KAAK,aAAa,SAAS,MAAM;CACvC,MAAM,QAAQ,QAAQ,QAAQ,eAAe,4BAA4B;CACzE,IAAI,UAAgC;CACpC,IAAI,cAAmC;CACvC,IAAI,gBAAgB;CACpB,OAAO;EACL,iBAAiB,YAAY;EAC7B,OAAO,YAAY;GACjB,IAAI,YAAY,MACd;GAEF,MAAM,UAAU,QAAQ,cAAoB;GAC5C,gBAAgB;GAChB,oBAA0B;IACxB,gBAAgB;IAChB,QAAQ,QAAQ,IAAI;GACtB;GACA,UAAU,QAAQ;IAChB;IACA;IACA;IACA,SAAS,QAAQ;IACjB,iBAAiB;GACnB,CAAC;GACD,MAAM,QAAQ,cAAc;IAC1B,UAAU;GACZ,CAAC;EACH;EACA,MAAM,YAAY;GAChB,cAAc;GACd,MAAM,SAAS,YAAY,CAAC,CAAC;EAC/B;CACF;AACF;;AAYA,eAAe,QAAQ,GAA6B;CAClD,MAAM,EAAE,SAAS,OAAO;CACxB,MAAM,QAAQ,aAAa,QAAQ,cAAc,QAAQ,SAAS,YAAY,GAAG,MAAM;CACvF,IAAI;EACF,MAAM,kBAAkB,GAAG,MAAM;EACjC,IAAI,CAAC,EAAE,UAAU,GACf,MAAM,iBAAiB,CAAC;CAE5B,UAAU;EACR,MAAM;CACR;AACF;;AAGA,eAAe,iBAAiB,GAA6B;CAC3D,MAAM,EAAE,SAAS,OAAO;CACxB,MAAM,OAAO,UAAU,CAAC;CACxB,MAAM,eAAe,mBAAmB,QAAQ,OAAO,QAAQ,SAAS;EACtE,KAAK,GAAG;EACR,QAAQ,GAAG,UAAU;EACrB,QAAQ,GAAG;CACb,CAAC;CACD,MAAM,EAAE;CACR,aAAa;CACb,MAAM,KAAK,KAAK;AAClB;;AAGA,SAAS,UAAU,GAAwB;CACzC,MAAM,EAAE,SAAS,OAAO;CACxB,MAAM,MAAM,OAAO,QAAoC;EACrD,MAAM,OAAO,IAAI,GAAG;CACtB;CACA,IAAI,gBAAgB,QAAQ,KAAK,GAC/B,OAAO,cAAc;EACnB,OAAO,QAAQ;EACf,OAAO,EAAE;EACT;EACA,QAAQ,GAAG;EACX,KAAK,GAAG;EACR,GAAI,QAAQ,YAAY,KAAA,IAAY,CAAC,IAAI,EAAE,SAAS,QAAQ,QAAQ;EACpE,GAAI,QAAQ,WAAW,KAAA,IAAY,CAAC,IAAI,EAAE,QAAQ,QAAQ,OAAO;CACnE,CAAC;CAEH,MAAM,cAAc,QAAQ,QAAQ,mBAAA;CACpC,OAAO,cAAc;EACnB,OAAO,QAAQ;EACf,OAAO,EAAE;EACT,QAAQ,cAAc;EACtB;EACA,QAAQ,GAAG;EACX,KAAK,GAAG;EACR,QAAQ,QAAQ;CAClB,CAAC;AACH;AAEA,SAAS,gBAAgB,OAA4D;CACnF,OAAO,OAAQ,MAAwC,gBAAgB;AACzE;AAEA,SAAS,aAAa,SAAiC,QAA4B;CACjF,OAAO;EACL,OAAO,QAAQ;EACf,UAAU,SAAS,SAAS,MAAM;EAClC,YAAY,QAAQ,QAAQ,cAAA;EAC5B,WAAW,QAAQ,QAAQ,aAAA;EAC3B;EACA,SAAS,QAAQ;EACjB,YAAY,QAAQ;EACpB,KAAK,QAAQ,OAAO,KAAK;EACzB,QAAQ,QAAQ;EAChB,OAAO,QAAQ;CACjB;AACF;;AAGA,SAAS,SAAS,SAAiC,QAA0C;CAC3F,MAAM,MAAmB,CAAC;CAC1B,IAAI,QAAQ,YAAY,KAAA,GACtB,IAAI,UAAU,qBAAqB;EACjC,SAAS,OAAO,QAAQ;GACtB,MAAM,QAAQ,MAAM,QAAQ,GAAG;EACjC;EACA,YAAY,QAAQ,QAAQ;EAC5B,GAAG,QAAQ;EACX;CACF,CAAC;CAEH,OAAO,OAAO,KAAK,QAAQ,QAAQ;CACnC,MAAM,sBAAM,IAAI,IAAyB;CACzC,KAAK,MAAM,CAAC,MAAM,YAAY,OAAO,QAAQ,GAAG,GAC9C,IAAI,YAAY,KAAA,GACd,IAAI,IAAI,MAAM,OAAO,YAAY,aAAa,EAAE,KAAK,QAAQ,IAAI,OAAO;CAG5E,OAAO;AACT"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import "node:path";
|
|
2
|
+
import "node:url";
|
|
3
|
+
import.meta.url;
|
|
4
|
+
import { REAL_TIMERS } from "../db-queue/timers.mjs";
|
|
5
|
+
import { idleDelayMs, isFastEmptyPoll } from "./idle.mjs";
|
|
6
|
+
// src/capture/consumer/loop.ts
|
|
7
|
+
/** Pause after a failed poll. */
|
|
8
|
+
const POLL_ERROR_DELAY_MS = 1e3;
|
|
9
|
+
/** Start consuming `queue` with `slots` concurrent jobs. */
|
|
10
|
+
function startSlotLoop(options) {
|
|
11
|
+
const state = {
|
|
12
|
+
busy: 0,
|
|
13
|
+
stopped: false,
|
|
14
|
+
emptyStreak: 0,
|
|
15
|
+
inFlight: /* @__PURE__ */ new Set(),
|
|
16
|
+
nudge: () => {}
|
|
17
|
+
};
|
|
18
|
+
const done = loop(options, state);
|
|
19
|
+
return {
|
|
20
|
+
get busy() {
|
|
21
|
+
return state.busy;
|
|
22
|
+
},
|
|
23
|
+
stop: async () => {
|
|
24
|
+
state.stopped = true;
|
|
25
|
+
state.nudge();
|
|
26
|
+
await done;
|
|
27
|
+
await Promise.allSettled(state.inFlight);
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
async function loop(options, state) {
|
|
32
|
+
while (!state.stopped) await step(options, state);
|
|
33
|
+
}
|
|
34
|
+
async function step(options, state) {
|
|
35
|
+
if (state.busy >= options.slots) {
|
|
36
|
+
await sleep(state, options.timers, null);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
state.busy += 1;
|
|
40
|
+
const polled = await pollTimed(options, state);
|
|
41
|
+
if (polled.job === null) {
|
|
42
|
+
state.busy -= 1;
|
|
43
|
+
await afterEmpty(options, state, polled.elapsedMs);
|
|
44
|
+
} else {
|
|
45
|
+
state.emptyStreak = 0;
|
|
46
|
+
launch(options, state, polled.job);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/** One poll with its duration; `elapsedMs` is null when the poll failed. */
|
|
50
|
+
async function pollTimed(options, state) {
|
|
51
|
+
const now = options.now ?? Date.now;
|
|
52
|
+
const startedAt = now();
|
|
53
|
+
const job = await pollOnce(options, state);
|
|
54
|
+
return job === void 0 ? {
|
|
55
|
+
job: null,
|
|
56
|
+
elapsedMs: null
|
|
57
|
+
} : {
|
|
58
|
+
job,
|
|
59
|
+
elapsedMs: now() - startedAt
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
/** Back off after an empty poll that did not block, or after a failed one (`elapsed` null). */
|
|
63
|
+
async function afterEmpty(options, state, elapsedMs) {
|
|
64
|
+
if (elapsedMs === null) {
|
|
65
|
+
await sleep(state, options.timers, POLL_ERROR_DELAY_MS);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
state.emptyStreak = isFastEmptyPoll(elapsedMs) ? state.emptyStreak + 1 : 0;
|
|
69
|
+
if (state.emptyStreak > 0) await sleep(state, options.timers, idleDelayMs(state.emptyStreak));
|
|
70
|
+
}
|
|
71
|
+
/** The next job, null when empty, undefined when the poll failed (logged). */
|
|
72
|
+
async function pollOnce(options, state) {
|
|
73
|
+
if (state.stopped) return null;
|
|
74
|
+
try {
|
|
75
|
+
return await options.queue.poll({ waitMs: options.waitMs });
|
|
76
|
+
} catch (error) {
|
|
77
|
+
options.logger.error({ err: error }, "poll failed");
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function launch(options, state, job) {
|
|
82
|
+
const running = options.run(job).catch((error) => {
|
|
83
|
+
options.logger.error({
|
|
84
|
+
err: error,
|
|
85
|
+
buildId: job.buildId
|
|
86
|
+
}, "job run failed");
|
|
87
|
+
}).finally(() => {
|
|
88
|
+
state.busy -= 1;
|
|
89
|
+
state.inFlight.delete(running);
|
|
90
|
+
state.nudge();
|
|
91
|
+
});
|
|
92
|
+
state.inFlight.add(running);
|
|
93
|
+
}
|
|
94
|
+
/** Wait for `ms` (or, without one, for a nudge); a nudge (slot freed, stop) ends it early. */
|
|
95
|
+
async function sleep(state, timers, ms) {
|
|
96
|
+
if (state.stopped) return;
|
|
97
|
+
const { promise, resolve } = Promise.withResolvers();
|
|
98
|
+
const wake = () => {
|
|
99
|
+
resolve(null);
|
|
100
|
+
};
|
|
101
|
+
const clock = timers ?? REAL_TIMERS;
|
|
102
|
+
const handle = ms === null ? null : clock.setTimeout(wake, ms);
|
|
103
|
+
state.nudge = wake;
|
|
104
|
+
await promise;
|
|
105
|
+
if (handle !== null) clock.clearTimeout(handle);
|
|
106
|
+
}
|
|
107
|
+
export { POLL_ERROR_DELAY_MS, startSlotLoop };
|
|
108
|
+
|
|
109
|
+
//# sourceMappingURL=loop.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loop.mjs","names":[],"sources":["../../../src/capture/consumer/loop.ts"],"sourcesContent":["/**\n * The slot-aware poll loop for queues that are not the database queue: a slot is taken BEFORE\n * polling (polling first would leave a job idle while every slot is busy, and its lease, a remote\n * queue's visibility or the forked parent's progress lease, would run out), polls block inside the\n * queue where they can, and an empty poll that returns at once backs off instead of spinning.\n */\nimport type { PollableCaptureQueue } from \"../../adapters/capture-queue.ts\";\nimport type { Logger } from \"../../logger.ts\";\nimport type { PollLoop } from \"../db-queue/poll-loop.ts\";\nimport { REAL_TIMERS, type QueueTimers } from \"../db-queue/timers.ts\";\nimport { idleDelayMs, isFastEmptyPoll } from \"./idle.ts\";\nimport type { ConsumerJob } from \"./types.ts\";\n\n/** Pause after a failed poll. */\nexport const POLL_ERROR_DELAY_MS = 1000;\n\n/** Options of {@link startSlotLoop}. */\nexport interface SlotLoopOptions {\n queue: PollableCaptureQueue;\n slots: number;\n /** Long-poll wait handed to `poll`. */\n waitMs: number;\n /** Run one claimed job to its end; must not reject. */\n run: (job: ConsumerJob) => Promise<void>;\n logger: Logger;\n now?: () => number;\n timers?: QueueTimers;\n}\n\ninterface LoopState {\n busy: number;\n stopped: boolean;\n emptyStreak: number;\n inFlight: Set<Promise<void>>;\n /** Resolves the current wait (slot freed, backoff sleep, stop) early. */\n nudge: () => void;\n}\n\n/** Start consuming `queue` with `slots` concurrent jobs. */\nexport function startSlotLoop(options: SlotLoopOptions): PollLoop {\n const state: LoopState = {\n busy: 0,\n stopped: false,\n emptyStreak: 0,\n inFlight: new Set(),\n nudge: () => {},\n };\n const done = loop(options, state);\n return {\n get busy() {\n return state.busy;\n },\n stop: async () => {\n state.stopped = true;\n state.nudge();\n await done;\n await Promise.allSettled(state.inFlight);\n },\n };\n}\n\nasync function loop(options: SlotLoopOptions, state: LoopState): Promise<void> {\n while (!state.stopped) {\n // oxlint-disable-next-line no-await-in-loop -- one step at a time is the loop\n await step(options, state);\n }\n}\n\nasync function step(options: SlotLoopOptions, state: LoopState): Promise<void> {\n if (state.busy >= options.slots) {\n await sleep(state, options.timers, null);\n return;\n }\n // The slot is held from here: released again unless a job came back to run in it.\n state.busy += 1;\n const polled = await pollTimed(options, state);\n if (polled.job === null) {\n state.busy -= 1;\n await afterEmpty(options, state, polled.elapsedMs);\n } else {\n state.emptyStreak = 0;\n launch(options, state, polled.job);\n }\n}\n\n/** One poll with its duration; `elapsedMs` is null when the poll failed. */\nasync function pollTimed(\n options: SlotLoopOptions,\n state: LoopState,\n): Promise<{ job: ConsumerJob | null; elapsedMs: number | null }> {\n const now = options.now ?? Date.now;\n const startedAt = now();\n const job = await pollOnce(options, state);\n return job === undefined ? { job: null, elapsedMs: null } : { job, elapsedMs: now() - startedAt };\n}\n\n/** Back off after an empty poll that did not block, or after a failed one (`elapsed` null). */\nasync function afterEmpty(\n options: SlotLoopOptions,\n state: LoopState,\n elapsedMs: number | null,\n): Promise<void> {\n if (elapsedMs === null) {\n await sleep(state, options.timers, POLL_ERROR_DELAY_MS);\n return;\n }\n // Queues without long-poll return instantly; back off instead of spinning.\n state.emptyStreak = isFastEmptyPoll(elapsedMs) ? state.emptyStreak + 1 : 0;\n if (state.emptyStreak > 0) {\n await sleep(state, options.timers, idleDelayMs(state.emptyStreak));\n }\n}\n\n/** The next job, null when empty, undefined when the poll failed (logged). */\nasync function pollOnce(\n options: SlotLoopOptions,\n state: LoopState,\n): Promise<ConsumerJob | null | undefined> {\n if (state.stopped) {\n return null;\n }\n try {\n return await options.queue.poll({ waitMs: options.waitMs });\n } catch (error) {\n options.logger.error({ err: error }, \"poll failed\");\n return undefined;\n }\n}\n\nfunction launch(options: SlotLoopOptions, state: LoopState, job: ConsumerJob): void {\n const running = options\n .run(job)\n .catch((error: unknown) => {\n options.logger.error({ err: error, buildId: job.buildId }, \"job run failed\");\n })\n .finally(() => {\n state.busy -= 1;\n state.inFlight.delete(running);\n state.nudge();\n });\n state.inFlight.add(running);\n}\n\n/** Wait for `ms` (or, without one, for a nudge); a nudge (slot freed, stop) ends it early. */\nasync function sleep(\n state: LoopState,\n timers: QueueTimers | undefined,\n ms: number | null,\n): Promise<void> {\n if (state.stopped) {\n return;\n }\n const { promise, resolve } = Promise.withResolvers<null>();\n const wake = (): void => {\n resolve(null);\n };\n const clock = timers ?? REAL_TIMERS;\n const handle = ms === null ? null : clock.setTimeout(wake, ms);\n state.nudge = wake;\n await promise;\n if (handle !== null) {\n clock.clearTimeout(handle);\n }\n}\n"],"mappings":";;;;;;;AAcA,MAAa,sBAAsB;;AAyBnC,SAAgB,cAAc,SAAoC;CAChE,MAAM,QAAmB;EACvB,MAAM;EACN,SAAS;EACT,aAAa;EACb,0BAAU,IAAI,IAAI;EAClB,aAAa,CAAC;CAChB;CACA,MAAM,OAAO,KAAK,SAAS,KAAK;CAChC,OAAO;EACL,IAAI,OAAO;GACT,OAAO,MAAM;EACf;EACA,MAAM,YAAY;GAChB,MAAM,UAAU;GAChB,MAAM,MAAM;GACZ,MAAM;GACN,MAAM,QAAQ,WAAW,MAAM,QAAQ;EACzC;CACF;AACF;AAEA,eAAe,KAAK,SAA0B,OAAiC;CAC7E,OAAO,CAAC,MAAM,SAEZ,MAAM,KAAK,SAAS,KAAK;AAE7B;AAEA,eAAe,KAAK,SAA0B,OAAiC;CAC7E,IAAI,MAAM,QAAQ,QAAQ,OAAO;EAC/B,MAAM,MAAM,OAAO,QAAQ,QAAQ,IAAI;EACvC;CACF;CAEA,MAAM,QAAQ;CACd,MAAM,SAAS,MAAM,UAAU,SAAS,KAAK;CAC7C,IAAI,OAAO,QAAQ,MAAM;EACvB,MAAM,QAAQ;EACd,MAAM,WAAW,SAAS,OAAO,OAAO,SAAS;CACnD,OAAO;EACL,MAAM,cAAc;EACpB,OAAO,SAAS,OAAO,OAAO,GAAG;CACnC;AACF;;AAGA,eAAe,UACb,SACA,OACgE;CAChE,MAAM,MAAM,QAAQ,OAAO,KAAK;CAChC,MAAM,YAAY,IAAI;CACtB,MAAM,MAAM,MAAM,SAAS,SAAS,KAAK;CACzC,OAAO,QAAQ,KAAA,IAAY;EAAE,KAAK;EAAM,WAAW;CAAK,IAAI;EAAE;EAAK,WAAW,IAAI,IAAI;CAAU;AAClG;;AAGA,eAAe,WACb,SACA,OACA,WACe;CACf,IAAI,cAAc,MAAM;EACtB,MAAM,MAAM,OAAO,QAAQ,QAAQ,mBAAmB;EACtD;CACF;CAEA,MAAM,cAAc,gBAAgB,SAAS,IAAI,MAAM,cAAc,IAAI;CACzE,IAAI,MAAM,cAAc,GACtB,MAAM,MAAM,OAAO,QAAQ,QAAQ,YAAY,MAAM,WAAW,CAAC;AAErE;;AAGA,eAAe,SACb,SACA,OACyC;CACzC,IAAI,MAAM,SACR,OAAO;CAET,IAAI;EACF,OAAO,MAAM,QAAQ,MAAM,KAAK,EAAE,QAAQ,QAAQ,OAAO,CAAC;CAC5D,SAAS,OAAO;EACd,QAAQ,OAAO,MAAM,EAAE,KAAK,MAAM,GAAG,aAAa;EAClD;CACF;AACF;AAEA,SAAS,OAAO,SAA0B,OAAkB,KAAwB;CAClF,MAAM,UAAU,QACb,IAAI,GAAG,CAAC,CACR,OAAO,UAAmB;EACzB,QAAQ,OAAO,MAAM;GAAE,KAAK;GAAO,SAAS,IAAI;EAAQ,GAAG,gBAAgB;CAC7E,CAAC,CAAC,CACD,cAAc;EACb,MAAM,QAAQ;EACd,MAAM,SAAS,OAAO,OAAO;EAC7B,MAAM,MAAM;CACd,CAAC;CACH,MAAM,SAAS,IAAI,OAAO;AAC5B;;AAGA,eAAe,MACb,OACA,QACA,IACe;CACf,IAAI,MAAM,SACR;CAEF,MAAM,EAAE,SAAS,YAAY,QAAQ,cAAoB;CACzD,MAAM,aAAmB;EACvB,QAAQ,IAAI;CACd;CACA,MAAM,QAAQ,UAAU;CACxB,MAAM,SAAS,OAAO,OAAO,OAAO,MAAM,WAAW,MAAM,EAAE;CAC7D,MAAM,QAAQ;CACd,MAAM;CACN,IAAI,WAAW,MACb,MAAM,aAAa,MAAM;AAE7B"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import __tsdown_shims_path from 'node:path';
|
|
2
|
+
import __tsdown_shims_url from 'node:url';
|
|
3
|
+
import "../../logger.mjs";
|
|
4
|
+
import { PollableCaptureQueue } from "../../adapter/capture-queue.mjs";
|
|
5
|
+
import { CaptureJobKind } from "../../schema/capture-job.mjs";
|
|
6
|
+
/** How a consumed job ended. */
|
|
7
|
+
type JobOutcome = "completed" | "retried" | "failed" | "cancelled" | "lost" | "dropped" | "unhandled";
|
|
8
|
+
/** A job the consumer finished with. */
|
|
9
|
+
interface JobFinishedEvent {
|
|
10
|
+
kind: CaptureJobKind;
|
|
11
|
+
outcome: JobOutcome;
|
|
12
|
+
/** Wall time from claim to settle. */
|
|
13
|
+
durationMs: number;
|
|
14
|
+
/** Delivery number from 1. */
|
|
15
|
+
attempt: number;
|
|
16
|
+
}
|
|
17
|
+
/** A reading of the queue's backlog. */
|
|
18
|
+
interface QueueSample {
|
|
19
|
+
/** Jobs waiting to be claimed. */
|
|
20
|
+
depth: number;
|
|
21
|
+
/** Jobs running anywhere. */
|
|
22
|
+
running: number;
|
|
23
|
+
/** Age of the oldest waiting job; 0 when none waits. */
|
|
24
|
+
oldestAgeMs: number;
|
|
25
|
+
}
|
|
26
|
+
/** Hooks the host may pass. Both are optional and must not throw. */
|
|
27
|
+
interface ConsumerMetrics {
|
|
28
|
+
jobFinished?(event: JobFinishedEvent): void;
|
|
29
|
+
/** Called every `sampleEveryMs` with the queue's depth and oldest waiting job. Costs one read. */
|
|
30
|
+
queueSample?(sample: QueueSample): void;
|
|
31
|
+
/** Sampling interval (default {@link DEFAULT_SAMPLE_MS}); sampling is off without `queueSample`. */
|
|
32
|
+
sampleEveryMs?: number;
|
|
33
|
+
}
|
|
34
|
+
/** Default interval between queue samples. */
|
|
35
|
+
declare const DEFAULT_SAMPLE_MS = 30000;
|
|
36
|
+
/** Read the backlog of `queue` from its `active()` view. */
|
|
37
|
+
declare function sampleQueue(queue: PollableCaptureQueue, now: number): Promise<QueueSample>;
|
|
38
|
+
export { ConsumerMetrics, DEFAULT_SAMPLE_MS, JobFinishedEvent, JobOutcome, QueueSample, sampleQueue };
|
|
39
|
+
//# sourceMappingURL=metrics.d.mts.map
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import "node:path";
|
|
2
|
+
import "node:url";
|
|
3
|
+
import.meta.url;
|
|
4
|
+
import { repeatEvery } from "../db-queue/timers.mjs";
|
|
5
|
+
// src/capture/consumer/metrics.ts
|
|
6
|
+
/** Default interval between queue samples. */
|
|
7
|
+
const DEFAULT_SAMPLE_MS = 3e4;
|
|
8
|
+
/** Read the backlog of `queue` from its `active()` view. */
|
|
9
|
+
async function sampleQueue(queue, now) {
|
|
10
|
+
const entries = await queue.active();
|
|
11
|
+
const waiting = entries.filter((entry) => entry.status === "queued");
|
|
12
|
+
let oldest = Number.POSITIVE_INFINITY;
|
|
13
|
+
for (const entry of waiting) oldest = Math.min(oldest, Date.parse(entry.queuedAt));
|
|
14
|
+
return {
|
|
15
|
+
depth: waiting.length,
|
|
16
|
+
running: entries.length - waiting.length,
|
|
17
|
+
oldestAgeMs: waiting.length === 0 ? 0 : Math.max(0, now - oldest)
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
/** Start sampling when the host asked for it; returns the stop function. */
|
|
21
|
+
function startQueueSampling(queue, metrics, env) {
|
|
22
|
+
if (metrics?.queueSample === void 0) return () => {};
|
|
23
|
+
return repeatEvery(env.timers, metrics.sampleEveryMs ?? 3e4, () => {
|
|
24
|
+
sampleQueue(queue, env.now()).then((sample) => metrics.queueSample?.(sample), (error) => {
|
|
25
|
+
env.logger.warn({ err: error }, "queue sample failed");
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
/** Tell the host a job ended; a throwing hook never reaches the consumer. */
|
|
30
|
+
function reportFinished(metrics, event) {
|
|
31
|
+
try {
|
|
32
|
+
metrics?.jobFinished?.(event);
|
|
33
|
+
} catch {}
|
|
34
|
+
}
|
|
35
|
+
export { DEFAULT_SAMPLE_MS, reportFinished, sampleQueue, startQueueSampling };
|
|
36
|
+
|
|
37
|
+
//# sourceMappingURL=metrics.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metrics.mjs","names":[],"sources":["../../../src/capture/consumer/metrics.ts"],"sourcesContent":["/**\n * Optional observation hooks of the consumer. Core owns no metrics library (the single OTEL owner is\n * `@storyshelf/observability`); the app adapts these two callbacks to its instruments.\n */\nimport type { PollableCaptureQueue } from \"../../adapters/capture-queue.ts\";\nimport type { Logger } from \"../../logger.ts\";\nimport type { CaptureJobKind } from \"../../schema/capture-job.ts\";\nimport { repeatEvery, type QueueTimers } from \"../db-queue/timers.ts\";\n\n/** How a consumed job ended. */\nexport type JobOutcome =\n | \"completed\"\n | \"retried\"\n | \"failed\"\n | \"cancelled\"\n | \"lost\"\n | \"dropped\"\n | \"unhandled\";\n\n/** A job the consumer finished with. */\nexport interface JobFinishedEvent {\n kind: CaptureJobKind;\n outcome: JobOutcome;\n /** Wall time from claim to settle. */\n durationMs: number;\n /** Delivery number from 1. */\n attempt: number;\n}\n\n/** A reading of the queue's backlog. */\nexport interface QueueSample {\n /** Jobs waiting to be claimed. */\n depth: number;\n /** Jobs running anywhere. */\n running: number;\n /** Age of the oldest waiting job; 0 when none waits. */\n oldestAgeMs: number;\n}\n\n/** Hooks the host may pass. Both are optional and must not throw. */\nexport interface ConsumerMetrics {\n jobFinished?(event: JobFinishedEvent): void;\n /** Called every `sampleEveryMs` with the queue's depth and oldest waiting job. Costs one read. */\n queueSample?(sample: QueueSample): void;\n /** Sampling interval (default {@link DEFAULT_SAMPLE_MS}); sampling is off without `queueSample`. */\n sampleEveryMs?: number;\n}\n\n/** Default interval between queue samples. */\nexport const DEFAULT_SAMPLE_MS = 30_000;\n\n/** Read the backlog of `queue` from its `active()` view. */\nexport async function sampleQueue(queue: PollableCaptureQueue, now: number): Promise<QueueSample> {\n const entries = await queue.active();\n const waiting = entries.filter((entry) => entry.status === \"queued\");\n let oldest = Number.POSITIVE_INFINITY;\n for (const entry of waiting) {\n oldest = Math.min(oldest, Date.parse(entry.queuedAt));\n }\n return {\n depth: waiting.length,\n running: entries.length - waiting.length,\n oldestAgeMs: waiting.length === 0 ? 0 : Math.max(0, now - oldest),\n };\n}\n\n/** Start sampling when the host asked for it; returns the stop function. */\nexport function startQueueSampling(\n queue: PollableCaptureQueue,\n metrics: ConsumerMetrics | undefined,\n env: { now: () => number; timers: QueueTimers; logger: Logger },\n): () => void {\n if (metrics?.queueSample === undefined) {\n return () => {};\n }\n return repeatEvery(env.timers, metrics.sampleEveryMs ?? DEFAULT_SAMPLE_MS, () => {\n sampleQueue(queue, env.now()).then(\n (sample) => metrics.queueSample?.(sample),\n (error: unknown) => {\n env.logger.warn({ err: error }, \"queue sample failed\");\n },\n );\n });\n}\n\n/** Tell the host a job ended; a throwing hook never reaches the consumer. */\nexport function reportFinished(\n metrics: ConsumerMetrics | undefined,\n event: JobFinishedEvent,\n): void {\n try {\n metrics?.jobFinished?.(event);\n } catch {\n // A metrics hook must never break job handling.\n }\n}\n"],"mappings":";;;;;;AAiDA,MAAa,oBAAoB;;AAGjC,eAAsB,YAAY,OAA6B,KAAmC;CAChG,MAAM,UAAU,MAAM,MAAM,OAAO;CACnC,MAAM,UAAU,QAAQ,QAAQ,UAAU,MAAM,WAAW,QAAQ;CACnE,IAAI,SAAS,OAAO;CACpB,KAAK,MAAM,SAAS,SAClB,SAAS,KAAK,IAAI,QAAQ,KAAK,MAAM,MAAM,QAAQ,CAAC;CAEtD,OAAO;EACL,OAAO,QAAQ;EACf,SAAS,QAAQ,SAAS,QAAQ;EAClC,aAAa,QAAQ,WAAW,IAAI,IAAI,KAAK,IAAI,GAAG,MAAM,MAAM;CAClE;AACF;;AAGA,SAAgB,mBACd,OACA,SACA,KACY;CACZ,IAAI,SAAS,gBAAgB,KAAA,GAC3B,aAAa,CAAC;CAEhB,OAAO,YAAY,IAAI,QAAQ,QAAQ,iBAAA,WAA0C;EAC/E,YAAY,OAAO,IAAI,IAAI,CAAC,CAAC,CAAC,MAC3B,WAAW,QAAQ,cAAc,MAAM,IACvC,UAAmB;GAClB,IAAI,OAAO,KAAK,EAAE,KAAK,MAAM,GAAG,qBAAqB;EACvD,CACF;CACF,CAAC;AACH;;AAGA,SAAgB,eACd,SACA,OACM;CACN,IAAI;EACF,SAAS,cAAc,KAAK;CAC9B,QAAQ,CAER;AACF"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import __tsdown_shims_path from 'node:path';
|
|
2
|
+
import __tsdown_shims_url from 'node:url';
|
|
3
|
+
import { PollableJob } from "../../adapter/capture-queue.mjs";
|
|
4
|
+
/** Retries after the first attempt before a job is failed for good (three attempts in total). */
|
|
5
|
+
declare const DEFAULT_MAX_RETRIES = 2;
|
|
6
|
+
/** Base of the exponential retry delay: the n-th retry waits `base * 2^n` ms. */
|
|
7
|
+
declare const DEFAULT_BACKOFF_MS = 1000;
|
|
8
|
+
/** Longest a retry waits, however many attempts were made. */
|
|
9
|
+
declare const MAX_BACKOFF_MS = 30000;
|
|
10
|
+
/** Long-poll wait handed to queues that block inside `poll` (SQS, Redis, Service Bus, IPC). */
|
|
11
|
+
declare const DEFAULT_WAIT_TIME_SECONDS = 20;
|
|
12
|
+
/** Retry knobs of the consumer. */
|
|
13
|
+
interface RetryConfig {
|
|
14
|
+
/** Retries after the first attempt (default {@link DEFAULT_MAX_RETRIES}). */
|
|
15
|
+
maxRetries?: number;
|
|
16
|
+
/** Base delay of the exponential backoff in ms (default {@link DEFAULT_BACKOFF_MS}). */
|
|
17
|
+
backoffMs?: number;
|
|
18
|
+
}
|
|
19
|
+
/** Delay before retry number `attempts` (0 = the first retry): `base * 2^attempts`, capped. */
|
|
20
|
+
declare function backoffDelayMs(attempts: number, baseMs?: number, maxMs?: number): number;
|
|
21
|
+
/**
|
|
22
|
+
* Whether this delivery is the job's last. A queue that enforces its own limit (the database queue
|
|
23
|
+
* stamps `maxAttempts` on every job and fails the job when the last attempt is nacked) decides:
|
|
24
|
+
* `attempt >= maxAttempts`. For queues that only count deliveries, `attempts >= maxRetries`.
|
|
25
|
+
*/
|
|
26
|
+
declare function isFinalAttempt(job: PollableJob & {
|
|
27
|
+
attempt?: number;
|
|
28
|
+
maxAttempts?: number;
|
|
29
|
+
}, maxRetries?: number): boolean;
|
|
30
|
+
export { DEFAULT_BACKOFF_MS, DEFAULT_MAX_RETRIES, DEFAULT_WAIT_TIME_SECONDS, MAX_BACKOFF_MS, RetryConfig, backoffDelayMs, isFinalAttempt };
|
|
31
|
+
//# sourceMappingURL=retry.d.mts.map
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import "node:path";
|
|
2
|
+
import "node:url";
|
|
3
|
+
import.meta.url;
|
|
4
|
+
// src/capture/consumer/retry.ts
|
|
5
|
+
/** Retries after the first attempt before a job is failed for good (three attempts in total). */
|
|
6
|
+
const DEFAULT_MAX_RETRIES = 2;
|
|
7
|
+
/** Base of the exponential retry delay: the n-th retry waits `base * 2^n` ms. */
|
|
8
|
+
const DEFAULT_BACKOFF_MS = 1e3;
|
|
9
|
+
/** Longest a retry waits, however many attempts were made. */
|
|
10
|
+
const MAX_BACKOFF_MS = 3e4;
|
|
11
|
+
/** Long-poll wait handed to queues that block inside `poll` (SQS, Redis, Service Bus, IPC). */
|
|
12
|
+
const DEFAULT_WAIT_TIME_SECONDS = 20;
|
|
13
|
+
/** Delay before retry number `attempts` (0 = the first retry): `base * 2^attempts`, capped. */
|
|
14
|
+
function backoffDelayMs(attempts, baseMs = DEFAULT_BACKOFF_MS, maxMs = MAX_BACKOFF_MS) {
|
|
15
|
+
return Math.min(maxMs, baseMs * 2 ** attempts);
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Whether this delivery is the job's last. A queue that enforces its own limit (the database queue
|
|
19
|
+
* stamps `maxAttempts` on every job and fails the job when the last attempt is nacked) decides:
|
|
20
|
+
* `attempt >= maxAttempts`. For queues that only count deliveries, `attempts >= maxRetries`.
|
|
21
|
+
*/
|
|
22
|
+
function isFinalAttempt(job, maxRetries = 2) {
|
|
23
|
+
const attempts = job.attempts ?? 0;
|
|
24
|
+
if (typeof job.maxAttempts === "number") return (job.attempt ?? attempts + 1) >= job.maxAttempts;
|
|
25
|
+
return attempts >= maxRetries;
|
|
26
|
+
}
|
|
27
|
+
/** Retries a job may still get, as the capture dispatch counts them (`maxAttempts - 1` when known). */
|
|
28
|
+
function retriesOf(job, fallback = 2) {
|
|
29
|
+
return typeof job.maxAttempts === "number" ? Math.max(0, job.maxAttempts - 1) : fallback;
|
|
30
|
+
}
|
|
31
|
+
export { DEFAULT_BACKOFF_MS, DEFAULT_MAX_RETRIES, DEFAULT_WAIT_TIME_SECONDS, MAX_BACKOFF_MS, backoffDelayMs, isFinalAttempt, retriesOf };
|
|
32
|
+
|
|
33
|
+
//# sourceMappingURL=retry.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"retry.mjs","names":[],"sources":["../../../src/capture/consumer/retry.ts"],"sourcesContent":["/**\n * Retry policy of the capture consumer, in one place: how many times a failing job is redelivered,\n * how long each retry waits, and which delivery is the last. The numbers match ADR 0006,\n * decision 11 (`testing.retries` defaults to 2: three attempts in total).\n */\nimport type { PollableJob } from \"../../adapters/capture-queue.ts\";\n\n/** Retries after the first attempt before a job is failed for good (three attempts in total). */\nexport const DEFAULT_MAX_RETRIES = 2;\n/** Base of the exponential retry delay: the n-th retry waits `base * 2^n` ms. */\nexport const DEFAULT_BACKOFF_MS = 1000;\n/** Longest a retry waits, however many attempts were made. */\nexport const MAX_BACKOFF_MS = 30_000;\n/** Long-poll wait handed to queues that block inside `poll` (SQS, Redis, Service Bus, IPC). */\nexport const DEFAULT_WAIT_TIME_SECONDS = 20;\n\n/** Retry knobs of the consumer. */\nexport interface RetryConfig {\n /** Retries after the first attempt (default {@link DEFAULT_MAX_RETRIES}). */\n maxRetries?: number;\n /** Base delay of the exponential backoff in ms (default {@link DEFAULT_BACKOFF_MS}). */\n backoffMs?: number;\n}\n\n/** Delay before retry number `attempts` (0 = the first retry): `base * 2^attempts`, capped. */\nexport function backoffDelayMs(\n attempts: number,\n baseMs: number = DEFAULT_BACKOFF_MS,\n maxMs: number = MAX_BACKOFF_MS,\n): number {\n return Math.min(maxMs, baseMs * 2 ** attempts);\n}\n\n/**\n * Whether this delivery is the job's last. A queue that enforces its own limit (the database queue\n * stamps `maxAttempts` on every job and fails the job when the last attempt is nacked) decides:\n * `attempt >= maxAttempts`. For queues that only count deliveries, `attempts >= maxRetries`.\n */\nexport function isFinalAttempt(\n job: PollableJob & { attempt?: number; maxAttempts?: number },\n maxRetries: number = DEFAULT_MAX_RETRIES,\n): boolean {\n const attempts = job.attempts ?? 0;\n if (typeof job.maxAttempts === \"number\") {\n return (job.attempt ?? attempts + 1) >= job.maxAttempts;\n }\n return attempts >= maxRetries;\n}\n\n/** Retries a job may still get, as the capture dispatch counts them (`maxAttempts - 1` when known). */\nexport function retriesOf(\n job: PollableJob & { maxAttempts?: number },\n fallback: number = DEFAULT_MAX_RETRIES,\n): number {\n return typeof job.maxAttempts === \"number\" ? Math.max(0, job.maxAttempts - 1) : fallback;\n}\n"],"mappings":";;;;;AAQA,MAAa,sBAAsB;;AAEnC,MAAa,qBAAqB;;AAElC,MAAa,iBAAiB;;AAE9B,MAAa,4BAA4B;;AAWzC,SAAgB,eACd,UACA,SAAiB,oBACjB,QAAgB,gBACR;CACR,OAAO,KAAK,IAAI,OAAO,SAAS,KAAK,QAAQ;AAC/C;;;;;;AAOA,SAAgB,eACd,KACA,aAAA,GACS;CACT,MAAM,WAAW,IAAI,YAAY;CACjC,IAAI,OAAO,IAAI,gBAAgB,UAC7B,QAAQ,IAAI,WAAW,WAAW,MAAM,IAAI;CAE9C,OAAO,YAAY;AACrB;;AAGA,SAAgB,UACd,KACA,WAAA,GACQ;CACR,OAAO,OAAO,IAAI,gBAAgB,WAAW,KAAK,IAAI,GAAG,IAAI,cAAc,CAAC,IAAI;AAClF"}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import "node:path";
|
|
2
|
+
import "node:url";
|
|
3
|
+
import.meta.url;
|
|
4
|
+
import { roundedTimings, runWithTimings } from "../../utils/timing.mjs";
|
|
5
|
+
import { backoffDelayMs, isFinalAttempt } from "./retry.mjs";
|
|
6
|
+
import { keepJobLease } from "../db-queue/keeper.mjs";
|
|
7
|
+
import { reportFinished } from "./metrics.mjs";
|
|
8
|
+
// src/capture/consumer/run-job.ts
|
|
9
|
+
/** Run `job` in the slot the caller holds; never rejects. */
|
|
10
|
+
async function runJob(rt, job) {
|
|
11
|
+
const kind = job.kind ?? "capture";
|
|
12
|
+
const startedAt = rt.now();
|
|
13
|
+
const logger = rt.logger.child({
|
|
14
|
+
buildId: job.buildId,
|
|
15
|
+
jobId: job.jobId ?? job.receipt,
|
|
16
|
+
kind,
|
|
17
|
+
reqId: job.reqId
|
|
18
|
+
});
|
|
19
|
+
const finish = (outcome) => {
|
|
20
|
+
reportFinished(rt.metrics, {
|
|
21
|
+
kind,
|
|
22
|
+
outcome,
|
|
23
|
+
durationMs: rt.now() - startedAt,
|
|
24
|
+
attempt: job.attempt ?? (job.attempts ?? 0) + 1
|
|
25
|
+
});
|
|
26
|
+
};
|
|
27
|
+
try {
|
|
28
|
+
finish(await execute(rt, job, logger));
|
|
29
|
+
} catch (error) {
|
|
30
|
+
logger.error({ err: error }, "failed to settle job");
|
|
31
|
+
finish("failed");
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
async function execute(rt, job, logger) {
|
|
35
|
+
const handler = rt.handlers.get(job.kind ?? "capture");
|
|
36
|
+
const refused = await refuse(rt, job, {
|
|
37
|
+
handler,
|
|
38
|
+
logger
|
|
39
|
+
});
|
|
40
|
+
if (refused !== null || handler === void 0) return refused ?? "unhandled";
|
|
41
|
+
return await runHandler(rt, {
|
|
42
|
+
job,
|
|
43
|
+
handler,
|
|
44
|
+
logger
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
/** Fail a job nobody can run (no handler for its kind) or that is malformed; null when runnable. */
|
|
48
|
+
async function refuse(rt, job, held) {
|
|
49
|
+
const { handler, logger } = held;
|
|
50
|
+
if (handler === void 0) {
|
|
51
|
+
logger.error("no handler registered for job kind; failing the job");
|
|
52
|
+
await failJob(rt, job, `no handler registered for job kind "${job.kind ?? "capture"}"`, logger);
|
|
53
|
+
return "unhandled";
|
|
54
|
+
}
|
|
55
|
+
if (handler.accepts?.(job) === false) {
|
|
56
|
+
logger.warn({ attempts: job.attempts }, "dropping malformed queue job");
|
|
57
|
+
await failJob(rt, job, "malformed job", logger);
|
|
58
|
+
return "dropped";
|
|
59
|
+
}
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
async function runHandler(rt, held) {
|
|
63
|
+
const { job, handler, logger } = held;
|
|
64
|
+
const keeper = keepLease(rt, job, logger);
|
|
65
|
+
const touch = () => {
|
|
66
|
+
keeper.touch();
|
|
67
|
+
rt.onProgress?.(job);
|
|
68
|
+
};
|
|
69
|
+
logger.info("consumer picked up job");
|
|
70
|
+
const run = invoke(handler, job, {
|
|
71
|
+
logger,
|
|
72
|
+
touch
|
|
73
|
+
});
|
|
74
|
+
try {
|
|
75
|
+
const raced = await Promise.race([run, endOf(keeper)]);
|
|
76
|
+
return raced.kind === "ended" ? await abandon(rt, held, raced.end) : await settle(rt, held, raced);
|
|
77
|
+
} finally {
|
|
78
|
+
keeper.stop();
|
|
79
|
+
run.catch(() => {});
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
function keepLease(rt, job, logger) {
|
|
83
|
+
return keepJobLease(rt.queue, job, {
|
|
84
|
+
...rt.lease,
|
|
85
|
+
now: rt.now,
|
|
86
|
+
...rt.timers === void 0 ? {} : { timers: rt.timers },
|
|
87
|
+
logger
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
/** Run the handler, timing it; its failure is a value so the race can carry it. */
|
|
91
|
+
async function invoke(handler, job, context) {
|
|
92
|
+
try {
|
|
93
|
+
const { timings } = await runWithTimings(async () => {
|
|
94
|
+
await handler.run(job, context);
|
|
95
|
+
});
|
|
96
|
+
context.logger.info({ timings: roundedTimings(timings) }, "consumer job timings");
|
|
97
|
+
return { kind: "settled" };
|
|
98
|
+
} catch (error) {
|
|
99
|
+
return {
|
|
100
|
+
kind: "settled",
|
|
101
|
+
error
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
async function endOf(keeper) {
|
|
106
|
+
return {
|
|
107
|
+
kind: "ended",
|
|
108
|
+
end: await keeper.ended
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
/** The handler returned: ack, or retry / fail on its error. */
|
|
112
|
+
async function settle(rt, held, raced) {
|
|
113
|
+
if (!("error" in raced)) {
|
|
114
|
+
await rt.queue.ack(held.job);
|
|
115
|
+
held.logger.info("consumer completed job");
|
|
116
|
+
return "completed";
|
|
117
|
+
}
|
|
118
|
+
return await retryOrFail(rt, held, raced.error);
|
|
119
|
+
}
|
|
120
|
+
/** The handler threw: requeue with backoff, or fail the job on its last attempt. */
|
|
121
|
+
async function retryOrFail(rt, held, error) {
|
|
122
|
+
const { job, logger } = held;
|
|
123
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
124
|
+
const attempts = job.attempts ?? 0;
|
|
125
|
+
if (isFinalAttempt(job, rt.maxRetries)) {
|
|
126
|
+
await failJob(rt, job, message, logger);
|
|
127
|
+
logger.error({
|
|
128
|
+
err: error,
|
|
129
|
+
attempts
|
|
130
|
+
}, "capture permanently failed");
|
|
131
|
+
return "failed";
|
|
132
|
+
}
|
|
133
|
+
await requeue(rt, held, {
|
|
134
|
+
error,
|
|
135
|
+
message,
|
|
136
|
+
attempts
|
|
137
|
+
});
|
|
138
|
+
return "retried";
|
|
139
|
+
}
|
|
140
|
+
/** Put the job back with the next backoff delay. */
|
|
141
|
+
async function requeue(rt, held, failure) {
|
|
142
|
+
const { backoff } = held.handler;
|
|
143
|
+
const delayMs = backoffDelayMs(failure.attempts, backoff?.baseMs ?? rt.backoffMs ?? 1e3, backoff?.maxMs);
|
|
144
|
+
await rt.queue.nack(held.job, {
|
|
145
|
+
requeue: true,
|
|
146
|
+
delayMs,
|
|
147
|
+
error: failure.message
|
|
148
|
+
});
|
|
149
|
+
held.logger.warn({
|
|
150
|
+
err: failure.error,
|
|
151
|
+
attempts: failure.attempts,
|
|
152
|
+
delayMs
|
|
153
|
+
}, "job failed, requeued");
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* The lease is gone or the job was cancelled while the handler ran: stop the work. A lost lease
|
|
157
|
+
* means another consumer owns the job (it settles it); a cancel is settled here as `cancelled`.
|
|
158
|
+
*/
|
|
159
|
+
async function abandon(rt, held, end) {
|
|
160
|
+
const { job, handler, logger } = held;
|
|
161
|
+
await handler.cancel?.(job).catch((error) => {
|
|
162
|
+
logger.warn({ err: error }, "failed to cancel the running job");
|
|
163
|
+
});
|
|
164
|
+
if (end === "lost") {
|
|
165
|
+
logger.warn("queue lease lost, aborting job");
|
|
166
|
+
return "lost";
|
|
167
|
+
}
|
|
168
|
+
logger.info("job cancelled, aborting");
|
|
169
|
+
await rt.queue.nack(job, {
|
|
170
|
+
requeue: false,
|
|
171
|
+
error: "cancelled"
|
|
172
|
+
});
|
|
173
|
+
return "cancelled";
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Final failure: nack without requeue so the queue records it failed. A queue that does not count
|
|
177
|
+
* attempts itself falls back to ack when the nack fails, so a poison job never loops; the database
|
|
178
|
+
* queue (it states `maxAttempts`) ends the job by itself, so there a failed nack is left to it.
|
|
179
|
+
*/
|
|
180
|
+
async function failJob(rt, job, message, logger) {
|
|
181
|
+
try {
|
|
182
|
+
await rt.queue.nack(job, {
|
|
183
|
+
requeue: false,
|
|
184
|
+
error: message
|
|
185
|
+
});
|
|
186
|
+
} catch (nackError) {
|
|
187
|
+
if (typeof job.maxAttempts === "number") throw nackError;
|
|
188
|
+
logger.warn({ err: nackError }, "final nack failed, acknowledging the job instead");
|
|
189
|
+
await rt.queue.ack(job);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
export { runJob };
|
|
193
|
+
|
|
194
|
+
//# sourceMappingURL=run-job.mjs.map
|