@storyshelf/core 0.6.2 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +49 -32
- package/dist/adapter/capture-queue.d.mts +44 -4
- package/dist/adapter/capture-runner.d.mts +134 -12
- package/dist/adapter/capture-runner.mjs +9 -12
- package/dist/adapter/database.d.mts +89 -10
- package/dist/adapter/database.mjs +4 -3
- package/dist/adapter/database.mjs.map +1 -1
- package/dist/adapter/design.d.mts +83 -0
- package/dist/adapter/design.mjs +27 -0
- package/dist/adapter/design.mjs.map +1 -0
- package/dist/adapter/email-sender.d.mts +0 -2
- package/dist/adapter/metadata.d.mts +3 -12
- package/dist/adapter/notifier.d.mts +6 -3
- package/dist/adapter/notifier.mjs +5 -1
- package/dist/adapter/oauth.d.mts +50 -0
- package/dist/adapter/setup.d.mts +4 -7
- package/dist/adapter/setup.mjs +3 -3
- package/dist/adapter/setup.mjs.map +1 -1
- package/dist/adapter/storage.d.mts +1 -3
- package/dist/adapter/webhook-events.d.mts +0 -2
- package/dist/adapter/webhook-events.mjs +13 -14
- package/dist/adapter/webhook-events.mjs.map +1 -1
- package/dist/adapters/capture-target-offers.d.mts +26 -0
- package/dist/adapters/capture-target-offers.mjs +57 -0
- package/dist/adapters/capture-target-offers.mjs.map +1 -0
- package/dist/adapters/capture-target.d.mts +69 -0
- package/dist/adapters/capture-target.mjs +75 -0
- package/dist/adapters/capture-target.mjs.map +1 -0
- package/dist/adapters/notifier/build-comment.d.mts +22 -0
- package/dist/adapters/notifier/build-comment.mjs +23 -0
- package/dist/adapters/notifier/build-comment.mjs.map +1 -0
- package/dist/adapters/notifier/build-state.d.mts +97 -0
- package/dist/adapters/notifier/build-state.mjs +217 -0
- package/dist/adapters/notifier/build-state.mjs.map +1 -0
- package/dist/adapters/notifier/events.d.mts +4 -4
- package/dist/adapters/notifier/events.mjs +32 -20
- package/dist/adapters/notifier/events.mjs.map +1 -1
- package/dist/adapters/notifier/format.d.mts +0 -2
- package/dist/adapters/notifier/format.mjs +1 -2
- package/dist/adapters/notifier/provider.d.mts +53 -9
- package/dist/adapters/notifier/provider.mjs +11 -0
- package/dist/adapters/notifier/provider.mjs.map +1 -0
- package/dist/adapters/notifier/state.d.mts +22 -0
- package/dist/adapters/notifier/state.mjs +65 -0
- package/dist/adapters/notifier/state.mjs.map +1 -0
- package/dist/adapters/notifier/types.d.mts +51 -4
- package/dist/adapters/remote-browser.d.mts +168 -0
- package/dist/adapters/remote-browser.mjs +201 -0
- package/dist/adapters/remote-browser.mjs.map +1 -0
- package/dist/adapters/remote-lease-attempt.mjs +39 -0
- package/dist/adapters/remote-lease-attempt.mjs.map +1 -0
- package/dist/adapters/remote-lease-error.d.mts +25 -0
- package/dist/adapters/remote-lease-error.mjs +54 -0
- package/dist/adapters/remote-lease-error.mjs.map +1 -0
- package/dist/adapters/remote-lease-held.d.mts +15 -0
- package/dist/adapters/remote-lease-held.mjs +86 -0
- package/dist/adapters/remote-lease-held.mjs.map +1 -0
- package/dist/adapters/remote-lease-shutdown.d.mts +12 -0
- package/dist/adapters/remote-lease-shutdown.mjs +32 -0
- package/dist/adapters/remote-lease-shutdown.mjs.map +1 -0
- package/dist/adapters/remote-lease.d.mts +39 -0
- package/dist/adapters/remote-lease.mjs +175 -0
- package/dist/adapters/remote-lease.mjs.map +1 -0
- package/dist/adapters/remote-limiter.mjs +121 -0
- package/dist/adapters/remote-limiter.mjs.map +1 -0
- package/dist/adapters/remote-pool.d.mts +56 -0
- package/dist/adapters/remote-pool.mjs +195 -0
- package/dist/adapters/remote-pool.mjs.map +1 -0
- package/dist/adapters/render-runs.d.mts +23 -0
- package/dist/adapters/render-runs.mjs +40 -0
- package/dist/adapters/render-runs.mjs.map +1 -0
- package/dist/adapters/setup-runner.d.mts +0 -2
- package/dist/adapters/setup-runner.mjs +3 -6
- package/dist/adapters/setup-runner.mjs.map +1 -1
- package/dist/adapters/sources.d.mts +10 -0
- package/dist/adapters/sources.mjs +19 -0
- package/dist/adapters/sources.mjs.map +1 -0
- package/dist/adapters/validate.d.mts +0 -2
- package/dist/adapters/validate.mjs +27 -20
- package/dist/adapters/validate.mjs.map +1 -1
- package/dist/ai.d.mts +103 -0
- package/dist/ai.mjs +18 -0
- package/dist/ai.mjs.map +1 -0
- package/dist/auth.d.mts +0 -2
- package/dist/auth.mjs +1 -2
- package/dist/brand-packs.d.mts +0 -2
- package/dist/brand-packs.mjs +0 -1
- package/dist/capture/a11y/axe-source.d.mts +16 -0
- package/dist/capture/a11y/axe-source.mjs +36 -0
- package/dist/capture/a11y/axe-source.mjs.map +1 -0
- package/dist/capture/a11y/in-page.d.mts +25 -0
- package/dist/capture/a11y/in-page.mjs +59 -0
- package/dist/capture/a11y/in-page.mjs.map +1 -0
- package/dist/capture/a11y/map.d.mts +22 -0
- package/dist/capture/a11y/map.mjs +47 -0
- package/dist/capture/a11y/map.mjs.map +1 -0
- package/dist/capture/a11y/options.d.mts +21 -0
- package/dist/capture/a11y/options.mjs +74 -0
- package/dist/capture/a11y/options.mjs.map +1 -0
- package/dist/capture/adapter.d.mts +46 -5
- package/dist/capture/adapter.mjs +1 -2
- package/dist/capture/adapter.mjs.map +1 -1
- package/dist/capture/affected.d.mts +2 -2
- package/dist/capture/affected.mjs +14 -6
- package/dist/capture/affected.mjs.map +1 -1
- package/dist/capture/attempt-logger.mjs +83 -0
- package/dist/capture/attempt-logger.mjs.map +1 -0
- package/dist/capture/browsers.d.mts +33 -0
- package/dist/capture/browsers.mjs +66 -0
- package/dist/capture/browsers.mjs.map +1 -0
- package/dist/capture/build-state/deliver.d.mts +30 -0
- package/dist/capture/build-state/deliver.mjs +117 -0
- package/dist/capture/build-state/deliver.mjs.map +1 -0
- package/dist/capture/build-state/index.d.mts +8 -0
- package/dist/capture/build-state/notify-job.d.mts +54 -0
- package/dist/capture/build-state/notify-job.mjs +132 -0
- package/dist/capture/build-state/notify-job.mjs.map +1 -0
- package/dist/capture/build-state/publisher.d.mts +21 -0
- package/dist/capture/build-state/publisher.mjs +106 -0
- package/dist/capture/build-state/publisher.mjs.map +1 -0
- package/dist/capture/build-state/resume.d.mts +7 -0
- package/dist/capture/build-state/resume.mjs +46 -0
- package/dist/capture/build-state/resume.mjs.map +1 -0
- package/dist/capture/build-state/types.d.mts +47 -0
- package/dist/capture/catalog/catalogue.d.mts +20 -0
- package/dist/capture/catalog/catalogue.mjs +52 -0
- package/dist/capture/catalog/catalogue.mjs.map +1 -0
- package/dist/capture/catalog/components.d.mts +21 -0
- package/dist/capture/catalog/components.mjs +54 -0
- package/dist/capture/catalog/components.mjs.map +1 -0
- package/dist/capture/catalog/history.d.mts +28 -0
- package/dist/capture/catalog/history.mjs +50 -0
- package/dist/capture/catalog/history.mjs.map +1 -0
- package/dist/capture/catalog/hook.d.mts +12 -0
- package/dist/capture/catalog/hook.mjs +18 -0
- package/dist/capture/catalog/hook.mjs.map +1 -0
- package/dist/capture/catalog/index.d.mts +11 -0
- package/dist/capture/catalog/load.d.mts +24 -0
- package/dist/capture/catalog/load.mjs +48 -0
- package/dist/capture/catalog/load.mjs.map +1 -0
- package/dist/capture/catalog/parse.d.mts +13 -0
- package/dist/capture/catalog/parse.mjs +55 -0
- package/dist/capture/catalog/parse.mjs.map +1 -0
- package/dist/capture/catalog/read.d.mts +15 -0
- package/dist/capture/catalog/read.mjs +39 -0
- package/dist/capture/catalog/read.mjs.map +1 -0
- package/dist/capture/catalog/types.d.mts +31 -0
- package/dist/capture/comparison-reference.d.mts +27 -0
- package/dist/capture/comparison-reference.mjs +75 -0
- package/dist/capture/comparison-reference.mjs.map +1 -0
- package/dist/capture/consumer/capture-handler.d.mts +56 -0
- package/dist/capture/consumer/capture-handler.mjs +117 -0
- package/dist/capture/consumer/capture-handler.mjs.map +1 -0
- package/dist/capture/consumer/idle.d.mts +15 -0
- package/dist/capture/consumer/idle.mjs +24 -0
- package/dist/capture/consumer/idle.mjs.map +1 -0
- package/dist/capture/consumer/index.d.mts +11 -0
- package/dist/capture/consumer/index.mjs +138 -0
- package/dist/capture/consumer/index.mjs.map +1 -0
- package/dist/capture/consumer/loop.mjs +109 -0
- package/dist/capture/consumer/loop.mjs.map +1 -0
- package/dist/capture/consumer/metrics.d.mts +39 -0
- package/dist/capture/consumer/metrics.mjs +37 -0
- package/dist/capture/consumer/metrics.mjs.map +1 -0
- package/dist/capture/consumer/retry.d.mts +31 -0
- package/dist/capture/consumer/retry.mjs +33 -0
- package/dist/capture/consumer/retry.mjs.map +1 -0
- package/dist/capture/consumer/run-job.mjs +194 -0
- package/dist/capture/consumer/run-job.mjs.map +1 -0
- package/dist/capture/consumer/startup.mjs +37 -0
- package/dist/capture/consumer/startup.mjs.map +1 -0
- package/dist/capture/consumer/types.d.mts +95 -0
- package/dist/capture/crash-failure.d.mts +23 -0
- package/dist/capture/crash-failure.mjs +69 -0
- package/dist/capture/crash-failure.mjs.map +1 -0
- package/dist/capture/db-queue/cadence.d.mts +30 -0
- package/dist/capture/db-queue/cadence.mjs +37 -0
- package/dist/capture/db-queue/cadence.mjs.map +1 -0
- package/dist/capture/db-queue/claim.d.mts +7 -0
- package/dist/capture/db-queue/claim.mjs +148 -0
- package/dist/capture/db-queue/claim.mjs.map +1 -0
- package/dist/capture/db-queue/clock.mjs +11 -0
- package/dist/capture/db-queue/clock.mjs.map +1 -0
- package/dist/capture/db-queue/codec.mjs +75 -0
- package/dist/capture/db-queue/codec.mjs.map +1 -0
- package/dist/capture/db-queue/index.d.mts +18 -0
- package/dist/capture/db-queue/index.mjs +89 -0
- package/dist/capture/db-queue/index.mjs.map +1 -0
- package/dist/capture/db-queue/keeper.d.mts +34 -0
- package/dist/capture/db-queue/keeper.mjs +91 -0
- package/dist/capture/db-queue/keeper.mjs.map +1 -0
- package/dist/capture/db-queue/lease.mjs +45 -0
- package/dist/capture/db-queue/lease.mjs.map +1 -0
- package/dist/capture/db-queue/lifecycle.mjs +35 -0
- package/dist/capture/db-queue/lifecycle.mjs.map +1 -0
- package/dist/capture/db-queue/operations.mjs +93 -0
- package/dist/capture/db-queue/operations.mjs.map +1 -0
- package/dist/capture/db-queue/poll-loop.d.mts +28 -0
- package/dist/capture/db-queue/poll-loop.mjs +88 -0
- package/dist/capture/db-queue/poll-loop.mjs.map +1 -0
- package/dist/capture/db-queue/poll.d.mts +8 -0
- package/dist/capture/db-queue/poll.mjs +68 -0
- package/dist/capture/db-queue/poll.mjs.map +1 -0
- package/dist/capture/db-queue/timers.d.mts +10 -0
- package/dist/capture/db-queue/timers.mjs +36 -0
- package/dist/capture/db-queue/timers.mjs.map +1 -0
- package/dist/capture/db-queue/types.d.mts +81 -0
- package/dist/capture/db-queue/types.mjs +11 -0
- package/dist/capture/db-queue/types.mjs.map +1 -0
- package/dist/capture/db-queue/wake.mjs +47 -0
- package/dist/capture/db-queue/wake.mjs.map +1 -0
- package/dist/capture/design.mjs +38 -0
- package/dist/capture/design.mjs.map +1 -0
- package/dist/capture/diff-record.mjs +24 -5
- package/dist/capture/diff-record.mjs.map +1 -1
- package/dist/capture/dispatch.d.mts +13 -10
- package/dist/capture/dispatch.mjs +102 -111
- package/dist/capture/dispatch.mjs.map +1 -1
- package/dist/capture/enrich.mjs +59 -0
- package/dist/capture/enrich.mjs.map +1 -0
- package/dist/capture/failure-sets.mjs +21 -0
- package/dist/capture/failure-sets.mjs.map +1 -0
- package/dist/capture/fingerprint.d.mts +40 -0
- package/dist/capture/fingerprint.mjs +82 -0
- package/dist/capture/fingerprint.mjs.map +1 -0
- package/dist/capture/forked/channel.d.mts +23 -0
- package/dist/capture/forked/child.d.mts +32 -0
- package/dist/capture/forked/child.mjs +201 -0
- package/dist/capture/forked/child.mjs.map +1 -0
- package/dist/capture/forked/crash-tracker.mjs +22 -0
- package/dist/capture/forked/crash-tracker.mjs.map +1 -0
- package/dist/capture/forked/decision.d.mts +24 -0
- package/dist/capture/forked/decision.mjs +26 -0
- package/dist/capture/forked/decision.mjs.map +1 -0
- package/dist/capture/forked/exit.mjs +11 -0
- package/dist/capture/forked/exit.mjs.map +1 -0
- package/dist/capture/forked/fork.d.mts +29 -0
- package/dist/capture/forked/fork.mjs +92 -0
- package/dist/capture/forked/fork.mjs.map +1 -0
- package/dist/capture/forked/forked-capture.d.mts +38 -0
- package/dist/capture/forked/forked-capture.mjs +239 -0
- package/dist/capture/forked/forked-capture.mjs.map +1 -0
- package/dist/capture/forked/lease-book.mjs +45 -0
- package/dist/capture/forked/lease-book.mjs.map +1 -0
- package/dist/capture/forked/protocol.d.mts +36 -0
- package/dist/capture/forked/protocol.mjs +24 -0
- package/dist/capture/forked/protocol.mjs.map +1 -0
- package/dist/capture/forked/runs.mjs +74 -0
- package/dist/capture/forked/runs.mjs.map +1 -0
- package/dist/capture/guards.mjs +1 -2
- package/dist/capture/ignore-regions.d.mts +36 -0
- package/dist/capture/ignore-regions.mjs +65 -0
- package/dist/capture/ignore-regions.mjs.map +1 -0
- package/dist/capture/isolate.mjs +140 -0
- package/dist/capture/isolate.mjs.map +1 -0
- package/dist/capture/job-lease.d.mts +26 -0
- package/dist/capture/job-lease.mjs +56 -0
- package/dist/capture/job-lease.mjs.map +1 -0
- package/dist/capture/matrix.mjs +298 -0
- package/dist/capture/matrix.mjs.map +1 -0
- package/dist/capture/modes.d.mts +23 -0
- package/dist/capture/modes.mjs +106 -0
- package/dist/capture/modes.mjs.map +1 -0
- package/dist/capture/orchestrator.d.mts +37 -7
- package/dist/capture/orchestrator.mjs +120 -110
- package/dist/capture/orchestrator.mjs.map +1 -1
- package/dist/capture/pipeline.d.mts +9 -4
- package/dist/capture/pipeline.mjs +284 -48
- package/dist/capture/pipeline.mjs.map +1 -1
- package/dist/capture/progress.d.mts +9 -0
- package/dist/capture/progress.mjs +27 -0
- package/dist/capture/progress.mjs.map +1 -0
- package/dist/capture/publish/accept.d.mts +37 -0
- package/dist/capture/publish/accept.mjs +27 -0
- package/dist/capture/publish/accept.mjs.map +1 -0
- package/dist/capture/publish/complete.d.mts +44 -0
- package/dist/capture/publish/complete.mjs +63 -0
- package/dist/capture/publish/complete.mjs.map +1 -0
- package/dist/capture/publish/extract.d.mts +18 -0
- package/dist/capture/publish/extract.mjs +120 -0
- package/dist/capture/publish/extract.mjs.map +1 -0
- package/dist/capture/publish/handler.d.mts +26 -0
- package/dist/capture/publish/handler.mjs +61 -0
- package/dist/capture/publish/handler.mjs.map +1 -0
- package/dist/capture/publish/index.d.mts +11 -0
- package/dist/capture/publish/invalid-zip.d.mts +8 -0
- package/dist/capture/publish/invalid-zip.mjs +11 -0
- package/dist/capture/publish/invalid-zip.mjs.map +1 -0
- package/dist/capture/publish/limits.d.mts +35 -0
- package/dist/capture/publish/limits.mjs +38 -0
- package/dist/capture/publish/limits.mjs.map +1 -0
- package/dist/capture/publish/published.d.mts +13 -0
- package/dist/capture/publish/published.mjs +17 -0
- package/dist/capture/publish/published.mjs.map +1 -0
- package/dist/capture/publish/run.d.mts +31 -0
- package/dist/capture/publish/run.mjs +29 -0
- package/dist/capture/publish/run.mjs.map +1 -0
- package/dist/capture/rediff.d.mts +1 -3
- package/dist/capture/rediff.mjs +23 -9
- package/dist/capture/rediff.mjs.map +1 -1
- package/dist/capture/render.mjs +52 -0
- package/dist/capture/render.mjs.map +1 -0
- package/dist/capture/scratch.d.mts +42 -0
- package/dist/capture/scratch.mjs +136 -0
- package/dist/capture/scratch.mjs.map +1 -0
- package/dist/capture/shard-plan.mjs +127 -0
- package/dist/capture/shard-plan.mjs.map +1 -0
- package/dist/capture/shards/context.mjs +51 -0
- package/dist/capture/shards/context.mjs.map +1 -0
- package/dist/capture/shards/execute.mjs +175 -0
- package/dist/capture/shards/execute.mjs.map +1 -0
- package/dist/capture/shards/fan-out.mjs +125 -0
- package/dist/capture/shards/fan-out.mjs.map +1 -0
- package/dist/capture/shards/history.mjs +34 -0
- package/dist/capture/shards/history.mjs.map +1 -0
- package/dist/capture/shards/reduce.mjs +96 -0
- package/dist/capture/shards/reduce.mjs.map +1 -0
- package/dist/capture/sizing.d.mts +0 -2
- package/dist/capture/sizing.mjs +2 -13
- package/dist/capture/sizing.mjs.map +1 -1
- package/dist/capture/skip-checks.mjs +3 -67
- package/dist/capture/skip-checks.mjs.map +1 -1
- package/dist/capture/statics.d.mts +7 -10
- package/dist/capture/statics.mjs +26 -74
- package/dist/capture/statics.mjs.map +1 -1
- package/dist/capture/storybook-urls.d.mts +38 -0
- package/dist/capture/storybook-urls.mjs +41 -0
- package/dist/capture/storybook-urls.mjs.map +1 -0
- package/dist/capture/storybook.d.mts +12 -6
- package/dist/capture/storybook.mjs +63 -19
- package/dist/capture/storybook.mjs.map +1 -1
- package/dist/capture/target.mjs +20 -0
- package/dist/capture/target.mjs.map +1 -0
- package/dist/capture/targets.d.mts +23 -0
- package/dist/capture/targets.mjs +31 -0
- package/dist/capture/targets.mjs.map +1 -0
- package/dist/capture/tests.d.mts +35 -0
- package/dist/capture/tests.mjs +136 -0
- package/dist/capture/tests.mjs.map +1 -0
- package/dist/capture/token-chunks.mjs +91 -0
- package/dist/capture/token-chunks.mjs.map +1 -0
- package/dist/capture.d.mts +63 -7
- package/dist/capture.mjs +51 -3
- package/dist/config.d.mts +136 -14
- package/dist/config.mjs +14 -8
- package/dist/config.mjs.map +1 -1
- package/dist/diff.d.mts +7 -3
- package/dist/diff.mjs +15 -3
- package/dist/diff.mjs.map +1 -0
- package/dist/index.d.mts +3 -3
- package/dist/index.mjs +2 -2
- package/dist/insights/budget.d.mts +18 -0
- package/dist/insights/budget.mjs +32 -0
- package/dist/insights/budget.mjs.map +1 -0
- package/dist/insights/envelope.d.mts +57 -0
- package/dist/insights/envelope.mjs +59 -0
- package/dist/insights/envelope.mjs.map +1 -0
- package/dist/insights/evidence-health.d.mts +30 -0
- package/dist/insights/evidence-health.mjs +27 -0
- package/dist/insights/evidence-health.mjs.map +1 -0
- package/dist/insights/evidence.d.mts +53 -0
- package/dist/insights/evidence.mjs +63 -0
- package/dist/insights/evidence.mjs.map +1 -0
- package/dist/insights/hash.d.mts +16 -0
- package/dist/insights/hash.mjs +20 -0
- package/dist/insights/hash.mjs.map +1 -0
- package/dist/insights/limits.d.mts +22 -0
- package/dist/insights/limits.mjs +23 -0
- package/dist/insights/limits.mjs.map +1 -0
- package/dist/insights/prompts/v1.d.mts +11 -0
- package/dist/insights/prompts/v1.mjs +33 -0
- package/dist/insights/prompts/v1.mjs.map +1 -0
- package/dist/insights/redact.d.mts +7 -0
- package/dist/insights/redact.mjs +23 -0
- package/dist/insights/redact.mjs.map +1 -0
- package/dist/insights/text.mjs +43 -0
- package/dist/insights/text.mjs.map +1 -0
- package/dist/insights.d.mts +11 -0
- package/dist/insights.mjs +12 -0
- package/dist/logger.d.mts +0 -2
- package/dist/logger.mjs +18 -3
- package/dist/logger.mjs.map +1 -1
- package/dist/models/ai-budget-alert.d.mts +21 -0
- package/dist/models/ai-budget-alert.mjs +37 -0
- package/dist/models/ai-budget-alert.mjs.map +1 -0
- package/dist/models/ai-usage.d.mts +38 -0
- package/dist/models/ai-usage.mjs +60 -0
- package/dist/models/ai-usage.mjs.map +1 -0
- package/dist/models/baseline-approval.d.mts +18 -0
- package/dist/models/baseline-approval.mjs +41 -0
- package/dist/models/baseline-approval.mjs.map +1 -0
- package/dist/models/baseline-archive.d.mts +82 -0
- package/dist/models/baseline-archive.mjs +105 -0
- package/dist/models/baseline-archive.mjs.map +1 -0
- package/dist/models/baseline-atomic.mjs +66 -0
- package/dist/models/baseline-atomic.mjs.map +1 -0
- package/dist/models/baseline-conflict.d.mts +6 -0
- package/dist/models/baseline-conflict.mjs +9 -0
- package/dist/models/baseline-conflict.mjs.map +1 -0
- package/dist/models/baseline-export.d.mts +17 -0
- package/dist/models/baseline-export.mjs +135 -0
- package/dist/models/baseline-export.mjs.map +1 -0
- package/dist/models/baseline-import.mjs +117 -0
- package/dist/models/baseline-import.mjs.map +1 -0
- package/dist/models/baseline-origin.d.mts +16 -0
- package/dist/models/baseline-origin.mjs +35 -0
- package/dist/models/baseline-origin.mjs.map +1 -0
- package/dist/models/baseline-status.d.mts +0 -2
- package/dist/models/baseline-status.mjs +1 -2
- package/dist/models/baseline-transfer.d.mts +48 -0
- package/dist/models/baseline-transfer.mjs +149 -0
- package/dist/models/baseline-transfer.mjs.map +1 -0
- package/dist/models/baseline.d.mts +37 -7
- package/dist/models/baseline.mjs +152 -28
- package/dist/models/baseline.mjs.map +1 -1
- package/dist/models/build-component.d.mts +26 -0
- package/dist/models/build-component.mjs +54 -0
- package/dist/models/build-component.mjs.map +1 -0
- package/dist/models/build-review-status.d.mts +27 -0
- package/dist/models/build-review-status.mjs +36 -0
- package/dist/models/build-review-status.mjs.map +1 -0
- package/dist/models/build-review.d.mts +26 -0
- package/dist/models/build-review.mjs +53 -0
- package/dist/models/build-review.mjs.map +1 -0
- package/dist/models/build.d.mts +25 -5
- package/dist/models/build.mjs +53 -15
- package/dist/models/build.mjs.map +1 -1
- package/dist/models/capture-attempt.d.mts +7 -3
- package/dist/models/capture-attempt.mjs +30 -6
- package/dist/models/capture-attempt.mjs.map +1 -1
- package/dist/models/capture-job.d.mts +104 -0
- package/dist/models/capture-job.mjs +287 -0
- package/dist/models/capture-job.mjs.map +1 -0
- package/dist/models/capture-log.d.mts +1 -3
- package/dist/models/capture-log.mjs +5 -5
- package/dist/models/capture-log.mjs.map +1 -1
- package/dist/models/capture-shard.d.mts +60 -0
- package/dist/models/capture-shard.mjs +189 -0
- package/dist/models/capture-shard.mjs.map +1 -0
- package/dist/models/changeset.d.mts +46 -0
- package/dist/models/changeset.mjs +80 -0
- package/dist/models/changeset.mjs.map +1 -0
- package/dist/models/columns.mjs +18 -0
- package/dist/models/columns.mjs.map +1 -0
- package/dist/models/comment.d.mts +4 -3
- package/dist/models/comment.mjs +8 -6
- package/dist/models/comment.mjs.map +1 -1
- package/dist/models/component.d.mts +41 -0
- package/dist/models/component.mjs +59 -0
- package/dist/models/component.mjs.map +1 -0
- package/dist/models/connections.d.mts +39 -0
- package/dist/models/connections.mjs +111 -0
- package/dist/models/connections.mjs.map +1 -0
- package/dist/models/content-ref.d.mts +1 -3
- package/dist/models/content-ref.mjs +2 -3
- package/dist/models/content-ref.mjs.map +1 -1
- package/dist/models/credentials.d.mts +0 -2
- package/dist/models/credentials.mjs +13 -4
- package/dist/models/credentials.mjs.map +1 -1
- package/dist/models/design-credentials.d.mts +39 -0
- package/dist/models/design-credentials.mjs +85 -0
- package/dist/models/design-credentials.mjs.map +1 -0
- package/dist/models/design-health.d.mts +14 -0
- package/dist/models/design-health.mjs +24 -0
- package/dist/models/design-health.mjs.map +1 -0
- package/dist/models/design-nodes.d.mts +81 -0
- package/dist/models/design-nodes.mjs +221 -0
- package/dist/models/design-nodes.mjs.map +1 -0
- package/dist/models/design-renders.mjs +73 -0
- package/dist/models/design-renders.mjs.map +1 -0
- package/dist/models/design-resolve.d.mts +27 -0
- package/dist/models/design-resolve.mjs +40 -0
- package/dist/models/design-resolve.mjs.map +1 -0
- package/dist/models/design.d.mts +37 -0
- package/dist/models/design.mjs +89 -0
- package/dist/models/design.mjs.map +1 -0
- package/dist/models/inherited-ratio.d.mts +19 -0
- package/dist/models/inherited-ratio.mjs +21 -0
- package/dist/models/inherited-ratio.mjs.map +1 -0
- package/dist/models/insight.d.mts +68 -0
- package/dist/models/insight.mjs +161 -0
- package/dist/models/insight.mjs.map +1 -0
- package/dist/models/label.d.mts +4 -4
- package/dist/models/label.mjs +26 -12
- package/dist/models/label.mjs.map +1 -1
- package/dist/models/member.d.mts +1 -3
- package/dist/models/member.mjs +6 -6
- package/dist/models/member.mjs.map +1 -1
- package/dist/models/notification-channel.d.mts +37 -5
- package/dist/models/notification-channel.mjs +89 -11
- package/dist/models/notification-channel.mjs.map +1 -1
- package/dist/models/notification-subscription.d.mts +1 -4
- package/dist/models/notification-subscription.mjs +8 -11
- package/dist/models/notification-subscription.mjs.map +1 -1
- package/dist/models/project-group-mapping.d.mts +6 -3
- package/dist/models/project-group-mapping.mjs +15 -6
- package/dist/models/project-group-mapping.mjs.map +1 -1
- package/dist/models/project.d.mts +9 -3
- package/dist/models/project.mjs +10 -6
- package/dist/models/project.mjs.map +1 -1
- package/dist/models/public-origin.d.mts +61 -0
- package/dist/models/public-origin.mjs +113 -0
- package/dist/models/public-origin.mjs.map +1 -0
- package/dist/models/review-switch-off.d.mts +12 -0
- package/dist/models/review-switch-off.mjs +25 -0
- package/dist/models/review-switch-off.mjs.map +1 -0
- package/dist/models/site-settings.d.mts +21 -0
- package/dist/models/site-settings.mjs +47 -0
- package/dist/models/site-settings.mjs.map +1 -0
- package/dist/models/snapshot.d.mts +39 -3
- package/dist/models/snapshot.mjs +54 -5
- package/dist/models/snapshot.mjs.map +1 -1
- package/dist/models/token.d.mts +13 -3
- package/dist/models/token.mjs +24 -7
- package/dist/models/token.mjs.map +1 -1
- package/dist/models/usage.d.mts +74 -0
- package/dist/models/usage.mjs +148 -0
- package/dist/models/usage.mjs.map +1 -0
- package/dist/models/user.d.mts +1 -3
- package/dist/models/user.mjs +5 -5
- package/dist/models/user.mjs.map +1 -1
- package/dist/models/webhook.d.mts +15 -3
- package/dist/models/webhook.mjs +34 -6
- package/dist/models/webhook.mjs.map +1 -1
- package/dist/models.d.mts +30 -4
- package/dist/models.mjs +31 -6
- package/dist/options.d.mts +16 -0
- package/dist/options.mjs +88 -0
- package/dist/options.mjs.map +1 -0
- package/dist/paths.d.mts +21 -6
- package/dist/paths.mjs +57 -12
- package/dist/paths.mjs.map +1 -1
- package/dist/retention/ai.d.mts +16 -0
- package/dist/retention/ai.mjs +22 -0
- package/dist/retention/ai.mjs.map +1 -0
- package/dist/retention/pending.mjs +46 -0
- package/dist/retention/pending.mjs.map +1 -0
- package/dist/retention/published-gzip-sweep.mjs +28 -0
- package/dist/retention/published-gzip-sweep.mjs.map +1 -0
- package/dist/retention/screenshot-sweep.d.mts +49 -0
- package/dist/retention/screenshot-sweep.mjs +127 -0
- package/dist/retention/screenshot-sweep.mjs.map +1 -0
- package/dist/retention.d.mts +16 -4
- package/dist/retention.mjs +76 -16
- package/dist/retention.mjs.map +1 -1
- package/dist/schema/baseline.d.mts +8 -2
- package/dist/schema/build-component.d.mts +23 -0
- package/dist/schema/build-review.d.mts +15 -0
- package/dist/schema/build.d.mts +7 -2
- package/dist/schema/capture-attempt.d.mts +0 -2
- package/dist/schema/capture-job.d.mts +58 -0
- package/dist/schema/capture-shard.d.mts +33 -0
- package/dist/schema/comment.d.mts +2 -2
- package/dist/schema/connection.d.mts +22 -0
- package/dist/schema/content-ref.d.mts +0 -2
- package/dist/schema/design.d.mts +56 -0
- package/dist/schema/insight.d.mts +52 -0
- package/dist/schema/label.d.mts +0 -2
- package/dist/schema/member.d.mts +0 -2
- package/dist/schema/notification.d.mts +4 -2
- package/dist/schema/project-group-mapping.d.mts +0 -2
- package/dist/schema/project.d.mts +21 -2
- package/dist/schema/site-setting.d.mts +19 -0
- package/dist/schema/snapshot.d.mts +17 -2
- package/dist/schema/token.d.mts +0 -2
- package/dist/schema/usage.d.mts +28 -0
- package/dist/schema/user.d.mts +0 -2
- package/dist/schema/webhook.d.mts +0 -2
- package/dist/schema.d.mts +12 -6
- package/dist/serverless.d.mts +19 -0
- package/dist/serverless.mjs +36 -0
- package/dist/serverless.mjs.map +1 -0
- package/dist/test-helpers/build-reference-contract.d.mts +14 -0
- package/dist/test-helpers/build-reference-contract.mjs +66 -0
- package/dist/test-helpers/build-reference-contract.mjs.map +1 -0
- package/dist/test-helpers/create-project.d.mts +1 -3
- package/dist/test-helpers/create-project.mjs +1 -2
- package/dist/test-helpers/database-contract.d.mts +0 -2
- package/dist/test-helpers/database-contract.mjs +37 -2
- package/dist/test-helpers/database-contract.mjs.map +1 -1
- package/dist/test-helpers/deferred.d.mts +37 -0
- package/dist/test-helpers/deferred.mjs +60 -0
- package/dist/test-helpers/deferred.mjs.map +1 -0
- package/dist/test-helpers/design-contract.d.mts +19 -0
- package/dist/test-helpers/design-contract.mjs +39 -0
- package/dist/test-helpers/design-contract.mjs.map +1 -0
- package/dist/test-helpers/fake-adapters.d.mts +0 -2
- package/dist/test-helpers/fake-adapters.mjs +1 -2
- package/dist/test-helpers/fake-database.d.mts +0 -2
- package/dist/test-helpers/fake-database.mjs +21 -4
- package/dist/test-helpers/fake-database.mjs.map +1 -1
- package/dist/test-helpers/fake-design.d.mts +41 -0
- package/dist/test-helpers/fake-design.mjs +113 -0
- package/dist/test-helpers/fake-design.mjs.map +1 -0
- package/dist/test-helpers/fake-notifier.d.mts +19 -0
- package/dist/test-helpers/fake-notifier.mjs +43 -0
- package/dist/test-helpers/fake-notifier.mjs.map +1 -0
- package/dist/test-helpers/fake-storage.d.mts +0 -2
- package/dist/test-helpers/fake-storage.mjs +1 -2
- package/dist/test-helpers/fake-tables.mjs +260 -69
- package/dist/test-helpers/fake-tables.mjs.map +1 -1
- package/dist/test-helpers/manual-clock.d.mts +25 -0
- package/dist/test-helpers/manual-clock.mjs +55 -0
- package/dist/test-helpers/manual-clock.mjs.map +1 -0
- package/dist/test-helpers/notifier-contract.d.mts +7 -3
- package/dist/test-helpers/notifier-contract.mjs +33 -5
- package/dist/test-helpers/notifier-contract.mjs.map +1 -1
- package/dist/test-helpers/queue-contract.d.mts +16 -4
- package/dist/test-helpers/queue-contract.mjs +196 -3
- package/dist/test-helpers/queue-contract.mjs.map +1 -1
- package/dist/test-helpers/queue-multi-instance.d.mts +18 -0
- package/dist/test-helpers/queue-multi-instance.mjs +205 -0
- package/dist/test-helpers/queue-multi-instance.mjs.map +1 -0
- package/dist/test-helpers/runner-contract.d.mts +0 -2
- package/dist/test-helpers/runner-contract.mjs +12 -2
- package/dist/test-helpers/runner-contract.mjs.map +1 -1
- package/dist/test-helpers/sql-chunks.mjs +25 -2
- package/dist/test-helpers/sql-chunks.mjs.map +1 -1
- package/dist/test-helpers/storage-contract.d.mts +0 -2
- package/dist/test-helpers/storage-contract.mjs +2 -3
- package/dist/test-helpers.d.mts +10 -4
- package/dist/test-helpers.mjs +9 -3
- package/dist/tracing.mjs +36 -6
- package/dist/tracing.mjs.map +1 -1
- package/dist/types.d.mts +14 -5
- package/dist/types.mjs +22 -6
- package/dist/types.mjs.map +1 -1
- package/dist/urls.d.mts +4 -2
- package/dist/urls.mjs +12 -4
- package/dist/urls.mjs.map +1 -1
- package/dist/utils/adaptive-concurrency.d.mts +64 -0
- package/dist/utils/adaptive-concurrency.mjs +148 -0
- package/dist/utils/adaptive-concurrency.mjs.map +1 -0
- package/dist/utils/connect-retry.d.mts +26 -0
- package/dist/utils/connect-retry.mjs +42 -0
- package/dist/utils/connect-retry.mjs.map +1 -0
- package/dist/utils/encrypt.d.mts +0 -2
- package/dist/utils/encrypt.mjs +1 -2
- package/dist/utils/environment.d.mts +17 -0
- package/dist/utils/environment.mjs +58 -0
- package/dist/utils/environment.mjs.map +1 -0
- package/dist/utils/hash.d.mts +0 -2
- package/dist/utils/hash.mjs +1 -2
- package/dist/utils/http.d.mts +29 -3
- package/dist/utils/http.mjs +100 -19
- package/dist/utils/http.mjs.map +1 -1
- package/dist/utils/map-adaptive.d.mts +13 -0
- package/dist/utils/map-adaptive.mjs +71 -0
- package/dist/utils/map-adaptive.mjs.map +1 -0
- package/dist/utils/map-limit.d.mts +8 -0
- package/dist/utils/map-limit.mjs +31 -0
- package/dist/utils/map-limit.mjs.map +1 -0
- package/dist/utils/mime.d.mts +0 -2
- package/dist/utils/mime.mjs +1 -2
- package/dist/utils/page-limiter.d.mts +35 -0
- package/dist/utils/page-limiter.mjs +55 -0
- package/dist/utils/page-limiter.mjs.map +1 -0
- package/dist/utils/path-security.d.mts +0 -2
- package/dist/utils/path-security.mjs +2 -4
- package/dist/utils/path-security.mjs.map +1 -1
- package/dist/utils/public-origin.d.mts +18 -0
- package/dist/utils/public-origin.mjs +74 -0
- package/dist/utils/public-origin.mjs.map +1 -0
- package/dist/utils/redact.d.mts +0 -2
- package/dist/utils/redact.mjs +4 -2
- package/dist/utils/redact.mjs.map +1 -1
- package/dist/utils/remote-environment.d.mts +76 -0
- package/dist/utils/remote-environment.mjs +163 -0
- package/dist/utils/remote-environment.mjs.map +1 -0
- package/dist/utils/resources.d.mts +62 -0
- package/dist/utils/resources.mjs +121 -0
- package/dist/utils/resources.mjs.map +1 -0
- package/dist/utils/screenshot-store.d.mts +13 -0
- package/dist/utils/screenshot-store.mjs +40 -0
- package/dist/utils/screenshot-store.mjs.map +1 -0
- package/dist/utils/session-gate.d.mts +23 -0
- package/dist/utils/session-gate.mjs +69 -0
- package/dist/utils/session-gate.mjs.map +1 -0
- package/dist/utils/storybook-token.d.mts +89 -0
- package/dist/utils/storybook-token.mjs +135 -0
- package/dist/utils/storybook-token.mjs.map +1 -0
- package/dist/utils/storybook-url.d.mts +13 -0
- package/dist/utils/storybook-url.mjs +20 -0
- package/dist/utils/storybook-url.mjs.map +1 -0
- package/dist/utils/timing.d.mts +0 -2
- package/dist/utils/timing.mjs +2 -3
- package/dist/utils/timing.mjs.map +1 -1
- package/dist/utils/ulid.d.mts +0 -2
- package/dist/utils/ulid.mjs +1 -2
- package/dist/utils/zip-read.mjs +128 -0
- package/dist/utils/zip-read.mjs.map +1 -0
- package/dist/utils/zip-write.d.mts +16 -0
- package/dist/utils/zip-write.mjs +187 -0
- package/dist/utils/zip-write.mjs.map +1 -0
- package/dist/utils.d.mts +17 -3
- package/dist/utils.mjs +18 -4
- package/package.json +35 -26
- package/dist/adapter/capture-runner.mjs.map +0 -1
- package/dist/adapter/git-host/comments.d.mts +0 -41
- package/dist/adapter/git-host/comments.mjs +0 -54
- package/dist/adapter/git-host/comments.mjs.map +0 -1
- package/dist/adapter/git-host/helpers.d.mts +0 -13
- package/dist/adapter/git-host/helpers.mjs +0 -30
- package/dist/adapter/git-host/helpers.mjs.map +0 -1
- package/dist/adapter/git-host.d.mts +0 -42
- package/dist/capture/queue.d.mts +0 -38
- package/dist/capture/queue.mjs +0 -98
- package/dist/capture/queue.mjs.map +0 -1
- package/dist/capture/recovery.d.mts +0 -21
- package/dist/capture/recovery.mjs +0 -83
- package/dist/capture/recovery.mjs.map +0 -1
- package/dist/capture/status-fanout.mjs +0 -72
- package/dist/capture/status-fanout.mjs.map +0 -1
- package/dist/diff/engine.d.mts +0 -16
- package/dist/diff/engine.mjs +0 -87
- package/dist/diff/engine.mjs.map +0 -1
- package/dist/diff/options.d.mts +0 -42
- package/dist/diff/options.mjs +0 -15
- package/dist/diff/options.mjs.map +0 -1
- package/dist/models/status-config.d.mts +0 -38
- package/dist/models/status-config.mjs +0 -63
- package/dist/models/status-config.mjs.map +0 -1
- package/dist/schema/status-config.d.mts +0 -16
- package/dist/test-helpers/git-contract.d.mts +0 -11
- package/dist/test-helpers/git-contract.mjs +0 -34
- package/dist/test-helpers/git-contract.mjs.map +0 -1
- /package/dist/adapter/{git-host.mjs → oauth.mjs} +0 -0
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import "node:path";
|
|
2
|
+
import "node:url";
|
|
3
|
+
import.meta.url;
|
|
4
|
+
import { CaptureJobModel, attemptRowId } from "../models/capture-job.mjs";
|
|
5
|
+
import { LEASE_EXHAUSTED_ERROR } from "../capture/db-queue/claim.mjs";
|
|
6
|
+
import { createDatabaseCaptureQueue } from "../capture/db-queue/index.mjs";
|
|
7
|
+
import { describe, expect, it } from "vitest";
|
|
8
|
+
// src/test-helpers/queue-multi-instance.ts
|
|
9
|
+
/**
|
|
10
|
+
* Multi-instance suite for the database capture queue: several queue instances (one per simulated
|
|
11
|
+
* app instance) share ONE database, the way horizontally scaled app instances do. Database packages
|
|
12
|
+
* run it against their own drivers (node:sqlite, PGlite, and the gated live Postgres, MySQL and
|
|
13
|
+
* Turso tests):
|
|
14
|
+
*
|
|
15
|
+
* ```ts
|
|
16
|
+
* import { databaseQueueMultiInstanceSuite } from "@storyshelf/core/test-helpers";
|
|
17
|
+
* databaseQueueMultiInstanceSuite("sqlite", async () => await setUp(createSqliteDatabase(":memory:")));
|
|
18
|
+
* ```
|
|
19
|
+
*
|
|
20
|
+
* Every instance reads one shared manual clock, so lease expiry and retry delays are exact and no
|
|
21
|
+
* case sleeps. Build ids are unique per run, so a shared live database needs no cleanup between runs.
|
|
22
|
+
*/
|
|
23
|
+
const LEASE_MS = 6e4;
|
|
24
|
+
const RETRIES = 2;
|
|
25
|
+
async function fleet(makeDb, size, kinds = ["capture"]) {
|
|
26
|
+
const db = await makeDb();
|
|
27
|
+
const clock = { now: Date.now() };
|
|
28
|
+
const queues = Array.from({ length: size }, (_unused, index) => createDatabaseCaptureQueue(db, {
|
|
29
|
+
kinds,
|
|
30
|
+
leaseMs: LEASE_MS,
|
|
31
|
+
retries: RETRIES,
|
|
32
|
+
owner: `instance-${String(index)}`,
|
|
33
|
+
now: () => clock.now
|
|
34
|
+
}));
|
|
35
|
+
const advance = (ms) => {
|
|
36
|
+
clock.now += ms;
|
|
37
|
+
};
|
|
38
|
+
return {
|
|
39
|
+
db,
|
|
40
|
+
queues,
|
|
41
|
+
advance,
|
|
42
|
+
runId: `mi-${String(clock.now)}-${Math.random().toString(36).slice(2, 8)}`
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/** Poll on every instance at once, as concurrent app instances would. */
|
|
46
|
+
async function pollAll(queues) {
|
|
47
|
+
return (await Promise.all(queues.map(async (queue) => await queue.poll()))).filter((job) => job !== null);
|
|
48
|
+
}
|
|
49
|
+
/** Poll every instance until one returns the job of `buildId` (others' jobs are acked away). */
|
|
50
|
+
async function claimOn(queue, buildId) {
|
|
51
|
+
for (let tries = 0; tries < 50; tries += 1) {
|
|
52
|
+
const job = await queue.poll();
|
|
53
|
+
if (job === null || job.buildId === buildId) return job;
|
|
54
|
+
await queue.ack(job);
|
|
55
|
+
}
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
/** Run the multi-instance suite against databases from `makeDb`. */
|
|
59
|
+
function databaseQueueMultiInstanceSuite(label, makeDb, options = {}) {
|
|
60
|
+
const size = options.instances ?? 4;
|
|
61
|
+
const jobs = options.jobs ?? 24;
|
|
62
|
+
describe(`database queue, ${String(size)} instances on one database: ${label}`, () => {
|
|
63
|
+
exactlyOnceCase(makeDb, size, jobs, options.timeoutMs);
|
|
64
|
+
leaseCases(makeDb, size, options.timeoutMs);
|
|
65
|
+
retryCases(makeDb, size, options.timeoutMs);
|
|
66
|
+
cancelAndKindCases(makeDb, size, options.timeoutMs);
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
function exactlyOnceCase(makeDb, size, jobs, timeoutMs) {
|
|
70
|
+
it("runs each job exactly once across instances polling at the same time", async () => {
|
|
71
|
+
const { queues, runId } = await fleet(makeDb, size);
|
|
72
|
+
const ids = Array.from({ length: jobs }, (_unused, index) => `${runId}-${String(index)}`);
|
|
73
|
+
await Promise.all(ids.map(async (buildId, index) => await queues[index % size].enqueue({ buildId })));
|
|
74
|
+
const claims = /* @__PURE__ */ new Map();
|
|
75
|
+
const ours = new Set(ids);
|
|
76
|
+
for (let round = 0; round < jobs * 2 + 20 && claims.size < jobs; round += 1) {
|
|
77
|
+
const claimed = await pollAll(queues);
|
|
78
|
+
for (const job of claimed.filter((one) => ours.has(one.buildId))) claims.set(job.buildId, (claims.get(job.buildId) ?? 0) + 1);
|
|
79
|
+
await Promise.all(claimed.map(async (job) => await queues[0].ack(job)));
|
|
80
|
+
}
|
|
81
|
+
expect([...claims.keys()].toSorted()).toEqual(ids.toSorted());
|
|
82
|
+
expect([...claims.values()].every((count) => count === 1)).toBe(true);
|
|
83
|
+
const statuses = await Promise.all(ids.map(async (id) => (await queues[1].status(id))?.status));
|
|
84
|
+
expect(new Set(statuses)).toEqual(/* @__PURE__ */ new Set(["completed"]));
|
|
85
|
+
}, timeoutMs);
|
|
86
|
+
}
|
|
87
|
+
function leaseCases(makeDb, size, timeoutMs) {
|
|
88
|
+
it("redelivers an expired lease to another instance and fences the stale holder", async () => {
|
|
89
|
+
const { db, queues, advance, runId } = await fleet(makeDb, size);
|
|
90
|
+
const [first, second] = [queues[0], queues[1]];
|
|
91
|
+
const buildId = `${runId}-lease`;
|
|
92
|
+
await first.enqueue({ buildId });
|
|
93
|
+
const stale = await claimOn(first, buildId);
|
|
94
|
+
expect(stale?.attempt).toBe(1);
|
|
95
|
+
expect(await claimOn(second, buildId)).toBeNull();
|
|
96
|
+
advance(60001);
|
|
97
|
+
const winners = (await pollAll(queues)).filter((job) => job.buildId === buildId);
|
|
98
|
+
expect(winners).toHaveLength(1);
|
|
99
|
+
const fresh = winners[0];
|
|
100
|
+
expect(fresh.attempt).toBe(2);
|
|
101
|
+
const model = new CaptureJobModel(db);
|
|
102
|
+
const leaseBefore = (await model.get(attemptRowId(fresh.jobId, 2)))?.visibleAt;
|
|
103
|
+
expect(await first.extend(stale)).toBe("lost");
|
|
104
|
+
expect(await first.inspect(stale)).toBe("lost");
|
|
105
|
+
await first.nack(stale, {
|
|
106
|
+
requeue: true,
|
|
107
|
+
error: "stale"
|
|
108
|
+
});
|
|
109
|
+
const after = await model.get(fresh.jobId);
|
|
110
|
+
expect(after).toMatchObject({
|
|
111
|
+
status: "running",
|
|
112
|
+
attempt: 2
|
|
113
|
+
});
|
|
114
|
+
expect((await model.get(attemptRowId(fresh.jobId, 2)))?.visibleAt).toBe(leaseBefore);
|
|
115
|
+
expect((await model.get(attemptRowId(fresh.jobId, 1)))?.status).toBe("expired");
|
|
116
|
+
expect(await second.extend(fresh)).toBe("ok");
|
|
117
|
+
await second.ack(fresh);
|
|
118
|
+
await first.ack(stale);
|
|
119
|
+
expect((await second.status(buildId))?.status).toBe("completed");
|
|
120
|
+
}, timeoutMs);
|
|
121
|
+
}
|
|
122
|
+
function retryCases(makeDb, size, timeoutMs) {
|
|
123
|
+
it("fails a job on any instance once its retries are spent", async () => {
|
|
124
|
+
const { queues, advance, runId } = await fleet(makeDb, size);
|
|
125
|
+
const buildId = `${runId}-retry`;
|
|
126
|
+
await queues[0].enqueue({ buildId });
|
|
127
|
+
for (let attempt = 1; attempt <= 3; attempt += 1) {
|
|
128
|
+
const queue = queues[attempt % size];
|
|
129
|
+
const job = await claimOn(queue, buildId);
|
|
130
|
+
expect(job?.attempt).toBe(attempt);
|
|
131
|
+
await queue.nack(job, {
|
|
132
|
+
requeue: true,
|
|
133
|
+
delayMs: 1e3,
|
|
134
|
+
error: `boom ${String(attempt)}`
|
|
135
|
+
});
|
|
136
|
+
advance(1001);
|
|
137
|
+
}
|
|
138
|
+
expect(await claimOn(queues[0], buildId)).toBeNull();
|
|
139
|
+
expect(await queues[2 % size].status(buildId)).toMatchObject({
|
|
140
|
+
status: "failed",
|
|
141
|
+
error: `boom ${String(3)}`
|
|
142
|
+
});
|
|
143
|
+
}, timeoutMs);
|
|
144
|
+
it("fails a job whose every attempt lost its lease", async () => {
|
|
145
|
+
const { queues, advance, runId } = await fleet(makeDb, size);
|
|
146
|
+
const buildId = `${runId}-hung`;
|
|
147
|
+
await queues[0].enqueue({ buildId });
|
|
148
|
+
for (let attempt = 1; attempt <= 3; attempt += 1) {
|
|
149
|
+
const job = await claimOn(queues[attempt % size], buildId);
|
|
150
|
+
expect(job?.attempt).toBe(attempt);
|
|
151
|
+
advance(60001);
|
|
152
|
+
}
|
|
153
|
+
expect(await claimOn(queues[0], buildId)).toBeNull();
|
|
154
|
+
expect(await queues[1].status(buildId)).toMatchObject({
|
|
155
|
+
status: "failed",
|
|
156
|
+
error: LEASE_EXHAUSTED_ERROR
|
|
157
|
+
});
|
|
158
|
+
}, timeoutMs);
|
|
159
|
+
}
|
|
160
|
+
function cancelAndKindCases(makeDb, size, timeoutMs) {
|
|
161
|
+
it("lets one instance cancel a job another instance holds", async () => {
|
|
162
|
+
const { queues, runId } = await fleet(makeDb, size);
|
|
163
|
+
const [holder, other] = [queues[0], queues[1]];
|
|
164
|
+
const buildId = `${runId}-cancel`;
|
|
165
|
+
await other.enqueue({ buildId });
|
|
166
|
+
const job = await claimOn(holder, buildId);
|
|
167
|
+
expect(await holder.inspect(job)).toBe("ok");
|
|
168
|
+
expect(await other.cancel(buildId)).toBe(true);
|
|
169
|
+
expect(await holder.inspect(job)).toBe("cancelled");
|
|
170
|
+
expect(await holder.extend(job)).toBe("cancelled");
|
|
171
|
+
await holder.nack(job, {
|
|
172
|
+
requeue: false,
|
|
173
|
+
error: "cancelled"
|
|
174
|
+
});
|
|
175
|
+
expect((await other.status(buildId))?.status).toBe("cancelled");
|
|
176
|
+
expect(await claimOn(other, buildId)).toBeNull();
|
|
177
|
+
}, timeoutMs);
|
|
178
|
+
it("keeps job kinds independent", async () => {
|
|
179
|
+
const shared = await makeDb();
|
|
180
|
+
const reuse = () => shared;
|
|
181
|
+
const capture = await fleet(reuse, 2, ["capture"]);
|
|
182
|
+
const publish = await fleet(reuse, 2, ["publish"]);
|
|
183
|
+
const buildId = `${capture.runId}-kinds`;
|
|
184
|
+
await capture.queues[0].enqueue({ buildId });
|
|
185
|
+
await publish.queues[0].enqueueJob({
|
|
186
|
+
kind: "publish",
|
|
187
|
+
buildId,
|
|
188
|
+
data: { zip: "k" }
|
|
189
|
+
});
|
|
190
|
+
const published = await claimOn(publish.queues[1], buildId);
|
|
191
|
+
expect(published).toMatchObject({
|
|
192
|
+
kind: "publish",
|
|
193
|
+
data: { zip: "k" }
|
|
194
|
+
});
|
|
195
|
+
const captured = await claimOn(capture.queues[1], buildId);
|
|
196
|
+
expect(captured?.kind).toBe("capture");
|
|
197
|
+
expect(await claimOn(publish.queues[0], buildId)).toBeNull();
|
|
198
|
+
await publish.queues[1].ack(published);
|
|
199
|
+
await capture.queues[1].ack(captured);
|
|
200
|
+
expect((await capture.queues[0].status(buildId))?.status).toBe("completed");
|
|
201
|
+
}, timeoutMs);
|
|
202
|
+
}
|
|
203
|
+
export { databaseQueueMultiInstanceSuite };
|
|
204
|
+
|
|
205
|
+
//# sourceMappingURL=queue-multi-instance.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queue-multi-instance.mjs","names":[],"sources":["../../src/test-helpers/queue-multi-instance.ts"],"sourcesContent":["/**\n * Multi-instance suite for the database capture queue: several queue instances (one per simulated\n * app instance) share ONE database, the way horizontally scaled app instances do. Database packages\n * run it against their own drivers (node:sqlite, PGlite, and the gated live Postgres, MySQL and\n * Turso tests):\n *\n * ```ts\n * import { databaseQueueMultiInstanceSuite } from \"@storyshelf/core/test-helpers\";\n * databaseQueueMultiInstanceSuite(\"sqlite\", async () => await setUp(createSqliteDatabase(\":memory:\")));\n * ```\n *\n * Every instance reads one shared manual clock, so lease expiry and retry delays are exact and no\n * case sleeps. Build ids are unique per run, so a shared live database needs no cleanup between runs.\n */\nimport { describe, expect, it } from \"vitest\";\nimport type { DatabaseAdapter } from \"../adapters/database.ts\";\nimport {\n createDatabaseCaptureQueue,\n LEASE_EXHAUSTED_ERROR,\n type DatabaseCaptureQueue,\n type DatabaseJob,\n} from \"../capture/db-queue/index.ts\";\nimport { attemptRowId, CaptureJobModel } from \"../models/capture-job.ts\";\nimport type { CaptureJobKind } from \"../schema/capture-job.ts\";\n\n/** A set-up database the instances of one case share. */\nexport type SharedDatabaseFactory = () => DatabaseAdapter | Promise<DatabaseAdapter>;\n\n/** Options of {@link databaseQueueMultiInstanceSuite}. */\nexport interface MultiInstanceOptions {\n /** Queue instances sharing the database (default 4). */\n instances?: number;\n /** Jobs in the exactly-once case (default 24). */\n jobs?: number;\n /** Per-case timeout in ms, for slow remote databases (default vitest's). */\n timeoutMs?: number;\n}\n\nconst LEASE_MS = 60_000;\nconst RETRIES = 2;\n\ninterface Fleet {\n db: DatabaseAdapter;\n queues: DatabaseCaptureQueue[];\n advance: (ms: number) => void;\n runId: string;\n}\n\nasync function fleet(\n makeDb: SharedDatabaseFactory,\n size: number,\n kinds: readonly CaptureJobKind[] = [\"capture\"],\n): Promise<Fleet> {\n const db = await makeDb();\n const clock = { now: Date.now() };\n const queues = Array.from({ length: size }, (_unused, index) =>\n createDatabaseCaptureQueue(db, {\n kinds,\n leaseMs: LEASE_MS,\n retries: RETRIES,\n owner: `instance-${String(index)}`,\n now: () => clock.now,\n }),\n );\n const advance = (ms: number): void => {\n clock.now += ms;\n };\n return {\n db,\n queues,\n advance,\n runId: `mi-${String(clock.now)}-${Math.random().toString(36).slice(2, 8)}`,\n };\n}\n\n/** Poll on every instance at once, as concurrent app instances would. */\nasync function pollAll(queues: DatabaseCaptureQueue[]): Promise<DatabaseJob[]> {\n const jobs = await Promise.all(queues.map(async (queue) => await queue.poll()));\n return jobs.filter((job): job is DatabaseJob => job !== null);\n}\n\n/** Poll every instance until one returns the job of `buildId` (others' jobs are acked away). */\nasync function claimOn(queue: DatabaseCaptureQueue, buildId: string): Promise<DatabaseJob | null> {\n for (let tries = 0; tries < 50; tries += 1) {\n // oxlint-disable-next-line eslint/no-await-in-loop -- sequential polls are the point\n const job = await queue.poll();\n if (job === null || job.buildId === buildId) {\n return job;\n }\n // oxlint-disable-next-line eslint/no-await-in-loop -- clear a leftover of another case\n await queue.ack(job);\n }\n return null;\n}\n\n/** Run the multi-instance suite against databases from `makeDb`. */\nexport function databaseQueueMultiInstanceSuite(\n label: string,\n makeDb: SharedDatabaseFactory,\n options: MultiInstanceOptions = {},\n): void {\n const size = options.instances ?? 4;\n const jobs = options.jobs ?? 24;\n describe(`database queue, ${String(size)} instances on one database: ${label}`, () => {\n exactlyOnceCase(makeDb, size, jobs, options.timeoutMs);\n leaseCases(makeDb, size, options.timeoutMs);\n retryCases(makeDb, size, options.timeoutMs);\n cancelAndKindCases(makeDb, size, options.timeoutMs);\n });\n}\n\nfunction exactlyOnceCase(\n makeDb: SharedDatabaseFactory,\n size: number,\n jobs: number,\n timeoutMs?: number,\n): void {\n it(\n \"runs each job exactly once across instances polling at the same time\",\n async () => {\n const { queues, runId } = await fleet(makeDb, size);\n const ids = Array.from({ length: jobs }, (_unused, index) => `${runId}-${String(index)}`);\n await Promise.all(\n ids.map(async (buildId, index) => await queues[index % size]!.enqueue({ buildId })),\n );\n const claims = new Map<string, number>();\n const ours = new Set(ids);\n for (let round = 0; round < jobs * 2 + 20 && claims.size < jobs; round += 1) {\n // oxlint-disable-next-line eslint/no-await-in-loop -- rounds of concurrent polls\n const claimed = await pollAll(queues);\n // A shared (live) database may hold other runs' jobs: settle them, count only ours.\n for (const job of claimed.filter((one) => ours.has(one.buildId))) {\n claims.set(job.buildId, (claims.get(job.buildId) ?? 0) + 1);\n }\n // oxlint-disable-next-line eslint/no-await-in-loop -- see above\n await Promise.all(claimed.map(async (job) => await queues[0]!.ack(job)));\n }\n expect([...claims.keys()].toSorted()).toEqual(ids.toSorted());\n expect([...claims.values()].every((count) => count === 1)).toBe(true);\n const statuses = await Promise.all(\n ids.map(async (id) => (await queues[1]!.status(id))?.status),\n );\n expect(new Set(statuses)).toEqual(new Set([\"completed\"]));\n },\n timeoutMs,\n );\n}\n\nfunction leaseCases(makeDb: SharedDatabaseFactory, size: number, timeoutMs?: number): void {\n it(\n \"redelivers an expired lease to another instance and fences the stale holder\",\n async () => {\n const { db, queues, advance, runId } = await fleet(makeDb, size);\n const [first, second] = [queues[0]!, queues[1]!];\n const buildId = `${runId}-lease`;\n await first.enqueue({ buildId });\n const stale = await claimOn(first, buildId);\n expect(stale?.attempt).toBe(1);\n expect(await claimOn(second, buildId)).toBeNull();\n advance(LEASE_MS + 1);\n // Every instance races for the expired lease; exactly one wins.\n const winners = (await pollAll(queues)).filter((job) => job.buildId === buildId);\n expect(winners).toHaveLength(1);\n const fresh = winners[0]!;\n expect(fresh.attempt).toBe(2);\n const model = new CaptureJobModel(db);\n const leaseBefore = (await model.get(attemptRowId(fresh.jobId, 2)))?.visibleAt;\n expect(await first.extend(stale!)).toBe(\"lost\");\n expect(await first.inspect(stale!)).toBe(\"lost\");\n await first.nack(stale!, { requeue: true, error: \"stale\" });\n const after = await model.get(fresh.jobId);\n expect(after).toMatchObject({ status: \"running\", attempt: 2 });\n expect((await model.get(attemptRowId(fresh.jobId, 2)))?.visibleAt).toBe(leaseBefore);\n expect((await model.get(attemptRowId(fresh.jobId, 1)))?.status).toBe(\"expired\");\n expect(await second.extend(fresh)).toBe(\"ok\");\n await second.ack(fresh);\n // A late finish by the stale holder costs a duplicate run, never a wrong result.\n await first.ack(stale!);\n expect((await second.status(buildId))?.status).toBe(\"completed\");\n },\n timeoutMs,\n );\n}\n\nfunction retryCases(makeDb: SharedDatabaseFactory, size: number, timeoutMs?: number): void {\n it(\n \"fails a job on any instance once its retries are spent\",\n async () => {\n const { queues, advance, runId } = await fleet(makeDb, size);\n const buildId = `${runId}-retry`;\n await queues[0]!.enqueue({ buildId });\n for (let attempt = 1; attempt <= RETRIES + 1; attempt += 1) {\n const queue = queues[attempt % size]!;\n // oxlint-disable-next-line eslint/no-await-in-loop -- attempts are sequential\n const job = await claimOn(queue, buildId);\n expect(job?.attempt).toBe(attempt);\n // oxlint-disable-next-line eslint/no-await-in-loop -- see above\n await queue.nack(job!, { requeue: true, delayMs: 1000, error: `boom ${String(attempt)}` });\n advance(1001);\n }\n expect(await claimOn(queues[0]!, buildId)).toBeNull();\n expect(await queues[2 % size]!.status(buildId)).toMatchObject({\n status: \"failed\",\n error: `boom ${String(RETRIES + 1)}`,\n });\n },\n timeoutMs,\n );\n\n it(\n \"fails a job whose every attempt lost its lease\",\n async () => {\n const { queues, advance, runId } = await fleet(makeDb, size);\n const buildId = `${runId}-hung`;\n await queues[0]!.enqueue({ buildId });\n for (let attempt = 1; attempt <= RETRIES + 1; attempt += 1) {\n // oxlint-disable-next-line eslint/no-await-in-loop -- attempts are sequential\n const job = await claimOn(queues[attempt % size]!, buildId);\n expect(job?.attempt).toBe(attempt);\n advance(LEASE_MS + 1);\n }\n expect(await claimOn(queues[0]!, buildId)).toBeNull();\n expect(await queues[1]!.status(buildId)).toMatchObject({\n status: \"failed\",\n error: LEASE_EXHAUSTED_ERROR,\n });\n },\n timeoutMs,\n );\n}\n\nfunction cancelAndKindCases(makeDb: SharedDatabaseFactory, size: number, timeoutMs?: number): void {\n it(\n \"lets one instance cancel a job another instance holds\",\n async () => {\n const { queues, runId } = await fleet(makeDb, size);\n const [holder, other] = [queues[0]!, queues[1]!];\n const buildId = `${runId}-cancel`;\n await other.enqueue({ buildId });\n const job = await claimOn(holder, buildId);\n expect(await holder.inspect(job!)).toBe(\"ok\");\n expect(await other.cancel(buildId)).toBe(true);\n expect(await holder.inspect(job!)).toBe(\"cancelled\");\n expect(await holder.extend(job!)).toBe(\"cancelled\");\n await holder.nack(job!, { requeue: false, error: \"cancelled\" });\n expect((await other.status(buildId))?.status).toBe(\"cancelled\");\n expect(await claimOn(other, buildId)).toBeNull();\n },\n timeoutMs,\n );\n\n it(\n \"keeps job kinds independent\",\n async () => {\n const shared = await makeDb();\n const reuse = (): DatabaseAdapter => shared;\n const capture = await fleet(reuse, 2, [\"capture\"]);\n const publish = await fleet(reuse, 2, [\"publish\"]);\n const buildId = `${capture.runId}-kinds`;\n await capture.queues[0]!.enqueue({ buildId });\n await publish.queues[0]!.enqueueJob({ kind: \"publish\", buildId, data: { zip: \"k\" } });\n const published = await claimOn(publish.queues[1]!, buildId);\n expect(published).toMatchObject({ kind: \"publish\", data: { zip: \"k\" } });\n const captured = await claimOn(capture.queues[1]!, buildId);\n expect(captured?.kind).toBe(\"capture\");\n expect(await claimOn(publish.queues[0]!, buildId)).toBeNull();\n await publish.queues[1]!.ack(published!);\n await capture.queues[1]!.ack(captured!);\n expect((await capture.queues[0]!.status(buildId))?.status).toBe(\"completed\");\n },\n timeoutMs,\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import __tsdown_shims_path from 'node:path';
|
|
2
2
|
import __tsdown_shims_url from 'node:url';
|
|
3
3
|
import { CaptureRunner } from "../adapter/capture-runner.mjs";
|
|
4
|
-
//#region src/test-helpers/runner-contract.d.ts
|
|
5
4
|
/** Build the adapter under test (sync or async factories both work). */
|
|
6
5
|
type RunnerFactory = () => CaptureRunner | Promise<CaptureRunner>;
|
|
7
6
|
/** Run the runner contract against a factory. */
|
|
8
7
|
declare function runnerContractSuite(label: string, make: RunnerFactory): void;
|
|
9
|
-
//#endregion
|
|
10
8
|
export { RunnerFactory, runnerContractSuite };
|
|
11
9
|
//# sourceMappingURL=runner-contract.d.mts.map
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import "node:path";
|
|
2
2
|
import "node:url";
|
|
3
3
|
import.meta.url;
|
|
4
|
+
import { isCaptureTarget } from "../adapters/capture-target.mjs";
|
|
4
5
|
import { describe, expect, it } from "vitest";
|
|
5
|
-
|
|
6
|
+
// src/test-helpers/runner-contract.ts
|
|
6
7
|
/**
|
|
7
8
|
* Published runner contract suite for first- and third-party adapters.
|
|
8
9
|
*
|
|
@@ -19,6 +20,16 @@ function runnerContractSuite(label, make) {
|
|
|
19
20
|
expect(runner.metadata.category).toBe("capture-runner");
|
|
20
21
|
expect(runner.metadata.kind.length).toBeGreaterThan(0);
|
|
21
22
|
});
|
|
23
|
+
it("lists its targets as unique, valid capture targets, default first", async () => {
|
|
24
|
+
const targets = (await make()).metadata.targets;
|
|
25
|
+
if (targets === void 0) return;
|
|
26
|
+
expect(targets.length).toBeGreaterThan(0);
|
|
27
|
+
for (const entry of targets) {
|
|
28
|
+
expect(isCaptureTarget(entry.target), entry.target).toBe(true);
|
|
29
|
+
expect(["loopback", "url"]).toContain(entry.reach);
|
|
30
|
+
}
|
|
31
|
+
expect(new Set(targets.map((entry) => entry.target)).size).toBe(targets.length);
|
|
32
|
+
});
|
|
22
33
|
it("exposes render and cancel", async () => {
|
|
23
34
|
const runner = await make();
|
|
24
35
|
expect(typeof runner.render).toBe("function");
|
|
@@ -26,7 +37,6 @@ function runnerContractSuite(label, make) {
|
|
|
26
37
|
});
|
|
27
38
|
});
|
|
28
39
|
}
|
|
29
|
-
//#endregion
|
|
30
40
|
export { runnerContractSuite };
|
|
31
41
|
|
|
32
42
|
//# sourceMappingURL=runner-contract.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runner-contract.mjs","names":[],"sources":["../../src/test-helpers/runner-contract.ts"],"sourcesContent":["/**\n * Published runner contract suite for first- and third-party adapters.\n *\n * Structural conformance (identity, render/cancel presence). Behavioral\n * rendering stays per-package behind real or mocked browsers — the\n * orchestrator owns timeouts, byte guards, and failure caps, so runners\n * only promise to return PNG buffers and honor `cancel`.\n */\nimport { describe, expect, it } from \"vitest\";\nimport type { CaptureRunner } from \"../adapters/capture-runner.ts\";\n\n/** Build the adapter under test (sync or async factories both work). */\nexport type RunnerFactory = () => CaptureRunner | Promise<CaptureRunner>;\n\n/** Run the runner contract against a factory. */\nexport function runnerContractSuite(label: string, make: RunnerFactory): void {\n describe(`runner contract: ${label}`, () => {\n it(\"exposes capture-runner metadata\", async () => {\n const runner = await make();\n expect(runner.metadata.category).toBe(\"capture-runner\");\n expect(runner.metadata.kind.length).toBeGreaterThan(0);\n });\n\n it(\"exposes render and cancel\", async () => {\n const runner = await make();\n expect(typeof runner.render).toBe(\"function\");\n expect(typeof runner.cancel).toBe(\"function\");\n });\n });\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"runner-contract.mjs","names":[],"sources":["../../src/test-helpers/runner-contract.ts"],"sourcesContent":["/**\n * Published runner contract suite for first- and third-party adapters.\n *\n * Structural conformance (identity, render/cancel presence). Behavioral\n * rendering stays per-package behind real or mocked browsers — the\n * orchestrator owns timeouts, byte guards, and failure caps, so runners\n * only promise to return PNG buffers and honor `cancel`.\n */\nimport { describe, expect, it } from \"vitest\";\nimport type { CaptureRunner } from \"../adapters/capture-runner.ts\";\nimport { isCaptureTarget } from \"../adapters/capture-target.ts\";\n\n/** Build the adapter under test (sync or async factories both work). */\nexport type RunnerFactory = () => CaptureRunner | Promise<CaptureRunner>;\n\n/** Run the runner contract against a factory. */\nexport function runnerContractSuite(label: string, make: RunnerFactory): void {\n describe(`runner contract: ${label}`, () => {\n it(\"exposes capture-runner metadata\", async () => {\n const runner = await make();\n expect(runner.metadata.category).toBe(\"capture-runner\");\n expect(runner.metadata.kind.length).toBeGreaterThan(0);\n });\n\n it(\"lists its targets as unique, valid capture targets, default first\", async () => {\n const targets = (await make()).metadata.targets;\n if (targets === undefined) {\n return;\n }\n expect(targets.length).toBeGreaterThan(0);\n for (const entry of targets) {\n expect(isCaptureTarget(entry.target), entry.target).toBe(true);\n expect([\"loopback\", \"url\"]).toContain(entry.reach);\n }\n expect(new Set(targets.map((entry) => entry.target)).size).toBe(targets.length);\n });\n\n it(\"exposes render and cancel\", async () => {\n const runner = await make();\n expect(typeof runner.render).toBe(\"function\");\n expect(typeof runner.cancel).toBe(\"function\");\n });\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;AAgBA,SAAgB,oBAAoB,OAAe,MAA2B;CAC5E,SAAS,oBAAoB,eAAe;EAC1C,GAAG,mCAAmC,YAAY;GAChD,MAAM,SAAS,MAAM,KAAK;GAC1B,OAAO,OAAO,SAAS,QAAQ,CAAC,CAAC,KAAK,gBAAgB;GACtD,OAAO,OAAO,SAAS,KAAK,MAAM,CAAC,CAAC,gBAAgB,CAAC;EACvD,CAAC;EAED,GAAG,qEAAqE,YAAY;GAClF,MAAM,WAAW,MAAM,KAAK,EAAA,CAAG,SAAS;GACxC,IAAI,YAAY,KAAA,GACd;GAEF,OAAO,QAAQ,MAAM,CAAC,CAAC,gBAAgB,CAAC;GACxC,KAAK,MAAM,SAAS,SAAS;IAC3B,OAAO,gBAAgB,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,CAAC,KAAK,IAAI;IAC7D,OAAO,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,UAAU,MAAM,KAAK;GACnD;GACA,OAAO,IAAI,IAAI,QAAQ,KAAK,UAAU,MAAM,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,QAAQ,MAAM;EAChF,CAAC;EAED,GAAG,6BAA6B,YAAY;GAC1C,MAAM,SAAS,MAAM,KAAK;GAC1B,OAAO,OAAO,OAAO,MAAM,CAAC,CAAC,KAAK,UAAU;GAC5C,OAAO,OAAO,OAAO,MAAM,CAAC,CAAC,KAAK,UAAU;EAC9C,CAAC;CACH,CAAC;AACH"}
|
|
@@ -2,7 +2,7 @@ import "node:path";
|
|
|
2
2
|
import "node:url";
|
|
3
3
|
import.meta.url;
|
|
4
4
|
import { getTableColumns } from "drizzle-orm";
|
|
5
|
-
|
|
5
|
+
// src/test-helpers/sql-chunks.ts
|
|
6
6
|
/** Flatten the string parts of a drizzle SQL chunk into a single string. */
|
|
7
7
|
function textOf(chunk) {
|
|
8
8
|
if (chunk === void 0 || !Array.isArray(chunk.value) || !chunk.value.every((part) => typeof part === "string")) return;
|
|
@@ -24,7 +24,11 @@ function matchChunk(chunks, row, table) {
|
|
|
24
24
|
const text = chunks.map((c) => textOf(c) ?? "").join("");
|
|
25
25
|
if (/is not null/iu.test(text)) return isNullMatches(chunks, row, table, true);
|
|
26
26
|
if (/is null/iu.test(text)) return isNullMatches(chunks, row, table, false);
|
|
27
|
+
if (text.includes(" <> ")) return !eqMatches(chunks, row, table);
|
|
28
|
+
if (text.includes(" like ")) return likeMatches(chunks, row, table);
|
|
27
29
|
if (text.includes(" in ")) return inArrayMatches(chunks, row, table);
|
|
30
|
+
if (text.includes(" >= ")) return !ltMatches(chunks, row, table);
|
|
31
|
+
if (text.includes(" <= ")) return lteMatches(chunks, row, table);
|
|
28
32
|
if (text.includes(" < ")) return ltMatches(chunks, row, table);
|
|
29
33
|
return eqMatches(chunks, row, table);
|
|
30
34
|
}
|
|
@@ -45,6 +49,21 @@ function eqMatches(chunks, row, table) {
|
|
|
45
49
|
if (columnName === void 0) return false;
|
|
46
50
|
return row[columnKey(table, columnName)] === argument;
|
|
47
51
|
}
|
|
52
|
+
/** Match `column like 'pattern'`: `%` is any run of characters, `_` any one; everything else literal. */
|
|
53
|
+
function likeMatches(chunks, row, table) {
|
|
54
|
+
let columnName;
|
|
55
|
+
let pattern;
|
|
56
|
+
for (const chunk of chunks) if (typeof chunk === "string") pattern = chunk;
|
|
57
|
+
else if (chunk !== null && typeof chunk === "object") {
|
|
58
|
+
const node = chunk;
|
|
59
|
+
if (typeof node.name === "string" && node.table !== void 0) columnName = node.name;
|
|
60
|
+
else if ("brand" in node) pattern = node.value;
|
|
61
|
+
}
|
|
62
|
+
const cell = columnName === void 0 ? void 0 : row[columnKey(table, columnName)];
|
|
63
|
+
if (typeof cell !== "string" || typeof pattern !== "string") return false;
|
|
64
|
+
const source = pattern.replaceAll(/[.*+?^${}()|[\]\\]/gu, String.raw`\$&`);
|
|
65
|
+
return new RegExp(`^${source.replaceAll("%", ".*").replaceAll("_", ".")}$`, "su").test(cell);
|
|
66
|
+
}
|
|
48
67
|
function inArrayMatches(chunks, row, table) {
|
|
49
68
|
let columnName;
|
|
50
69
|
const values = [];
|
|
@@ -78,6 +97,11 @@ function inArrayMatches(chunks, row, table) {
|
|
|
78
97
|
const key = columnKey(table, columnName);
|
|
79
98
|
return values.includes(row[key]);
|
|
80
99
|
}
|
|
100
|
+
/** `column <= value`: the cell is present and not greater than the argument. */
|
|
101
|
+
function lteMatches(chunks, row, table) {
|
|
102
|
+
if (ltMatches(chunks, row, table)) return true;
|
|
103
|
+
return eqMatches(chunks, row, table);
|
|
104
|
+
}
|
|
81
105
|
function ltMatches(chunks, row, table) {
|
|
82
106
|
let columnName;
|
|
83
107
|
let argument;
|
|
@@ -132,7 +156,6 @@ function columnKey(table, dbName) {
|
|
|
132
156
|
for (const [property, column] of Object.entries(getTableColumns(table))) if (column.name === dbName) return property;
|
|
133
157
|
return dbName;
|
|
134
158
|
}
|
|
135
|
-
//#endregion
|
|
136
159
|
export { orderRows, whereMatches };
|
|
137
160
|
|
|
138
161
|
//# sourceMappingURL=sql-chunks.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sql-chunks.mjs","names":[],"sources":["../../src/test-helpers/sql-chunks.ts"],"sourcesContent":["import { getTableColumns, type SQL, type Table } from \"drizzle-orm\";\n\n/* eslint-disable require-await, no-unnecessary-type-assertion, no-unnecessary-type-parameters, non-nullable-type-assertion-style */\n\n/** A drizzle SQL chunk inspected for matching and ordering. */\nexport interface SqlChunk {\n value?: unknown;\n name?: string;\n table?: unknown;\n brand?: unknown;\n queryChunks?: SqlChunk[];\n}\n\n/** Flatten the string parts of a drizzle SQL chunk into a single string. */\nfunction textOf(chunk: SqlChunk | undefined): string | undefined {\n if (\n chunk === undefined ||\n !Array.isArray(chunk.value) ||\n !chunk.value.every((part) => typeof part === \"string\")\n ) {\n return undefined;\n }\n return chunk.value.join(\"\");\n}\n\n/** Test whether an in-memory row satisfies a drizzle where-clause. */\nexport function whereMatches(where: SQL, row: Record<string, unknown>, table: Table): boolean {\n let current = where.queryChunks as unknown as SqlChunk[];\n let wrapped = current[1]?.queryChunks;\n while (wrapped !== undefined && textOf(current[0]) === \"(\" && textOf(current.at(-1)) === \")\") {\n current = wrapped;\n wrapped = current[1]?.queryChunks;\n }\n // Drizzle's `and(singleCondition)` emits the condition directly, without\n // the enclosing parens produced for two or more clauses, so unwrap it.\n if (current.length === 1 && current[0]?.queryChunks !== undefined) {\n current = current[0].queryChunks as unknown as SqlChunk[];\n }\n if (current.some((chunk) => textOf(chunk)?.trim() === \"and\")) {\n return current\n .filter((chunk) => textOf(chunk) === undefined)\n .every((chunk) => matchChunk(chunk.queryChunks ?? [], row, table));\n }\n return matchChunk(current, row, table);\n}\n\nfunction matchChunk(chunks: SqlChunk[], row: Record<string, unknown>, table: Table): boolean {\n const text = chunks.map((c) => textOf(c) ?? \"\").join(\"\");\n if (/is not null/iu.test(text)) {\n return isNullMatches(chunks, row, table, true);\n }\n if (/is null/iu.test(text)) {\n return isNullMatches(chunks, row, table, false);\n }\n if (text.includes(\" in \")) {\n return inArrayMatches(chunks, row, table);\n }\n if (text.includes(\" < \")) {\n return ltMatches(chunks, row, table);\n }\n return eqMatches(chunks, row, table);\n}\n\n/** Match `is null` / `is not null` predicates (no value argument). */\nfunction isNullMatches(\n chunks: SqlChunk[],\n row: Record<string, unknown>,\n table: Table,\n negated: boolean,\n): boolean {\n let columnName: string | undefined;\n for (const chunk of chunks) {\n if (typeof chunk.name === \"string\" && chunk.table !== undefined) {\n columnName = chunk.name;\n }\n }\n if (columnName === undefined) {\n return false;\n }\n const cell = row[columnKey(table, columnName)];\n const missing = cell === null || cell === undefined;\n return negated ? !missing : missing;\n}\n\nfunction eqMatches(chunks: SqlChunk[], row: Record<string, unknown>, table: Table): boolean {\n let columnName: string | undefined;\n let argument: unknown;\n for (const chunk of chunks) {\n if (typeof chunk.name === \"string\" && chunk.table !== undefined) {\n columnName = chunk.name;\n } else if (\"brand\" in chunk) {\n argument = chunk.value;\n }\n }\n if (columnName === undefined) {\n return false;\n }\n return row[columnKey(table, columnName)] === argument;\n}\n\n/* eslint-disable complexity, max-depth -- handles drizzle's varied chunk shapes */\nfunction inArrayMatches(chunks: SqlChunk[], row: Record<string, unknown>, table: Table): boolean {\n let columnName: string | undefined;\n const values: unknown[] = [];\n const collect = (nodes: unknown[]): void => {\n for (const raw of nodes as SqlChunk[]) {\n const chunk = raw as SqlChunk & Record<string, unknown>;\n // Handle array-like chunk (inArray values stored as array of chunks)\n if (\n Array.isArray(chunk) ||\n (chunk && typeof chunk === \"object\" && Object.keys(chunk).every((k) => /^\\d+$/u.test(k)))\n ) {\n collect(Object.values(chunk as unknown as Record<string, unknown>) as unknown[]);\n continue;\n }\n if (typeof chunk.name === \"string\" && chunk.table !== undefined) {\n columnName = chunk.name as string;\n }\n if (\"brand\" in chunk && (chunk as Record<string, unknown>)[\"value\"] !== undefined) {\n const val = (chunk as Record<string, unknown>)[\"value\"];\n if (Array.isArray(val)) {\n values.push(...(val as unknown[]));\n } else {\n values.push(val);\n }\n }\n if (chunk.queryChunks) {\n collect(chunk.queryChunks as unknown[]);\n }\n const val = (chunk as Record<string, unknown>)[\"value\"];\n if (Array.isArray(val)) {\n for (const item of val as unknown[]) {\n if (\n item &&\n typeof item === \"object\" &&\n \"queryChunks\" in (item as Record<string, unknown>)\n ) {\n collect(((item as Record<string, unknown>)[\"queryChunks\"] as unknown[]) ?? []);\n }\n if (item && typeof item === \"object\" && \"value\" in (item as Record<string, unknown>)) {\n const iv = (item as Record<string, unknown>)[\"value\"];\n if (iv !== undefined) {\n values.push(iv);\n }\n }\n if (typeof item === \"string\") {\n values.push(item);\n }\n }\n }\n }\n };\n collect(chunks as unknown[]);\n if (columnName === undefined) {\n return false;\n }\n const key = columnKey(table, columnName);\n return values.includes(row[key]);\n}\n/* eslint-enable complexity, max-depth */\n\nfunction ltMatches(chunks: SqlChunk[], row: Record<string, unknown>, table: Table): boolean {\n let columnName: string | undefined;\n let argument: unknown;\n for (const chunk of chunks) {\n if (typeof chunk.name === \"string\" && chunk.table !== undefined) {\n columnName = chunk.name;\n } else if (\"brand\" in chunk) {\n argument = chunk.value;\n }\n }\n if (columnName === undefined) {\n return false;\n }\n const key = columnKey(table, columnName);\n const cell = row[key];\n if (typeof cell === \"string\" && typeof argument === \"string\") {\n return cell < argument;\n }\n if (typeof cell === \"number\" && typeof argument === \"number\") {\n return cell < argument;\n }\n return String(cell) < String(argument);\n}\n\nfunction compareCells(left: unknown, right: unknown): number {\n if (typeof left === \"string\" && typeof right === \"string\") {\n if (left === right) {\n return 0;\n }\n return left < right ? -1 : 1;\n }\n if (typeof left === \"number\" && typeof right === \"number\") {\n return left - right;\n }\n const leftText = String(left);\n const rightText = String(right);\n if (leftText === rightText) {\n return 0;\n }\n return leftText < rightText ? -1 : 1;\n}\n\n/** Collect every raw string part of a drizzle SQL chunk tree. */\nfunction sqlText(nodes: SqlChunk[]): string {\n return nodes\n .map((node) => {\n const own = textOf(node) ?? \"\";\n const nested = node.queryChunks ? sqlText(node.queryChunks as unknown as SqlChunk[]) : \"\";\n return own + nested;\n })\n .join(\"\");\n}\n\n/** Find the ordered column reference in a drizzle order-by expression. */\nfunction findOrderColumn(nodes: SqlChunk[]): string | undefined {\n for (const node of nodes) {\n if (typeof node.name === \"string\" && node.table !== undefined) {\n return node.name;\n }\n if (node.queryChunks) {\n const nested = findOrderColumn(node.queryChunks as unknown as SqlChunk[]);\n if (nested !== undefined) {\n return nested;\n }\n }\n }\n return undefined;\n}\n\n/** Sort rows per a drizzle order-by expression (single-column asc/desc). */\nexport function orderRows(rows: unknown[], orderBy: SQL, table: Table): unknown[] {\n const chunks = orderBy.queryChunks as unknown as SqlChunk[];\n const columnName = findOrderColumn(chunks);\n if (columnName === undefined) {\n return rows;\n }\n const key = columnKey(table, columnName);\n const descending = /desc/iu.test(sqlText(chunks));\n return rows.toSorted((left, right) => {\n const order = compareCells(\n (left as Record<string, unknown>)[key],\n (right as Record<string, unknown>)[key],\n );\n return descending ? -order : order;\n });\n}\n\nfunction columnKey(table: Table, dbName: string): string {\n for (const [property, column] of Object.entries(getTableColumns(table))) {\n if (column.name === dbName) {\n return property;\n }\n }\n return dbName;\n}\n/* eslint-enable require-await, no-unnecessary-type-assertion, no-unnecessary-type-parameters, non-nullable-type-assertion-style */\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"sql-chunks.mjs","names":[],"sources":["../../src/test-helpers/sql-chunks.ts"],"sourcesContent":["import { getTableColumns, type SQL, type Table } from \"drizzle-orm\";\n\n/* eslint-disable require-await, no-unnecessary-type-assertion, no-unnecessary-type-parameters, non-nullable-type-assertion-style */\n\n/** A drizzle SQL chunk inspected for matching and ordering. */\nexport interface SqlChunk {\n value?: unknown;\n name?: string;\n table?: unknown;\n brand?: unknown;\n queryChunks?: SqlChunk[];\n}\n\n/** Flatten the string parts of a drizzle SQL chunk into a single string. */\nfunction textOf(chunk: SqlChunk | undefined): string | undefined {\n if (\n chunk === undefined ||\n !Array.isArray(chunk.value) ||\n !chunk.value.every((part) => typeof part === \"string\")\n ) {\n return undefined;\n }\n return chunk.value.join(\"\");\n}\n\n/** Test whether an in-memory row satisfies a drizzle where-clause. */\nexport function whereMatches(where: SQL, row: Record<string, unknown>, table: Table): boolean {\n let current = where.queryChunks as unknown as SqlChunk[];\n let wrapped = current[1]?.queryChunks;\n while (wrapped !== undefined && textOf(current[0]) === \"(\" && textOf(current.at(-1)) === \")\") {\n current = wrapped;\n wrapped = current[1]?.queryChunks;\n }\n // Drizzle's `and(singleCondition)` emits the condition directly, without\n // the enclosing parens produced for two or more clauses, so unwrap it.\n if (current.length === 1 && current[0]?.queryChunks !== undefined) {\n current = current[0].queryChunks as unknown as SqlChunk[];\n }\n if (current.some((chunk) => textOf(chunk)?.trim() === \"and\")) {\n return current\n .filter((chunk) => textOf(chunk) === undefined)\n .every((chunk) => matchChunk(chunk.queryChunks ?? [], row, table));\n }\n return matchChunk(current, row, table);\n}\n\nfunction matchChunk(chunks: SqlChunk[], row: Record<string, unknown>, table: Table): boolean {\n const text = chunks.map((c) => textOf(c) ?? \"\").join(\"\");\n if (/is not null/iu.test(text)) {\n return isNullMatches(chunks, row, table, true);\n }\n if (/is null/iu.test(text)) {\n return isNullMatches(chunks, row, table, false);\n }\n if (text.includes(\" <> \")) {\n return !eqMatches(chunks, row, table);\n }\n if (text.includes(\" like \")) {\n return likeMatches(chunks, row, table);\n }\n if (text.includes(\" in \")) {\n return inArrayMatches(chunks, row, table);\n }\n if (text.includes(\" >= \")) {\n return !ltMatches(chunks, row, table);\n }\n if (text.includes(\" <= \")) {\n return lteMatches(chunks, row, table);\n }\n if (text.includes(\" < \")) {\n return ltMatches(chunks, row, table);\n }\n return eqMatches(chunks, row, table);\n}\n\n/** Match `is null` / `is not null` predicates (no value argument). */\nfunction isNullMatches(\n chunks: SqlChunk[],\n row: Record<string, unknown>,\n table: Table,\n negated: boolean,\n): boolean {\n let columnName: string | undefined;\n for (const chunk of chunks) {\n if (typeof chunk.name === \"string\" && chunk.table !== undefined) {\n columnName = chunk.name;\n }\n }\n if (columnName === undefined) {\n return false;\n }\n const cell = row[columnKey(table, columnName)];\n const missing = cell === null || cell === undefined;\n return negated ? !missing : missing;\n}\n\nfunction eqMatches(chunks: SqlChunk[], row: Record<string, unknown>, table: Table): boolean {\n let columnName: string | undefined;\n let argument: unknown;\n for (const chunk of chunks) {\n if (typeof chunk.name === \"string\" && chunk.table !== undefined) {\n columnName = chunk.name;\n } else if (\"brand\" in chunk) {\n argument = chunk.value;\n }\n }\n if (columnName === undefined) {\n return false;\n }\n return row[columnKey(table, columnName)] === argument;\n}\n\n/** Match `column like 'pattern'`: `%` is any run of characters, `_` any one; everything else literal. */\nfunction likeMatches(chunks: SqlChunk[], row: Record<string, unknown>, table: Table): boolean {\n let columnName: string | undefined;\n let pattern: unknown;\n for (const chunk of chunks as unknown[]) {\n if (typeof chunk === \"string\") {\n pattern = chunk;\n } else if (chunk !== null && typeof chunk === \"object\") {\n const node = chunk as SqlChunk;\n if (typeof node.name === \"string\" && node.table !== undefined) {\n columnName = node.name;\n } else if (\"brand\" in node) {\n pattern = node.value;\n }\n }\n }\n const cell = columnName === undefined ? undefined : row[columnKey(table, columnName)];\n if (typeof cell !== \"string\" || typeof pattern !== \"string\") {\n return false;\n }\n const source = pattern.replaceAll(/[.*+?^${}()|[\\]\\\\]/gu, String.raw`\\$&`);\n return new RegExp(`^${source.replaceAll(\"%\", \".*\").replaceAll(\"_\", \".\")}$`, \"su\").test(cell);\n}\n\n/* eslint-disable complexity, max-depth -- handles drizzle's varied chunk shapes */\nfunction inArrayMatches(chunks: SqlChunk[], row: Record<string, unknown>, table: Table): boolean {\n let columnName: string | undefined;\n const values: unknown[] = [];\n const collect = (nodes: unknown[]): void => {\n for (const raw of nodes as SqlChunk[]) {\n const chunk = raw as SqlChunk & Record<string, unknown>;\n // Handle array-like chunk (inArray values stored as array of chunks)\n if (\n Array.isArray(chunk) ||\n (chunk && typeof chunk === \"object\" && Object.keys(chunk).every((k) => /^\\d+$/u.test(k)))\n ) {\n collect(Object.values(chunk as unknown as Record<string, unknown>) as unknown[]);\n continue;\n }\n if (typeof chunk.name === \"string\" && chunk.table !== undefined) {\n columnName = chunk.name as string;\n }\n if (\"brand\" in chunk && (chunk as Record<string, unknown>)[\"value\"] !== undefined) {\n const val = (chunk as Record<string, unknown>)[\"value\"];\n if (Array.isArray(val)) {\n values.push(...(val as unknown[]));\n } else {\n values.push(val);\n }\n }\n if (chunk.queryChunks) {\n collect(chunk.queryChunks as unknown[]);\n }\n const val = (chunk as Record<string, unknown>)[\"value\"];\n if (Array.isArray(val)) {\n for (const item of val as unknown[]) {\n if (\n item &&\n typeof item === \"object\" &&\n \"queryChunks\" in (item as Record<string, unknown>)\n ) {\n collect(((item as Record<string, unknown>)[\"queryChunks\"] as unknown[]) ?? []);\n }\n if (item && typeof item === \"object\" && \"value\" in (item as Record<string, unknown>)) {\n const iv = (item as Record<string, unknown>)[\"value\"];\n if (iv !== undefined) {\n values.push(iv);\n }\n }\n if (typeof item === \"string\") {\n values.push(item);\n }\n }\n }\n }\n };\n collect(chunks as unknown[]);\n if (columnName === undefined) {\n return false;\n }\n const key = columnKey(table, columnName);\n return values.includes(row[key]);\n}\n/* eslint-enable complexity, max-depth */\n\n/** `column <= value`: the cell is present and not greater than the argument. */\nfunction lteMatches(chunks: SqlChunk[], row: Record<string, unknown>, table: Table): boolean {\n if (ltMatches(chunks, row, table)) {\n return true;\n }\n return eqMatches(chunks, row, table);\n}\n\nfunction ltMatches(chunks: SqlChunk[], row: Record<string, unknown>, table: Table): boolean {\n let columnName: string | undefined;\n let argument: unknown;\n for (const chunk of chunks) {\n if (typeof chunk.name === \"string\" && chunk.table !== undefined) {\n columnName = chunk.name;\n } else if (\"brand\" in chunk) {\n argument = chunk.value;\n }\n }\n if (columnName === undefined) {\n return false;\n }\n const key = columnKey(table, columnName);\n const cell = row[key];\n if (typeof cell === \"string\" && typeof argument === \"string\") {\n return cell < argument;\n }\n if (typeof cell === \"number\" && typeof argument === \"number\") {\n return cell < argument;\n }\n return String(cell) < String(argument);\n}\n\nfunction compareCells(left: unknown, right: unknown): number {\n if (typeof left === \"string\" && typeof right === \"string\") {\n if (left === right) {\n return 0;\n }\n return left < right ? -1 : 1;\n }\n if (typeof left === \"number\" && typeof right === \"number\") {\n return left - right;\n }\n const leftText = String(left);\n const rightText = String(right);\n if (leftText === rightText) {\n return 0;\n }\n return leftText < rightText ? -1 : 1;\n}\n\n/** Collect every raw string part of a drizzle SQL chunk tree. */\nfunction sqlText(nodes: SqlChunk[]): string {\n return nodes\n .map((node) => {\n const own = textOf(node) ?? \"\";\n const nested = node.queryChunks ? sqlText(node.queryChunks as unknown as SqlChunk[]) : \"\";\n return own + nested;\n })\n .join(\"\");\n}\n\n/** Find the ordered column reference in a drizzle order-by expression. */\nfunction findOrderColumn(nodes: SqlChunk[]): string | undefined {\n for (const node of nodes) {\n if (typeof node.name === \"string\" && node.table !== undefined) {\n return node.name;\n }\n if (node.queryChunks) {\n const nested = findOrderColumn(node.queryChunks as unknown as SqlChunk[]);\n if (nested !== undefined) {\n return nested;\n }\n }\n }\n return undefined;\n}\n\n/** Sort rows per a drizzle order-by expression (single-column asc/desc). */\nexport function orderRows(rows: unknown[], orderBy: SQL, table: Table): unknown[] {\n const chunks = orderBy.queryChunks as unknown as SqlChunk[];\n const columnName = findOrderColumn(chunks);\n if (columnName === undefined) {\n return rows;\n }\n const key = columnKey(table, columnName);\n const descending = /desc/iu.test(sqlText(chunks));\n return rows.toSorted((left, right) => {\n const order = compareCells(\n (left as Record<string, unknown>)[key],\n (right as Record<string, unknown>)[key],\n );\n return descending ? -order : order;\n });\n}\n\nfunction columnKey(table: Table, dbName: string): string {\n for (const [property, column] of Object.entries(getTableColumns(table))) {\n if (column.name === dbName) {\n return property;\n }\n }\n return dbName;\n}\n/* eslint-enable require-await, no-unnecessary-type-assertion, no-unnecessary-type-parameters, non-nullable-type-assertion-style */\n"],"mappings":"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"}
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import __tsdown_shims_path from 'node:path';
|
|
2
2
|
import __tsdown_shims_url from 'node:url';
|
|
3
3
|
import { StorageAdapter } from "../adapter/storage.mjs";
|
|
4
|
-
//#region src/test-helpers/storage-contract.d.ts
|
|
5
4
|
/** Build the adapter under test (sync or async factories both work). */
|
|
6
5
|
type StorageFactory = () => StorageAdapter | Promise<StorageAdapter>;
|
|
7
6
|
/** Run the full storage contract against a factory. */
|
|
8
7
|
declare function storageContractSuite(label: string, make: StorageFactory): void;
|
|
9
|
-
//#endregion
|
|
10
8
|
export { StorageFactory, storageContractSuite };
|
|
11
9
|
//# sourceMappingURL=storage-contract.d.mts.map
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import "node:path";
|
|
2
2
|
import "node:url";
|
|
3
3
|
import.meta.url;
|
|
4
|
-
import { describe, expect, it } from "vitest";
|
|
5
4
|
import { Readable } from "node:stream";
|
|
6
|
-
|
|
5
|
+
import { describe, expect, it } from "vitest";
|
|
6
|
+
// src/test-helpers/storage-contract.ts
|
|
7
7
|
/**
|
|
8
8
|
* Published storage contract suite for first- and third-party adapters.
|
|
9
9
|
*
|
|
@@ -81,7 +81,6 @@ function storageContractSuite(label, make) {
|
|
|
81
81
|
});
|
|
82
82
|
});
|
|
83
83
|
}
|
|
84
|
-
//#endregion
|
|
85
84
|
export { storageContractSuite };
|
|
86
85
|
|
|
87
86
|
//# sourceMappingURL=storage-contract.mjs.map
|
package/dist/test-helpers.d.mts
CHANGED
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
import __tsdown_shims_path from 'node:path';
|
|
2
2
|
import __tsdown_shims_url from 'node:url';
|
|
3
|
+
import { Deferred, Latch, ReleaseQueue, createLatch, createReleaseQueue, deferred } from "./test-helpers/deferred.mjs";
|
|
4
|
+
import { DesignContractSamples, designContractSuite } from "./test-helpers/design-contract.mjs";
|
|
3
5
|
import { createTestBuild, createTestProject } from "./test-helpers/create-project.mjs";
|
|
4
6
|
import { REQUIRED_TABLES, databaseContractSuite } from "./test-helpers/database-contract.mjs";
|
|
5
|
-
import {
|
|
7
|
+
import { FAKE_PNG, FakeDesign, fakeUrl, makeFakeDesign } from "./test-helpers/fake-design.mjs";
|
|
6
8
|
import { makeDatabase } from "./test-helpers/fake-database.mjs";
|
|
7
9
|
import { FakeStorage, makeStorage } from "./test-helpers/fake-storage.mjs";
|
|
8
10
|
import { stubTables } from "./test-helpers/fake-adapters.mjs";
|
|
9
|
-
import { notifierContractSuite } from "./test-helpers/notifier-contract.mjs";
|
|
10
|
-
import { queueContractSuite } from "./test-helpers/queue-contract.mjs";
|
|
11
|
+
import { notifierContractSuite, stateNotifierContractSuite } from "./test-helpers/notifier-contract.mjs";
|
|
12
|
+
import { PollableContractOptions, QueueContractOptions, QueueFactory, queueContractSuite } from "./test-helpers/queue-contract.mjs";
|
|
13
|
+
import { MultiInstanceOptions, SharedDatabaseFactory, databaseQueueMultiInstanceSuite } from "./test-helpers/queue-multi-instance.mjs";
|
|
14
|
+
import { ManualClock, createManualClock } from "./test-helpers/manual-clock.mjs";
|
|
11
15
|
import { runnerContractSuite } from "./test-helpers/runner-contract.mjs";
|
|
12
16
|
import { storageContractSuite } from "./test-helpers/storage-contract.mjs";
|
|
13
|
-
|
|
17
|
+
import { FakeStateNotifier, makeFakeStateNotifier } from "./test-helpers/fake-notifier.mjs";
|
|
18
|
+
import { BuildReferenceContractFixture, BuildReferenceContractFixtureRunner, buildReferenceContractSuite } from "./test-helpers/build-reference-contract.mjs";
|
|
19
|
+
export { type BuildReferenceContractFixture, type BuildReferenceContractFixtureRunner, type Deferred, type DesignContractSamples, FAKE_PNG, type FakeDesign, type FakeStateNotifier, type FakeStorage, type Latch, type ManualClock, type MultiInstanceOptions, type PollableContractOptions, type QueueContractOptions, type QueueFactory, REQUIRED_TABLES, type ReleaseQueue, type SharedDatabaseFactory, buildReferenceContractSuite, createLatch, createManualClock, createReleaseQueue, createTestBuild, createTestProject, databaseContractSuite, databaseQueueMultiInstanceSuite, deferred, designContractSuite, fakeUrl, makeDatabase, makeFakeDesign, makeFakeStateNotifier, makeStorage, notifierContractSuite, queueContractSuite, runnerContractSuite, stateNotifierContractSuite, storageContractSuite, stubTables };
|
package/dist/test-helpers.mjs
CHANGED
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
import "node:path";
|
|
2
2
|
import "node:url";
|
|
3
3
|
import.meta.url;
|
|
4
|
+
import { createLatch, createReleaseQueue, deferred } from "./test-helpers/deferred.mjs";
|
|
5
|
+
import { designContractSuite } from "./test-helpers/design-contract.mjs";
|
|
4
6
|
import { createTestBuild, createTestProject } from "./test-helpers/create-project.mjs";
|
|
5
7
|
import { REQUIRED_TABLES, databaseContractSuite } from "./test-helpers/database-contract.mjs";
|
|
6
|
-
import {
|
|
8
|
+
import { FAKE_PNG, fakeUrl, makeFakeDesign } from "./test-helpers/fake-design.mjs";
|
|
7
9
|
import { makeDatabase } from "./test-helpers/fake-database.mjs";
|
|
8
10
|
import { makeStorage } from "./test-helpers/fake-storage.mjs";
|
|
9
11
|
import { stubTables } from "./test-helpers/fake-adapters.mjs";
|
|
10
|
-
import { notifierContractSuite } from "./test-helpers/notifier-contract.mjs";
|
|
12
|
+
import { notifierContractSuite, stateNotifierContractSuite } from "./test-helpers/notifier-contract.mjs";
|
|
11
13
|
import { queueContractSuite } from "./test-helpers/queue-contract.mjs";
|
|
14
|
+
import { databaseQueueMultiInstanceSuite } from "./test-helpers/queue-multi-instance.mjs";
|
|
15
|
+
import { createManualClock } from "./test-helpers/manual-clock.mjs";
|
|
12
16
|
import { runnerContractSuite } from "./test-helpers/runner-contract.mjs";
|
|
13
17
|
import { storageContractSuite } from "./test-helpers/storage-contract.mjs";
|
|
14
|
-
|
|
18
|
+
import { makeFakeStateNotifier } from "./test-helpers/fake-notifier.mjs";
|
|
19
|
+
import { buildReferenceContractSuite } from "./test-helpers/build-reference-contract.mjs";
|
|
20
|
+
export { FAKE_PNG, REQUIRED_TABLES, buildReferenceContractSuite, createLatch, createManualClock, createReleaseQueue, createTestBuild, createTestProject, databaseContractSuite, databaseQueueMultiInstanceSuite, deferred, designContractSuite, fakeUrl, makeDatabase, makeFakeDesign, makeFakeStateNotifier, makeStorage, notifierContractSuite, queueContractSuite, runnerContractSuite, stateNotifierContractSuite, storageContractSuite, stubTables };
|
package/dist/tracing.mjs
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import "node:path";
|
|
2
2
|
import "node:url";
|
|
3
3
|
import.meta.url;
|
|
4
|
+
import { redactSecrets } from "./utils/redact.mjs";
|
|
4
5
|
import { SpanKind, SpanStatusCode, context, propagation, trace } from "@opentelemetry/api";
|
|
5
|
-
|
|
6
|
+
// src/tracing.ts
|
|
6
7
|
/**
|
|
7
8
|
* Internal span and propagation helpers over `@opentelemetry/api`.
|
|
8
9
|
*
|
|
@@ -15,11 +16,41 @@ import { SpanKind, SpanStatusCode, context, propagation, trace } from "@opentele
|
|
|
15
16
|
const SCOPE = "storyshelf.core";
|
|
16
17
|
/** W3C traceparent header name. */
|
|
17
18
|
const TRACEPARENT_HEADER = "traceparent";
|
|
19
|
+
/** What a span records of an error instead of its message, set by the code that knows the error. */
|
|
20
|
+
const spanMessages = /* @__PURE__ */ new WeakMap();
|
|
21
|
+
/**
|
|
22
|
+
* State what spans record as the message of `error`, instead of its own: an outbound HTTP failure
|
|
23
|
+
* is recorded as its status, never the response body it carries. The error itself is not touched,
|
|
24
|
+
* so what a caller catches is unchanged.
|
|
25
|
+
*/
|
|
26
|
+
function setSpanMessage(error, message) {
|
|
27
|
+
if (typeof error === "object" && error !== null) spanMessages.set(error, message);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* The form of a failure that may be recorded on a span: a new error of the same name whose message
|
|
31
|
+
* (the one given to {@link setSpanMessage}, else the error's own) has gone through
|
|
32
|
+
* `redactSecrets`, with the original's stack frames. Never the original, whose message, stack
|
|
33
|
+
* header or `cause` can repeat a URL, a token or a response body. A thrown non-error is stringified
|
|
34
|
+
* and redacted. This is the one way core records an exception on a span.
|
|
35
|
+
*/
|
|
36
|
+
function spanException(error) {
|
|
37
|
+
if (!(error instanceof Error)) return redactSecrets(String(error));
|
|
38
|
+
const message = redactSecrets(spanMessages.get(error) ?? error.message);
|
|
39
|
+
const copy = new Error(message);
|
|
40
|
+
copy.name = error.name;
|
|
41
|
+
copy.stack = `${error.name}: ${message}${redactSecrets(framesOf(error))}`;
|
|
42
|
+
return copy;
|
|
43
|
+
}
|
|
44
|
+
/** The stack after its `Name: message` header, which may hold what the span must not record. */
|
|
45
|
+
function framesOf(error) {
|
|
46
|
+
const header = `${error.name}: ${error.message}`;
|
|
47
|
+
return error.stack?.startsWith(header) ? error.stack.slice(header.length) : "";
|
|
48
|
+
}
|
|
18
49
|
/**
|
|
19
50
|
* Run `fn` inside an internal span.
|
|
20
51
|
*
|
|
21
|
-
* On throw the span records the exception, is marked
|
|
22
|
-
* is rethrown — mirroring the pino `{ err }` convention.
|
|
52
|
+
* On throw the span records a redacted copy of the exception ({@link spanException}), is marked
|
|
53
|
+
* ERROR, and the original error is rethrown untouched — mirroring the pino `{ err }` convention.
|
|
23
54
|
*
|
|
24
55
|
* @param name - Span name (`domain.operation`, never a high-cardinality value).
|
|
25
56
|
* @param fn - Work to run with the span active.
|
|
@@ -35,7 +66,7 @@ async function withSpan(name, fn, attributes, parent) {
|
|
|
35
66
|
try {
|
|
36
67
|
return await fn(span);
|
|
37
68
|
} catch (error) {
|
|
38
|
-
span.recordException(
|
|
69
|
+
span.recordException(spanException(error));
|
|
39
70
|
span.setStatus({ code: SpanStatusCode.ERROR });
|
|
40
71
|
throw error;
|
|
41
72
|
} finally {
|
|
@@ -57,7 +88,6 @@ function parentContext(traceparent) {
|
|
|
57
88
|
function injectTraceContext(headers) {
|
|
58
89
|
propagation.inject(context.active(), headers);
|
|
59
90
|
}
|
|
60
|
-
|
|
61
|
-
export { injectTraceContext, parentContext, withSpan };
|
|
91
|
+
export { injectTraceContext, parentContext, setSpanMessage, spanException, withSpan };
|
|
62
92
|
|
|
63
93
|
//# sourceMappingURL=tracing.mjs.map
|
package/dist/tracing.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tracing.mjs","names":[],"sources":["../src/tracing.ts"],"sourcesContent":["/**\n * Internal span and propagation helpers over `@opentelemetry/api`.\n *\n * Not exported from the package surface (see `package.json` exports): other\n * packages use `@storyshelf/observability`. Without a configured provider\n * every helper is a cheap noop, so call sites need no enabled-checks and\n * the hermetic vitest suite stays network-free.\n */\nimport {\n SpanKind,\n SpanStatusCode,\n context,\n propagation,\n trace,\n type Attributes,\n type Context,\n type Span,\n} from \"@opentelemetry/api\";\n\n/** Instrumentation scope for core spans. */\nconst SCOPE = \"storyshelf.core\";\n\n/** W3C traceparent header name. */\nconst TRACEPARENT_HEADER = \"traceparent\";\n\n/**\n * Run `fn` inside an internal span.\n *\n * On throw the span records the exception, is marked ERROR, and the error
|
|
1
|
+
{"version":3,"file":"tracing.mjs","names":[],"sources":["../src/tracing.ts"],"sourcesContent":["/**\n * Internal span and propagation helpers over `@opentelemetry/api`.\n *\n * Not exported from the package surface (see `package.json` exports): other\n * packages use `@storyshelf/observability`. Without a configured provider\n * every helper is a cheap noop, so call sites need no enabled-checks and\n * the hermetic vitest suite stays network-free.\n */\nimport {\n SpanKind,\n SpanStatusCode,\n context,\n propagation,\n trace,\n type Attributes,\n type Context,\n type Span,\n} from \"@opentelemetry/api\";\nimport { redactSecrets } from \"./utils/redact.ts\";\n\n/** Instrumentation scope for core spans. */\nconst SCOPE = \"storyshelf.core\";\n\n/** W3C traceparent header name. */\nconst TRACEPARENT_HEADER = \"traceparent\";\n\n/** What a span records of an error instead of its message, set by the code that knows the error. */\nconst spanMessages = new WeakMap<object, string>();\n\n/**\n * State what spans record as the message of `error`, instead of its own: an outbound HTTP failure\n * is recorded as its status, never the response body it carries. The error itself is not touched,\n * so what a caller catches is unchanged.\n */\nexport function setSpanMessage(error: unknown, message: string): void {\n if (typeof error === \"object\" && error !== null) {\n spanMessages.set(error, message);\n }\n}\n\n/**\n * The form of a failure that may be recorded on a span: a new error of the same name whose message\n * (the one given to {@link setSpanMessage}, else the error's own) has gone through\n * `redactSecrets`, with the original's stack frames. Never the original, whose message, stack\n * header or `cause` can repeat a URL, a token or a response body. A thrown non-error is stringified\n * and redacted. This is the one way core records an exception on a span.\n */\nexport function spanException(error: unknown): Error | string {\n if (!(error instanceof Error)) {\n return redactSecrets(String(error));\n }\n const message = redactSecrets(spanMessages.get(error) ?? error.message);\n const copy = new Error(message);\n copy.name = error.name;\n copy.stack = `${error.name}: ${message}${redactSecrets(framesOf(error))}`;\n return copy;\n}\n\n/** The stack after its `Name: message` header, which may hold what the span must not record. */\nfunction framesOf(error: Error): string {\n const header = `${error.name}: ${error.message}`;\n return error.stack?.startsWith(header) ? error.stack.slice(header.length) : \"\";\n}\n\n/**\n * Run `fn` inside an internal span.\n *\n * On throw the span records a redacted copy of the exception ({@link spanException}), is marked\n * ERROR, and the original error is rethrown untouched — mirroring the pino `{ err }` convention.\n *\n * @param name - Span name (`domain.operation`, never a high-cardinality value).\n * @param fn - Work to run with the span active.\n * @param attributes - Fixed span attributes.\n * @param parent - Parent context (defaults to the active context).\n * @returns Whatever `fn` returns.\n */\nexport async function withSpan<T>(\n name: string,\n fn: (span: Span) => Promise<T>,\n attributes?: Attributes,\n parent?: Context,\n): Promise<T> {\n const tracer = trace.getTracer(SCOPE);\n return await tracer.startActiveSpan(\n name,\n { kind: SpanKind.INTERNAL, attributes },\n parent ?? context.active(),\n async (span) => {\n try {\n return await fn(span);\n } catch (error) {\n span.recordException(spanException(error));\n span.setStatus({ code: SpanStatusCode.ERROR });\n throw error;\n } finally {\n span.end();\n }\n },\n );\n}\n\n/**\n * Resolve the parent context from an inbound `traceparent` value.\n *\n * @param traceparent - W3C value from a queue payload; absent keeps context.\n * @returns The extracted remote context, or the active context when absent.\n */\nexport function parentContext(traceparent?: string): Context {\n if (!traceparent) {\n return context.active();\n }\n return propagation.extract(context.active(), { [TRACEPARENT_HEADER]: traceparent });\n}\n\n/** Inject the active span context into a mutable header record. */\nexport function injectTraceContext(headers: Record<string, string>): void {\n propagation.inject(context.active(), headers);\n}\n\n/**\n * Read the current W3C `traceparent` value for queue payloads.\n *\n * @returns The header value, or undefined when no span is active.\n */\nexport function currentTraceparent(): string | undefined {\n const carrier: Record<string, string> = {};\n propagation.inject(context.active(), carrier);\n return carrier[TRACEPARENT_HEADER];\n}\n"],"mappings":";;;;;;;;;;;;;;;AAqBA,MAAM,QAAQ;;AAGd,MAAM,qBAAqB;;AAG3B,MAAM,+BAAe,IAAI,QAAwB;;;;;;AAOjD,SAAgB,eAAe,OAAgB,SAAuB;CACpE,IAAI,OAAO,UAAU,YAAY,UAAU,MACzC,aAAa,IAAI,OAAO,OAAO;AAEnC;;;;;;;;AASA,SAAgB,cAAc,OAAgC;CAC5D,IAAI,EAAE,iBAAiB,QACrB,OAAO,cAAc,OAAO,KAAK,CAAC;CAEpC,MAAM,UAAU,cAAc,aAAa,IAAI,KAAK,KAAK,MAAM,OAAO;CACtE,MAAM,OAAO,IAAI,MAAM,OAAO;CAC9B,KAAK,OAAO,MAAM;CAClB,KAAK,QAAQ,GAAG,MAAM,KAAK,IAAI,UAAU,cAAc,SAAS,KAAK,CAAC;CACtE,OAAO;AACT;;AAGA,SAAS,SAAS,OAAsB;CACtC,MAAM,SAAS,GAAG,MAAM,KAAK,IAAI,MAAM;CACvC,OAAO,MAAM,OAAO,WAAW,MAAM,IAAI,MAAM,MAAM,MAAM,OAAO,MAAM,IAAI;AAC9E;;;;;;;;;;;;;AAcA,eAAsB,SACpB,MACA,IACA,YACA,QACY;CAEZ,OAAO,MADQ,MAAM,UAAU,KACb,CAAC,CAAC,gBAClB,MACA;EAAE,MAAM,SAAS;EAAU;CAAW,GACtC,UAAU,QAAQ,OAAO,GACzB,OAAO,SAAS;EACd,IAAI;GACF,OAAO,MAAM,GAAG,IAAI;EACtB,SAAS,OAAO;GACd,KAAK,gBAAgB,cAAc,KAAK,CAAC;GACzC,KAAK,UAAU,EAAE,MAAM,eAAe,MAAM,CAAC;GAC7C,MAAM;EACR,UAAU;GACR,KAAK,IAAI;EACX;CACF,CACF;AACF;;;;;;;AAQA,SAAgB,cAAc,aAA+B;CAC3D,IAAI,CAAC,aACH,OAAO,QAAQ,OAAO;CAExB,OAAO,YAAY,QAAQ,QAAQ,OAAO,GAAG,GAAG,qBAAqB,YAAY,CAAC;AACpF;;AAGA,SAAgB,mBAAmB,SAAuC;CACxE,YAAY,OAAO,QAAQ,OAAO,GAAG,OAAO;AAC9C"}
|