@storyshelf/core 0.6.1 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +49 -32
- package/dist/adapter/capture-queue.d.mts +44 -2
- package/dist/adapter/capture-runner.d.mts +127 -10
- package/dist/adapter/capture-runner.mjs +5 -12
- package/dist/adapter/database.d.mts +27 -8
- package/dist/adapter/database.mjs.map +1 -1
- package/dist/adapter/design.d.mts +85 -0
- package/dist/adapter/design.mjs +28 -0
- package/dist/adapter/design.mjs.map +1 -0
- package/dist/adapter/metadata.d.mts +3 -10
- package/dist/adapter/notifier.d.mts +6 -3
- package/dist/adapter/notifier.mjs +5 -1
- package/dist/adapter/oauth.d.mts +52 -0
- package/dist/adapter/setup.d.mts +4 -5
- package/dist/adapter/setup.mjs +2 -1
- package/dist/adapter/setup.mjs.map +1 -1
- package/dist/adapter/webhook-events.mjs +1 -1
- package/dist/adapters/capture-target-offers.d.mts +28 -0
- package/dist/adapters/capture-target-offers.mjs +58 -0
- package/dist/adapters/capture-target-offers.mjs.map +1 -0
- package/dist/adapters/capture-target.d.mts +71 -0
- package/dist/adapters/capture-target.mjs +76 -0
- package/dist/adapters/capture-target.mjs.map +1 -0
- package/dist/adapters/notifier/build-comment.d.mts +24 -0
- package/dist/adapters/notifier/build-comment.mjs +24 -0
- package/dist/adapters/notifier/build-comment.mjs.map +1 -0
- package/dist/adapters/notifier/build-state.d.mts +99 -0
- package/dist/adapters/notifier/build-state.mjs +218 -0
- package/dist/adapters/notifier/build-state.mjs.map +1 -0
- package/dist/adapters/notifier/events.d.mts +4 -2
- package/dist/adapters/notifier/events.mjs +31 -18
- package/dist/adapters/notifier/events.mjs.map +1 -1
- package/dist/adapters/notifier/provider.d.mts +53 -7
- package/dist/adapters/notifier/provider.mjs +12 -0
- package/dist/adapters/notifier/provider.mjs.map +1 -0
- package/dist/adapters/notifier/state.d.mts +24 -0
- package/dist/adapters/notifier/state.mjs +66 -0
- package/dist/adapters/notifier/state.mjs.map +1 -0
- package/dist/adapters/notifier/types.d.mts +51 -2
- package/dist/adapters/remote-browser.d.mts +87 -0
- package/dist/adapters/remote-browser.mjs +103 -0
- package/dist/adapters/remote-browser.mjs.map +1 -0
- package/dist/adapters/render-runs.d.mts +25 -0
- package/dist/adapters/render-runs.mjs +41 -0
- package/dist/adapters/render-runs.mjs.map +1 -0
- package/dist/adapters/setup-runner.mjs +2 -4
- package/dist/adapters/setup-runner.mjs.map +1 -1
- package/dist/adapters/sources.d.mts +12 -0
- package/dist/adapters/sources.mjs +20 -0
- package/dist/adapters/sources.mjs.map +1 -0
- package/dist/adapters/validate.mjs +25 -18
- package/dist/adapters/validate.mjs.map +1 -1
- package/dist/ai.d.mts +105 -0
- package/dist/ai.mjs +19 -0
- package/dist/ai.mjs.map +1 -0
- package/dist/auth.d.mts +8 -1
- package/dist/auth.mjs +14 -1
- package/dist/auth.mjs.map +1 -0
- package/dist/capture/a11y/axe-source.d.mts +18 -0
- package/dist/capture/a11y/axe-source.mjs +37 -0
- package/dist/capture/a11y/axe-source.mjs.map +1 -0
- package/dist/capture/a11y/in-page.d.mts +27 -0
- package/dist/capture/a11y/in-page.mjs +60 -0
- package/dist/capture/a11y/in-page.mjs.map +1 -0
- package/dist/capture/a11y/map.d.mts +24 -0
- package/dist/capture/a11y/map.mjs +48 -0
- package/dist/capture/a11y/map.mjs.map +1 -0
- package/dist/capture/a11y/options.d.mts +23 -0
- package/dist/capture/a11y/options.mjs +75 -0
- package/dist/capture/a11y/options.mjs.map +1 -0
- package/dist/capture/adapter.d.mts +41 -3
- package/dist/capture/adapter.mjs.map +1 -1
- package/dist/capture/affected.d.mts +2 -0
- package/dist/capture/affected.mjs +13 -4
- package/dist/capture/affected.mjs.map +1 -1
- package/dist/capture/attempt-logger.mjs +84 -0
- package/dist/capture/attempt-logger.mjs.map +1 -0
- package/dist/capture/browsers.d.mts +35 -0
- package/dist/capture/browsers.mjs +67 -0
- package/dist/capture/browsers.mjs.map +1 -0
- package/dist/capture/build-state/deliver.d.mts +32 -0
- package/dist/capture/build-state/deliver.mjs +118 -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 +56 -0
- package/dist/capture/build-state/notify-job.mjs +133 -0
- package/dist/capture/build-state/notify-job.mjs.map +1 -0
- package/dist/capture/build-state/publisher.d.mts +23 -0
- package/dist/capture/build-state/publisher.mjs +107 -0
- package/dist/capture/build-state/publisher.mjs.map +1 -0
- package/dist/capture/build-state/resume.d.mts +9 -0
- package/dist/capture/build-state/resume.mjs +47 -0
- package/dist/capture/build-state/resume.mjs.map +1 -0
- package/dist/capture/build-state/types.d.mts +49 -0
- package/dist/capture/catalog/catalogue.d.mts +22 -0
- package/dist/capture/catalog/catalogue.mjs +53 -0
- package/dist/capture/catalog/catalogue.mjs.map +1 -0
- package/dist/capture/catalog/components.d.mts +23 -0
- package/dist/capture/catalog/components.mjs +55 -0
- package/dist/capture/catalog/components.mjs.map +1 -0
- package/dist/capture/catalog/history.d.mts +30 -0
- package/dist/capture/catalog/history.mjs +51 -0
- package/dist/capture/catalog/history.mjs.map +1 -0
- package/dist/capture/catalog/hook.d.mts +14 -0
- package/dist/capture/catalog/hook.mjs +19 -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 +26 -0
- package/dist/capture/catalog/load.mjs +49 -0
- package/dist/capture/catalog/load.mjs.map +1 -0
- package/dist/capture/catalog/parse.d.mts +15 -0
- package/dist/capture/catalog/parse.mjs +56 -0
- package/dist/capture/catalog/parse.mjs.map +1 -0
- package/dist/capture/catalog/read.d.mts +12 -0
- package/dist/capture/catalog/read.mjs +33 -0
- package/dist/capture/catalog/read.mjs.map +1 -0
- package/dist/capture/catalog/types.d.mts +33 -0
- package/dist/capture/consumer/capture-handler.d.mts +58 -0
- package/dist/capture/consumer/capture-handler.mjs +118 -0
- package/dist/capture/consumer/capture-handler.mjs.map +1 -0
- package/dist/capture/consumer/idle.d.mts +17 -0
- package/dist/capture/consumer/idle.mjs +25 -0
- package/dist/capture/consumer/idle.mjs.map +1 -0
- package/dist/capture/consumer/index.d.mts +13 -0
- package/dist/capture/consumer/index.mjs +139 -0
- package/dist/capture/consumer/index.mjs.map +1 -0
- package/dist/capture/consumer/loop.mjs +110 -0
- package/dist/capture/consumer/loop.mjs.map +1 -0
- package/dist/capture/consumer/metrics.d.mts +41 -0
- package/dist/capture/consumer/metrics.mjs +38 -0
- package/dist/capture/consumer/metrics.mjs.map +1 -0
- package/dist/capture/consumer/retry.d.mts +33 -0
- package/dist/capture/consumer/retry.mjs +34 -0
- package/dist/capture/consumer/retry.mjs.map +1 -0
- package/dist/capture/consumer/run-job.mjs +195 -0
- package/dist/capture/consumer/run-job.mjs.map +1 -0
- package/dist/capture/consumer/startup.mjs +38 -0
- package/dist/capture/consumer/startup.mjs.map +1 -0
- package/dist/capture/consumer/types.d.mts +97 -0
- package/dist/capture/crash-failure.d.mts +25 -0
- package/dist/capture/crash-failure.mjs +70 -0
- package/dist/capture/crash-failure.mjs.map +1 -0
- package/dist/capture/db-queue/cadence.d.mts +32 -0
- package/dist/capture/db-queue/cadence.mjs +38 -0
- package/dist/capture/db-queue/cadence.mjs.map +1 -0
- package/dist/capture/db-queue/claim.d.mts +9 -0
- package/dist/capture/db-queue/claim.mjs +132 -0
- package/dist/capture/db-queue/claim.mjs.map +1 -0
- package/dist/capture/db-queue/clock.mjs +12 -0
- package/dist/capture/db-queue/clock.mjs.map +1 -0
- package/dist/capture/db-queue/codec.mjs +76 -0
- package/dist/capture/db-queue/codec.mjs.map +1 -0
- package/dist/capture/db-queue/index.d.mts +20 -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 +36 -0
- package/dist/capture/db-queue/keeper.mjs +92 -0
- package/dist/capture/db-queue/keeper.mjs.map +1 -0
- package/dist/capture/db-queue/lease.mjs +46 -0
- package/dist/capture/db-queue/lease.mjs.map +1 -0
- package/dist/capture/db-queue/lifecycle.mjs +36 -0
- package/dist/capture/db-queue/lifecycle.mjs.map +1 -0
- package/dist/capture/db-queue/operations.mjs +94 -0
- package/dist/capture/db-queue/operations.mjs.map +1 -0
- package/dist/capture/db-queue/poll-loop.d.mts +30 -0
- package/dist/capture/db-queue/poll-loop.mjs +89 -0
- package/dist/capture/db-queue/poll-loop.mjs.map +1 -0
- package/dist/capture/db-queue/poll.d.mts +10 -0
- package/dist/capture/db-queue/poll.mjs +69 -0
- package/dist/capture/db-queue/poll.mjs.map +1 -0
- package/dist/capture/db-queue/timers.d.mts +12 -0
- package/dist/capture/db-queue/timers.mjs +37 -0
- package/dist/capture/db-queue/timers.mjs.map +1 -0
- package/dist/capture/db-queue/types.d.mts +83 -0
- package/dist/capture/db-queue/types.mjs +12 -0
- package/dist/capture/db-queue/types.mjs.map +1 -0
- package/dist/capture/db-queue/wake.mjs +48 -0
- package/dist/capture/db-queue/wake.mjs.map +1 -0
- package/dist/capture/design.mjs +39 -0
- package/dist/capture/design.mjs.map +1 -0
- package/dist/capture/diff-record.mjs +23 -3
- package/dist/capture/diff-record.mjs.map +1 -1
- package/dist/capture/dispatch.d.mts +12 -7
- package/dist/capture/dispatch.mjs +86 -107
- package/dist/capture/dispatch.mjs.map +1 -1
- package/dist/capture/enrich.mjs +60 -0
- package/dist/capture/enrich.mjs.map +1 -0
- package/dist/capture/failure-sets.mjs +22 -0
- package/dist/capture/failure-sets.mjs.map +1 -0
- package/dist/capture/fingerprint.d.mts +42 -0
- package/dist/capture/fingerprint.mjs +83 -0
- package/dist/capture/fingerprint.mjs.map +1 -0
- package/dist/capture/forked/channel.d.mts +25 -0
- package/dist/capture/forked/child.d.mts +34 -0
- package/dist/capture/forked/child.mjs +202 -0
- package/dist/capture/forked/child.mjs.map +1 -0
- package/dist/capture/forked/crash-tracker.mjs +23 -0
- package/dist/capture/forked/crash-tracker.mjs.map +1 -0
- package/dist/capture/forked/decision.d.mts +26 -0
- package/dist/capture/forked/decision.mjs +27 -0
- package/dist/capture/forked/decision.mjs.map +1 -0
- package/dist/capture/forked/exit.mjs +12 -0
- package/dist/capture/forked/exit.mjs.map +1 -0
- package/dist/capture/forked/fork.d.mts +31 -0
- package/dist/capture/forked/fork.mjs +93 -0
- package/dist/capture/forked/fork.mjs.map +1 -0
- package/dist/capture/forked/forked-capture.d.mts +40 -0
- package/dist/capture/forked/forked-capture.mjs +240 -0
- package/dist/capture/forked/forked-capture.mjs.map +1 -0
- package/dist/capture/forked/lease-book.mjs +46 -0
- package/dist/capture/forked/lease-book.mjs.map +1 -0
- package/dist/capture/forked/protocol.d.mts +38 -0
- package/dist/capture/forked/protocol.mjs +25 -0
- package/dist/capture/forked/protocol.mjs.map +1 -0
- package/dist/capture/forked/runs.mjs +75 -0
- package/dist/capture/forked/runs.mjs.map +1 -0
- package/dist/capture/isolate.mjs +141 -0
- package/dist/capture/isolate.mjs.map +1 -0
- package/dist/capture/job-lease.d.mts +28 -0
- package/dist/capture/job-lease.mjs +57 -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 +25 -0
- package/dist/capture/modes.mjs +107 -0
- package/dist/capture/modes.mjs.map +1 -0
- package/dist/capture/orchestrator.d.mts +37 -5
- package/dist/capture/orchestrator.mjs +118 -107
- package/dist/capture/orchestrator.mjs.map +1 -1
- package/dist/capture/pipeline.d.mts +9 -2
- package/dist/capture/pipeline.mjs +250 -45
- package/dist/capture/pipeline.mjs.map +1 -1
- package/dist/capture/progress.d.mts +11 -0
- package/dist/capture/progress.mjs +28 -0
- package/dist/capture/progress.mjs.map +1 -0
- package/dist/capture/publish/accept.d.mts +39 -0
- package/dist/capture/publish/accept.mjs +28 -0
- package/dist/capture/publish/accept.mjs.map +1 -0
- package/dist/capture/publish/complete.d.mts +46 -0
- package/dist/capture/publish/complete.mjs +64 -0
- package/dist/capture/publish/complete.mjs.map +1 -0
- package/dist/capture/publish/extract.d.mts +20 -0
- package/dist/capture/publish/extract.mjs +119 -0
- package/dist/capture/publish/extract.mjs.map +1 -0
- package/dist/capture/publish/handler.d.mts +28 -0
- package/dist/capture/publish/handler.mjs +62 -0
- package/dist/capture/publish/handler.mjs.map +1 -0
- package/dist/capture/publish/index.d.mts +10 -0
- package/dist/capture/publish/limits.d.mts +37 -0
- package/dist/capture/publish/limits.mjs +39 -0
- package/dist/capture/publish/limits.mjs.map +1 -0
- package/dist/capture/publish/published.d.mts +15 -0
- package/dist/capture/publish/published.mjs +18 -0
- package/dist/capture/publish/published.mjs.map +1 -0
- package/dist/capture/publish/run.d.mts +33 -0
- package/dist/capture/publish/run.mjs +30 -0
- package/dist/capture/publish/run.mjs.map +1 -0
- package/dist/capture/rediff.d.mts +1 -1
- package/dist/capture/rediff.mjs +4 -3
- package/dist/capture/rediff.mjs.map +1 -1
- package/dist/capture/render.mjs +53 -0
- package/dist/capture/render.mjs.map +1 -0
- package/dist/capture/scratch.d.mts +44 -0
- package/dist/capture/scratch.mjs +138 -0
- package/dist/capture/scratch.mjs.map +1 -0
- package/dist/capture/shard-plan.mjs +128 -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 +174 -0
- package/dist/capture/shards/execute.mjs.map +1 -0
- package/dist/capture/shards/fan-out.mjs +126 -0
- package/dist/capture/shards/fan-out.mjs.map +1 -0
- package/dist/capture/shards/history.mjs +35 -0
- package/dist/capture/shards/history.mjs.map +1 -0
- package/dist/capture/shards/reduce.mjs +97 -0
- package/dist/capture/shards/reduce.mjs.map +1 -0
- package/dist/capture/sizing.mjs +1 -11
- package/dist/capture/sizing.mjs.map +1 -1
- package/dist/capture/skip-checks.mjs +2 -65
- package/dist/capture/skip-checks.mjs.map +1 -1
- package/dist/capture/statics.d.mts +7 -8
- package/dist/capture/statics.mjs +25 -71
- package/dist/capture/statics.mjs.map +1 -1
- package/dist/capture/storybook-urls.d.mts +40 -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 -4
- package/dist/capture/storybook.mjs +56 -16
- package/dist/capture/storybook.mjs.map +1 -1
- package/dist/capture/target.mjs +21 -0
- package/dist/capture/target.mjs.map +1 -0
- package/dist/capture/targets.d.mts +25 -0
- package/dist/capture/targets.mjs +32 -0
- package/dist/capture/targets.mjs.map +1 -0
- package/dist/capture/tests.d.mts +37 -0
- package/dist/capture/tests.mjs +126 -0
- package/dist/capture/tests.mjs.map +1 -0
- package/dist/capture/token-chunks.mjs +74 -0
- package/dist/capture/token-chunks.mjs.map +1 -0
- package/dist/capture.d.mts +60 -7
- package/dist/capture.mjs +48 -3
- package/dist/config.d.mts +121 -12
- package/dist/config.mjs +9 -6
- package/dist/config.mjs.map +1 -1
- package/dist/diff.d.mts +9 -3
- package/dist/diff.mjs +16 -3
- package/dist/diff.mjs.map +1 -0
- package/dist/index.d.mts +1 -1
- package/dist/insights/budget.d.mts +20 -0
- package/dist/insights/budget.mjs +33 -0
- package/dist/insights/budget.mjs.map +1 -0
- package/dist/insights/envelope.d.mts +59 -0
- package/dist/insights/envelope.mjs +60 -0
- package/dist/insights/envelope.mjs.map +1 -0
- package/dist/insights/evidence-health.d.mts +32 -0
- package/dist/insights/evidence-health.mjs +28 -0
- package/dist/insights/evidence-health.mjs.map +1 -0
- package/dist/insights/evidence.d.mts +55 -0
- package/dist/insights/evidence.mjs +64 -0
- package/dist/insights/evidence.mjs.map +1 -0
- package/dist/insights/hash.d.mts +18 -0
- package/dist/insights/hash.mjs +21 -0
- package/dist/insights/hash.mjs.map +1 -0
- package/dist/insights/limits.d.mts +24 -0
- package/dist/insights/limits.mjs +24 -0
- package/dist/insights/limits.mjs.map +1 -0
- package/dist/insights/prompts/v1.d.mts +13 -0
- package/dist/insights/prompts/v1.mjs +34 -0
- package/dist/insights/prompts/v1.mjs.map +1 -0
- package/dist/insights/redact.d.mts +9 -0
- package/dist/insights/redact.mjs +24 -0
- package/dist/insights/redact.mjs.map +1 -0
- package/dist/insights/text.mjs +44 -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.mjs +17 -1
- package/dist/logger.mjs.map +1 -1
- package/dist/models/ai-budget-alert.d.mts +23 -0
- package/dist/models/ai-budget-alert.mjs +38 -0
- package/dist/models/ai-budget-alert.mjs.map +1 -0
- package/dist/models/ai-usage.d.mts +40 -0
- package/dist/models/ai-usage.mjs +61 -0
- package/dist/models/ai-usage.mjs.map +1 -0
- package/dist/models/baseline-archive.d.mts +84 -0
- package/dist/models/baseline-archive.mjs +106 -0
- package/dist/models/baseline-archive.mjs.map +1 -0
- package/dist/models/baseline-export.d.mts +19 -0
- package/dist/models/baseline-export.mjs +136 -0
- package/dist/models/baseline-export.mjs.map +1 -0
- package/dist/models/baseline-import.mjs +118 -0
- package/dist/models/baseline-import.mjs.map +1 -0
- package/dist/models/baseline-transfer.d.mts +49 -0
- package/dist/models/baseline-transfer.mjs +121 -0
- package/dist/models/baseline-transfer.mjs.map +1 -0
- package/dist/models/baseline.d.mts +32 -5
- package/dist/models/baseline.mjs +65 -20
- package/dist/models/baseline.mjs.map +1 -1
- package/dist/models/build-component.d.mts +28 -0
- package/dist/models/build-component.mjs +55 -0
- package/dist/models/build-component.mjs.map +1 -0
- package/dist/models/build-review-status.d.mts +29 -0
- package/dist/models/build-review-status.mjs +37 -0
- package/dist/models/build-review-status.mjs.map +1 -0
- package/dist/models/build-review.d.mts +28 -0
- package/dist/models/build-review.mjs +54 -0
- package/dist/models/build-review.mjs.map +1 -0
- package/dist/models/build.d.mts +25 -3
- package/dist/models/build.mjs +50 -11
- package/dist/models/build.mjs.map +1 -1
- package/dist/models/capture-attempt.d.mts +7 -1
- package/dist/models/capture-attempt.mjs +29 -4
- package/dist/models/capture-attempt.mjs.map +1 -1
- package/dist/models/capture-job.d.mts +106 -0
- package/dist/models/capture-job.mjs +288 -0
- package/dist/models/capture-job.mjs.map +1 -0
- package/dist/models/capture-log.d.mts +1 -1
- package/dist/models/capture-log.mjs +4 -3
- package/dist/models/capture-log.mjs.map +1 -1
- package/dist/models/capture-shard.d.mts +62 -0
- package/dist/models/capture-shard.mjs +190 -0
- package/dist/models/capture-shard.mjs.map +1 -0
- package/dist/models/changeset.d.mts +48 -0
- package/dist/models/changeset.mjs +81 -0
- package/dist/models/changeset.mjs.map +1 -0
- package/dist/models/columns.mjs +19 -0
- package/dist/models/columns.mjs.map +1 -0
- package/dist/models/comment.d.mts +4 -1
- package/dist/models/comment.mjs +7 -4
- package/dist/models/comment.mjs.map +1 -1
- package/dist/models/component.d.mts +43 -0
- package/dist/models/component.mjs +60 -0
- package/dist/models/component.mjs.map +1 -0
- package/dist/models/connections.d.mts +41 -0
- package/dist/models/connections.mjs +112 -0
- package/dist/models/connections.mjs.map +1 -0
- package/dist/models/content-ref.d.mts +1 -1
- package/dist/models/content-ref.mjs +1 -1
- package/dist/models/content-ref.mjs.map +1 -1
- package/dist/models/credentials.mjs +12 -2
- package/dist/models/credentials.mjs.map +1 -1
- package/dist/models/design-credentials.d.mts +41 -0
- package/dist/models/design-credentials.mjs +86 -0
- package/dist/models/design-credentials.mjs.map +1 -0
- package/dist/models/design-health.d.mts +16 -0
- package/dist/models/design-health.mjs +25 -0
- package/dist/models/design-health.mjs.map +1 -0
- package/dist/models/design-nodes.d.mts +83 -0
- package/dist/models/design-nodes.mjs +222 -0
- package/dist/models/design-nodes.mjs.map +1 -0
- package/dist/models/design-renders.mjs +74 -0
- package/dist/models/design-renders.mjs.map +1 -0
- package/dist/models/design-resolve.d.mts +29 -0
- package/dist/models/design-resolve.mjs +41 -0
- package/dist/models/design-resolve.mjs.map +1 -0
- package/dist/models/design.d.mts +39 -0
- package/dist/models/design.mjs +90 -0
- package/dist/models/design.mjs.map +1 -0
- package/dist/models/inherited-ratio.d.mts +21 -0
- package/dist/models/inherited-ratio.mjs +22 -0
- package/dist/models/inherited-ratio.mjs.map +1 -0
- package/dist/models/insight.d.mts +70 -0
- package/dist/models/insight.mjs +162 -0
- package/dist/models/insight.mjs.map +1 -0
- package/dist/models/label.d.mts +4 -2
- package/dist/models/label.mjs +25 -10
- package/dist/models/label.mjs.map +1 -1
- package/dist/models/member.d.mts +1 -1
- package/dist/models/member.mjs +5 -4
- package/dist/models/member.mjs.map +1 -1
- package/dist/models/notification-channel.d.mts +37 -3
- package/dist/models/notification-channel.mjs +88 -9
- package/dist/models/notification-channel.mjs.map +1 -1
- package/dist/models/notification-subscription.d.mts +1 -2
- package/dist/models/notification-subscription.mjs +7 -9
- package/dist/models/notification-subscription.mjs.map +1 -1
- package/dist/models/project-group-mapping.d.mts +6 -1
- package/dist/models/project-group-mapping.mjs +14 -4
- package/dist/models/project-group-mapping.mjs.map +1 -1
- package/dist/models/project.d.mts +9 -1
- package/dist/models/project.mjs +9 -4
- package/dist/models/project.mjs.map +1 -1
- package/dist/models/public-origin.d.mts +63 -0
- package/dist/models/public-origin.mjs +114 -0
- package/dist/models/public-origin.mjs.map +1 -0
- package/dist/models/review-switch-off.d.mts +14 -0
- package/dist/models/review-switch-off.mjs +26 -0
- package/dist/models/review-switch-off.mjs.map +1 -0
- package/dist/models/site-settings.d.mts +23 -0
- package/dist/models/site-settings.mjs +48 -0
- package/dist/models/site-settings.mjs.map +1 -0
- package/dist/models/snapshot.d.mts +36 -1
- package/dist/models/snapshot.mjs +50 -3
- package/dist/models/snapshot.mjs.map +1 -1
- package/dist/models/token.d.mts +13 -1
- package/dist/models/token.mjs +23 -5
- package/dist/models/token.mjs.map +1 -1
- package/dist/models/usage.d.mts +76 -0
- package/dist/models/usage.mjs +149 -0
- package/dist/models/usage.mjs.map +1 -0
- package/dist/models/user.d.mts +1 -1
- package/dist/models/user.mjs +4 -3
- package/dist/models/user.mjs.map +1 -1
- package/dist/models/webhook.d.mts +15 -1
- package/dist/models/webhook.mjs +33 -4
- package/dist/models/webhook.mjs.map +1 -1
- package/dist/models.d.mts +26 -3
- package/dist/models.mjs +27 -5
- package/dist/options.d.mts +18 -0
- package/dist/options.mjs +90 -0
- package/dist/options.mjs.map +1 -0
- package/dist/paths.d.mts +19 -4
- package/dist/paths.mjs +51 -10
- package/dist/paths.mjs.map +1 -1
- package/dist/retention/ai.d.mts +18 -0
- package/dist/retention/ai.mjs +23 -0
- package/dist/retention/ai.mjs.map +1 -0
- package/dist/retention/pending.mjs +47 -0
- package/dist/retention/pending.mjs.map +1 -0
- package/dist/retention/screenshot-sweep.d.mts +51 -0
- package/dist/retention/screenshot-sweep.mjs +128 -0
- package/dist/retention/screenshot-sweep.mjs.map +1 -0
- package/dist/retention.d.mts +16 -2
- package/dist/retention.mjs +71 -14
- package/dist/retention.mjs.map +1 -1
- package/dist/schema/baseline.d.mts +6 -0
- package/dist/schema/build-component.d.mts +25 -0
- package/dist/schema/build-review.d.mts +17 -0
- package/dist/schema/build.d.mts +7 -0
- package/dist/schema/capture-job.d.mts +60 -0
- package/dist/schema/capture-shard.d.mts +35 -0
- package/dist/schema/comment.d.mts +2 -0
- package/dist/schema/connection.d.mts +24 -0
- package/dist/schema/design.d.mts +58 -0
- package/dist/schema/insight.d.mts +54 -0
- package/dist/schema/notification.d.mts +4 -0
- package/dist/schema/project.d.mts +21 -0
- package/dist/schema/site-setting.d.mts +21 -0
- package/dist/schema/snapshot.d.mts +12 -0
- package/dist/schema/usage.d.mts +30 -0
- package/dist/schema.d.mts +11 -3
- package/dist/serverless.d.mts +21 -0
- package/dist/serverless.mjs +37 -0
- package/dist/serverless.mjs.map +1 -0
- package/dist/test-helpers/create-project.d.mts +1 -1
- package/dist/test-helpers/deferred.d.mts +39 -0
- package/dist/test-helpers/deferred.mjs +61 -0
- package/dist/test-helpers/deferred.mjs.map +1 -0
- package/dist/test-helpers/design-contract.d.mts +21 -0
- package/dist/test-helpers/design-contract.mjs +40 -0
- package/dist/test-helpers/design-contract.mjs.map +1 -0
- package/dist/test-helpers/fake-database.mjs +14 -2
- package/dist/test-helpers/fake-database.mjs.map +1 -1
- package/dist/test-helpers/fake-design.d.mts +43 -0
- package/dist/test-helpers/fake-design.mjs +114 -0
- package/dist/test-helpers/fake-design.mjs.map +1 -0
- package/dist/test-helpers/fake-notifier.d.mts +21 -0
- package/dist/test-helpers/fake-notifier.mjs +44 -0
- package/dist/test-helpers/fake-notifier.mjs.map +1 -0
- package/dist/test-helpers/fake-tables.mjs +255 -67
- package/dist/test-helpers/fake-tables.mjs.map +1 -1
- package/dist/test-helpers/manual-clock.d.mts +27 -0
- package/dist/test-helpers/manual-clock.mjs +56 -0
- package/dist/test-helpers/manual-clock.mjs.map +1 -0
- package/dist/test-helpers/notifier-contract.d.mts +7 -1
- package/dist/test-helpers/notifier-contract.mjs +32 -3
- package/dist/test-helpers/notifier-contract.mjs.map +1 -1
- package/dist/test-helpers/queue-contract.d.mts +16 -2
- package/dist/test-helpers/queue-contract.mjs +195 -1
- package/dist/test-helpers/queue-contract.mjs.map +1 -1
- package/dist/test-helpers/queue-multi-instance.d.mts +20 -0
- package/dist/test-helpers/queue-multi-instance.mjs +206 -0
- package/dist/test-helpers/queue-multi-instance.mjs.map +1 -0
- package/dist/test-helpers/runner-contract.mjs +11 -0
- package/dist/test-helpers/runner-contract.mjs.map +1 -1
- package/dist/test-helpers/sql-chunks.mjs +23 -0
- package/dist/test-helpers/sql-chunks.mjs.map +1 -1
- package/dist/test-helpers/storage-contract.mjs +1 -1
- package/dist/test-helpers.d.mts +9 -4
- package/dist/test-helpers.mjs +8 -3
- package/dist/types.d.mts +14 -3
- package/dist/types.mjs +21 -4
- package/dist/types.mjs.map +1 -1
- package/dist/urls.d.mts +4 -0
- package/dist/urls.mjs +11 -2
- package/dist/urls.mjs.map +1 -1
- package/dist/utils/adaptive-concurrency.d.mts +66 -0
- package/dist/utils/adaptive-concurrency.mjs +149 -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 +43 -0
- package/dist/utils/connect-retry.mjs.map +1 -0
- package/dist/utils/environment.d.mts +19 -0
- package/dist/utils/environment.mjs +59 -0
- package/dist/utils/environment.mjs.map +1 -0
- package/dist/utils/http.d.mts +17 -1
- package/dist/utils/http.mjs +22 -3
- package/dist/utils/http.mjs.map +1 -1
- package/dist/utils/map-adaptive.d.mts +15 -0
- package/dist/utils/map-adaptive.mjs +72 -0
- package/dist/utils/map-adaptive.mjs.map +1 -0
- package/dist/utils/map-limit.d.mts +10 -0
- package/dist/utils/map-limit.mjs +32 -0
- package/dist/utils/map-limit.mjs.map +1 -0
- package/dist/utils/page-limiter.d.mts +37 -0
- package/dist/utils/page-limiter.mjs +56 -0
- package/dist/utils/page-limiter.mjs.map +1 -0
- package/dist/utils/public-origin.d.mts +20 -0
- package/dist/utils/public-origin.mjs +75 -0
- package/dist/utils/public-origin.mjs.map +1 -0
- package/dist/utils/redact.mjs +3 -0
- package/dist/utils/redact.mjs.map +1 -1
- package/dist/utils/remote-environment.d.mts +39 -0
- package/dist/utils/remote-environment.mjs +114 -0
- package/dist/utils/remote-environment.mjs.map +1 -0
- package/dist/utils/resources.d.mts +64 -0
- package/dist/utils/resources.mjs +122 -0
- package/dist/utils/resources.mjs.map +1 -0
- package/dist/utils/screenshot-store.d.mts +15 -0
- package/dist/utils/screenshot-store.mjs +41 -0
- package/dist/utils/screenshot-store.mjs.map +1 -0
- package/dist/utils/session-gate.d.mts +22 -0
- package/dist/utils/session-gate.mjs +53 -0
- package/dist/utils/session-gate.mjs.map +1 -0
- package/dist/utils/storybook-token.d.mts +81 -0
- package/dist/utils/storybook-token.mjs +134 -0
- package/dist/utils/storybook-token.mjs.map +1 -0
- package/dist/utils/storybook-url.d.mts +15 -0
- package/dist/utils/storybook-url.mjs +21 -0
- package/dist/utils/storybook-url.mjs.map +1 -0
- package/dist/utils/timing.mjs +1 -1
- package/dist/utils/timing.mjs.map +1 -1
- package/dist/utils/zip-read.mjs +129 -0
- package/dist/utils/zip-read.mjs.map +1 -0
- package/dist/utils/zip-write.d.mts +18 -0
- package/dist/utils/zip-write.mjs +188 -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 +32 -20
- 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,94 @@
|
|
|
1
|
+
import "node:path";
|
|
2
|
+
import "node:url";
|
|
3
|
+
import.meta.url;
|
|
4
|
+
import { ulid } from "../../utils/ulid.mjs";
|
|
5
|
+
import { attemptRowId, isTerminalJob } from "../../models/capture-job.mjs";
|
|
6
|
+
import { encodePayload, toQueueEntry } from "./codec.mjs";
|
|
7
|
+
import { isoAt } from "./clock.mjs";
|
|
8
|
+
//#region src/capture/db-queue/operations.ts
|
|
9
|
+
/** Insert a queued job row and wake this instance's poller; resolves to the job id. */
|
|
10
|
+
async function enqueueJob(rt, job) {
|
|
11
|
+
const id = ulid();
|
|
12
|
+
const now = isoAt(rt);
|
|
13
|
+
await rt.model.create({
|
|
14
|
+
id,
|
|
15
|
+
kind: job.kind,
|
|
16
|
+
buildId: job.buildId ?? null,
|
|
17
|
+
shardId: job.shard ?? null,
|
|
18
|
+
maxAttempts: job.maxAttempts ?? rt.maxAttempts,
|
|
19
|
+
payload: encodePayload(job)
|
|
20
|
+
}, now);
|
|
21
|
+
rt.logger?.info({
|
|
22
|
+
jobId: id,
|
|
23
|
+
kind: job.kind,
|
|
24
|
+
buildId: job.buildId
|
|
25
|
+
}, "job queued");
|
|
26
|
+
rt.wake.notify();
|
|
27
|
+
return id;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* The kinds the queue's views (`status`, `active`, `recent`, `cancel`) show: every polled kind but
|
|
31
|
+
* `notify` (unless it is the only one), whose many short delivery jobs would bury a build's capture and publish jobs.
|
|
32
|
+
*/
|
|
33
|
+
function viewKinds(rt) {
|
|
34
|
+
const shown = rt.kinds.filter((kind) => kind !== "notify");
|
|
35
|
+
return shown.length > 0 ? shown : rt.kinds;
|
|
36
|
+
}
|
|
37
|
+
/** `CaptureQueue.enqueue`: a capture job for a build or one of its shards. */
|
|
38
|
+
async function enqueueCapture(rt, job) {
|
|
39
|
+
await enqueueJob(rt, {
|
|
40
|
+
...job,
|
|
41
|
+
kind: "capture"
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
/** The build's own job most recently queued, as a queue entry. */
|
|
45
|
+
async function statusOf(rt, buildId) {
|
|
46
|
+
const row = await rt.model.latestForBuild(buildId, viewKinds(rt));
|
|
47
|
+
return row === null ? null : toQueueEntry(row);
|
|
48
|
+
}
|
|
49
|
+
/** Queued and running jobs of this queue's kinds on every instance, oldest first. */
|
|
50
|
+
async function activeEntries(rt) {
|
|
51
|
+
return (await rt.model.listActive(viewKinds(rt))).map((row) => toQueueEntry(row));
|
|
52
|
+
}
|
|
53
|
+
/** The newest jobs of this queue's kinds, newest first. */
|
|
54
|
+
async function recentEntries(rt, limit) {
|
|
55
|
+
return (await rt.model.listRecent(viewKinds(rt), limit)).map((row) => toQueueEntry(row));
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Cancel a job (by job id) or every unfinished job of a build (by build id), of every kind that
|
|
59
|
+
* shares the table (capture, publish and notify), whichever kinds this queue polls. A queued job ends `cancelled` at once; a running one is flagged on its job row and on
|
|
60
|
+
* its current attempt row, so the holder sees it on its next renewal or cancel check.
|
|
61
|
+
*/
|
|
62
|
+
async function cancelJobs(rt, id) {
|
|
63
|
+
const targets = (await targetsOf(rt, id)).filter((row) => !isTerminalJob(row));
|
|
64
|
+
await Promise.all(targets.map(async (row) => {
|
|
65
|
+
await cancelOne(rt, row);
|
|
66
|
+
}));
|
|
67
|
+
if (targets.length > 0) rt.logger?.info({
|
|
68
|
+
id,
|
|
69
|
+
jobs: targets.length
|
|
70
|
+
}, "cancel requested");
|
|
71
|
+
return targets.length > 0;
|
|
72
|
+
}
|
|
73
|
+
async function targetsOf(rt, id) {
|
|
74
|
+
const row = await rt.model.get(id);
|
|
75
|
+
if (row?.entry === "job") return [row];
|
|
76
|
+
return await rt.model.listForBuild(id);
|
|
77
|
+
}
|
|
78
|
+
async function cancelOne(rt, row) {
|
|
79
|
+
const now = isoAt(rt);
|
|
80
|
+
await rt.model.flagCancel(row.jobId, now);
|
|
81
|
+
if (row.status === "queued") {
|
|
82
|
+
await rt.model.finishJob(row.jobId, "cancelled", now);
|
|
83
|
+
await flagAttempt(rt, attemptRowId(row.jobId, row.attempt + 1), now);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
await flagAttempt(rt, attemptRowId(row.jobId, row.attempt), now);
|
|
87
|
+
}
|
|
88
|
+
async function flagAttempt(rt, attemptId, now) {
|
|
89
|
+
if ((await rt.model.get(attemptId))?.status === "running") await rt.model.flagCancel(attemptId, now);
|
|
90
|
+
}
|
|
91
|
+
//#endregion
|
|
92
|
+
export { activeEntries, cancelJobs, enqueueCapture, enqueueJob, recentEntries, statusOf };
|
|
93
|
+
|
|
94
|
+
//# sourceMappingURL=operations.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"operations.mjs","names":[],"sources":["../../../src/capture/db-queue/operations.ts"],"sourcesContent":["/** Enqueue, cancel and the cross-instance views (`status`/`active`/`recent`) of the database queue. */\nimport type { CaptureJob, QueueEntry } from \"../../adapters/capture-queue.ts\";\nimport { attemptRowId, isTerminalJob } from \"../../models/capture-job.ts\";\nimport type { CaptureJobKind, CaptureJobRow } from \"../../schema/capture-job.ts\";\nimport { ulid } from \"../../utils/ulid.ts\";\nimport { isoAt } from \"./clock.ts\";\nimport { encodePayload, toQueueEntry } from \"./codec.ts\";\nimport type { QueueJobInput, QueueRuntime } from \"./types.ts\";\n\n/** Insert a queued job row and wake this instance's poller; resolves to the job id. */\nexport async function enqueueJob(rt: QueueRuntime, job: QueueJobInput): Promise<string> {\n const id = ulid();\n const now = isoAt(rt);\n await rt.model.create(\n {\n id,\n kind: job.kind,\n buildId: job.buildId ?? null,\n shardId: job.shard ?? null,\n maxAttempts: job.maxAttempts ?? rt.maxAttempts,\n payload: encodePayload(job),\n },\n now,\n );\n rt.logger?.info({ jobId: id, kind: job.kind, buildId: job.buildId }, \"job queued\");\n rt.wake.notify();\n return id;\n}\n\n/**\n * The kinds the queue's views (`status`, `active`, `recent`, `cancel`) show: every polled kind but\n * `notify` (unless it is the only one), whose many short delivery jobs would bury a build's capture and publish jobs.\n */\nfunction viewKinds(rt: QueueRuntime): readonly CaptureJobKind[] {\n const shown = rt.kinds.filter((kind) => kind !== \"notify\");\n return shown.length > 0 ? shown : rt.kinds;\n}\n\n/** `CaptureQueue.enqueue`: a capture job for a build or one of its shards. */\nexport async function enqueueCapture(rt: QueueRuntime, job: CaptureJob): Promise<void> {\n await enqueueJob(rt, { ...job, kind: \"capture\" });\n}\n\n/** The build's own job most recently queued, as a queue entry. */\nexport async function statusOf(rt: QueueRuntime, buildId: string): Promise<QueueEntry | null> {\n const row = await rt.model.latestForBuild(buildId, viewKinds(rt));\n return row === null ? null : toQueueEntry(row);\n}\n\n/** Queued and running jobs of this queue's kinds on every instance, oldest first. */\nexport async function activeEntries(rt: QueueRuntime): Promise<QueueEntry[]> {\n return (await rt.model.listActive(viewKinds(rt))).map((row) => toQueueEntry(row));\n}\n\n/** The newest jobs of this queue's kinds, newest first. */\nexport async function recentEntries(rt: QueueRuntime, limit: number): Promise<QueueEntry[]> {\n return (await rt.model.listRecent(viewKinds(rt), limit)).map((row) => toQueueEntry(row));\n}\n\n/**\n * Cancel a job (by job id) or every unfinished job of a build (by build id), of every kind that\n * shares the table (capture, publish and notify), whichever kinds this queue polls. A queued job ends `cancelled` at once; a running one is flagged on its job row and on\n * its current attempt row, so the holder sees it on its next renewal or cancel check.\n */\nexport async function cancelJobs(rt: QueueRuntime, id: string): Promise<boolean> {\n const targets = (await targetsOf(rt, id)).filter((row) => !isTerminalJob(row));\n await Promise.all(\n targets.map(async (row) => {\n await cancelOne(rt, row);\n }),\n );\n if (targets.length > 0) {\n rt.logger?.info({ id, jobs: targets.length }, \"cancel requested\");\n }\n return targets.length > 0;\n}\n\nasync function targetsOf(rt: QueueRuntime, id: string): Promise<CaptureJobRow[]> {\n const row = await rt.model.get(id);\n if (row?.entry === \"job\") {\n return [row];\n }\n return await rt.model.listForBuild(id);\n}\n\nasync function cancelOne(rt: QueueRuntime, row: CaptureJobRow): Promise<void> {\n const now = isoAt(rt);\n // The flag goes first: it is never cleared, so a claim racing this cancel still sees it.\n await rt.model.flagCancel(row.jobId, now);\n if (row.status === \"queued\") {\n await rt.model.finishJob(row.jobId, \"cancelled\", now);\n // A claimer may have inserted the next attempt between our read and the write above.\n await flagAttempt(rt, attemptRowId(row.jobId, row.attempt + 1), now);\n return;\n }\n await flagAttempt(rt, attemptRowId(row.jobId, row.attempt), now);\n}\n\nasync function flagAttempt(rt: QueueRuntime, attemptId: string, now: string): Promise<void> {\n const attempt = await rt.model.get(attemptId);\n if (attempt?.status === \"running\") {\n await rt.model.flagCancel(attemptId, now);\n }\n}\n"],"mappings":";;;;;;;;;AAUA,eAAsB,WAAW,IAAkB,KAAqC;CACtF,MAAM,KAAK,KAAK;CAChB,MAAM,MAAM,MAAM,EAAE;CACpB,MAAM,GAAG,MAAM,OACb;EACE;EACA,MAAM,IAAI;EACV,SAAS,IAAI,WAAW;EACxB,SAAS,IAAI,SAAS;EACtB,aAAa,IAAI,eAAe,GAAG;EACnC,SAAS,cAAc,GAAG;CAC5B,GACA,GACF;CACA,GAAG,QAAQ,KAAK;EAAE,OAAO;EAAI,MAAM,IAAI;EAAM,SAAS,IAAI;CAAQ,GAAG,YAAY;CACjF,GAAG,KAAK,OAAO;CACf,OAAO;AACT;;;;;AAMA,SAAS,UAAU,IAA6C;CAC9D,MAAM,QAAQ,GAAG,MAAM,QAAQ,SAAS,SAAS,QAAQ;CACzD,OAAO,MAAM,SAAS,IAAI,QAAQ,GAAG;AACvC;;AAGA,eAAsB,eAAe,IAAkB,KAAgC;CACrF,MAAM,WAAW,IAAI;EAAE,GAAG;EAAK,MAAM;CAAU,CAAC;AAClD;;AAGA,eAAsB,SAAS,IAAkB,SAA6C;CAC5F,MAAM,MAAM,MAAM,GAAG,MAAM,eAAe,SAAS,UAAU,EAAE,CAAC;CAChE,OAAO,QAAQ,OAAO,OAAO,aAAa,GAAG;AAC/C;;AAGA,eAAsB,cAAc,IAAyC;CAC3E,QAAQ,MAAM,GAAG,MAAM,WAAW,UAAU,EAAE,CAAC,EAAA,CAAG,KAAK,QAAQ,aAAa,GAAG,CAAC;AAClF;;AAGA,eAAsB,cAAc,IAAkB,OAAsC;CAC1F,QAAQ,MAAM,GAAG,MAAM,WAAW,UAAU,EAAE,GAAG,KAAK,EAAA,CAAG,KAAK,QAAQ,aAAa,GAAG,CAAC;AACzF;;;;;;AAOA,eAAsB,WAAW,IAAkB,IAA8B;CAC/E,MAAM,WAAW,MAAM,UAAU,IAAI,EAAE,EAAA,CAAG,QAAQ,QAAQ,CAAC,cAAc,GAAG,CAAC;CAC7E,MAAM,QAAQ,IACZ,QAAQ,IAAI,OAAO,QAAQ;EACzB,MAAM,UAAU,IAAI,GAAG;CACzB,CAAC,CACH;CACA,IAAI,QAAQ,SAAS,GACnB,GAAG,QAAQ,KAAK;EAAE;EAAI,MAAM,QAAQ;CAAO,GAAG,kBAAkB;CAElE,OAAO,QAAQ,SAAS;AAC1B;AAEA,eAAe,UAAU,IAAkB,IAAsC;CAC/E,MAAM,MAAM,MAAM,GAAG,MAAM,IAAI,EAAE;CACjC,IAAI,KAAK,UAAU,OACjB,OAAO,CAAC,GAAG;CAEb,OAAO,MAAM,GAAG,MAAM,aAAa,EAAE;AACvC;AAEA,eAAe,UAAU,IAAkB,KAAmC;CAC5E,MAAM,MAAM,MAAM,EAAE;CAEpB,MAAM,GAAG,MAAM,WAAW,IAAI,OAAO,GAAG;CACxC,IAAI,IAAI,WAAW,UAAU;EAC3B,MAAM,GAAG,MAAM,UAAU,IAAI,OAAO,aAAa,GAAG;EAEpD,MAAM,YAAY,IAAI,aAAa,IAAI,OAAO,IAAI,UAAU,CAAC,GAAG,GAAG;EACnE;CACF;CACA,MAAM,YAAY,IAAI,aAAa,IAAI,OAAO,IAAI,OAAO,GAAG,GAAG;AACjE;AAEA,eAAe,YAAY,IAAkB,WAAmB,KAA4B;CAE1F,KAAI,MADkB,GAAG,MAAM,IAAI,SAAS,EAAA,EAC/B,WAAW,WACtB,MAAM,GAAG,MAAM,WAAW,WAAW,GAAG;AAE5C"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import __tsdown_shims_path from 'node:path';
|
|
2
|
+
import __tsdown_shims_url from 'node:url';
|
|
3
|
+
import { Logger } from "../../logger.mjs";
|
|
4
|
+
import { PollCadence } from "./cadence.mjs";
|
|
5
|
+
import { DatabaseCaptureQueue, DatabaseJob } from "./types.mjs";
|
|
6
|
+
//#region src/capture/db-queue/poll-loop.d.ts
|
|
7
|
+
/** Options of {@link startPollLoop}. */
|
|
8
|
+
interface PollLoopOptions {
|
|
9
|
+
queue: Pick<DatabaseCaptureQueue, "poll" | "waitForWork">;
|
|
10
|
+
/** Jobs this instance runs at once (`testing.concurrency`). */
|
|
11
|
+
slots: number;
|
|
12
|
+
/** Run one claimed job to its end (ack or nack it); rejections are logged, never fatal. */
|
|
13
|
+
run: (job: DatabaseJob) => Promise<void>;
|
|
14
|
+
cadence?: PollCadence;
|
|
15
|
+
now?: () => number;
|
|
16
|
+
random?: () => number;
|
|
17
|
+
logger?: Logger;
|
|
18
|
+
}
|
|
19
|
+
/** Handle on a running poll loop. */
|
|
20
|
+
interface PollLoop {
|
|
21
|
+
/** Jobs running right now. */
|
|
22
|
+
readonly busy: number;
|
|
23
|
+
/** Stop polling and wait for the jobs in flight to finish; idempotent. */
|
|
24
|
+
stop(): Promise<void>;
|
|
25
|
+
}
|
|
26
|
+
/** Start polling `queue` with `slots` concurrent jobs. */
|
|
27
|
+
declare function startPollLoop(options: PollLoopOptions): PollLoop;
|
|
28
|
+
//#endregion
|
|
29
|
+
export { PollLoop, PollLoopOptions, startPollLoop };
|
|
30
|
+
//# sourceMappingURL=poll-loop.d.mts.map
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import "node:path";
|
|
2
|
+
import "node:url";
|
|
3
|
+
import.meta.url;
|
|
4
|
+
import { DEFAULT_CADENCE, nextPollDelay } from "./cadence.mjs";
|
|
5
|
+
//#region src/capture/db-queue/poll-loop.ts
|
|
6
|
+
/** Start polling `queue` with `slots` concurrent jobs. */
|
|
7
|
+
function startPollLoop(options) {
|
|
8
|
+
const state = {
|
|
9
|
+
busy: 0,
|
|
10
|
+
stopped: false,
|
|
11
|
+
lastWorkAt: null,
|
|
12
|
+
inFlight: /* @__PURE__ */ new Set(),
|
|
13
|
+
nudge: () => {}
|
|
14
|
+
};
|
|
15
|
+
const done = loop(options, state);
|
|
16
|
+
return {
|
|
17
|
+
get busy() {
|
|
18
|
+
return state.busy;
|
|
19
|
+
},
|
|
20
|
+
stop: async () => {
|
|
21
|
+
state.stopped = true;
|
|
22
|
+
state.nudge();
|
|
23
|
+
await done;
|
|
24
|
+
await Promise.allSettled(state.inFlight);
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
async function loop(options, state) {
|
|
29
|
+
const now = options.now ?? Date.now;
|
|
30
|
+
while (!state.stopped) await step(options, state, now);
|
|
31
|
+
}
|
|
32
|
+
/** One turn: wait for a slot (no poll while all are busy), else probe once and run or idle. */
|
|
33
|
+
async function step(options, state, now) {
|
|
34
|
+
if (state.busy >= options.slots) {
|
|
35
|
+
await nudged(state);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const job = await pollOnce(options);
|
|
39
|
+
if (job === null) {
|
|
40
|
+
await idle(options, state, now);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
state.lastWorkAt = now();
|
|
44
|
+
launch(options, state, job);
|
|
45
|
+
}
|
|
46
|
+
async function pollOnce(options) {
|
|
47
|
+
try {
|
|
48
|
+
return await options.queue.poll();
|
|
49
|
+
} catch (error) {
|
|
50
|
+
options.logger?.warn({ err: error }, "queue poll failed");
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
async function idle(options, state, now) {
|
|
55
|
+
const delay = nextPollDelay(state.lastWorkAt, now(), options.cadence ?? DEFAULT_CADENCE, options.random);
|
|
56
|
+
if (await Promise.race([options.queue.waitForWork(delay), nudged(state).then(() => false)])) state.lastWorkAt = now();
|
|
57
|
+
}
|
|
58
|
+
function launch(options, state, job) {
|
|
59
|
+
state.busy += 1;
|
|
60
|
+
const running = runSafely(options, job).finally(() => {
|
|
61
|
+
state.busy -= 1;
|
|
62
|
+
state.inFlight.delete(running);
|
|
63
|
+
state.nudge();
|
|
64
|
+
});
|
|
65
|
+
state.inFlight.add(running);
|
|
66
|
+
}
|
|
67
|
+
async function runSafely(options, job) {
|
|
68
|
+
try {
|
|
69
|
+
await options.run(job);
|
|
70
|
+
} catch (error) {
|
|
71
|
+
options.logger?.error({
|
|
72
|
+
err: error,
|
|
73
|
+
jobId: job.jobId
|
|
74
|
+
}, "queue job run failed");
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/** A promise the next nudge (slot freed, stop) resolves. */
|
|
78
|
+
async function nudged(state) {
|
|
79
|
+
if (state.stopped) return;
|
|
80
|
+
const { promise, resolve } = Promise.withResolvers();
|
|
81
|
+
state.nudge = () => {
|
|
82
|
+
resolve(null);
|
|
83
|
+
};
|
|
84
|
+
await promise;
|
|
85
|
+
}
|
|
86
|
+
//#endregion
|
|
87
|
+
export { startPollLoop };
|
|
88
|
+
|
|
89
|
+
//# sourceMappingURL=poll-loop.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"poll-loop.mjs","names":[],"sources":["../../../src/capture/db-queue/poll-loop.ts"],"sourcesContent":["/**\n * The slot-aware poll loop over the database queue: an instance polls only while it has a free\n * slot, at the cadence of `cadence.ts`, and dispatches at once on a local enqueue. Each claimed job\n * runs in a slot; the caller's `run` owns ack/nack and the lease. The full consumer (retries, the\n * final-attempt rule, drain on SIGTERM) builds on this loop.\n */\nimport type { Logger } from \"../../logger.ts\";\nimport { DEFAULT_CADENCE, nextPollDelay, type PollCadence } from \"./cadence.ts\";\nimport type { DatabaseCaptureQueue, DatabaseJob } from \"./types.ts\";\n\n/** Options of {@link startPollLoop}. */\nexport interface PollLoopOptions {\n queue: Pick<DatabaseCaptureQueue, \"poll\" | \"waitForWork\">;\n /** Jobs this instance runs at once (`testing.concurrency`). */\n slots: number;\n /** Run one claimed job to its end (ack or nack it); rejections are logged, never fatal. */\n run: (job: DatabaseJob) => Promise<void>;\n cadence?: PollCadence;\n now?: () => number;\n random?: () => number;\n logger?: Logger;\n}\n\n/** Handle on a running poll loop. */\nexport interface PollLoop {\n /** Jobs running right now. */\n readonly busy: number;\n /** Stop polling and wait for the jobs in flight to finish; idempotent. */\n stop(): Promise<void>;\n}\n\ninterface LoopState {\n busy: number;\n stopped: boolean;\n lastWorkAt: number | null;\n inFlight: Set<Promise<void>>;\n /** Resolves the current idle wait early (slot freed or stop). */\n nudge: () => void;\n}\n\n/** Start polling `queue` with `slots` concurrent jobs. */\nexport function startPollLoop(options: PollLoopOptions): PollLoop {\n const state: LoopState = {\n busy: 0,\n stopped: false,\n lastWorkAt: null,\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: PollLoopOptions, state: LoopState): Promise<void> {\n const now = options.now ?? Date.now;\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, now);\n }\n}\n\n/** One turn: wait for a slot (no poll while all are busy), else probe once and run or idle. */\nasync function step(options: PollLoopOptions, state: LoopState, now: () => number): Promise<void> {\n if (state.busy >= options.slots) {\n await nudged(state);\n return;\n }\n const job = await pollOnce(options);\n if (job === null) {\n await idle(options, state, now);\n return;\n }\n state.lastWorkAt = now();\n launch(options, state, job);\n}\n\nasync function pollOnce(options: PollLoopOptions): Promise<DatabaseJob | null> {\n try {\n return await options.queue.poll();\n } catch (error) {\n options.logger?.warn({ err: error }, \"queue poll failed\");\n return null;\n }\n}\n\nasync function idle(options: PollLoopOptions, state: LoopState, now: () => number): Promise<void> {\n const delay = nextPollDelay(\n state.lastWorkAt,\n now(),\n options.cadence ?? DEFAULT_CADENCE,\n options.random,\n );\n const woken = await Promise.race([\n options.queue.waitForWork(delay),\n nudged(state).then(() => false),\n ]);\n if (woken) {\n state.lastWorkAt = now();\n }\n}\n\nfunction launch(options: PollLoopOptions, state: LoopState, job: DatabaseJob): void {\n state.busy += 1;\n const running = runSafely(options, job).finally(() => {\n state.busy -= 1;\n state.inFlight.delete(running);\n state.nudge();\n });\n state.inFlight.add(running);\n}\n\nasync function runSafely(options: PollLoopOptions, job: DatabaseJob): Promise<void> {\n try {\n await options.run(job);\n } catch (error) {\n options.logger?.error({ err: error, jobId: job.jobId }, \"queue job run failed\");\n }\n}\n\n/** A promise the next nudge (slot freed, stop) resolves. */\nasync function nudged(state: LoopState): Promise<void> {\n if (state.stopped) {\n return;\n }\n const { promise, resolve } = Promise.withResolvers<null>();\n state.nudge = (): void => {\n resolve(null);\n };\n await promise;\n}\n"],"mappings":";;;;;;AAyCA,SAAgB,cAAc,SAAoC;CAChE,MAAM,QAAmB;EACvB,MAAM;EACN,SAAS;EACT,YAAY;EACZ,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,MAAM,MAAM,QAAQ,OAAO,KAAK;CAChC,OAAO,CAAC,MAAM,SAEZ,MAAM,KAAK,SAAS,OAAO,GAAG;AAElC;;AAGA,eAAe,KAAK,SAA0B,OAAkB,KAAkC;CAChG,IAAI,MAAM,QAAQ,QAAQ,OAAO;EAC/B,MAAM,OAAO,KAAK;EAClB;CACF;CACA,MAAM,MAAM,MAAM,SAAS,OAAO;CAClC,IAAI,QAAQ,MAAM;EAChB,MAAM,KAAK,SAAS,OAAO,GAAG;EAC9B;CACF;CACA,MAAM,aAAa,IAAI;CACvB,OAAO,SAAS,OAAO,GAAG;AAC5B;AAEA,eAAe,SAAS,SAAuD;CAC7E,IAAI;EACF,OAAO,MAAM,QAAQ,MAAM,KAAK;CAClC,SAAS,OAAO;EACd,QAAQ,QAAQ,KAAK,EAAE,KAAK,MAAM,GAAG,mBAAmB;EACxD,OAAO;CACT;AACF;AAEA,eAAe,KAAK,SAA0B,OAAkB,KAAkC;CAChG,MAAM,QAAQ,cACZ,MAAM,YACN,IAAI,GACJ,QAAQ,WAAW,iBACnB,QAAQ,MACV;CAKA,IAAI,MAJgB,QAAQ,KAAK,CAC/B,QAAQ,MAAM,YAAY,KAAK,GAC/B,OAAO,KAAK,CAAC,CAAC,WAAW,KAAK,CAChC,CAAC,GAEC,MAAM,aAAa,IAAI;AAE3B;AAEA,SAAS,OAAO,SAA0B,OAAkB,KAAwB;CAClF,MAAM,QAAQ;CACd,MAAM,UAAU,UAAU,SAAS,GAAG,CAAC,CAAC,cAAc;EACpD,MAAM,QAAQ;EACd,MAAM,SAAS,OAAO,OAAO;EAC7B,MAAM,MAAM;CACd,CAAC;CACD,MAAM,SAAS,IAAI,OAAO;AAC5B;AAEA,eAAe,UAAU,SAA0B,KAAiC;CAClF,IAAI;EACF,MAAM,QAAQ,IAAI,GAAG;CACvB,SAAS,OAAO;EACd,QAAQ,QAAQ,MAAM;GAAE,KAAK;GAAO,OAAO,IAAI;EAAM,GAAG,sBAAsB;CAChF;AACF;;AAGA,eAAe,OAAO,OAAiC;CACrD,IAAI,MAAM,SACR;CAEF,MAAM,EAAE,SAAS,YAAY,QAAQ,cAAoB;CACzD,MAAM,cAAoB;EACxB,QAAQ,IAAI;CACd;CACA,MAAM;AACR"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import __tsdown_shims_path from 'node:path';
|
|
2
|
+
import __tsdown_shims_url from 'node:url';
|
|
3
|
+
import "../../adapter/capture-queue.mjs";
|
|
4
|
+
import "./types.mjs";
|
|
5
|
+
//#region src/capture/db-queue/poll.d.ts
|
|
6
|
+
/** Longest a nack may delay a retry; larger delays are clamped. */
|
|
7
|
+
declare const MAX_NACK_DELAY_MS: number;
|
|
8
|
+
//#endregion
|
|
9
|
+
export { MAX_NACK_DELAY_MS };
|
|
10
|
+
//# sourceMappingURL=poll.d.mts.map
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import "node:path";
|
|
2
|
+
import "node:url";
|
|
3
|
+
import.meta.url;
|
|
4
|
+
import { isTerminalJob, parseAttemptRowId } from "../../models/capture-job.mjs";
|
|
5
|
+
import { isoAt } from "./clock.mjs";
|
|
6
|
+
import { claimNext } from "./claim.mjs";
|
|
7
|
+
//#region src/capture/db-queue/poll.ts
|
|
8
|
+
/** Longest a nack may delay a retry; larger delays are clamped. */
|
|
9
|
+
const MAX_NACK_DELAY_MS = 36e5;
|
|
10
|
+
/**
|
|
11
|
+
* Claim one job. Probes once; with `waitMs`, an empty probe waits for a local enqueue (or the
|
|
12
|
+
* timeout) and probes once more only when woken, so an idle wait costs a single probe.
|
|
13
|
+
*/
|
|
14
|
+
async function pollJob(rt, options) {
|
|
15
|
+
const since = rt.wake.version();
|
|
16
|
+
rt.pollVersion = since;
|
|
17
|
+
const job = await claimNext(rt);
|
|
18
|
+
const waitMs = options?.waitMs ?? 0;
|
|
19
|
+
if (job !== null || waitMs <= 0) return job;
|
|
20
|
+
return await rt.wake.wait(waitMs, since) ? await claimNext(rt) : null;
|
|
21
|
+
}
|
|
22
|
+
/** The attempt succeeded: its row and the job row become `done` (a cancelled job stays cancelled). */
|
|
23
|
+
async function ackJob(rt, job) {
|
|
24
|
+
const ref = attemptRef(job);
|
|
25
|
+
const now = isoAt(rt);
|
|
26
|
+
await rt.model.finishAttempt(ref.receipt, "done", now);
|
|
27
|
+
if ((await rt.model.get(ref.jobId))?.status !== "cancelled") await rt.model.finishJob(ref.jobId, "done", now);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* The attempt failed. Only the current holder settles the job: a holder whose attempt was taken
|
|
31
|
+
* over (its row is no longer `running`, or the job moved on to a later attempt) writes nothing, so a
|
|
32
|
+
* stale holder can never requeue or fail a job another instance is running.
|
|
33
|
+
*/
|
|
34
|
+
async function nackJob(rt, job, options = {}) {
|
|
35
|
+
const ref = attemptRef(job);
|
|
36
|
+
if ((await rt.model.get(ref.receipt))?.status !== "running") return;
|
|
37
|
+
await rt.model.finishAttempt(ref.receipt, "failed", isoAt(rt), options.error ?? null);
|
|
38
|
+
const row = await rt.model.get(ref.jobId);
|
|
39
|
+
if (row === null || isTerminalJob(row) || row.attempt !== ref.attempt) return;
|
|
40
|
+
await settleFailure(rt, row, options);
|
|
41
|
+
}
|
|
42
|
+
/** Cancelled, final, or retried after `delayMs` (clamped to `[0, MAX_NACK_DELAY_MS]`). */
|
|
43
|
+
async function settleFailure(rt, row, options) {
|
|
44
|
+
const now = isoAt(rt);
|
|
45
|
+
const error = options.error ?? null;
|
|
46
|
+
if (row.cancelRequestedAt !== null) {
|
|
47
|
+
await rt.model.finishJob(row.jobId, "cancelled", now, error);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
if (options.requeue === false || row.attempt >= row.maxAttempts) {
|
|
51
|
+
await rt.model.finishJob(row.jobId, "failed", now, error ?? "failed");
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const delayMs = Math.min(Math.max(options.delayMs ?? 0, 0), MAX_NACK_DELAY_MS);
|
|
55
|
+
await rt.model.requeue(row.jobId, isoAt(rt, delayMs), error, now);
|
|
56
|
+
}
|
|
57
|
+
/** The attempt a polled job refers to, from its receipt (`<jobId>:<attempt>`). */
|
|
58
|
+
function attemptRef(job) {
|
|
59
|
+
const parsed = job.receipt === void 0 ? null : parseAttemptRowId(job.receipt);
|
|
60
|
+
if (parsed === null || job.receipt === void 0) throw new Error("not a database queue job: missing or malformed receipt");
|
|
61
|
+
return {
|
|
62
|
+
receipt: job.receipt,
|
|
63
|
+
...parsed
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
//#endregion
|
|
67
|
+
export { MAX_NACK_DELAY_MS, ackJob, attemptRef, nackJob, pollJob };
|
|
68
|
+
|
|
69
|
+
//# sourceMappingURL=poll.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"poll.mjs","names":[],"sources":["../../../src/capture/db-queue/poll.ts"],"sourcesContent":["/** `poll` / `ack` / `nack` of the database queue: claim, finish and retry-with-backoff of attempts. */\nimport type { PollableJob } from \"../../adapters/capture-queue.ts\";\nimport { isTerminalJob, parseAttemptRowId } from \"../../models/capture-job.ts\";\nimport type { CaptureJobRow } from \"../../schema/capture-job.ts\";\nimport { claimNext } from \"./claim.ts\";\nimport { isoAt } from \"./clock.ts\";\nimport type { DatabaseJob, QueueRuntime } from \"./types.ts\";\n\n/** Longest a nack may delay a retry; larger delays are clamped. */\nexport const MAX_NACK_DELAY_MS = 60 * 60_000;\n\n/** Options of `nack`, as `PollableCaptureQueue` defines them. */\nexport interface NackOptions {\n requeue?: boolean;\n delayMs?: number;\n error?: string;\n}\n\n/**\n * Claim one job. Probes once; with `waitMs`, an empty probe waits for a local enqueue (or the\n * timeout) and probes once more only when woken, so an idle wait costs a single probe.\n */\nexport async function pollJob(\n rt: QueueRuntime,\n options?: { waitMs?: number },\n): Promise<DatabaseJob | null> {\n const since = rt.wake.version();\n rt.pollVersion = since;\n const job = await claimNext(rt);\n const waitMs = options?.waitMs ?? 0;\n if (job !== null || waitMs <= 0) {\n return job;\n }\n return (await rt.wake.wait(waitMs, since)) ? await claimNext(rt) : null;\n}\n\n/** The attempt succeeded: its row and the job row become `done` (a cancelled job stays cancelled). */\nexport async function ackJob(rt: QueueRuntime, job: PollableJob): Promise<void> {\n const ref = attemptRef(job);\n const now = isoAt(rt);\n await rt.model.finishAttempt(ref.receipt, \"done\", now);\n if ((await rt.model.get(ref.jobId))?.status !== \"cancelled\") {\n await rt.model.finishJob(ref.jobId, \"done\", now);\n }\n}\n\n/**\n * The attempt failed. Only the current holder settles the job: a holder whose attempt was taken\n * over (its row is no longer `running`, or the job moved on to a later attempt) writes nothing, so a\n * stale holder can never requeue or fail a job another instance is running.\n */\nexport async function nackJob(\n rt: QueueRuntime,\n job: PollableJob,\n options: NackOptions = {},\n): Promise<void> {\n const ref = attemptRef(job);\n if ((await rt.model.get(ref.receipt))?.status !== \"running\") {\n return;\n }\n await rt.model.finishAttempt(ref.receipt, \"failed\", isoAt(rt), options.error ?? null);\n const row = await rt.model.get(ref.jobId);\n if (row === null || isTerminalJob(row) || row.attempt !== ref.attempt) {\n return;\n }\n await settleFailure(rt, row, options);\n}\n\n/** Cancelled, final, or retried after `delayMs` (clamped to `[0, MAX_NACK_DELAY_MS]`). */\nasync function settleFailure(\n rt: QueueRuntime,\n row: CaptureJobRow,\n options: NackOptions,\n): Promise<void> {\n const now = isoAt(rt);\n const error = options.error ?? null;\n if (row.cancelRequestedAt !== null) {\n await rt.model.finishJob(row.jobId, \"cancelled\", now, error);\n return;\n }\n if (options.requeue === false || row.attempt >= row.maxAttempts) {\n await rt.model.finishJob(row.jobId, \"failed\", now, error ?? \"failed\");\n return;\n }\n const delayMs = Math.min(Math.max(options.delayMs ?? 0, 0), MAX_NACK_DELAY_MS);\n await rt.model.requeue(row.jobId, isoAt(rt, delayMs), error, now);\n}\n\n/** The attempt a polled job refers to, from its receipt (`<jobId>:<attempt>`). */\nexport function attemptRef(job: PollableJob): { receipt: string; jobId: string; attempt: number } {\n const parsed = job.receipt === undefined ? null : parseAttemptRowId(job.receipt);\n if (parsed === null || job.receipt === undefined) {\n throw new Error(\"not a database queue job: missing or malformed receipt\");\n }\n return { receipt: job.receipt, ...parsed };\n}\n"],"mappings":";;;;;;;;AASA,MAAa,oBAAoB;;;;;AAajC,eAAsB,QACpB,IACA,SAC6B;CAC7B,MAAM,QAAQ,GAAG,KAAK,QAAQ;CAC9B,GAAG,cAAc;CACjB,MAAM,MAAM,MAAM,UAAU,EAAE;CAC9B,MAAM,SAAS,SAAS,UAAU;CAClC,IAAI,QAAQ,QAAQ,UAAU,GAC5B,OAAO;CAET,OAAQ,MAAM,GAAG,KAAK,KAAK,QAAQ,KAAK,IAAK,MAAM,UAAU,EAAE,IAAI;AACrE;;AAGA,eAAsB,OAAO,IAAkB,KAAiC;CAC9E,MAAM,MAAM,WAAW,GAAG;CAC1B,MAAM,MAAM,MAAM,EAAE;CACpB,MAAM,GAAG,MAAM,cAAc,IAAI,SAAS,QAAQ,GAAG;CACrD,KAAK,MAAM,GAAG,MAAM,IAAI,IAAI,KAAK,EAAA,EAAI,WAAW,aAC9C,MAAM,GAAG,MAAM,UAAU,IAAI,OAAO,QAAQ,GAAG;AAEnD;;;;;;AAOA,eAAsB,QACpB,IACA,KACA,UAAuB,CAAC,GACT;CACf,MAAM,MAAM,WAAW,GAAG;CAC1B,KAAK,MAAM,GAAG,MAAM,IAAI,IAAI,OAAO,EAAA,EAAI,WAAW,WAChD;CAEF,MAAM,GAAG,MAAM,cAAc,IAAI,SAAS,UAAU,MAAM,EAAE,GAAG,QAAQ,SAAS,IAAI;CACpF,MAAM,MAAM,MAAM,GAAG,MAAM,IAAI,IAAI,KAAK;CACxC,IAAI,QAAQ,QAAQ,cAAc,GAAG,KAAK,IAAI,YAAY,IAAI,SAC5D;CAEF,MAAM,cAAc,IAAI,KAAK,OAAO;AACtC;;AAGA,eAAe,cACb,IACA,KACA,SACe;CACf,MAAM,MAAM,MAAM,EAAE;CACpB,MAAM,QAAQ,QAAQ,SAAS;CAC/B,IAAI,IAAI,sBAAsB,MAAM;EAClC,MAAM,GAAG,MAAM,UAAU,IAAI,OAAO,aAAa,KAAK,KAAK;EAC3D;CACF;CACA,IAAI,QAAQ,YAAY,SAAS,IAAI,WAAW,IAAI,aAAa;EAC/D,MAAM,GAAG,MAAM,UAAU,IAAI,OAAO,UAAU,KAAK,SAAS,QAAQ;EACpE;CACF;CACA,MAAM,UAAU,KAAK,IAAI,KAAK,IAAI,QAAQ,WAAW,GAAG,CAAC,GAAG,iBAAiB;CAC7E,MAAM,GAAG,MAAM,QAAQ,IAAI,OAAO,MAAM,IAAI,OAAO,GAAG,OAAO,GAAG;AAClE;;AAGA,SAAgB,WAAW,KAAuE;CAChG,MAAM,SAAS,IAAI,YAAY,KAAA,IAAY,OAAO,kBAAkB,IAAI,OAAO;CAC/E,IAAI,WAAW,QAAQ,IAAI,YAAY,KAAA,GACrC,MAAM,IAAI,MAAM,wDAAwD;CAE1E,OAAO;EAAE,SAAS,IAAI;EAAS,GAAG;CAAO;AAC3C"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import __tsdown_shims_path from 'node:path';
|
|
2
|
+
import __tsdown_shims_url from 'node:url';
|
|
3
|
+
//#region src/capture/db-queue/timers.d.ts
|
|
4
|
+
/** Timer seam of the database queue: production uses the global timers, tests a manual clock. */
|
|
5
|
+
/** The two timer calls the queue, its wake signal and the lease keeper make. */
|
|
6
|
+
interface QueueTimers {
|
|
7
|
+
setTimeout(callback: () => void, ms: number): unknown;
|
|
8
|
+
clearTimeout(handle: unknown): void;
|
|
9
|
+
}
|
|
10
|
+
//#endregion
|
|
11
|
+
export { QueueTimers };
|
|
12
|
+
//# sourceMappingURL=timers.d.mts.map
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import "node:path";
|
|
2
|
+
import "node:url";
|
|
3
|
+
import.meta.url;
|
|
4
|
+
//#region src/capture/db-queue/timers.ts
|
|
5
|
+
/** Global timers, unref'd so a waiting poll or a lease check never holds the process open. */
|
|
6
|
+
const REAL_TIMERS = {
|
|
7
|
+
setTimeout: (callback, ms) => {
|
|
8
|
+
const handle = setTimeout(callback, Math.max(0, ms));
|
|
9
|
+
handle.unref?.();
|
|
10
|
+
return handle;
|
|
11
|
+
},
|
|
12
|
+
clearTimeout: (handle) => {
|
|
13
|
+
clearTimeout(handle);
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
/** Call `callback` every `ms` until the returned stop function runs; built on `setTimeout`. */
|
|
17
|
+
function repeatEvery(timers, ms, callback) {
|
|
18
|
+
let handle;
|
|
19
|
+
let stopped = false;
|
|
20
|
+
const arm = () => {
|
|
21
|
+
handle = timers.setTimeout(() => {
|
|
22
|
+
if (!stopped) {
|
|
23
|
+
callback();
|
|
24
|
+
arm();
|
|
25
|
+
}
|
|
26
|
+
}, ms);
|
|
27
|
+
};
|
|
28
|
+
arm();
|
|
29
|
+
return () => {
|
|
30
|
+
stopped = true;
|
|
31
|
+
timers.clearTimeout(handle);
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
//#endregion
|
|
35
|
+
export { REAL_TIMERS, repeatEvery };
|
|
36
|
+
|
|
37
|
+
//# sourceMappingURL=timers.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"timers.mjs","names":[],"sources":["../../../src/capture/db-queue/timers.ts"],"sourcesContent":["/** Timer seam of the database queue: production uses the global timers, tests a manual clock. */\n\n/** The two timer calls the queue, its wake signal and the lease keeper make. */\nexport interface QueueTimers {\n setTimeout(callback: () => void, ms: number): unknown;\n clearTimeout(handle: unknown): void;\n}\n\n/** Global timers, unref'd so a waiting poll or a lease check never holds the process open. */\nexport const REAL_TIMERS: QueueTimers = {\n setTimeout: (callback, ms) => {\n const handle = setTimeout(callback, Math.max(0, ms));\n handle.unref?.();\n return handle;\n },\n clearTimeout: (handle) => {\n clearTimeout(handle as ReturnType<typeof setTimeout>);\n },\n};\n\n/** Call `callback` every `ms` until the returned stop function runs; built on `setTimeout`. */\nexport function repeatEvery(timers: QueueTimers, ms: number, callback: () => void): () => void {\n let handle: unknown;\n let stopped = false;\n const arm = (): void => {\n handle = timers.setTimeout(() => {\n if (!stopped) {\n callback();\n arm();\n }\n }, ms);\n };\n arm();\n return () => {\n stopped = true;\n timers.clearTimeout(handle);\n };\n}\n"],"mappings":";;;;;AASA,MAAa,cAA2B;CACtC,aAAa,UAAU,OAAO;EAC5B,MAAM,SAAS,WAAW,UAAU,KAAK,IAAI,GAAG,EAAE,CAAC;EACnD,OAAO,QAAQ;EACf,OAAO;CACT;CACA,eAAe,WAAW;EACxB,aAAa,MAAuC;CACtD;AACF;;AAGA,SAAgB,YAAY,QAAqB,IAAY,UAAkC;CAC7F,IAAI;CACJ,IAAI,UAAU;CACd,MAAM,YAAkB;EACtB,SAAS,OAAO,iBAAiB;GAC/B,IAAI,CAAC,SAAS;IACZ,SAAS;IACT,IAAI;GACN;EACF,GAAG,EAAE;CACP;CACA,IAAI;CACJ,aAAa;EACX,UAAU;EACV,OAAO,aAAa,MAAM;CAC5B;AACF"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import __tsdown_shims_path from 'node:path';
|
|
2
|
+
import __tsdown_shims_url from 'node:url';
|
|
3
|
+
import { Logger } from "../../logger.mjs";
|
|
4
|
+
import { AdapterLifecycle } from "../../adapter/metadata.mjs";
|
|
5
|
+
import { CaptureJob, LeaseRenewal, PollableCaptureQueue, PollableJob } from "../../adapter/capture-queue.mjs";
|
|
6
|
+
import { CaptureJobKind } from "../../schema/capture-job.mjs";
|
|
7
|
+
import { QueueTimers } from "./timers.mjs";
|
|
8
|
+
import "../../models/capture-job.mjs";
|
|
9
|
+
//#region src/capture/db-queue/types.d.ts
|
|
10
|
+
/** Options of {@link createDatabaseCaptureQueue}. */
|
|
11
|
+
interface DatabaseCaptureQueueOptions {
|
|
12
|
+
/** Job kinds this instance polls (default `["capture"]`); enqueue accepts every kind. */
|
|
13
|
+
kinds?: readonly CaptureJobKind[];
|
|
14
|
+
/** Lease granted per attempt, renewed by a heartbeat and by progress (default {@link DEFAULT_LEASE_MS}, 5 min; `testing.leaseSeconds`). */
|
|
15
|
+
leaseMs?: number;
|
|
16
|
+
/** Retries after the first attempt (`testing.retries`, default 2: three attempts in total). */
|
|
17
|
+
retries?: number;
|
|
18
|
+
/** Identity written on the attempt rows this instance holds (default host, pid and a random tag). */
|
|
19
|
+
owner?: string;
|
|
20
|
+
/** Clock in epoch ms (default `Date.now`); tests inject one to drive leases and visibility. */
|
|
21
|
+
now?: () => number;
|
|
22
|
+
/** Timers of the local wake wait (default the global timers); tests inject a manual clock. */
|
|
23
|
+
timers?: QueueTimers;
|
|
24
|
+
/** Explicit logger override; the host normally binds one through `setLogger`. */
|
|
25
|
+
logger?: Logger;
|
|
26
|
+
/**
|
|
27
|
+
* Called after a job failed because its last attempt lost its lease: nothing was running to
|
|
28
|
+
* record the failure, so the host writes it (a build left `capturing`). May run on every
|
|
29
|
+
* instance that finds the job, so it must be idempotent; a throw is logged and ignored.
|
|
30
|
+
*/
|
|
31
|
+
onExhausted?: (job: DatabaseJob, error: string) => Promise<void>;
|
|
32
|
+
}
|
|
33
|
+
/** Default lease per attempt: long enough for a slow stage, short enough to recover a crash. */
|
|
34
|
+
declare const DEFAULT_LEASE_MS: number;
|
|
35
|
+
/** Default `testing.retries` (ADR 0006, decision 11). */
|
|
36
|
+
declare const DEFAULT_RETRIES = 2;
|
|
37
|
+
/** A job of any kind, as `enqueueJob` takes it. */
|
|
38
|
+
interface QueueJobInput {
|
|
39
|
+
kind: CaptureJobKind;
|
|
40
|
+
/** Build the job works on; notify jobs may have none. */
|
|
41
|
+
buildId?: string | null;
|
|
42
|
+
/** `capture_shards` row, for a shard capture job. */
|
|
43
|
+
shard?: string;
|
|
44
|
+
reqId?: string;
|
|
45
|
+
traceparent?: string;
|
|
46
|
+
/** Kind-specific fields (channel id, zip key, …), stored as JSON. */
|
|
47
|
+
data?: Record<string, unknown>;
|
|
48
|
+
/** Attempts allowed in total for this job; defaults to the queue's (`retries` + 1). */
|
|
49
|
+
maxAttempts?: number;
|
|
50
|
+
}
|
|
51
|
+
/** A job claimed from the database queue. */
|
|
52
|
+
interface DatabaseJob extends PollableJob {
|
|
53
|
+
/** Id of the job row (`receipt` is the attempt row, `<jobId>:<attempt>`). */
|
|
54
|
+
jobId: string;
|
|
55
|
+
kind: CaptureJobKind;
|
|
56
|
+
/** Attempt number, from 1 (`attempts` is the number made before this one). */
|
|
57
|
+
attempt: number;
|
|
58
|
+
maxAttempts: number;
|
|
59
|
+
/** Kind-specific fields given at enqueue. */
|
|
60
|
+
data: Record<string, unknown>;
|
|
61
|
+
}
|
|
62
|
+
/** The database queue: a {@link PollableCaptureQueue} plus multi-kind enqueue and a local wake. */
|
|
63
|
+
interface DatabaseCaptureQueue extends PollableCaptureQueue {
|
|
64
|
+
readonly lifecycle: AdapterLifecycle;
|
|
65
|
+
poll(options?: {
|
|
66
|
+
waitMs?: number;
|
|
67
|
+
}): Promise<DatabaseJob | null>;
|
|
68
|
+
extend(job: PollableJob): Promise<LeaseRenewal>;
|
|
69
|
+
/** Put a job of any kind on the queue; resolves to its job id. */
|
|
70
|
+
enqueueJob(job: QueueJobInput): Promise<string>;
|
|
71
|
+
/** Cancel a job by job id, or every unfinished job of a build (every kind) by build id. */
|
|
72
|
+
cancel(id: string): Promise<boolean>;
|
|
73
|
+
/** Read the job's state without renewing its lease (the 5 s cancel check). */
|
|
74
|
+
inspect(job: PollableJob): Promise<LeaseRenewal>;
|
|
75
|
+
/**
|
|
76
|
+
* Resolve true when this instance enqueues a job (including one enqueued since the last `poll`
|
|
77
|
+
* began, so no wake-up is lost), false after `ms`.
|
|
78
|
+
*/
|
|
79
|
+
waitForWork(ms: number): Promise<boolean>;
|
|
80
|
+
}
|
|
81
|
+
//#endregion
|
|
82
|
+
export { DEFAULT_LEASE_MS, DEFAULT_RETRIES, DatabaseCaptureQueue, DatabaseCaptureQueueOptions, DatabaseJob, QueueJobInput };
|
|
83
|
+
//# sourceMappingURL=types.d.mts.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import "node:path";
|
|
2
|
+
import "node:url";
|
|
3
|
+
import.meta.url;
|
|
4
|
+
//#region src/capture/db-queue/types.ts
|
|
5
|
+
/** Default lease per attempt: long enough for a slow stage, short enough to recover a crash. */
|
|
6
|
+
const DEFAULT_LEASE_MS = 3e5;
|
|
7
|
+
/** Default `testing.retries` (ADR 0006, decision 11). */
|
|
8
|
+
const DEFAULT_RETRIES = 2;
|
|
9
|
+
//#endregion
|
|
10
|
+
export { DEFAULT_LEASE_MS, DEFAULT_RETRIES };
|
|
11
|
+
|
|
12
|
+
//# sourceMappingURL=types.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.mjs","names":[],"sources":["../../../src/capture/db-queue/types.ts"],"sourcesContent":["/** Option, job and runtime types of the database capture queue. */\nimport type {\n CaptureJob,\n LeaseRenewal,\n PollableCaptureQueue,\n PollableJob,\n} from \"../../adapters/capture-queue.ts\";\nimport type { AdapterLifecycle } from \"../../adapters/metadata.ts\";\nimport type { Logger } from \"../../logger.ts\";\nimport type { CaptureJobModel } from \"../../models/capture-job.ts\";\nimport type { CaptureJobKind } from \"../../schema/capture-job.ts\";\nimport type { QueueTimers } from \"./timers.ts\";\nimport type { WakeSignal } from \"./wake.ts\";\n\n/** Options of {@link createDatabaseCaptureQueue}. */\nexport interface DatabaseCaptureQueueOptions {\n /** Job kinds this instance polls (default `[\"capture\"]`); enqueue accepts every kind. */\n kinds?: readonly CaptureJobKind[];\n /** Lease granted per attempt, renewed by a heartbeat and by progress (default {@link DEFAULT_LEASE_MS}, 5 min; `testing.leaseSeconds`). */\n leaseMs?: number;\n /** Retries after the first attempt (`testing.retries`, default 2: three attempts in total). */\n retries?: number;\n /** Identity written on the attempt rows this instance holds (default host, pid and a random tag). */\n owner?: string;\n /** Clock in epoch ms (default `Date.now`); tests inject one to drive leases and visibility. */\n now?: () => number;\n /** Timers of the local wake wait (default the global timers); tests inject a manual clock. */\n timers?: QueueTimers;\n /** Explicit logger override; the host normally binds one through `setLogger`. */\n logger?: Logger;\n /**\n * Called after a job failed because its last attempt lost its lease: nothing was running to\n * record the failure, so the host writes it (a build left `capturing`). May run on every\n * instance that finds the job, so it must be idempotent; a throw is logged and ignored.\n */\n onExhausted?: (job: DatabaseJob, error: string) => Promise<void>;\n}\n\n/** Default lease per attempt: long enough for a slow stage, short enough to recover a crash. */\nexport const DEFAULT_LEASE_MS = 5 * 60_000;\n\n/** Default `testing.retries` (ADR 0006, decision 11). */\nexport const DEFAULT_RETRIES = 2;\n\n/** A job of any kind, as `enqueueJob` takes it. */\nexport interface QueueJobInput {\n kind: CaptureJobKind;\n /** Build the job works on; notify jobs may have none. */\n buildId?: string | null;\n /** `capture_shards` row, for a shard capture job. */\n shard?: string;\n reqId?: string;\n traceparent?: string;\n /** Kind-specific fields (channel id, zip key, …), stored as JSON. */\n data?: Record<string, unknown>;\n /** Attempts allowed in total for this job; defaults to the queue's (`retries` + 1). */\n maxAttempts?: number;\n}\n\n/** A job claimed from the database queue. */\nexport interface DatabaseJob extends PollableJob {\n /** Id of the job row (`receipt` is the attempt row, `<jobId>:<attempt>`). */\n jobId: string;\n kind: CaptureJobKind;\n /** Attempt number, from 1 (`attempts` is the number made before this one). */\n attempt: number;\n maxAttempts: number;\n /** Kind-specific fields given at enqueue. */\n data: Record<string, unknown>;\n}\n\n/** The database queue: a {@link PollableCaptureQueue} plus multi-kind enqueue and a local wake. */\nexport interface DatabaseCaptureQueue extends PollableCaptureQueue {\n readonly lifecycle: AdapterLifecycle;\n poll(options?: { waitMs?: number }): Promise<DatabaseJob | null>;\n extend(job: PollableJob): Promise<LeaseRenewal>;\n /** Put a job of any kind on the queue; resolves to its job id. */\n enqueueJob(job: QueueJobInput): Promise<string>;\n /** Cancel a job by job id, or every unfinished job of a build (every kind) by build id. */\n cancel(id: string): Promise<boolean>;\n /** Read the job's state without renewing its lease (the 5 s cancel check). */\n inspect(job: PollableJob): Promise<LeaseRenewal>;\n /**\n * Resolve true when this instance enqueues a job (including one enqueued since the last `poll`\n * began, so no wake-up is lost), false after `ms`.\n */\n waitForWork(ms: number): Promise<boolean>;\n}\n\n/** State shared by the queue's modules. */\nexport interface QueueRuntime {\n model: CaptureJobModel;\n kinds: readonly CaptureJobKind[];\n leaseMs: number;\n maxAttempts: number;\n owner: string;\n now: () => number;\n wake: WakeSignal;\n /** Wake version read when the latest poll began. */\n pollVersion: number;\n logger?: Logger;\n onExhausted?: DatabaseCaptureQueueOptions[\"onExhausted\"];\n}\n\n/** The capture-shaped job `enqueue` takes, re-exported for the modules. */\nexport type { CaptureJob };\n"],"mappings":";;;;;AAuCA,MAAa,mBAAmB;;AAGhC,MAAa,kBAAkB"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import "node:path";
|
|
2
|
+
import "node:url";
|
|
3
|
+
import.meta.url;
|
|
4
|
+
import { REAL_TIMERS } from "./timers.mjs";
|
|
5
|
+
//#region src/capture/db-queue/wake.ts
|
|
6
|
+
/** In-process wake signal: an enqueue on this instance wakes its waiting poller at once. */
|
|
7
|
+
/** Create a wake signal over `timers` (unref'd globals by default). */
|
|
8
|
+
function createWakeSignal(timers = REAL_TIMERS) {
|
|
9
|
+
const waiters = /* @__PURE__ */ new Set();
|
|
10
|
+
let closed = false;
|
|
11
|
+
let notifies = 0;
|
|
12
|
+
const settleAll = (woken) => {
|
|
13
|
+
for (const settle of waiters) settle(woken);
|
|
14
|
+
waiters.clear();
|
|
15
|
+
};
|
|
16
|
+
return {
|
|
17
|
+
notify: () => {
|
|
18
|
+
notifies += 1;
|
|
19
|
+
settleAll(true);
|
|
20
|
+
},
|
|
21
|
+
version: () => notifies,
|
|
22
|
+
wait: async (ms, since) => {
|
|
23
|
+
if (closed || since !== void 0 && since !== notifies) return !closed;
|
|
24
|
+
return await waitFor(timers, waiters, ms);
|
|
25
|
+
},
|
|
26
|
+
close: () => {
|
|
27
|
+
closed = true;
|
|
28
|
+
settleAll(false);
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
async function waitFor(timers, waiters, ms) {
|
|
33
|
+
const { promise, resolve } = Promise.withResolvers();
|
|
34
|
+
const timer = timers.setTimeout(() => {
|
|
35
|
+
waiters.delete(settle);
|
|
36
|
+
resolve(false);
|
|
37
|
+
}, ms);
|
|
38
|
+
const settle = (woken) => {
|
|
39
|
+
timers.clearTimeout(timer);
|
|
40
|
+
resolve(woken);
|
|
41
|
+
};
|
|
42
|
+
waiters.add(settle);
|
|
43
|
+
return await promise;
|
|
44
|
+
}
|
|
45
|
+
//#endregion
|
|
46
|
+
export { createWakeSignal };
|
|
47
|
+
|
|
48
|
+
//# sourceMappingURL=wake.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wake.mjs","names":[],"sources":["../../../src/capture/db-queue/wake.ts"],"sourcesContent":["/** In-process wake signal: an enqueue on this instance wakes its waiting poller at once. */\nimport { REAL_TIMERS, type QueueTimers } from \"./timers.ts\";\n\n/** Wakes waiters on local enqueue; other instances find the job on their next poll. */\nexport interface WakeSignal {\n /** Wake every current waiter. */\n notify(): void;\n /** Count of notifies so far; pass it to `wait` to catch a notify that came before the wait. */\n version(): number;\n /**\n * Resolve true when notified, false after `ms` or once closed. With `since`, resolve true at once\n * if a notify came after that version was read (no lost wake-up between a probe and its wait).\n */\n wait(ms: number, since?: number): Promise<boolean>;\n /** Release every waiter (false) and make later waits resolve at once; idempotent. */\n close(): void;\n}\n\n/** Create a wake signal over `timers` (unref'd globals by default). */\nexport function createWakeSignal(timers: QueueTimers = REAL_TIMERS): WakeSignal {\n const waiters = new Set<(woken: boolean) => void>();\n let closed = false;\n let notifies = 0;\n const settleAll = (woken: boolean): void => {\n for (const settle of waiters) {\n settle(woken);\n }\n waiters.clear();\n };\n return {\n notify: () => {\n notifies += 1;\n settleAll(true);\n },\n version: () => notifies,\n wait: async (ms, since) => {\n if (closed || (since !== undefined && since !== notifies)) {\n return !closed;\n }\n return await waitFor(timers, waiters, ms);\n },\n close: () => {\n closed = true;\n settleAll(false);\n },\n };\n}\n\nasync function waitFor(\n timers: QueueTimers,\n waiters: Set<(woken: boolean) => void>,\n ms: number,\n): Promise<boolean> {\n const { promise, resolve } = Promise.withResolvers<boolean>();\n const timer = timers.setTimeout(() => {\n waiters.delete(settle);\n resolve(false);\n }, ms);\n const settle = (woken: boolean): void => {\n timers.clearTimeout(timer);\n resolve(woken);\n };\n waiters.add(settle);\n return await promise;\n}\n"],"mappings":";;;;;;;AAmBA,SAAgB,iBAAiB,SAAsB,aAAyB;CAC9E,MAAM,0BAAU,IAAI,IAA8B;CAClD,IAAI,SAAS;CACb,IAAI,WAAW;CACf,MAAM,aAAa,UAAyB;EAC1C,KAAK,MAAM,UAAU,SACnB,OAAO,KAAK;EAEd,QAAQ,MAAM;CAChB;CACA,OAAO;EACL,cAAc;GACZ,YAAY;GACZ,UAAU,IAAI;EAChB;EACA,eAAe;EACf,MAAM,OAAO,IAAI,UAAU;GACzB,IAAI,UAAW,UAAU,KAAA,KAAa,UAAU,UAC9C,OAAO,CAAC;GAEV,OAAO,MAAM,QAAQ,QAAQ,SAAS,EAAE;EAC1C;EACA,aAAa;GACX,SAAS;GACT,UAAU,KAAK;EACjB;CACF;AACF;AAEA,eAAe,QACb,QACA,SACA,IACkB;CAClB,MAAM,EAAE,SAAS,YAAY,QAAQ,cAAuB;CAC5D,MAAM,QAAQ,OAAO,iBAAiB;EACpC,QAAQ,OAAO,MAAM;EACrB,QAAQ,KAAK;CACf,GAAG,EAAE;CACL,MAAM,UAAU,UAAyB;EACvC,OAAO,aAAa,KAAK;EACzB,QAAQ,KAAK;CACf;CACA,QAAQ,IAAI,MAAM;CAClB,OAAO,MAAM;AACf"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import "node:path";
|
|
2
|
+
import "node:url";
|
|
3
|
+
import.meta.url;
|
|
4
|
+
//#region src/capture/design.ts
|
|
5
|
+
/**
|
|
6
|
+
* Resolve a story's design URL. `storyshelf.figma` (plain string) wins over the
|
|
7
|
+
* `design` addon shape (`string | {url} | {type,url} | {type,url}[]`, first URL wins).
|
|
8
|
+
*/
|
|
9
|
+
function extractDesignUrl(parameters, parse) {
|
|
10
|
+
const explicit = parameters?.storyshelf?.figma;
|
|
11
|
+
const raw = typeof explicit === "string" && explicit.trim() !== "" ? explicit : firstDesignUrl(parameters?.design);
|
|
12
|
+
if (raw === void 0 || raw.trim() === "") return {};
|
|
13
|
+
const parsed = parse(raw);
|
|
14
|
+
return parsed ? { url: parsed.url } : { invalid: raw };
|
|
15
|
+
}
|
|
16
|
+
/** First URL of the `design` addon value, whatever shape it takes. */
|
|
17
|
+
function firstDesignUrl(value) {
|
|
18
|
+
if (typeof value === "string") return value;
|
|
19
|
+
if (Array.isArray(value)) {
|
|
20
|
+
for (const item of value) {
|
|
21
|
+
const url = firstDesignUrl(item);
|
|
22
|
+
if (url !== void 0) return url;
|
|
23
|
+
}
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const url = value?.url;
|
|
27
|
+
return typeof url === "string" ? url : void 0;
|
|
28
|
+
}
|
|
29
|
+
/** Log (once per story) design values that were declared but are not usable Figma URLs. */
|
|
30
|
+
function logInvalidDesigns(logger, stories) {
|
|
31
|
+
for (const story of stories) if (story.designInvalid !== void 0) logger?.warn({
|
|
32
|
+
storyId: story.id,
|
|
33
|
+
design: story.designInvalid
|
|
34
|
+
}, "ignored invalid design url");
|
|
35
|
+
}
|
|
36
|
+
//#endregion
|
|
37
|
+
export { extractDesignUrl, logInvalidDesigns };
|
|
38
|
+
|
|
39
|
+
//# sourceMappingURL=design.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"design.mjs","names":[],"sources":["../../src/capture/design.ts"],"sourcesContent":["/** Design-source extraction from Storybook story parameters (`design` addon + `storyshelf.figma`). */\n\n/** Story parameters relevant to design linking, as serialized in the Storybook index. */\nexport interface DesignParameters {\n design?: unknown;\n storyshelf?: { figma?: unknown };\n}\n\n/** Parses a declared link into its canonical URL; null when no design provider accepts it. */\nexport type DesignUrlParser = (raw: string) => { url: string } | null;\n\n/** Outcome of extraction: a valid design URL, a rejected raw value, or nothing declared. */\nexport interface DesignExtraction {\n url?: string;\n /** Raw value that was declared but is not a usable design URL (logged by the caller). */\n invalid?: string;\n}\n\n/**\n * Resolve a story's design URL. `storyshelf.figma` (plain string) wins over the\n * `design` addon shape (`string | {url} | {type,url} | {type,url}[]`, first URL wins).\n */\nexport function extractDesignUrl(\n parameters: DesignParameters | undefined,\n parse: DesignUrlParser,\n): DesignExtraction {\n const explicit = parameters?.storyshelf?.figma;\n const raw =\n typeof explicit === \"string\" && explicit.trim() !== \"\"\n ? explicit\n : firstDesignUrl(parameters?.design);\n if (raw === undefined || raw.trim() === \"\") return {};\n const parsed = parse(raw);\n return parsed ? { url: parsed.url } : { invalid: raw };\n}\n\n/** First URL of the `design` addon value, whatever shape it takes. */\nfunction firstDesignUrl(value: unknown): string | undefined {\n if (typeof value === \"string\") return value;\n if (Array.isArray(value)) {\n for (const item of value as unknown[]) {\n const url = firstDesignUrl(item);\n if (url !== undefined) return url;\n }\n return undefined;\n }\n const url = (value as { url?: unknown } | null | undefined)?.url;\n return typeof url === \"string\" ? url : undefined;\n}\n\n/** Log (once per story) design values that were declared but are not usable Figma URLs. */\nexport function logInvalidDesigns(\n logger: { warn(obj: object, msg: string): void } | undefined,\n stories: readonly { id: string; designInvalid?: string }[],\n): void {\n for (const story of stories) {\n if (story.designInvalid !== undefined) {\n logger?.warn(\n { storyId: story.id, design: story.designInvalid },\n \"ignored invalid design url\",\n );\n }\n }\n}\n"],"mappings":";;;;;;;;AAsBA,SAAgB,iBACd,YACA,OACkB;CAClB,MAAM,WAAW,YAAY,YAAY;CACzC,MAAM,MACJ,OAAO,aAAa,YAAY,SAAS,KAAK,MAAM,KAChD,WACA,eAAe,YAAY,MAAM;CACvC,IAAI,QAAQ,KAAA,KAAa,IAAI,KAAK,MAAM,IAAI,OAAO,CAAC;CACpD,MAAM,SAAS,MAAM,GAAG;CACxB,OAAO,SAAS,EAAE,KAAK,OAAO,IAAI,IAAI,EAAE,SAAS,IAAI;AACvD;;AAGA,SAAS,eAAe,OAAoC;CAC1D,IAAI,OAAO,UAAU,UAAU,OAAO;CACtC,IAAI,MAAM,QAAQ,KAAK,GAAG;EACxB,KAAK,MAAM,QAAQ,OAAoB;GACrC,MAAM,MAAM,eAAe,IAAI;GAC/B,IAAI,QAAQ,KAAA,GAAW,OAAO;EAChC;EACA;CACF;CACA,MAAM,MAAO,OAAgD;CAC7D,OAAO,OAAO,QAAQ,WAAW,MAAM,KAAA;AACzC;;AAGA,SAAgB,kBACd,QACA,SACM;CACN,KAAK,MAAM,SAAS,SAClB,IAAI,MAAM,kBAAkB,KAAA,GAC1B,QAAQ,KACN;EAAE,SAAS,MAAM;EAAI,QAAQ,MAAM;CAAc,GACjD,4BACF;AAGN"}
|