@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
package/dist/urls.mjs
CHANGED
|
@@ -19,12 +19,14 @@ function createUrlBuilder(baseUrl, publishedBaseDomain) {
|
|
|
19
19
|
buildsList: (slug) => `${root}/projects/${slug}/builds`,
|
|
20
20
|
build: (slug, buildId) => `${root}/projects/${slug}/builds/${buildId}`,
|
|
21
21
|
buildDiff: (slug, buildId) => `${root}/projects/${slug}/builds/${buildId}/diff`,
|
|
22
|
+
snapshot: (slug, buildId, snapshotId) => `${root}/projects/${slug}/builds/${buildId}/diff?snapshot=${encodeURIComponent(snapshotId)}`,
|
|
22
23
|
library: (slug) => `${root}/projects/${slug}/library`,
|
|
23
24
|
jobs: (slug) => `${root}/projects/${slug}/jobs`,
|
|
24
25
|
labels: (slug) => `${root}/projects/${slug}/labels`,
|
|
25
|
-
label: (slug, key, value) => `${root}/projects/${slug}/labels/${encodeURIComponent(key)}/${
|
|
26
|
+
label: (slug, key, value) => `${root}/projects/${slug}/labels/${encodeURIComponent(key)}/${encodePath(value)}`,
|
|
27
|
+
component: (slug, importPath) => `${root}/projects/${slug}/components/${encodePath(importPath.replace(/^(?:\.\/)+/u, ""))}`,
|
|
26
28
|
storybook: (slug) => publishedBaseDomain ? `https://${slug}.${publishedBaseDomain}` : `${root}/projects/${slug}/storybook`,
|
|
27
|
-
storybookLabel: (slug, key, value) => `${root}/projects/${slug}/storybook/${encodeURIComponent(key)}/${
|
|
29
|
+
storybookLabel: (slug, key, value) => `${root}/projects/${slug}/storybook/${encodeURIComponent(key)}/${encodePath(value)}`,
|
|
28
30
|
storybookBuild: (slug, buildId) => publishedBaseDomain ? `https://${buildId}.${slug}.${publishedBaseDomain}` : `${root}/projects/${slug}/storybook/build/${buildId}`,
|
|
29
31
|
settings: (slug) => `${root}/projects/${slug}/settings`,
|
|
30
32
|
short: (id) => `${root}/_/${id}/`
|
|
@@ -43,6 +45,13 @@ function safeImageUrl(value) {
|
|
|
43
45
|
return null;
|
|
44
46
|
}
|
|
45
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* Encode a value that may hold slashes (a label value, a branch, an import path) as URL path
|
|
50
|
+
* segments: each segment is escaped (`?`, `#` and `%` included), the slashes stay.
|
|
51
|
+
*/
|
|
52
|
+
function encodePath(value) {
|
|
53
|
+
return value.split("/").map((segment) => encodeURIComponent(segment)).join("/");
|
|
54
|
+
}
|
|
46
55
|
//#endregion
|
|
47
56
|
export { createUrlBuilder, safeImageUrl };
|
|
48
57
|
|
package/dist/urls.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"urls.mjs","names":[],"sources":["../src/urls.ts"],"sourcesContent":["/** Type-safe URL builder for project pages and published Storybooks. */\nexport interface UrlBuilder {\n projectsList(): string;\n projects(): string;\n project(slug: string): string;\n projectsNew(): string;\n buildsList(slug: string): string;\n build(slug: string, buildId: string): string;\n buildDiff(slug: string, buildId: string): string;\n library(slug: string): string;\n jobs(slug: string): string;\n labels(slug: string): string;\n label(slug: string, key: string, value: string): string;\n storybook(slug: string): string;\n storybookLabel(slug: string, key: string, value: string): string;\n storybookBuild(slug: string, buildId: string): string;\n settings(slug: string): string;\n short(id: string): string;\n}\n\n/**\n * Create a URL builder rooted at the given base URL.\n *\n * @param baseUrl - Public root URL of the shelf server.\n * @param publishedBaseDomain - Optional wildcard domain for published Storybooks.\n * @returns The URL builder.\n */\nexport function createUrlBuilder(baseUrl: string, publishedBaseDomain?: string): UrlBuilder {\n const root = baseUrl.replace(/\\/+$/u, \"\");\n\n return {\n projectsList: () => root || \"/\",\n projects: () => `${root}/projects`,\n project: (slug) => `${root}/projects/${slug}`,\n projectsNew: () => `${root}/projects/new`,\n buildsList: (slug) => `${root}/projects/${slug}/builds`,\n build: (slug, buildId) => `${root}/projects/${slug}/builds/${buildId}`,\n buildDiff: (slug, buildId) => `${root}/projects/${slug}/builds/${buildId}/diff`,\n library: (slug) => `${root}/projects/${slug}/library`,\n jobs: (slug) => `${root}/projects/${slug}/jobs`,\n labels: (slug) => `${root}/projects/${slug}/labels`,\n label: (slug, key, value) =>\n `${root}/projects/${slug}/labels/${encodeURIComponent(key)}/${
|
|
1
|
+
{"version":3,"file":"urls.mjs","names":[],"sources":["../src/urls.ts"],"sourcesContent":["/** Type-safe URL builder for project pages and published Storybooks. */\nexport interface UrlBuilder {\n projectsList(): string;\n projects(): string;\n project(slug: string): string;\n projectsNew(): string;\n buildsList(slug: string): string;\n build(slug: string, buildId: string): string;\n buildDiff(slug: string, buildId: string): string;\n /** Review page of one snapshot: the build's diff page with that snapshot selected. */\n snapshot(slug: string, buildId: string, snapshotId: string): string;\n library(slug: string): string;\n jobs(slug: string): string;\n labels(slug: string): string;\n label(slug: string, key: string, value: string): string;\n /** Stable component page, keyed by the story file's import path. */\n component(slug: string, importPath: string): string;\n storybook(slug: string): string;\n storybookLabel(slug: string, key: string, value: string): string;\n storybookBuild(slug: string, buildId: string): string;\n settings(slug: string): string;\n short(id: string): string;\n}\n\n/**\n * Create a URL builder rooted at the given base URL.\n *\n * @param baseUrl - Public root URL of the shelf server.\n * @param publishedBaseDomain - Optional wildcard domain for published Storybooks.\n * @returns The URL builder.\n */\nexport function createUrlBuilder(baseUrl: string, publishedBaseDomain?: string): UrlBuilder {\n const root = baseUrl.replace(/\\/+$/u, \"\");\n\n return {\n projectsList: () => root || \"/\",\n projects: () => `${root}/projects`,\n project: (slug) => `${root}/projects/${slug}`,\n projectsNew: () => `${root}/projects/new`,\n buildsList: (slug) => `${root}/projects/${slug}/builds`,\n build: (slug, buildId) => `${root}/projects/${slug}/builds/${buildId}`,\n buildDiff: (slug, buildId) => `${root}/projects/${slug}/builds/${buildId}/diff`,\n snapshot: (slug, buildId, snapshotId) =>\n `${root}/projects/${slug}/builds/${buildId}/diff?snapshot=${encodeURIComponent(snapshotId)}`,\n library: (slug) => `${root}/projects/${slug}/library`,\n jobs: (slug) => `${root}/projects/${slug}/jobs`,\n labels: (slug) => `${root}/projects/${slug}/labels`,\n label: (slug, key, value) =>\n `${root}/projects/${slug}/labels/${encodeURIComponent(key)}/${encodePath(value)}`,\n component: (slug, importPath) =>\n `${root}/projects/${slug}/components/${encodePath(importPath.replace(/^(?:\\.\\/)+/u, \"\"))}`,\n storybook: (slug) =>\n publishedBaseDomain\n ? `https://${slug}.${publishedBaseDomain}`\n : `${root}/projects/${slug}/storybook`,\n storybookLabel: (slug, key, value) =>\n `${root}/projects/${slug}/storybook/${encodeURIComponent(key)}/${encodePath(value)}`,\n storybookBuild: (slug, buildId) =>\n publishedBaseDomain\n ? `https://${buildId}.${slug}.${publishedBaseDomain}`\n : `${root}/projects/${slug}/storybook/build/${buildId}`,\n settings: (slug) => `${root}/projects/${slug}/settings`,\n short: (id) => `${root}/_/${id}/`,\n };\n}\n\n/**\n * Embeddable image URL check for IdP-supplied avatars: `https:` and\n * app-relative paths only, so `javascript:`/`data:` payloads fall back.\n */\nexport function safeImageUrl(value: string | undefined): string | null {\n if (!value) {\n return null;\n }\n if (value.startsWith(\"/\") && !value.startsWith(\"//\")) {\n return value;\n }\n try {\n const url = new URL(value);\n return url.protocol === \"https:\" ? value : null;\n } catch {\n return null;\n }\n}\n\n/**\n * Encode a value that may hold slashes (a label value, a branch, an import path) as URL path\n * segments: each segment is escaped (`?`, `#` and `%` included), the slashes stay.\n */\nfunction encodePath(value: string): string {\n return value\n .split(\"/\")\n .map((segment) => encodeURIComponent(segment))\n .join(\"/\");\n}\n"],"mappings":";;;;;;;;;;;AA+BA,SAAgB,iBAAiB,SAAiB,qBAA0C;CAC1F,MAAM,OAAO,QAAQ,QAAQ,SAAS,EAAE;CAExC,OAAO;EACL,oBAAoB,QAAQ;EAC5B,gBAAgB,GAAG,KAAK;EACxB,UAAU,SAAS,GAAG,KAAK,YAAY;EACvC,mBAAmB,GAAG,KAAK;EAC3B,aAAa,SAAS,GAAG,KAAK,YAAY,KAAK;EAC/C,QAAQ,MAAM,YAAY,GAAG,KAAK,YAAY,KAAK,UAAU;EAC7D,YAAY,MAAM,YAAY,GAAG,KAAK,YAAY,KAAK,UAAU,QAAQ;EACzE,WAAW,MAAM,SAAS,eACxB,GAAG,KAAK,YAAY,KAAK,UAAU,QAAQ,iBAAiB,mBAAmB,UAAU;EAC3F,UAAU,SAAS,GAAG,KAAK,YAAY,KAAK;EAC5C,OAAO,SAAS,GAAG,KAAK,YAAY,KAAK;EACzC,SAAS,SAAS,GAAG,KAAK,YAAY,KAAK;EAC3C,QAAQ,MAAM,KAAK,UACjB,GAAG,KAAK,YAAY,KAAK,UAAU,mBAAmB,GAAG,EAAE,GAAG,WAAW,KAAK;EAChF,YAAY,MAAM,eAChB,GAAG,KAAK,YAAY,KAAK,cAAc,WAAW,WAAW,QAAQ,eAAe,EAAE,CAAC;EACzF,YAAY,SACV,sBACI,WAAW,KAAK,GAAG,wBACnB,GAAG,KAAK,YAAY,KAAK;EAC/B,iBAAiB,MAAM,KAAK,UAC1B,GAAG,KAAK,YAAY,KAAK,aAAa,mBAAmB,GAAG,EAAE,GAAG,WAAW,KAAK;EACnF,iBAAiB,MAAM,YACrB,sBACI,WAAW,QAAQ,GAAG,KAAK,GAAG,wBAC9B,GAAG,KAAK,YAAY,KAAK,mBAAmB;EAClD,WAAW,SAAS,GAAG,KAAK,YAAY,KAAK;EAC7C,QAAQ,OAAO,GAAG,KAAK,KAAK,GAAG;CACjC;AACF;;;;;AAMA,SAAgB,aAAa,OAA0C;CACrE,IAAI,CAAC,OACH,OAAO;CAET,IAAI,MAAM,WAAW,GAAG,KAAK,CAAC,MAAM,WAAW,IAAI,GACjD,OAAO;CAET,IAAI;EAEF,OAAO,IADS,IAAI,KACX,CAAC,CAAC,aAAa,WAAW,QAAQ;CAC7C,QAAQ;EACN,OAAO;CACT;AACF;;;;;AAMA,SAAS,WAAW,OAAuB;CACzC,OAAO,MACJ,MAAM,GAAG,CAAC,CACV,KAAK,YAAY,mBAAmB,OAAO,CAAC,CAAC,CAC7C,KAAK,GAAG;AACb"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import __tsdown_shims_path from 'node:path';
|
|
2
|
+
import __tsdown_shims_url from 'node:url';
|
|
3
|
+
import { Logger } from "../logger.mjs";
|
|
4
|
+
//#region src/utils/adaptive-concurrency.d.ts
|
|
5
|
+
/** Tuning for {@link AdaptiveConcurrency}. */
|
|
6
|
+
interface AdaptiveOptions {
|
|
7
|
+
/** Starting in-flight limit. */
|
|
8
|
+
initial: number;
|
|
9
|
+
/** Lowest limit (default 1). */
|
|
10
|
+
min?: number;
|
|
11
|
+
/** Highest limit. */
|
|
12
|
+
max: number;
|
|
13
|
+
/** Event-loop lag p99 (ms) since the previous reading; defaults to a `perf_hooks` histogram. */
|
|
14
|
+
readLagMs?: () => number;
|
|
15
|
+
/** p95 latency may grow this much over the first window before the window counts as unhealthy (default 1.25). */
|
|
16
|
+
latencyTolerance?: number;
|
|
17
|
+
/** Event-loop lag p99 above this (ms) counts as unhealthy (default 100). */
|
|
18
|
+
lagLimitMs?: number;
|
|
19
|
+
/** Windows to hold after a timeout back-off before raising again (default 3). */
|
|
20
|
+
holdWindows?: number;
|
|
21
|
+
/** Scoped logger; every limit change is logged with its reason. */
|
|
22
|
+
logger?: Logger;
|
|
23
|
+
}
|
|
24
|
+
/** What one finished capture reports. */
|
|
25
|
+
interface Observation {
|
|
26
|
+
durationMs: number;
|
|
27
|
+
/** The capture hit a navigation/action timeout. */
|
|
28
|
+
timedOut?: boolean;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* AIMD controller over the number of pages rendering at once.
|
|
32
|
+
*
|
|
33
|
+
* Captures are grouped in windows (at least 8, or two per slot). A window with any timeout halves
|
|
34
|
+
* the limit and holds it there for a few windows; a window whose p95 latency grew more than the
|
|
35
|
+
* tolerance over the first window, or whose event-loop lag p99 is high, lowers it by one; a healthy
|
|
36
|
+
* window raises it by one. The first window without timeouts sets the latency baseline, so it only
|
|
37
|
+
* ever holds.
|
|
38
|
+
*/
|
|
39
|
+
declare class AdaptiveConcurrency {
|
|
40
|
+
private current;
|
|
41
|
+
private readonly min;
|
|
42
|
+
private readonly max;
|
|
43
|
+
private readonly options;
|
|
44
|
+
private window;
|
|
45
|
+
private baselineP95;
|
|
46
|
+
private hold;
|
|
47
|
+
private readonly lag;
|
|
48
|
+
constructor(options: AdaptiveOptions);
|
|
49
|
+
/** Pages allowed in flight right now. */
|
|
50
|
+
get limit(): number;
|
|
51
|
+
/** Record a finished capture; may change {@link limit} at a window boundary. */
|
|
52
|
+
observe(observation: Observation): void;
|
|
53
|
+
/** Release the lag monitor. */
|
|
54
|
+
dispose(): void;
|
|
55
|
+
private closeWindow;
|
|
56
|
+
/** Decide what a finished window means; updates the post-back-off hold. */
|
|
57
|
+
private judge;
|
|
58
|
+
/** A healthy window: set the baseline, sit out a back-off hold, or raise by one. */
|
|
59
|
+
private raiseOrHold;
|
|
60
|
+
private isSlow;
|
|
61
|
+
}
|
|
62
|
+
/** Whether an error is a navigation/action timeout (Playwright and Puppeteer both name it `TimeoutError`). */
|
|
63
|
+
declare function isTimeoutError(error: unknown): boolean;
|
|
64
|
+
//#endregion
|
|
65
|
+
export { AdaptiveConcurrency, AdaptiveOptions, Observation, isTimeoutError };
|
|
66
|
+
//# sourceMappingURL=adaptive-concurrency.d.mts.map
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import "node:path";
|
|
2
|
+
import "node:url";
|
|
3
|
+
import.meta.url;
|
|
4
|
+
import { monitorEventLoopDelay } from "node:perf_hooks";
|
|
5
|
+
//#region src/utils/adaptive-concurrency.ts
|
|
6
|
+
/** Adaptive page concurrency: raise while capture stays healthy, back off when it does not. */
|
|
7
|
+
const MIN_WINDOW = 8;
|
|
8
|
+
/**
|
|
9
|
+
* AIMD controller over the number of pages rendering at once.
|
|
10
|
+
*
|
|
11
|
+
* Captures are grouped in windows (at least 8, or two per slot). A window with any timeout halves
|
|
12
|
+
* the limit and holds it there for a few windows; a window whose p95 latency grew more than the
|
|
13
|
+
* tolerance over the first window, or whose event-loop lag p99 is high, lowers it by one; a healthy
|
|
14
|
+
* window raises it by one. The first window without timeouts sets the latency baseline, so it only
|
|
15
|
+
* ever holds.
|
|
16
|
+
*/
|
|
17
|
+
var AdaptiveConcurrency = class {
|
|
18
|
+
current;
|
|
19
|
+
min;
|
|
20
|
+
max;
|
|
21
|
+
options;
|
|
22
|
+
window = [];
|
|
23
|
+
baselineP95 = null;
|
|
24
|
+
hold = 0;
|
|
25
|
+
lag;
|
|
26
|
+
constructor(options) {
|
|
27
|
+
this.options = options;
|
|
28
|
+
this.min = Math.max(1, Math.floor(options.min ?? 1));
|
|
29
|
+
this.max = Math.max(this.min, Math.floor(options.max));
|
|
30
|
+
this.current = clamp(Math.floor(options.initial), this.min, this.max);
|
|
31
|
+
this.lag = options.readLagMs ? {
|
|
32
|
+
read: options.readLagMs,
|
|
33
|
+
stop: noop
|
|
34
|
+
} : eventLoopLagReader();
|
|
35
|
+
}
|
|
36
|
+
/** Pages allowed in flight right now. */
|
|
37
|
+
get limit() {
|
|
38
|
+
return this.current;
|
|
39
|
+
}
|
|
40
|
+
/** Record a finished capture; may change {@link limit} at a window boundary. */
|
|
41
|
+
observe(observation) {
|
|
42
|
+
this.window.push(observation);
|
|
43
|
+
if (this.window.length >= Math.max(MIN_WINDOW, this.current * 2)) this.closeWindow();
|
|
44
|
+
}
|
|
45
|
+
/** Release the lag monitor. */
|
|
46
|
+
dispose() {
|
|
47
|
+
this.lag.stop();
|
|
48
|
+
}
|
|
49
|
+
closeWindow() {
|
|
50
|
+
const samples = this.window;
|
|
51
|
+
this.window = [];
|
|
52
|
+
const stats = {
|
|
53
|
+
p95: percentile(samples.map((sample) => sample.durationMs), .95),
|
|
54
|
+
timeouts: samples.filter((sample) => sample.timedOut).length,
|
|
55
|
+
lagP99: this.lag.read()
|
|
56
|
+
};
|
|
57
|
+
const from = this.current;
|
|
58
|
+
const verdict = this.judge(stats);
|
|
59
|
+
if (stats.timeouts === 0) this.baselineP95 ??= stats.p95;
|
|
60
|
+
this.current = verdict.halve ? Math.max(this.min, Math.floor(from / 2)) : clamp(from + verdict.delta, this.min, this.max);
|
|
61
|
+
if (this.current !== from) this.options.logger?.info({
|
|
62
|
+
from,
|
|
63
|
+
to: this.current,
|
|
64
|
+
reason: verdict.reason,
|
|
65
|
+
...roundedStats(stats)
|
|
66
|
+
}, "capture concurrency adjusted");
|
|
67
|
+
}
|
|
68
|
+
/** Decide what a finished window means; updates the post-back-off hold. */
|
|
69
|
+
judge(window) {
|
|
70
|
+
if (window.timeouts > 0) {
|
|
71
|
+
this.hold = this.options.holdWindows ?? 3;
|
|
72
|
+
return {
|
|
73
|
+
delta: 0,
|
|
74
|
+
halve: true,
|
|
75
|
+
reason: "timeouts"
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
const slow = this.isSlow(window.p95);
|
|
79
|
+
if (slow || window.lagP99 > (this.options.lagLimitMs ?? 100)) return {
|
|
80
|
+
delta: -1,
|
|
81
|
+
halve: false,
|
|
82
|
+
reason: slow ? "latency" : "event-loop lag"
|
|
83
|
+
};
|
|
84
|
+
return this.raiseOrHold();
|
|
85
|
+
}
|
|
86
|
+
/** A healthy window: set the baseline, sit out a back-off hold, or raise by one. */
|
|
87
|
+
raiseOrHold() {
|
|
88
|
+
if (this.baselineP95 === null) return {
|
|
89
|
+
delta: 0,
|
|
90
|
+
halve: false,
|
|
91
|
+
reason: "baseline"
|
|
92
|
+
};
|
|
93
|
+
if (this.hold > 0) {
|
|
94
|
+
this.hold -= 1;
|
|
95
|
+
return {
|
|
96
|
+
delta: 0,
|
|
97
|
+
halve: false,
|
|
98
|
+
reason: "holding after back-off"
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
return {
|
|
102
|
+
delta: 1,
|
|
103
|
+
halve: false,
|
|
104
|
+
reason: "healthy"
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
isSlow(p95) {
|
|
108
|
+
return this.baselineP95 !== null && p95 > this.baselineP95 * (this.options.latencyTolerance ?? 1.25);
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
function roundedStats(stats) {
|
|
112
|
+
return {
|
|
113
|
+
p95Ms: Math.round(stats.p95),
|
|
114
|
+
lagP99Ms: Math.round(stats.lagP99),
|
|
115
|
+
timeouts: stats.timeouts
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
/** Whether an error is a navigation/action timeout (Playwright and Puppeteer both name it `TimeoutError`). */
|
|
119
|
+
function isTimeoutError(error) {
|
|
120
|
+
if (!(error instanceof Error)) return false;
|
|
121
|
+
return error.name === "TimeoutError" || /\btimed? ?out\b/iu.test(error.message);
|
|
122
|
+
}
|
|
123
|
+
function noop() {}
|
|
124
|
+
function clamp(value, min, max) {
|
|
125
|
+
return Math.min(max, Math.max(min, value));
|
|
126
|
+
}
|
|
127
|
+
function percentile(values, fraction) {
|
|
128
|
+
const sorted = values.toSorted((left, right) => left - right);
|
|
129
|
+
return sorted[Math.min(sorted.length - 1, Math.floor(sorted.length * fraction))] ?? 0;
|
|
130
|
+
}
|
|
131
|
+
/** p99 event-loop delay (ms) since the previous read, from a `perf_hooks` histogram. */
|
|
132
|
+
function eventLoopLagReader() {
|
|
133
|
+
const histogram = monitorEventLoopDelay({ resolution: 10 });
|
|
134
|
+
histogram.enable();
|
|
135
|
+
return {
|
|
136
|
+
read: () => {
|
|
137
|
+
const lag = histogram.percentile(99) / 1e6;
|
|
138
|
+
histogram.reset();
|
|
139
|
+
return lag;
|
|
140
|
+
},
|
|
141
|
+
stop: () => {
|
|
142
|
+
histogram.disable();
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
//#endregion
|
|
147
|
+
export { AdaptiveConcurrency, isTimeoutError };
|
|
148
|
+
|
|
149
|
+
//# sourceMappingURL=adaptive-concurrency.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adaptive-concurrency.mjs","names":[],"sources":["../../src/utils/adaptive-concurrency.ts"],"sourcesContent":["/** Adaptive page concurrency: raise while capture stays healthy, back off when it does not. */\nimport { monitorEventLoopDelay } from \"node:perf_hooks\";\nimport type { Logger } from \"../logger.ts\";\n\n/** Tuning for {@link AdaptiveConcurrency}. */\nexport interface AdaptiveOptions {\n /** Starting in-flight limit. */\n initial: number;\n /** Lowest limit (default 1). */\n min?: number;\n /** Highest limit. */\n max: number;\n /** Event-loop lag p99 (ms) since the previous reading; defaults to a `perf_hooks` histogram. */\n readLagMs?: () => number;\n /** p95 latency may grow this much over the first window before the window counts as unhealthy (default 1.25). */\n latencyTolerance?: number;\n /** Event-loop lag p99 above this (ms) counts as unhealthy (default 100). */\n lagLimitMs?: number;\n /** Windows to hold after a timeout back-off before raising again (default 3). */\n holdWindows?: number;\n /** Scoped logger; every limit change is logged with its reason. */\n logger?: Logger;\n}\n\n/** What one finished capture reports. */\nexport interface Observation {\n durationMs: number;\n /** The capture hit a navigation/action timeout. */\n timedOut?: boolean;\n}\n\nconst MIN_WINDOW = 8;\n\n/**\n * AIMD controller over the number of pages rendering at once.\n *\n * Captures are grouped in windows (at least 8, or two per slot). A window with any timeout halves\n * the limit and holds it there for a few windows; a window whose p95 latency grew more than the\n * tolerance over the first window, or whose event-loop lag p99 is high, lowers it by one; a healthy\n * window raises it by one. The first window without timeouts sets the latency baseline, so it only\n * ever holds.\n */\nexport class AdaptiveConcurrency {\n private current: number;\n private readonly min: number;\n private readonly max: number;\n private readonly options: AdaptiveOptions;\n private window: Observation[] = [];\n private baselineP95: number | null = null;\n private hold = 0;\n private readonly lag: { read: () => number; stop: () => void };\n\n constructor(options: AdaptiveOptions) {\n this.options = options;\n this.min = Math.max(1, Math.floor(options.min ?? 1));\n this.max = Math.max(this.min, Math.floor(options.max));\n this.current = clamp(Math.floor(options.initial), this.min, this.max);\n this.lag = options.readLagMs ? { read: options.readLagMs, stop: noop } : eventLoopLagReader();\n }\n\n /** Pages allowed in flight right now. */\n get limit(): number {\n return this.current;\n }\n\n /** Record a finished capture; may change {@link limit} at a window boundary. */\n observe(observation: Observation): void {\n this.window.push(observation);\n if (this.window.length >= Math.max(MIN_WINDOW, this.current * 2)) {\n this.closeWindow();\n }\n }\n\n /** Release the lag monitor. */\n dispose(): void {\n this.lag.stop();\n }\n\n private closeWindow(): void {\n const samples = this.window;\n this.window = [];\n const stats = {\n p95: percentile(\n samples.map((sample) => sample.durationMs),\n 0.95,\n ),\n timeouts: samples.filter((sample) => sample.timedOut).length,\n lagP99: this.lag.read(),\n };\n const from = this.current;\n const verdict = this.judge(stats);\n // A window with a timeout carries the timeout's duration in its p95: never a baseline.\n if (stats.timeouts === 0) {\n this.baselineP95 ??= stats.p95;\n }\n this.current = verdict.halve\n ? Math.max(this.min, Math.floor(from / 2))\n : clamp(from + verdict.delta, this.min, this.max);\n if (this.current !== from) {\n this.options.logger?.info(\n { from, to: this.current, reason: verdict.reason, ...roundedStats(stats) },\n \"capture concurrency adjusted\",\n );\n }\n }\n\n /** Decide what a finished window means; updates the post-back-off hold. */\n private judge(window: WindowStats): Verdict {\n if (window.timeouts > 0) {\n this.hold = this.options.holdWindows ?? 3;\n return { delta: 0, halve: true, reason: \"timeouts\" };\n }\n const slow = this.isSlow(window.p95);\n if (slow || window.lagP99 > (this.options.lagLimitMs ?? 100)) {\n return { delta: -1, halve: false, reason: slow ? \"latency\" : \"event-loop lag\" };\n }\n return this.raiseOrHold();\n }\n\n /** A healthy window: set the baseline, sit out a back-off hold, or raise by one. */\n private raiseOrHold(): Verdict {\n if (this.baselineP95 === null) {\n return { delta: 0, halve: false, reason: \"baseline\" };\n }\n if (this.hold > 0) {\n this.hold -= 1;\n return { delta: 0, halve: false, reason: \"holding after back-off\" };\n }\n return { delta: 1, halve: false, reason: \"healthy\" };\n }\n\n private isSlow(p95: number): boolean {\n return (\n this.baselineP95 !== null && p95 > this.baselineP95 * (this.options.latencyTolerance ?? 1.25)\n );\n }\n}\n\ninterface WindowStats {\n p95: number;\n timeouts: number;\n lagP99: number;\n}\n\nfunction roundedStats(stats: WindowStats): Record<string, number> {\n return {\n p95Ms: Math.round(stats.p95),\n lagP99Ms: Math.round(stats.lagP99),\n timeouts: stats.timeouts,\n };\n}\n\ninterface Verdict {\n delta: number;\n halve: boolean;\n reason: string;\n}\n\n/** Whether an error is a navigation/action timeout (Playwright and Puppeteer both name it `TimeoutError`). */\nexport function isTimeoutError(error: unknown): boolean {\n if (!(error instanceof Error)) {\n return false;\n }\n return error.name === \"TimeoutError\" || /\\btimed? ?out\\b/iu.test(error.message);\n}\n\nfunction noop(): void {}\n\nfunction clamp(value: number, min: number, max: number): number {\n return Math.min(max, Math.max(min, value));\n}\n\nfunction percentile(values: number[], fraction: number): number {\n const sorted = values.toSorted((left, right) => left - right);\n return sorted[Math.min(sorted.length - 1, Math.floor(sorted.length * fraction))] ?? 0;\n}\n\n/** p99 event-loop delay (ms) since the previous read, from a `perf_hooks` histogram. */\nfunction eventLoopLagReader(): { read: () => number; stop: () => void } {\n const histogram = monitorEventLoopDelay({ resolution: 10 });\n histogram.enable();\n return {\n read: () => {\n const lag = histogram.percentile(99) / 1e6;\n histogram.reset();\n return lag;\n },\n stop: () => {\n histogram.disable();\n },\n };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import __tsdown_shims_path from 'node:path';
|
|
2
|
+
import __tsdown_shims_url from 'node:url';
|
|
3
|
+
//#region src/utils/connect-retry.d.ts
|
|
4
|
+
/** Retrying a connection to a remote browser service (ADR 0007, decision 4). */
|
|
5
|
+
/** Options of {@link retryConnect}. */
|
|
6
|
+
interface RetryConnectOptions {
|
|
7
|
+
/** Retries after the first attempt. */
|
|
8
|
+
retries: number;
|
|
9
|
+
/** Delay before the first retry in ms; doubles each time, with jitter, capped at 10 s. */
|
|
10
|
+
backoffMs: number;
|
|
11
|
+
/** Called before each retry with the error, the retry's number (from 1) and its delay. */
|
|
12
|
+
onRetry?: (error: unknown, retry: number, delayMs: number) => void;
|
|
13
|
+
/** Injectable for tests. */
|
|
14
|
+
sleep?: (ms: number) => Promise<void>;
|
|
15
|
+
}
|
|
16
|
+
/** Whether a connect error is worth retrying: connection-level, not a configuration mistake. */
|
|
17
|
+
declare function isRetryableConnectError(error: unknown): boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Run `connect`, retrying a connection-level failure with exponential backoff, so a provider
|
|
20
|
+
* outage or a full plan is absorbed here and crash isolation only ever sees a browser that died
|
|
21
|
+
* while rendering. The last error is thrown once the retries are spent.
|
|
22
|
+
*/
|
|
23
|
+
declare function retryConnect<T>(connect: () => Promise<T>, options: RetryConnectOptions): Promise<T>;
|
|
24
|
+
//#endregion
|
|
25
|
+
export { RetryConnectOptions, isRetryableConnectError, retryConnect };
|
|
26
|
+
//# sourceMappingURL=connect-retry.d.mts.map
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import "node:path";
|
|
2
|
+
import "node:url";
|
|
3
|
+
import.meta.url;
|
|
4
|
+
//#region src/utils/connect-retry.ts
|
|
5
|
+
const MAX_DELAY_MS = 1e4;
|
|
6
|
+
/** Errors that no retry fixes: a refused credential, a missing endpoint, mismatched versions. */
|
|
7
|
+
const PERMANENT = /\b(?:401|403|404|428|unauthorized|forbidden|version mismatch|invalid (?:token|credentials)|not found)\b/iu;
|
|
8
|
+
/** Errors of the connection itself: refused, dropped, timed out, or a service with no free session. */
|
|
9
|
+
const CONNECTION = /(?:ECONNREFUSED|ECONNRESET|EPIPE|ETIMEDOUT|ENOTFOUND|EAI_AGAIN|EHOSTUNREACH|socket hang up|websocket error|connection (?:closed|refused|reset)|timeout|timed out|capacity|too many|queue(?:d)? full|no (?:free )?(?:session|browser)|\b(?:408|425|429|500|502|503|504)\b)/iu;
|
|
10
|
+
/** Whether a connect error is worth retrying: connection-level, not a configuration mistake. */
|
|
11
|
+
function isRetryableConnectError(error) {
|
|
12
|
+
const text = error instanceof Error ? error.message : String(error);
|
|
13
|
+
return !PERMANENT.test(text) && CONNECTION.test(text);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Run `connect`, retrying a connection-level failure with exponential backoff, so a provider
|
|
17
|
+
* outage or a full plan is absorbed here and crash isolation only ever sees a browser that died
|
|
18
|
+
* while rendering. The last error is thrown once the retries are spent.
|
|
19
|
+
*/
|
|
20
|
+
async function retryConnect(connect, options) {
|
|
21
|
+
const sleep = options.sleep ?? sleepFor;
|
|
22
|
+
for (let retry = 0;; retry += 1) try {
|
|
23
|
+
return await connect();
|
|
24
|
+
} catch (error) {
|
|
25
|
+
if (retry >= options.retries || !isRetryableConnectError(error)) throw error;
|
|
26
|
+
const delayMs = Math.min(MAX_DELAY_MS, options.backoffMs * 2 ** retry) * jitter();
|
|
27
|
+
options.onRetry?.(error, retry + 1, Math.round(delayMs));
|
|
28
|
+
await sleep(delayMs);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/** 0.75 to 1.25: providers refusing for capacity should not see every shard retry in step. */
|
|
32
|
+
function jitter() {
|
|
33
|
+
return .75 + Math.random() * .5;
|
|
34
|
+
}
|
|
35
|
+
async function sleepFor(ms) {
|
|
36
|
+
await new Promise((resolve) => {
|
|
37
|
+
setTimeout(resolve, ms);
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
//#endregion
|
|
41
|
+
export { isRetryableConnectError, retryConnect };
|
|
42
|
+
|
|
43
|
+
//# sourceMappingURL=connect-retry.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connect-retry.mjs","names":[],"sources":["../../src/utils/connect-retry.ts"],"sourcesContent":["/** Retrying a connection to a remote browser service (ADR 0007, decision 4). */\n\n/** Options of {@link retryConnect}. */\nexport interface RetryConnectOptions {\n /** Retries after the first attempt. */\n retries: number;\n /** Delay before the first retry in ms; doubles each time, with jitter, capped at 10 s. */\n backoffMs: number;\n /** Called before each retry with the error, the retry's number (from 1) and its delay. */\n onRetry?: (error: unknown, retry: number, delayMs: number) => void;\n /** Injectable for tests. */\n sleep?: (ms: number) => Promise<void>;\n}\n\nconst MAX_DELAY_MS = 10_000;\n\n/** Errors that no retry fixes: a refused credential, a missing endpoint, mismatched versions. */\nconst PERMANENT =\n /\\b(?:401|403|404|428|unauthorized|forbidden|version mismatch|invalid (?:token|credentials)|not found)\\b/iu;\n\n/** Errors of the connection itself: refused, dropped, timed out, or a service with no free session. */\nconst CONNECTION =\n /(?:ECONNREFUSED|ECONNRESET|EPIPE|ETIMEDOUT|ENOTFOUND|EAI_AGAIN|EHOSTUNREACH|socket hang up|websocket error|connection (?:closed|refused|reset)|timeout|timed out|capacity|too many|queue(?:d)? full|no (?:free )?(?:session|browser)|\\b(?:408|425|429|500|502|503|504)\\b)/iu;\n\n/** Whether a connect error is worth retrying: connection-level, not a configuration mistake. */\nexport function isRetryableConnectError(error: unknown): boolean {\n const text = error instanceof Error ? error.message : String(error);\n return !PERMANENT.test(text) && CONNECTION.test(text);\n}\n\n/**\n * Run `connect`, retrying a connection-level failure with exponential backoff, so a provider\n * outage or a full plan is absorbed here and crash isolation only ever sees a browser that died\n * while rendering. The last error is thrown once the retries are spent.\n */\nexport async function retryConnect<T>(\n connect: () => Promise<T>,\n options: RetryConnectOptions,\n): Promise<T> {\n const sleep = options.sleep ?? sleepFor;\n for (let retry = 0; ; retry += 1) {\n try {\n // oxlint-disable-next-line eslint/no-await-in-loop -- attempts are sequential by design\n return await connect();\n } catch (error) {\n if (retry >= options.retries || !isRetryableConnectError(error)) {\n throw error;\n }\n const delayMs = Math.min(MAX_DELAY_MS, options.backoffMs * 2 ** retry) * jitter();\n options.onRetry?.(error, retry + 1, Math.round(delayMs));\n // oxlint-disable-next-line eslint/no-await-in-loop -- backoff between sequential attempts\n await sleep(delayMs);\n }\n }\n}\n\n/** 0.75 to 1.25: providers refusing for capacity should not see every shard retry in step. */\nfunction jitter(): number {\n return 0.75 + Math.random() * 0.5;\n}\n\nasync function sleepFor(ms: number): Promise<void> {\n await new Promise<void>((resolve) => {\n setTimeout(resolve, ms);\n });\n}\n"],"mappings":";;;;AAcA,MAAM,eAAe;;AAGrB,MAAM,YACJ;;AAGF,MAAM,aACJ;;AAGF,SAAgB,wBAAwB,OAAyB;CAC/D,MAAM,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;CAClE,OAAO,CAAC,UAAU,KAAK,IAAI,KAAK,WAAW,KAAK,IAAI;AACtD;;;;;;AAOA,eAAsB,aACpB,SACA,SACY;CACZ,MAAM,QAAQ,QAAQ,SAAS;CAC/B,KAAK,IAAI,QAAQ,IAAK,SAAS,GAC7B,IAAI;EAEF,OAAO,MAAM,QAAQ;CACvB,SAAS,OAAO;EACd,IAAI,SAAS,QAAQ,WAAW,CAAC,wBAAwB,KAAK,GAC5D,MAAM;EAER,MAAM,UAAU,KAAK,IAAI,cAAc,QAAQ,YAAY,KAAK,KAAK,IAAI,OAAO;EAChF,QAAQ,UAAU,OAAO,QAAQ,GAAG,KAAK,MAAM,OAAO,CAAC;EAEvD,MAAM,MAAM,OAAO;CACrB;AAEJ;;AAGA,SAAS,SAAiB;CACxB,OAAO,MAAO,KAAK,OAAO,IAAI;AAChC;AAEA,eAAe,SAAS,IAA2B;CACjD,MAAM,IAAI,SAAe,YAAY;EACnC,WAAW,SAAS,EAAE;CACxB,CAAC;AACH"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import __tsdown_shims_path from 'node:path';
|
|
2
|
+
import __tsdown_shims_url from 'node:url';
|
|
3
|
+
import { CaptureEnvironment } from "../adapter/capture-runner.mjs";
|
|
4
|
+
//#region src/utils/environment.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* Digest of the installed system font families (`fc-list`), or undefined where fontconfig is not
|
|
7
|
+
* available. Cached for the process (fonts do not change under a running server), except a failed
|
|
8
|
+
* run such as a timeout under load: that is retried next time rather than reported as "no fonts"
|
|
9
|
+
* for the life of the process.
|
|
10
|
+
*/
|
|
11
|
+
declare function systemFontsHash(): Promise<string | undefined>;
|
|
12
|
+
/**
|
|
13
|
+
* The environment a runner captured in: browser build, host platform, device scale factor and a
|
|
14
|
+
* font digest. Runners call this once per render with the version their driver reports.
|
|
15
|
+
*/
|
|
16
|
+
declare function detectCaptureEnvironment(browserVersion?: string, deviceScaleFactor?: number): Promise<CaptureEnvironment>;
|
|
17
|
+
//#endregion
|
|
18
|
+
export { detectCaptureEnvironment, systemFontsHash };
|
|
19
|
+
//# sourceMappingURL=environment.d.mts.map
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import "node:path";
|
|
2
|
+
import "node:url";
|
|
3
|
+
import.meta.url;
|
|
4
|
+
import { sha256 } from "./hash.mjs";
|
|
5
|
+
//#region src/utils/environment.ts
|
|
6
|
+
let fonts;
|
|
7
|
+
/**
|
|
8
|
+
* Digest of the installed system font families (`fc-list`), or undefined where fontconfig is not
|
|
9
|
+
* available. Cached for the process (fonts do not change under a running server), except a failed
|
|
10
|
+
* run such as a timeout under load: that is retried next time rather than reported as "no fonts"
|
|
11
|
+
* for the life of the process.
|
|
12
|
+
*/
|
|
13
|
+
async function systemFontsHash() {
|
|
14
|
+
fonts ??= listFonts().then((result) => {
|
|
15
|
+
if (!result.settled) fonts = void 0;
|
|
16
|
+
return result.hash;
|
|
17
|
+
});
|
|
18
|
+
return await fonts;
|
|
19
|
+
}
|
|
20
|
+
async function listFonts() {
|
|
21
|
+
const childProcess = process.getBuiltinModule?.call(process, "node:child_process");
|
|
22
|
+
if (!childProcess) return {
|
|
23
|
+
hash: void 0,
|
|
24
|
+
settled: true
|
|
25
|
+
};
|
|
26
|
+
return await new Promise((resolve) => {
|
|
27
|
+
childProcess.execFile("fc-list", [":", "family"], { timeout: 3e3 }, (error, stdout) => {
|
|
28
|
+
resolve(error ? {
|
|
29
|
+
hash: void 0,
|
|
30
|
+
settled: error.code === "ENOENT"
|
|
31
|
+
} : {
|
|
32
|
+
hash: digestOf(stdout),
|
|
33
|
+
settled: true
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* The environment a runner captured in: browser build, host platform, device scale factor and a
|
|
40
|
+
* font digest. Runners call this once per render with the version their driver reports.
|
|
41
|
+
*/
|
|
42
|
+
async function detectCaptureEnvironment(browserVersion, deviceScaleFactor = 1) {
|
|
43
|
+
const fontsHash = await systemFontsHash();
|
|
44
|
+
return {
|
|
45
|
+
...browserVersion ? { browserVersion } : {},
|
|
46
|
+
platform: `${process.platform}-${process.arch}`,
|
|
47
|
+
deviceScaleFactor,
|
|
48
|
+
...fontsHash ? { fontsHash } : {}
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
/** Digest of the distinct family names in `fc-list` output; undefined when there are none. */
|
|
52
|
+
function digestOf(stdout) {
|
|
53
|
+
const families = [...new Set(stdout.split("\n").map((line) => line.trim()))].filter(Boolean);
|
|
54
|
+
return families.length > 0 ? sha256(families.toSorted().join("\n")).slice(0, 16) : void 0;
|
|
55
|
+
}
|
|
56
|
+
//#endregion
|
|
57
|
+
export { detectCaptureEnvironment, systemFontsHash };
|
|
58
|
+
|
|
59
|
+
//# sourceMappingURL=environment.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"environment.mjs","names":[],"sources":["../../src/utils/environment.ts"],"sourcesContent":["/** Detects the rendering environment a capture runner reports (see `CaptureEnvironment`). */\nimport type { CaptureEnvironment } from \"../adapters/capture-runner.ts\";\nimport { sha256 } from \"./hash.ts\";\n\ntype ChildProcessModule = typeof import(\"node:child_process\");\n\nlet fonts: Promise<string | undefined> | undefined;\n\n/**\n * Digest of the installed system font families (`fc-list`), or undefined where fontconfig is not\n * available. Cached for the process (fonts do not change under a running server), except a failed\n * run such as a timeout under load: that is retried next time rather than reported as \"no fonts\"\n * for the life of the process.\n */\nexport async function systemFontsHash(): Promise<string | undefined> {\n fonts ??= listFonts().then((result) => {\n if (!result.settled) {\n fonts = undefined;\n }\n return result.hash;\n });\n return await fonts;\n}\n\n/** An `fc-list` outcome; `settled` is false for a failure worth retrying. */\ninterface FontsResult {\n hash: string | undefined;\n settled: boolean;\n}\n\nasync function listFonts(): Promise<FontsResult> {\n const getBuiltin = (process as { getBuiltinModule?: (id: string) => unknown }).getBuiltinModule;\n const childProcess = getBuiltin?.call(process, \"node:child_process\") as\n | ChildProcessModule\n | undefined;\n if (!childProcess) {\n return { hash: undefined, settled: true };\n }\n return await new Promise<FontsResult>((resolve) => {\n childProcess.execFile(\"fc-list\", [\":\", \"family\"], { timeout: 3000 }, (error, stdout) => {\n resolve(\n error\n ? { hash: undefined, settled: (error as { code?: unknown }).code === \"ENOENT\" }\n : { hash: digestOf(stdout), settled: true },\n );\n });\n });\n}\n\n/**\n * The environment a runner captured in: browser build, host platform, device scale factor and a\n * font digest. Runners call this once per render with the version their driver reports.\n */\nexport async function detectCaptureEnvironment(\n browserVersion?: string,\n deviceScaleFactor = 1,\n): Promise<CaptureEnvironment> {\n const fontsHash = await systemFontsHash();\n return {\n ...(browserVersion ? { browserVersion } : {}),\n platform: `${process.platform}-${process.arch}`,\n deviceScaleFactor,\n ...(fontsHash ? { fontsHash } : {}),\n };\n}\n\n/** Digest of the distinct family names in `fc-list` output; undefined when there are none. */\nfunction digestOf(stdout: string): string | undefined {\n const families = [...new Set(stdout.split(\"\\n\").map((line) => line.trim()))].filter(Boolean);\n return families.length > 0 ? sha256(families.toSorted().join(\"\\n\")).slice(0, 16) : undefined;\n}\n"],"mappings":";;;;;AAMA,IAAI;;;;;;;AAQJ,eAAsB,kBAA+C;CACnE,UAAU,UAAU,CAAC,CAAC,MAAM,WAAW;EACrC,IAAI,CAAC,OAAO,SACV,QAAQ,KAAA;EAEV,OAAO,OAAO;CAChB,CAAC;CACD,OAAO,MAAM;AACf;AAQA,eAAe,YAAkC;CAE/C,MAAM,eADc,QAA2D,kBAC9C,KAAK,SAAS,oBAAoB;CAGnE,IAAI,CAAC,cACH,OAAO;EAAE,MAAM,KAAA;EAAW,SAAS;CAAK;CAE1C,OAAO,MAAM,IAAI,SAAsB,YAAY;EACjD,aAAa,SAAS,WAAW,CAAC,KAAK,QAAQ,GAAG,EAAE,SAAS,IAAK,IAAI,OAAO,WAAW;GACtF,QACE,QACI;IAAE,MAAM,KAAA;IAAW,SAAU,MAA6B,SAAS;GAAS,IAC5E;IAAE,MAAM,SAAS,MAAM;IAAG,SAAS;GAAK,CAC9C;EACF,CAAC;CACH,CAAC;AACH;;;;;AAMA,eAAsB,yBACpB,gBACA,oBAAoB,GACS;CAC7B,MAAM,YAAY,MAAM,gBAAgB;CACxC,OAAO;EACL,GAAI,iBAAiB,EAAE,eAAe,IAAI,CAAC;EAC3C,UAAU,GAAG,QAAQ,SAAS,GAAG,QAAQ;EACzC;EACA,GAAI,YAAY,EAAE,UAAU,IAAI,CAAC;CACnC;AACF;;AAGA,SAAS,SAAS,QAAoC;CACpD,MAAM,WAAW,CAAC,GAAG,IAAI,IAAI,OAAO,MAAM,IAAI,CAAC,CAAC,KAAK,SAAS,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,OAAO;CAC3F,OAAO,SAAS,SAAS,IAAI,OAAO,SAAS,SAAS,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,MAAM,GAAG,EAAE,IAAI,KAAA;AACrF"}
|
package/dist/utils/http.d.mts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import __tsdown_shims_path from 'node:path';
|
|
2
2
|
import __tsdown_shims_url from 'node:url';
|
|
3
3
|
import { Logger } from "../logger.mjs";
|
|
4
|
+
import { Buffer } from "node:buffer";
|
|
4
5
|
//#region src/utils/http.d.ts
|
|
5
6
|
/**
|
|
6
7
|
* Fetch JSON with timeout, retries, and structured errors.
|
|
@@ -13,6 +14,15 @@ import { Logger } from "../logger.mjs";
|
|
|
13
14
|
* @returns The parsed JSON response body.
|
|
14
15
|
*/
|
|
15
16
|
declare function httpJson<T>(url: string, options?: HttpRequestOptions): Promise<T>;
|
|
17
|
+
/**
|
|
18
|
+
* Download a binary response (images) with the same timeout, retry, and
|
|
19
|
+
* `Retry-After` semantics as {@link httpJson}.
|
|
20
|
+
*
|
|
21
|
+
* @param url - Absolute request URL.
|
|
22
|
+
* @param options - Method, headers, timeout, retries, logger.
|
|
23
|
+
* @returns The raw response bytes.
|
|
24
|
+
*/
|
|
25
|
+
declare function httpBytes(url: string, options?: HttpRequestOptions): Promise<Buffer>;
|
|
16
26
|
/**
|
|
17
27
|
* POST/PUT plain payloads (e.g. Slack/Teams webhook cards) with the same
|
|
18
28
|
* timeout, retry, and `Retry-After` semantics as {@link httpJson}.
|
|
@@ -35,10 +45,14 @@ interface HttpRequestOptions {
|
|
|
35
45
|
headers?: Record<string, string>;
|
|
36
46
|
/** Body serialized as JSON with a `content-type` header. */
|
|
37
47
|
json?: unknown;
|
|
48
|
+
/** Body serialized as `application/x-www-form-urlencoded` (ignored when `json` is set). */
|
|
49
|
+
form?: Record<string, string>;
|
|
38
50
|
/** Per-attempt timeout in milliseconds (default 30s). */
|
|
39
51
|
timeoutMs?: number;
|
|
40
52
|
/** Total attempts including the first (default 3). */
|
|
41
53
|
retries?: number;
|
|
54
|
+
/** Aborts the request (and any further retry) when it fires. */
|
|
55
|
+
signal?: AbortSignal;
|
|
42
56
|
logger?: Logger;
|
|
43
57
|
}
|
|
44
58
|
/** Options for {@link httpText}. */
|
|
@@ -51,8 +65,10 @@ interface HttpTextOptions {
|
|
|
51
65
|
timeoutMs?: number;
|
|
52
66
|
/** Total attempts including the first (default 3). */
|
|
53
67
|
retries?: number;
|
|
68
|
+
/** Aborts the request (and any further retry) when it fires. */
|
|
69
|
+
signal?: AbortSignal;
|
|
54
70
|
logger?: Logger;
|
|
55
71
|
}
|
|
56
72
|
//#endregion
|
|
57
|
-
export { HttpError, HttpRequestOptions, HttpTextOptions, httpJson, httpText };
|
|
73
|
+
export { HttpError, HttpRequestOptions, HttpTextOptions, httpBytes, httpJson, httpText };
|
|
58
74
|
//# sourceMappingURL=http.d.mts.map
|
package/dist/utils/http.mjs
CHANGED
|
@@ -2,6 +2,7 @@ import "node:path";
|
|
|
2
2
|
import "node:url";
|
|
3
3
|
import.meta.url;
|
|
4
4
|
import { injectTraceContext, withSpan } from "../tracing.mjs";
|
|
5
|
+
import { Buffer } from "node:buffer";
|
|
5
6
|
//#region src/utils/http.ts
|
|
6
7
|
/**
|
|
7
8
|
* Fetch JSON with timeout, retries, and structured errors.
|
|
@@ -17,6 +18,17 @@ async function httpJson(url, options = {}) {
|
|
|
17
18
|
return await requestWithRetry(url, options, options.retries ?? DEFAULT_RETRIES, options.timeoutMs ?? DEFAULT_TIMEOUT_MS, doFetchJson);
|
|
18
19
|
}
|
|
19
20
|
/**
|
|
21
|
+
* Download a binary response (images) with the same timeout, retry, and
|
|
22
|
+
* `Retry-After` semantics as {@link httpJson}.
|
|
23
|
+
*
|
|
24
|
+
* @param url - Absolute request URL.
|
|
25
|
+
* @param options - Method, headers, timeout, retries, logger.
|
|
26
|
+
* @returns The raw response bytes.
|
|
27
|
+
*/
|
|
28
|
+
async function httpBytes(url, options = {}) {
|
|
29
|
+
return await requestWithRetry(url, options, options.retries ?? DEFAULT_RETRIES, options.timeoutMs ?? DEFAULT_TIMEOUT_MS, doFetchBytes);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
20
32
|
* POST/PUT plain payloads (e.g. Slack/Teams webhook cards) with the same
|
|
21
33
|
* timeout, retry, and `Retry-After` semantics as {@link httpJson}.
|
|
22
34
|
*
|
|
@@ -80,7 +92,7 @@ async function requestWithRetry(url, options, maxAttempts, timeoutMs, perform) {
|
|
|
80
92
|
return await perform(url, options, timeoutMs);
|
|
81
93
|
} catch (error) {
|
|
82
94
|
span.recordException(error instanceof Error ? error : String(error));
|
|
83
|
-
if (!shouldRetry(error, attempt, maxAttempts)) throw error;
|
|
95
|
+
if (options.signal?.aborted || !shouldRetry(error, attempt, maxAttempts)) throw error;
|
|
84
96
|
span.setAttribute("http.retry.count", attempt);
|
|
85
97
|
await backoff(error, attempt, url, options.logger);
|
|
86
98
|
}
|
|
@@ -109,6 +121,9 @@ function buildRequest(options) {
|
|
|
109
121
|
if (options.json !== void 0) {
|
|
110
122
|
headers["content-type"] = "application/json";
|
|
111
123
|
body = JSON.stringify(options.json);
|
|
124
|
+
} else if (options.form) {
|
|
125
|
+
headers["content-type"] = "application/x-www-form-urlencoded";
|
|
126
|
+
body = new URLSearchParams(options.form).toString();
|
|
112
127
|
}
|
|
113
128
|
return {
|
|
114
129
|
headers,
|
|
@@ -122,6 +137,10 @@ async function doFetchJson(url, options, timeoutMs) {
|
|
|
122
137
|
async function doFetchText(url, options, timeoutMs) {
|
|
123
138
|
return await (await doFetchResponse(url, options, timeoutMs)).text();
|
|
124
139
|
}
|
|
140
|
+
async function doFetchBytes(url, options, timeoutMs) {
|
|
141
|
+
const response = await doFetchResponse(url, options, timeoutMs);
|
|
142
|
+
return Buffer.from(await response.arrayBuffer());
|
|
143
|
+
}
|
|
125
144
|
async function doFetchResponse(url, options, timeoutMs) {
|
|
126
145
|
const request = buildRequest(options);
|
|
127
146
|
injectTraceContext(request.headers);
|
|
@@ -133,7 +152,7 @@ async function doFetchResponse(url, options, timeoutMs) {
|
|
|
133
152
|
method: request.method,
|
|
134
153
|
headers: request.headers,
|
|
135
154
|
body: request.body,
|
|
136
|
-
signal: AbortSignal.timeout(timeoutMs)
|
|
155
|
+
signal: options.signal ? AbortSignal.any([options.signal, AbortSignal.timeout(timeoutMs)]) : AbortSignal.timeout(timeoutMs)
|
|
137
156
|
});
|
|
138
157
|
if (!response.ok) {
|
|
139
158
|
const text = await response.text();
|
|
@@ -142,6 +161,6 @@ async function doFetchResponse(url, options, timeoutMs) {
|
|
|
142
161
|
return response;
|
|
143
162
|
}
|
|
144
163
|
//#endregion
|
|
145
|
-
export { HttpError, httpJson, httpText };
|
|
164
|
+
export { HttpError, httpBytes, httpJson, httpText };
|
|
146
165
|
|
|
147
166
|
//# sourceMappingURL=http.mjs.map
|
package/dist/utils/http.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http.mjs","names":[],"sources":["../../src/utils/http.ts"],"sourcesContent":["import type { Logger } from \"../logger.ts\";\nimport { injectTraceContext, withSpan } from \"../tracing.ts\";\n\n/**\n * Fetch JSON with timeout, retries, and structured errors.\n *\n * Retries network failures, 429, and 502/503/504 (honoring `Retry-After`);\n * every other status throws {@link HttpError} immediately.\n *\n * @param url - Absolute request URL.\n * @param options - Method, headers, JSON body, timeout, retries, logger.\n * @returns The parsed JSON response body.\n */\nexport async function httpJson<T>(url: string, options: HttpRequestOptions = {}): Promise<T> {\n return await requestWithRetry<T>(\n url,\n options,\n options.retries ?? DEFAULT_RETRIES,\n options.timeoutMs ?? DEFAULT_TIMEOUT_MS,\n doFetchJson,\n );\n}\n\n/**\n * POST/PUT plain payloads (e.g. Slack/Teams webhook cards) with the same\n * timeout, retry, and `Retry-After` semantics as {@link httpJson}.\n *\n * @param url - Absolute request URL.\n * @param options - Method, headers, text body, timeout, retries, logger.\n * @returns The raw response body text.\n */\nexport async function httpText(url: string, options: HttpTextOptions = {}): Promise<string> {\n return await requestWithRetry<string>(\n url,\n options,\n options.retries ?? DEFAULT_RETRIES,\n options.timeoutMs ?? DEFAULT_TIMEOUT_MS,\n doFetchText,\n );\n}\n\n/** Failed HTTP response: status code plus a snippet of the response body. */\nexport class HttpError extends Error {\n readonly status: number;\n readonly body: string;\n readonly headers: Headers;\n\n constructor(status: number, body: string, headers?: Headers) {\n super(`HTTP ${status}: ${body}`);\n this.name = \"HttpError\";\n this.status = status;\n this.body = body;\n this.headers = headers ?? new Headers();\n }\n}\n\n/** Options for {@link httpJson}. */\nexport interface HttpRequestOptions {\n method?: string;\n headers?: Record<string, string>;\n /** Body serialized as JSON with a `content-type` header. */\n json?: unknown;\n /** Per-attempt timeout in milliseconds (default 30s). */\n timeoutMs?: number;\n /** Total attempts including the first (default 3). */\n retries?: number;\n logger?: Logger;\n}\n\nconst DEFAULT_TIMEOUT_MS = 30_000;\n\n/** Options for {@link httpText}. */\nexport interface HttpTextOptions {\n method?: string;\n headers?: Record<string, string>;\n /** Body serialized as JSON with a `content-type` header. */\n json?: unknown;\n /** Per-attempt timeout in milliseconds (default 30s). */\n timeoutMs?: number;\n /** Total attempts including the first (default 3). */\n retries?: number;\n logger?: Logger;\n}\nconst DEFAULT_RETRIES = 3;\nconst MAX_RETRY_AFTER_MS = 30_000;\nconst BACKOFF_BASE_MS = 1000;\nconst MAX_BODY_SNIPPET = 500;\n\n/** Whether a failure is worth retrying: network errors, 429, and 502/503/504. */\nfunction shouldRetry(error: unknown, attempt: number, maxAttempts: number): boolean {\n if (attempt >= maxAttempts) {\n return false;\n }\n if (!(error instanceof HttpError)) {\n return true;\n }\n return error.status === 429 || (error.status >= 502 && error.status <= 504);\n}\n\n/** Parse a Retry-After value (seconds or HTTP-date) into milliseconds. */\nfunction parseRetryAfter(value: string | null): number | null {\n if (!value) {\n return null;\n }\n const seconds = Number(value);\n if (Number.isFinite(seconds) && seconds >= 0) {\n return Math.min(seconds * 1000, MAX_RETRY_AFTER_MS);\n }\n const date = Date.parse(value);\n if (!Number.isNaN(date)) {\n return Math.min(Math.max(date - Date.now(), 0), MAX_RETRY_AFTER_MS);\n }\n return null;\n}\n\n/** Delay before the next attempt: Retry-After when present, else 1s/2s/4s. */\nfunction retryDelayMs(error: unknown, attempt: number): number {\n if (error instanceof HttpError) {\n const retryAfter = parseRetryAfter(error.headers.get(\"retry-after\"));\n if (retryAfter !== null) {\n return retryAfter;\n }\n }\n return Math.min(BACKOFF_BASE_MS * 2 ** (attempt - 1), MAX_RETRY_AFTER_MS);\n}\n\n/** Sleep helper for retry backoff. */\nasync function sleep(ms: number): Promise<void> {\n await new Promise((resolve) => {\n setTimeout(resolve, ms);\n });\n}\n\n/* oxlint-disable eslint/no-await-in-loop -- retry attempts run sequentially by design */\nasync function requestWithRetry<T>(\n url: string,\n options: HttpRequestOptions,\n maxAttempts: number,\n timeoutMs: number,\n perform: (url: string, options: HttpRequestOptions, timeoutMs: number) => Promise<T>,\n): Promise<T> {\n return await withSpan(\n \"http.client\",\n async (span) => {\n for (let attempt = 1; ; attempt += 1) {\n try {\n return await perform(url, options, timeoutMs);\n } catch (error) {\n span.recordException(error instanceof Error ? error : String(error));\n if (!shouldRetry(error, attempt, maxAttempts)) {\n throw error;\n }\n span.setAttribute(\"http.retry.count\", attempt);\n await backoff(error, attempt, url, options.logger);\n }\n }\n },\n { \"http.request.method\": options.method ?? \"GET\", \"url.full\": redactUrl(url) },\n );\n}\n/* oxlint-enable eslint/no-await-in-loop */\n\n/** Strip the query string (tokens, signatures) from a URL for span attributes. */\nfunction redactUrl(url: string): string {\n const query = url.indexOf(\"?\");\n return query === -1 ? url : url.slice(0, query);\n}\n\nasync function backoff(\n error: unknown,\n attempt: number,\n url: string,\n logger?: Logger,\n): Promise<void> {\n const delay = retryDelayMs(error, attempt);\n logger?.warn({ url, attempt, delayMs: delay }, \"http request failed, retrying\");\n await sleep(delay);\n}\n\nfunction buildRequest(options: HttpRequestOptions): {\n headers: Record<string, string>;\n body: string | undefined;\n method: string;\n} {\n const headers: Record<string, string> = { ...options.headers };\n let body: string | undefined;\n if (options.json !== undefined) {\n headers[\"content-type\"] = \"application/json\";\n body = JSON.stringify(options.json);\n }\n return { headers, body, method: options.method ?? \"GET\" };\n}\n\nasync function doFetchJson<T>(\n url: string,\n options: HttpRequestOptions,\n timeoutMs: number,\n): Promise<T> {\n const response = await doFetchResponse(url, options, timeoutMs);\n return (await response.json()) as T;\n}\n\nasync function doFetchText(\n url: string,\n options: HttpRequestOptions,\n timeoutMs: number,\n): Promise<string> {\n const response = await doFetchResponse(url, options, timeoutMs);\n return await response.text();\n}\n\nasync function doFetchResponse(\n url: string,\n options: HttpRequestOptions,\n timeoutMs: number,\n): Promise<Response> {\n const request = buildRequest(options);\n injectTraceContext(request.headers);\n options.logger?.debug({ url, method: request.method }, \"http request\");\n const response = await fetch(url, {\n method: request.method,\n headers: request.headers,\n body: request.body,\n signal: AbortSignal.timeout(timeoutMs),\n });\n if (!response.ok) {\n const text = await response.text();\n throw new HttpError(response.status, text.slice(0, MAX_BODY_SNIPPET), response.headers);\n }\n return response;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"http.mjs","names":[],"sources":["../../src/utils/http.ts"],"sourcesContent":["import { Buffer } from \"node:buffer\";\nimport type { Logger } from \"../logger.ts\";\nimport { injectTraceContext, withSpan } from \"../tracing.ts\";\n\n/**\n * Fetch JSON with timeout, retries, and structured errors.\n *\n * Retries network failures, 429, and 502/503/504 (honoring `Retry-After`);\n * every other status throws {@link HttpError} immediately.\n *\n * @param url - Absolute request URL.\n * @param options - Method, headers, JSON body, timeout, retries, logger.\n * @returns The parsed JSON response body.\n */\nexport async function httpJson<T>(url: string, options: HttpRequestOptions = {}): Promise<T> {\n return await requestWithRetry<T>(\n url,\n options,\n options.retries ?? DEFAULT_RETRIES,\n options.timeoutMs ?? DEFAULT_TIMEOUT_MS,\n doFetchJson,\n );\n}\n\n/**\n * Download a binary response (images) with the same timeout, retry, and\n * `Retry-After` semantics as {@link httpJson}.\n *\n * @param url - Absolute request URL.\n * @param options - Method, headers, timeout, retries, logger.\n * @returns The raw response bytes.\n */\nexport async function httpBytes(url: string, options: HttpRequestOptions = {}): Promise<Buffer> {\n return await requestWithRetry<Buffer>(\n url,\n options,\n options.retries ?? DEFAULT_RETRIES,\n options.timeoutMs ?? DEFAULT_TIMEOUT_MS,\n doFetchBytes,\n );\n}\n\n/**\n * POST/PUT plain payloads (e.g. Slack/Teams webhook cards) with the same\n * timeout, retry, and `Retry-After` semantics as {@link httpJson}.\n *\n * @param url - Absolute request URL.\n * @param options - Method, headers, text body, timeout, retries, logger.\n * @returns The raw response body text.\n */\nexport async function httpText(url: string, options: HttpTextOptions = {}): Promise<string> {\n return await requestWithRetry<string>(\n url,\n options,\n options.retries ?? DEFAULT_RETRIES,\n options.timeoutMs ?? DEFAULT_TIMEOUT_MS,\n doFetchText,\n );\n}\n\n/** Failed HTTP response: status code plus a snippet of the response body. */\nexport class HttpError extends Error {\n readonly status: number;\n readonly body: string;\n readonly headers: Headers;\n\n constructor(status: number, body: string, headers?: Headers) {\n super(`HTTP ${status}: ${body}`);\n this.name = \"HttpError\";\n this.status = status;\n this.body = body;\n this.headers = headers ?? new Headers();\n }\n}\n\n/** Options for {@link httpJson}. */\nexport interface HttpRequestOptions {\n method?: string;\n headers?: Record<string, string>;\n /** Body serialized as JSON with a `content-type` header. */\n json?: unknown;\n /** Body serialized as `application/x-www-form-urlencoded` (ignored when `json` is set). */\n form?: Record<string, string>;\n /** Per-attempt timeout in milliseconds (default 30s). */\n timeoutMs?: number;\n /** Total attempts including the first (default 3). */\n retries?: number;\n /** Aborts the request (and any further retry) when it fires. */\n signal?: AbortSignal;\n logger?: Logger;\n}\n\nconst DEFAULT_TIMEOUT_MS = 30_000;\n\n/** Options for {@link httpText}. */\nexport interface HttpTextOptions {\n method?: string;\n headers?: Record<string, string>;\n /** Body serialized as JSON with a `content-type` header. */\n json?: unknown;\n /** Per-attempt timeout in milliseconds (default 30s). */\n timeoutMs?: number;\n /** Total attempts including the first (default 3). */\n retries?: number;\n /** Aborts the request (and any further retry) when it fires. */\n signal?: AbortSignal;\n logger?: Logger;\n}\nconst DEFAULT_RETRIES = 3;\nconst MAX_RETRY_AFTER_MS = 30_000;\nconst BACKOFF_BASE_MS = 1000;\nconst MAX_BODY_SNIPPET = 500;\n\n/** Whether a failure is worth retrying: network errors, 429, and 502/503/504. */\nfunction shouldRetry(error: unknown, attempt: number, maxAttempts: number): boolean {\n if (attempt >= maxAttempts) {\n return false;\n }\n if (!(error instanceof HttpError)) {\n return true;\n }\n return error.status === 429 || (error.status >= 502 && error.status <= 504);\n}\n\n/** Parse a Retry-After value (seconds or HTTP-date) into milliseconds. */\nfunction parseRetryAfter(value: string | null): number | null {\n if (!value) {\n return null;\n }\n const seconds = Number(value);\n if (Number.isFinite(seconds) && seconds >= 0) {\n return Math.min(seconds * 1000, MAX_RETRY_AFTER_MS);\n }\n const date = Date.parse(value);\n if (!Number.isNaN(date)) {\n return Math.min(Math.max(date - Date.now(), 0), MAX_RETRY_AFTER_MS);\n }\n return null;\n}\n\n/** Delay before the next attempt: Retry-After when present, else 1s/2s/4s. */\nfunction retryDelayMs(error: unknown, attempt: number): number {\n if (error instanceof HttpError) {\n const retryAfter = parseRetryAfter(error.headers.get(\"retry-after\"));\n if (retryAfter !== null) {\n return retryAfter;\n }\n }\n return Math.min(BACKOFF_BASE_MS * 2 ** (attempt - 1), MAX_RETRY_AFTER_MS);\n}\n\n/** Sleep helper for retry backoff. */\nasync function sleep(ms: number): Promise<void> {\n await new Promise((resolve) => {\n setTimeout(resolve, ms);\n });\n}\n\n/* oxlint-disable eslint/no-await-in-loop -- retry attempts run sequentially by design */\nasync function requestWithRetry<T>(\n url: string,\n options: HttpRequestOptions,\n maxAttempts: number,\n timeoutMs: number,\n perform: (url: string, options: HttpRequestOptions, timeoutMs: number) => Promise<T>,\n): Promise<T> {\n return await withSpan(\n \"http.client\",\n async (span) => {\n for (let attempt = 1; ; attempt += 1) {\n try {\n return await perform(url, options, timeoutMs);\n } catch (error) {\n span.recordException(error instanceof Error ? error : String(error));\n if (options.signal?.aborted || !shouldRetry(error, attempt, maxAttempts)) {\n throw error;\n }\n span.setAttribute(\"http.retry.count\", attempt);\n await backoff(error, attempt, url, options.logger);\n }\n }\n },\n { \"http.request.method\": options.method ?? \"GET\", \"url.full\": redactUrl(url) },\n );\n}\n/* oxlint-enable eslint/no-await-in-loop */\n\n/** Strip the query string (tokens, signatures) from a URL for span attributes. */\nfunction redactUrl(url: string): string {\n const query = url.indexOf(\"?\");\n return query === -1 ? url : url.slice(0, query);\n}\n\nasync function backoff(\n error: unknown,\n attempt: number,\n url: string,\n logger?: Logger,\n): Promise<void> {\n const delay = retryDelayMs(error, attempt);\n logger?.warn({ url, attempt, delayMs: delay }, \"http request failed, retrying\");\n await sleep(delay);\n}\n\nfunction buildRequest(options: HttpRequestOptions): {\n headers: Record<string, string>;\n body: string | undefined;\n method: string;\n} {\n const headers: Record<string, string> = { ...options.headers };\n let body: string | undefined;\n if (options.json !== undefined) {\n headers[\"content-type\"] = \"application/json\";\n body = JSON.stringify(options.json);\n } else if (options.form) {\n headers[\"content-type\"] = \"application/x-www-form-urlencoded\";\n body = new URLSearchParams(options.form).toString();\n }\n return { headers, body, method: options.method ?? \"GET\" };\n}\n\nasync function doFetchJson<T>(\n url: string,\n options: HttpRequestOptions,\n timeoutMs: number,\n): Promise<T> {\n const response = await doFetchResponse(url, options, timeoutMs);\n return (await response.json()) as T;\n}\n\nasync function doFetchText(\n url: string,\n options: HttpRequestOptions,\n timeoutMs: number,\n): Promise<string> {\n const response = await doFetchResponse(url, options, timeoutMs);\n return await response.text();\n}\n\nasync function doFetchBytes(\n url: string,\n options: HttpRequestOptions,\n timeoutMs: number,\n): Promise<Buffer> {\n const response = await doFetchResponse(url, options, timeoutMs);\n return Buffer.from(await response.arrayBuffer());\n}\n\nasync function doFetchResponse(\n url: string,\n options: HttpRequestOptions,\n timeoutMs: number,\n): Promise<Response> {\n const request = buildRequest(options);\n injectTraceContext(request.headers);\n options.logger?.debug({ url, method: request.method }, \"http request\");\n const response = await fetch(url, {\n method: request.method,\n headers: request.headers,\n body: request.body,\n signal: options.signal\n ? AbortSignal.any([options.signal, AbortSignal.timeout(timeoutMs)])\n : AbortSignal.timeout(timeoutMs),\n });\n if (!response.ok) {\n const text = await response.text();\n throw new HttpError(response.status, text.slice(0, MAX_BODY_SNIPPET), response.headers);\n }\n return response;\n}\n"],"mappings":"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"}
|