@storyshelf/core 0.6.2 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +49 -32
- package/dist/adapter/capture-queue.d.mts +44 -2
- package/dist/adapter/capture-runner.d.mts +127 -10
- package/dist/adapter/capture-runner.mjs +5 -12
- package/dist/adapter/database.d.mts +27 -8
- package/dist/adapter/database.mjs.map +1 -1
- package/dist/adapter/design.d.mts +85 -0
- package/dist/adapter/design.mjs +28 -0
- package/dist/adapter/design.mjs.map +1 -0
- package/dist/adapter/metadata.d.mts +3 -10
- package/dist/adapter/notifier.d.mts +6 -3
- package/dist/adapter/notifier.mjs +5 -1
- package/dist/adapter/oauth.d.mts +52 -0
- package/dist/adapter/setup.d.mts +4 -5
- package/dist/adapter/setup.mjs +2 -1
- package/dist/adapter/setup.mjs.map +1 -1
- package/dist/adapter/webhook-events.mjs +1 -1
- package/dist/adapters/capture-target-offers.d.mts +28 -0
- package/dist/adapters/capture-target-offers.mjs +58 -0
- package/dist/adapters/capture-target-offers.mjs.map +1 -0
- package/dist/adapters/capture-target.d.mts +71 -0
- package/dist/adapters/capture-target.mjs +76 -0
- package/dist/adapters/capture-target.mjs.map +1 -0
- package/dist/adapters/notifier/build-comment.d.mts +24 -0
- package/dist/adapters/notifier/build-comment.mjs +24 -0
- package/dist/adapters/notifier/build-comment.mjs.map +1 -0
- package/dist/adapters/notifier/build-state.d.mts +99 -0
- package/dist/adapters/notifier/build-state.mjs +218 -0
- package/dist/adapters/notifier/build-state.mjs.map +1 -0
- package/dist/adapters/notifier/events.d.mts +4 -2
- package/dist/adapters/notifier/events.mjs +31 -18
- package/dist/adapters/notifier/events.mjs.map +1 -1
- package/dist/adapters/notifier/provider.d.mts +53 -7
- package/dist/adapters/notifier/provider.mjs +12 -0
- package/dist/adapters/notifier/provider.mjs.map +1 -0
- package/dist/adapters/notifier/state.d.mts +24 -0
- package/dist/adapters/notifier/state.mjs +66 -0
- package/dist/adapters/notifier/state.mjs.map +1 -0
- package/dist/adapters/notifier/types.d.mts +51 -2
- package/dist/adapters/remote-browser.d.mts +87 -0
- package/dist/adapters/remote-browser.mjs +103 -0
- package/dist/adapters/remote-browser.mjs.map +1 -0
- package/dist/adapters/render-runs.d.mts +25 -0
- package/dist/adapters/render-runs.mjs +41 -0
- package/dist/adapters/render-runs.mjs.map +1 -0
- package/dist/adapters/setup-runner.mjs +2 -4
- package/dist/adapters/setup-runner.mjs.map +1 -1
- package/dist/adapters/sources.d.mts +12 -0
- package/dist/adapters/sources.mjs +20 -0
- package/dist/adapters/sources.mjs.map +1 -0
- package/dist/adapters/validate.mjs +25 -18
- package/dist/adapters/validate.mjs.map +1 -1
- package/dist/ai.d.mts +105 -0
- package/dist/ai.mjs +19 -0
- package/dist/ai.mjs.map +1 -0
- package/dist/capture/a11y/axe-source.d.mts +18 -0
- package/dist/capture/a11y/axe-source.mjs +37 -0
- package/dist/capture/a11y/axe-source.mjs.map +1 -0
- package/dist/capture/a11y/in-page.d.mts +27 -0
- package/dist/capture/a11y/in-page.mjs +60 -0
- package/dist/capture/a11y/in-page.mjs.map +1 -0
- package/dist/capture/a11y/map.d.mts +24 -0
- package/dist/capture/a11y/map.mjs +48 -0
- package/dist/capture/a11y/map.mjs.map +1 -0
- package/dist/capture/a11y/options.d.mts +23 -0
- package/dist/capture/a11y/options.mjs +75 -0
- package/dist/capture/a11y/options.mjs.map +1 -0
- package/dist/capture/adapter.d.mts +41 -3
- package/dist/capture/adapter.mjs.map +1 -1
- package/dist/capture/affected.d.mts +2 -0
- package/dist/capture/affected.mjs +13 -4
- package/dist/capture/affected.mjs.map +1 -1
- package/dist/capture/attempt-logger.mjs +84 -0
- package/dist/capture/attempt-logger.mjs.map +1 -0
- package/dist/capture/browsers.d.mts +35 -0
- package/dist/capture/browsers.mjs +67 -0
- package/dist/capture/browsers.mjs.map +1 -0
- package/dist/capture/build-state/deliver.d.mts +32 -0
- package/dist/capture/build-state/deliver.mjs +118 -0
- package/dist/capture/build-state/deliver.mjs.map +1 -0
- package/dist/capture/build-state/index.d.mts +8 -0
- package/dist/capture/build-state/notify-job.d.mts +56 -0
- package/dist/capture/build-state/notify-job.mjs +133 -0
- package/dist/capture/build-state/notify-job.mjs.map +1 -0
- package/dist/capture/build-state/publisher.d.mts +23 -0
- package/dist/capture/build-state/publisher.mjs +107 -0
- package/dist/capture/build-state/publisher.mjs.map +1 -0
- package/dist/capture/build-state/resume.d.mts +9 -0
- package/dist/capture/build-state/resume.mjs +47 -0
- package/dist/capture/build-state/resume.mjs.map +1 -0
- package/dist/capture/build-state/types.d.mts +49 -0
- package/dist/capture/catalog/catalogue.d.mts +22 -0
- package/dist/capture/catalog/catalogue.mjs +53 -0
- package/dist/capture/catalog/catalogue.mjs.map +1 -0
- package/dist/capture/catalog/components.d.mts +23 -0
- package/dist/capture/catalog/components.mjs +55 -0
- package/dist/capture/catalog/components.mjs.map +1 -0
- package/dist/capture/catalog/history.d.mts +30 -0
- package/dist/capture/catalog/history.mjs +51 -0
- package/dist/capture/catalog/history.mjs.map +1 -0
- package/dist/capture/catalog/hook.d.mts +14 -0
- package/dist/capture/catalog/hook.mjs +19 -0
- package/dist/capture/catalog/hook.mjs.map +1 -0
- package/dist/capture/catalog/index.d.mts +11 -0
- package/dist/capture/catalog/load.d.mts +26 -0
- package/dist/capture/catalog/load.mjs +49 -0
- package/dist/capture/catalog/load.mjs.map +1 -0
- package/dist/capture/catalog/parse.d.mts +15 -0
- package/dist/capture/catalog/parse.mjs +56 -0
- package/dist/capture/catalog/parse.mjs.map +1 -0
- package/dist/capture/catalog/read.d.mts +12 -0
- package/dist/capture/catalog/read.mjs +33 -0
- package/dist/capture/catalog/read.mjs.map +1 -0
- package/dist/capture/catalog/types.d.mts +33 -0
- package/dist/capture/consumer/capture-handler.d.mts +58 -0
- package/dist/capture/consumer/capture-handler.mjs +118 -0
- package/dist/capture/consumer/capture-handler.mjs.map +1 -0
- package/dist/capture/consumer/idle.d.mts +17 -0
- package/dist/capture/consumer/idle.mjs +25 -0
- package/dist/capture/consumer/idle.mjs.map +1 -0
- package/dist/capture/consumer/index.d.mts +13 -0
- package/dist/capture/consumer/index.mjs +139 -0
- package/dist/capture/consumer/index.mjs.map +1 -0
- package/dist/capture/consumer/loop.mjs +110 -0
- package/dist/capture/consumer/loop.mjs.map +1 -0
- package/dist/capture/consumer/metrics.d.mts +41 -0
- package/dist/capture/consumer/metrics.mjs +38 -0
- package/dist/capture/consumer/metrics.mjs.map +1 -0
- package/dist/capture/consumer/retry.d.mts +33 -0
- package/dist/capture/consumer/retry.mjs +34 -0
- package/dist/capture/consumer/retry.mjs.map +1 -0
- package/dist/capture/consumer/run-job.mjs +195 -0
- package/dist/capture/consumer/run-job.mjs.map +1 -0
- package/dist/capture/consumer/startup.mjs +38 -0
- package/dist/capture/consumer/startup.mjs.map +1 -0
- package/dist/capture/consumer/types.d.mts +97 -0
- package/dist/capture/crash-failure.d.mts +25 -0
- package/dist/capture/crash-failure.mjs +70 -0
- package/dist/capture/crash-failure.mjs.map +1 -0
- package/dist/capture/db-queue/cadence.d.mts +32 -0
- package/dist/capture/db-queue/cadence.mjs +38 -0
- package/dist/capture/db-queue/cadence.mjs.map +1 -0
- package/dist/capture/db-queue/claim.d.mts +9 -0
- package/dist/capture/db-queue/claim.mjs +132 -0
- package/dist/capture/db-queue/claim.mjs.map +1 -0
- package/dist/capture/db-queue/clock.mjs +12 -0
- package/dist/capture/db-queue/clock.mjs.map +1 -0
- package/dist/capture/db-queue/codec.mjs +76 -0
- package/dist/capture/db-queue/codec.mjs.map +1 -0
- package/dist/capture/db-queue/index.d.mts +20 -0
- package/dist/capture/db-queue/index.mjs +89 -0
- package/dist/capture/db-queue/index.mjs.map +1 -0
- package/dist/capture/db-queue/keeper.d.mts +36 -0
- package/dist/capture/db-queue/keeper.mjs +92 -0
- package/dist/capture/db-queue/keeper.mjs.map +1 -0
- package/dist/capture/db-queue/lease.mjs +46 -0
- package/dist/capture/db-queue/lease.mjs.map +1 -0
- package/dist/capture/db-queue/lifecycle.mjs +36 -0
- package/dist/capture/db-queue/lifecycle.mjs.map +1 -0
- package/dist/capture/db-queue/operations.mjs +94 -0
- package/dist/capture/db-queue/operations.mjs.map +1 -0
- package/dist/capture/db-queue/poll-loop.d.mts +30 -0
- package/dist/capture/db-queue/poll-loop.mjs +89 -0
- package/dist/capture/db-queue/poll-loop.mjs.map +1 -0
- package/dist/capture/db-queue/poll.d.mts +10 -0
- package/dist/capture/db-queue/poll.mjs +69 -0
- package/dist/capture/db-queue/poll.mjs.map +1 -0
- package/dist/capture/db-queue/timers.d.mts +12 -0
- package/dist/capture/db-queue/timers.mjs +37 -0
- package/dist/capture/db-queue/timers.mjs.map +1 -0
- package/dist/capture/db-queue/types.d.mts +83 -0
- package/dist/capture/db-queue/types.mjs +12 -0
- package/dist/capture/db-queue/types.mjs.map +1 -0
- package/dist/capture/db-queue/wake.mjs +48 -0
- package/dist/capture/db-queue/wake.mjs.map +1 -0
- package/dist/capture/design.mjs +39 -0
- package/dist/capture/design.mjs.map +1 -0
- package/dist/capture/diff-record.mjs +23 -3
- package/dist/capture/diff-record.mjs.map +1 -1
- package/dist/capture/dispatch.d.mts +12 -7
- package/dist/capture/dispatch.mjs +86 -107
- package/dist/capture/dispatch.mjs.map +1 -1
- package/dist/capture/enrich.mjs +60 -0
- package/dist/capture/enrich.mjs.map +1 -0
- package/dist/capture/failure-sets.mjs +22 -0
- package/dist/capture/failure-sets.mjs.map +1 -0
- package/dist/capture/fingerprint.d.mts +42 -0
- package/dist/capture/fingerprint.mjs +83 -0
- package/dist/capture/fingerprint.mjs.map +1 -0
- package/dist/capture/forked/channel.d.mts +25 -0
- package/dist/capture/forked/child.d.mts +34 -0
- package/dist/capture/forked/child.mjs +202 -0
- package/dist/capture/forked/child.mjs.map +1 -0
- package/dist/capture/forked/crash-tracker.mjs +23 -0
- package/dist/capture/forked/crash-tracker.mjs.map +1 -0
- package/dist/capture/forked/decision.d.mts +26 -0
- package/dist/capture/forked/decision.mjs +27 -0
- package/dist/capture/forked/decision.mjs.map +1 -0
- package/dist/capture/forked/exit.mjs +12 -0
- package/dist/capture/forked/exit.mjs.map +1 -0
- package/dist/capture/forked/fork.d.mts +31 -0
- package/dist/capture/forked/fork.mjs +93 -0
- package/dist/capture/forked/fork.mjs.map +1 -0
- package/dist/capture/forked/forked-capture.d.mts +40 -0
- package/dist/capture/forked/forked-capture.mjs +240 -0
- package/dist/capture/forked/forked-capture.mjs.map +1 -0
- package/dist/capture/forked/lease-book.mjs +46 -0
- package/dist/capture/forked/lease-book.mjs.map +1 -0
- package/dist/capture/forked/protocol.d.mts +38 -0
- package/dist/capture/forked/protocol.mjs +25 -0
- package/dist/capture/forked/protocol.mjs.map +1 -0
- package/dist/capture/forked/runs.mjs +75 -0
- package/dist/capture/forked/runs.mjs.map +1 -0
- package/dist/capture/isolate.mjs +141 -0
- package/dist/capture/isolate.mjs.map +1 -0
- package/dist/capture/job-lease.d.mts +28 -0
- package/dist/capture/job-lease.mjs +57 -0
- package/dist/capture/job-lease.mjs.map +1 -0
- package/dist/capture/matrix.mjs +298 -0
- package/dist/capture/matrix.mjs.map +1 -0
- package/dist/capture/modes.d.mts +25 -0
- package/dist/capture/modes.mjs +107 -0
- package/dist/capture/modes.mjs.map +1 -0
- package/dist/capture/orchestrator.d.mts +37 -5
- package/dist/capture/orchestrator.mjs +118 -107
- package/dist/capture/orchestrator.mjs.map +1 -1
- package/dist/capture/pipeline.d.mts +9 -2
- package/dist/capture/pipeline.mjs +250 -45
- package/dist/capture/pipeline.mjs.map +1 -1
- package/dist/capture/progress.d.mts +11 -0
- package/dist/capture/progress.mjs +28 -0
- package/dist/capture/progress.mjs.map +1 -0
- package/dist/capture/publish/accept.d.mts +39 -0
- package/dist/capture/publish/accept.mjs +28 -0
- package/dist/capture/publish/accept.mjs.map +1 -0
- package/dist/capture/publish/complete.d.mts +46 -0
- package/dist/capture/publish/complete.mjs +64 -0
- package/dist/capture/publish/complete.mjs.map +1 -0
- package/dist/capture/publish/extract.d.mts +20 -0
- package/dist/capture/publish/extract.mjs +119 -0
- package/dist/capture/publish/extract.mjs.map +1 -0
- package/dist/capture/publish/handler.d.mts +28 -0
- package/dist/capture/publish/handler.mjs +62 -0
- package/dist/capture/publish/handler.mjs.map +1 -0
- package/dist/capture/publish/index.d.mts +10 -0
- package/dist/capture/publish/limits.d.mts +37 -0
- package/dist/capture/publish/limits.mjs +39 -0
- package/dist/capture/publish/limits.mjs.map +1 -0
- package/dist/capture/publish/published.d.mts +15 -0
- package/dist/capture/publish/published.mjs +18 -0
- package/dist/capture/publish/published.mjs.map +1 -0
- package/dist/capture/publish/run.d.mts +33 -0
- package/dist/capture/publish/run.mjs +30 -0
- package/dist/capture/publish/run.mjs.map +1 -0
- package/dist/capture/rediff.d.mts +1 -1
- package/dist/capture/rediff.mjs +4 -3
- package/dist/capture/rediff.mjs.map +1 -1
- package/dist/capture/render.mjs +53 -0
- package/dist/capture/render.mjs.map +1 -0
- package/dist/capture/scratch.d.mts +44 -0
- package/dist/capture/scratch.mjs +138 -0
- package/dist/capture/scratch.mjs.map +1 -0
- package/dist/capture/shard-plan.mjs +128 -0
- package/dist/capture/shard-plan.mjs.map +1 -0
- package/dist/capture/shards/context.mjs +51 -0
- package/dist/capture/shards/context.mjs.map +1 -0
- package/dist/capture/shards/execute.mjs +174 -0
- package/dist/capture/shards/execute.mjs.map +1 -0
- package/dist/capture/shards/fan-out.mjs +126 -0
- package/dist/capture/shards/fan-out.mjs.map +1 -0
- package/dist/capture/shards/history.mjs +35 -0
- package/dist/capture/shards/history.mjs.map +1 -0
- package/dist/capture/shards/reduce.mjs +97 -0
- package/dist/capture/shards/reduce.mjs.map +1 -0
- package/dist/capture/sizing.mjs +1 -11
- package/dist/capture/sizing.mjs.map +1 -1
- package/dist/capture/skip-checks.mjs +2 -65
- package/dist/capture/skip-checks.mjs.map +1 -1
- package/dist/capture/statics.d.mts +7 -8
- package/dist/capture/statics.mjs +25 -71
- package/dist/capture/statics.mjs.map +1 -1
- package/dist/capture/storybook-urls.d.mts +40 -0
- package/dist/capture/storybook-urls.mjs +41 -0
- package/dist/capture/storybook-urls.mjs.map +1 -0
- package/dist/capture/storybook.d.mts +12 -4
- package/dist/capture/storybook.mjs +56 -16
- package/dist/capture/storybook.mjs.map +1 -1
- package/dist/capture/target.mjs +21 -0
- package/dist/capture/target.mjs.map +1 -0
- package/dist/capture/targets.d.mts +25 -0
- package/dist/capture/targets.mjs +32 -0
- package/dist/capture/targets.mjs.map +1 -0
- package/dist/capture/tests.d.mts +37 -0
- package/dist/capture/tests.mjs +126 -0
- package/dist/capture/tests.mjs.map +1 -0
- package/dist/capture/token-chunks.mjs +74 -0
- package/dist/capture/token-chunks.mjs.map +1 -0
- package/dist/capture.d.mts +60 -7
- package/dist/capture.mjs +48 -3
- package/dist/config.d.mts +121 -12
- package/dist/config.mjs +9 -6
- package/dist/config.mjs.map +1 -1
- package/dist/diff.d.mts +9 -3
- package/dist/diff.mjs +16 -3
- package/dist/diff.mjs.map +1 -0
- package/dist/index.d.mts +1 -1
- package/dist/insights/budget.d.mts +20 -0
- package/dist/insights/budget.mjs +33 -0
- package/dist/insights/budget.mjs.map +1 -0
- package/dist/insights/envelope.d.mts +59 -0
- package/dist/insights/envelope.mjs +60 -0
- package/dist/insights/envelope.mjs.map +1 -0
- package/dist/insights/evidence-health.d.mts +32 -0
- package/dist/insights/evidence-health.mjs +28 -0
- package/dist/insights/evidence-health.mjs.map +1 -0
- package/dist/insights/evidence.d.mts +55 -0
- package/dist/insights/evidence.mjs +64 -0
- package/dist/insights/evidence.mjs.map +1 -0
- package/dist/insights/hash.d.mts +18 -0
- package/dist/insights/hash.mjs +21 -0
- package/dist/insights/hash.mjs.map +1 -0
- package/dist/insights/limits.d.mts +24 -0
- package/dist/insights/limits.mjs +24 -0
- package/dist/insights/limits.mjs.map +1 -0
- package/dist/insights/prompts/v1.d.mts +13 -0
- package/dist/insights/prompts/v1.mjs +34 -0
- package/dist/insights/prompts/v1.mjs.map +1 -0
- package/dist/insights/redact.d.mts +9 -0
- package/dist/insights/redact.mjs +24 -0
- package/dist/insights/redact.mjs.map +1 -0
- package/dist/insights/text.mjs +44 -0
- package/dist/insights/text.mjs.map +1 -0
- package/dist/insights.d.mts +11 -0
- package/dist/insights.mjs +12 -0
- package/dist/logger.mjs +17 -1
- package/dist/logger.mjs.map +1 -1
- package/dist/models/ai-budget-alert.d.mts +23 -0
- package/dist/models/ai-budget-alert.mjs +38 -0
- package/dist/models/ai-budget-alert.mjs.map +1 -0
- package/dist/models/ai-usage.d.mts +40 -0
- package/dist/models/ai-usage.mjs +61 -0
- package/dist/models/ai-usage.mjs.map +1 -0
- package/dist/models/baseline-archive.d.mts +84 -0
- package/dist/models/baseline-archive.mjs +106 -0
- package/dist/models/baseline-archive.mjs.map +1 -0
- package/dist/models/baseline-export.d.mts +19 -0
- package/dist/models/baseline-export.mjs +136 -0
- package/dist/models/baseline-export.mjs.map +1 -0
- package/dist/models/baseline-import.mjs +118 -0
- package/dist/models/baseline-import.mjs.map +1 -0
- package/dist/models/baseline-transfer.d.mts +49 -0
- package/dist/models/baseline-transfer.mjs +121 -0
- package/dist/models/baseline-transfer.mjs.map +1 -0
- package/dist/models/baseline.d.mts +32 -5
- package/dist/models/baseline.mjs +65 -20
- package/dist/models/baseline.mjs.map +1 -1
- package/dist/models/build-component.d.mts +28 -0
- package/dist/models/build-component.mjs +55 -0
- package/dist/models/build-component.mjs.map +1 -0
- package/dist/models/build-review-status.d.mts +29 -0
- package/dist/models/build-review-status.mjs +37 -0
- package/dist/models/build-review-status.mjs.map +1 -0
- package/dist/models/build-review.d.mts +28 -0
- package/dist/models/build-review.mjs +54 -0
- package/dist/models/build-review.mjs.map +1 -0
- package/dist/models/build.d.mts +25 -3
- package/dist/models/build.mjs +50 -11
- package/dist/models/build.mjs.map +1 -1
- package/dist/models/capture-attempt.d.mts +7 -1
- package/dist/models/capture-attempt.mjs +29 -4
- package/dist/models/capture-attempt.mjs.map +1 -1
- package/dist/models/capture-job.d.mts +106 -0
- package/dist/models/capture-job.mjs +288 -0
- package/dist/models/capture-job.mjs.map +1 -0
- package/dist/models/capture-log.d.mts +1 -1
- package/dist/models/capture-log.mjs +4 -3
- package/dist/models/capture-log.mjs.map +1 -1
- package/dist/models/capture-shard.d.mts +62 -0
- package/dist/models/capture-shard.mjs +190 -0
- package/dist/models/capture-shard.mjs.map +1 -0
- package/dist/models/changeset.d.mts +48 -0
- package/dist/models/changeset.mjs +81 -0
- package/dist/models/changeset.mjs.map +1 -0
- package/dist/models/columns.mjs +19 -0
- package/dist/models/columns.mjs.map +1 -0
- package/dist/models/comment.d.mts +4 -1
- package/dist/models/comment.mjs +7 -4
- package/dist/models/comment.mjs.map +1 -1
- package/dist/models/component.d.mts +43 -0
- package/dist/models/component.mjs +60 -0
- package/dist/models/component.mjs.map +1 -0
- package/dist/models/connections.d.mts +41 -0
- package/dist/models/connections.mjs +112 -0
- package/dist/models/connections.mjs.map +1 -0
- package/dist/models/content-ref.d.mts +1 -1
- package/dist/models/content-ref.mjs +1 -1
- package/dist/models/content-ref.mjs.map +1 -1
- package/dist/models/credentials.mjs +12 -2
- package/dist/models/credentials.mjs.map +1 -1
- package/dist/models/design-credentials.d.mts +41 -0
- package/dist/models/design-credentials.mjs +86 -0
- package/dist/models/design-credentials.mjs.map +1 -0
- package/dist/models/design-health.d.mts +16 -0
- package/dist/models/design-health.mjs +25 -0
- package/dist/models/design-health.mjs.map +1 -0
- package/dist/models/design-nodes.d.mts +83 -0
- package/dist/models/design-nodes.mjs +222 -0
- package/dist/models/design-nodes.mjs.map +1 -0
- package/dist/models/design-renders.mjs +74 -0
- package/dist/models/design-renders.mjs.map +1 -0
- package/dist/models/design-resolve.d.mts +29 -0
- package/dist/models/design-resolve.mjs +41 -0
- package/dist/models/design-resolve.mjs.map +1 -0
- package/dist/models/design.d.mts +39 -0
- package/dist/models/design.mjs +90 -0
- package/dist/models/design.mjs.map +1 -0
- package/dist/models/inherited-ratio.d.mts +21 -0
- package/dist/models/inherited-ratio.mjs +22 -0
- package/dist/models/inherited-ratio.mjs.map +1 -0
- package/dist/models/insight.d.mts +70 -0
- package/dist/models/insight.mjs +162 -0
- package/dist/models/insight.mjs.map +1 -0
- package/dist/models/label.d.mts +4 -2
- package/dist/models/label.mjs +25 -10
- package/dist/models/label.mjs.map +1 -1
- package/dist/models/member.d.mts +1 -1
- package/dist/models/member.mjs +5 -4
- package/dist/models/member.mjs.map +1 -1
- package/dist/models/notification-channel.d.mts +37 -3
- package/dist/models/notification-channel.mjs +88 -9
- package/dist/models/notification-channel.mjs.map +1 -1
- package/dist/models/notification-subscription.d.mts +1 -2
- package/dist/models/notification-subscription.mjs +7 -9
- package/dist/models/notification-subscription.mjs.map +1 -1
- package/dist/models/project-group-mapping.d.mts +6 -1
- package/dist/models/project-group-mapping.mjs +14 -4
- package/dist/models/project-group-mapping.mjs.map +1 -1
- package/dist/models/project.d.mts +9 -1
- package/dist/models/project.mjs +9 -4
- package/dist/models/project.mjs.map +1 -1
- package/dist/models/public-origin.d.mts +63 -0
- package/dist/models/public-origin.mjs +114 -0
- package/dist/models/public-origin.mjs.map +1 -0
- package/dist/models/review-switch-off.d.mts +14 -0
- package/dist/models/review-switch-off.mjs +26 -0
- package/dist/models/review-switch-off.mjs.map +1 -0
- package/dist/models/site-settings.d.mts +23 -0
- package/dist/models/site-settings.mjs +48 -0
- package/dist/models/site-settings.mjs.map +1 -0
- package/dist/models/snapshot.d.mts +36 -1
- package/dist/models/snapshot.mjs +50 -3
- package/dist/models/snapshot.mjs.map +1 -1
- package/dist/models/token.d.mts +13 -1
- package/dist/models/token.mjs +23 -5
- package/dist/models/token.mjs.map +1 -1
- package/dist/models/usage.d.mts +76 -0
- package/dist/models/usage.mjs +149 -0
- package/dist/models/usage.mjs.map +1 -0
- package/dist/models/user.d.mts +1 -1
- package/dist/models/user.mjs +4 -3
- package/dist/models/user.mjs.map +1 -1
- package/dist/models/webhook.d.mts +15 -1
- package/dist/models/webhook.mjs +33 -4
- package/dist/models/webhook.mjs.map +1 -1
- package/dist/models.d.mts +26 -3
- package/dist/models.mjs +27 -5
- package/dist/options.d.mts +18 -0
- package/dist/options.mjs +90 -0
- package/dist/options.mjs.map +1 -0
- package/dist/paths.d.mts +19 -4
- package/dist/paths.mjs +51 -10
- package/dist/paths.mjs.map +1 -1
- package/dist/retention/ai.d.mts +18 -0
- package/dist/retention/ai.mjs +23 -0
- package/dist/retention/ai.mjs.map +1 -0
- package/dist/retention/pending.mjs +47 -0
- package/dist/retention/pending.mjs.map +1 -0
- package/dist/retention/screenshot-sweep.d.mts +51 -0
- package/dist/retention/screenshot-sweep.mjs +128 -0
- package/dist/retention/screenshot-sweep.mjs.map +1 -0
- package/dist/retention.d.mts +16 -2
- package/dist/retention.mjs +71 -14
- package/dist/retention.mjs.map +1 -1
- package/dist/schema/baseline.d.mts +6 -0
- package/dist/schema/build-component.d.mts +25 -0
- package/dist/schema/build-review.d.mts +17 -0
- package/dist/schema/build.d.mts +7 -0
- package/dist/schema/capture-job.d.mts +60 -0
- package/dist/schema/capture-shard.d.mts +35 -0
- package/dist/schema/comment.d.mts +2 -0
- package/dist/schema/connection.d.mts +24 -0
- package/dist/schema/design.d.mts +58 -0
- package/dist/schema/insight.d.mts +54 -0
- package/dist/schema/notification.d.mts +4 -0
- package/dist/schema/project.d.mts +21 -0
- package/dist/schema/site-setting.d.mts +21 -0
- package/dist/schema/snapshot.d.mts +12 -0
- package/dist/schema/usage.d.mts +30 -0
- package/dist/schema.d.mts +11 -3
- package/dist/serverless.d.mts +21 -0
- package/dist/serverless.mjs +37 -0
- package/dist/serverless.mjs.map +1 -0
- package/dist/test-helpers/create-project.d.mts +1 -1
- package/dist/test-helpers/deferred.d.mts +39 -0
- package/dist/test-helpers/deferred.mjs +61 -0
- package/dist/test-helpers/deferred.mjs.map +1 -0
- package/dist/test-helpers/design-contract.d.mts +21 -0
- package/dist/test-helpers/design-contract.mjs +40 -0
- package/dist/test-helpers/design-contract.mjs.map +1 -0
- package/dist/test-helpers/fake-database.mjs +14 -2
- package/dist/test-helpers/fake-database.mjs.map +1 -1
- package/dist/test-helpers/fake-design.d.mts +43 -0
- package/dist/test-helpers/fake-design.mjs +114 -0
- package/dist/test-helpers/fake-design.mjs.map +1 -0
- package/dist/test-helpers/fake-notifier.d.mts +21 -0
- package/dist/test-helpers/fake-notifier.mjs +44 -0
- package/dist/test-helpers/fake-notifier.mjs.map +1 -0
- package/dist/test-helpers/fake-tables.mjs +255 -67
- package/dist/test-helpers/fake-tables.mjs.map +1 -1
- package/dist/test-helpers/manual-clock.d.mts +27 -0
- package/dist/test-helpers/manual-clock.mjs +56 -0
- package/dist/test-helpers/manual-clock.mjs.map +1 -0
- package/dist/test-helpers/notifier-contract.d.mts +7 -1
- package/dist/test-helpers/notifier-contract.mjs +32 -3
- package/dist/test-helpers/notifier-contract.mjs.map +1 -1
- package/dist/test-helpers/queue-contract.d.mts +16 -2
- package/dist/test-helpers/queue-contract.mjs +195 -1
- package/dist/test-helpers/queue-contract.mjs.map +1 -1
- package/dist/test-helpers/queue-multi-instance.d.mts +20 -0
- package/dist/test-helpers/queue-multi-instance.mjs +206 -0
- package/dist/test-helpers/queue-multi-instance.mjs.map +1 -0
- package/dist/test-helpers/runner-contract.mjs +11 -0
- package/dist/test-helpers/runner-contract.mjs.map +1 -1
- package/dist/test-helpers/sql-chunks.mjs +23 -0
- package/dist/test-helpers/sql-chunks.mjs.map +1 -1
- package/dist/test-helpers/storage-contract.mjs +1 -1
- package/dist/test-helpers.d.mts +9 -4
- package/dist/test-helpers.mjs +8 -3
- package/dist/types.d.mts +14 -3
- package/dist/types.mjs +21 -4
- package/dist/types.mjs.map +1 -1
- package/dist/urls.d.mts +4 -0
- package/dist/urls.mjs +11 -2
- package/dist/urls.mjs.map +1 -1
- package/dist/utils/adaptive-concurrency.d.mts +66 -0
- package/dist/utils/adaptive-concurrency.mjs +149 -0
- package/dist/utils/adaptive-concurrency.mjs.map +1 -0
- package/dist/utils/connect-retry.d.mts +26 -0
- package/dist/utils/connect-retry.mjs +43 -0
- package/dist/utils/connect-retry.mjs.map +1 -0
- package/dist/utils/environment.d.mts +19 -0
- package/dist/utils/environment.mjs +59 -0
- package/dist/utils/environment.mjs.map +1 -0
- package/dist/utils/http.d.mts +17 -1
- package/dist/utils/http.mjs +22 -3
- package/dist/utils/http.mjs.map +1 -1
- package/dist/utils/map-adaptive.d.mts +15 -0
- package/dist/utils/map-adaptive.mjs +72 -0
- package/dist/utils/map-adaptive.mjs.map +1 -0
- package/dist/utils/map-limit.d.mts +10 -0
- package/dist/utils/map-limit.mjs +32 -0
- package/dist/utils/map-limit.mjs.map +1 -0
- package/dist/utils/page-limiter.d.mts +37 -0
- package/dist/utils/page-limiter.mjs +56 -0
- package/dist/utils/page-limiter.mjs.map +1 -0
- package/dist/utils/public-origin.d.mts +20 -0
- package/dist/utils/public-origin.mjs +75 -0
- package/dist/utils/public-origin.mjs.map +1 -0
- package/dist/utils/redact.mjs +3 -0
- package/dist/utils/redact.mjs.map +1 -1
- package/dist/utils/remote-environment.d.mts +39 -0
- package/dist/utils/remote-environment.mjs +114 -0
- package/dist/utils/remote-environment.mjs.map +1 -0
- package/dist/utils/resources.d.mts +64 -0
- package/dist/utils/resources.mjs +122 -0
- package/dist/utils/resources.mjs.map +1 -0
- package/dist/utils/screenshot-store.d.mts +15 -0
- package/dist/utils/screenshot-store.mjs +41 -0
- package/dist/utils/screenshot-store.mjs.map +1 -0
- package/dist/utils/session-gate.d.mts +22 -0
- package/dist/utils/session-gate.mjs +53 -0
- package/dist/utils/session-gate.mjs.map +1 -0
- package/dist/utils/storybook-token.d.mts +81 -0
- package/dist/utils/storybook-token.mjs +134 -0
- package/dist/utils/storybook-token.mjs.map +1 -0
- package/dist/utils/storybook-url.d.mts +15 -0
- package/dist/utils/storybook-url.mjs +21 -0
- package/dist/utils/storybook-url.mjs.map +1 -0
- package/dist/utils/timing.mjs +1 -1
- package/dist/utils/timing.mjs.map +1 -1
- package/dist/utils/zip-read.mjs +129 -0
- package/dist/utils/zip-read.mjs.map +1 -0
- package/dist/utils/zip-write.d.mts +18 -0
- package/dist/utils/zip-write.mjs +188 -0
- package/dist/utils/zip-write.mjs.map +1 -0
- package/dist/utils.d.mts +17 -3
- package/dist/utils.mjs +18 -4
- package/package.json +32 -23
- 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
|
@@ -7,27 +7,54 @@ import { WebhookTables } from "./webhook.mjs";
|
|
|
7
7
|
import { Baseline } from "../schema/baseline.mjs";
|
|
8
8
|
import { Table } from "drizzle-orm";
|
|
9
9
|
//#region src/models/baseline.d.ts
|
|
10
|
+
/** Extra identity/metadata for {@link BaselineModel.upsert}. */
|
|
11
|
+
interface BaselineUpsertOptions {
|
|
12
|
+
/** Engine the baseline belongs to (default chromium). */
|
|
13
|
+
browser?: string;
|
|
14
|
+
/** Resolved mode definition as JSON (display only). */
|
|
15
|
+
mode?: string | null;
|
|
16
|
+
/** JSON `Fingerprint` behind the `infraHash` being written. */
|
|
17
|
+
fingerprint?: string | null;
|
|
18
|
+
}
|
|
10
19
|
/** Tables required by {@link BaselineModel}. */
|
|
11
20
|
interface BaselineTables {
|
|
12
21
|
baselines: Table;
|
|
13
22
|
}
|
|
14
23
|
/** Data operations for baseline screenshots. */
|
|
15
24
|
declare class BaselineModel {
|
|
16
|
-
private
|
|
25
|
+
#private;
|
|
17
26
|
private readonly tables;
|
|
18
27
|
private readonly storage;
|
|
19
28
|
private readonly secret?;
|
|
20
29
|
private readonly webhookTables?;
|
|
30
|
+
private readonly db;
|
|
21
31
|
constructor(db: DatabaseAdapter, tables?: BaselineTables, storage?: StorageAdapter, secret?: SecretInput, webhookTables?: WebhookTables);
|
|
22
|
-
getFor(projectId: string, storyId: string, viewport: string, branch: string): Promise<Baseline | null>;
|
|
23
|
-
resolve(projectId: string, storyId: string, viewport: string, branch: string, defaultBranch: string): Promise<Baseline | null>;
|
|
32
|
+
getFor(projectId: string, storyId: string, viewport: string, branch: string, browser?: string): Promise<Baseline | null>;
|
|
33
|
+
resolve(projectId: string, storyId: string, viewport: string, branch: string, defaultBranch: string, browser?: string): Promise<Baseline | null>;
|
|
34
|
+
/** Whether the project has any baseline captured on this engine. */
|
|
35
|
+
hasAny(projectId: string, browser: string): Promise<boolean>;
|
|
36
|
+
/**
|
|
37
|
+
* Record the fingerprint of a baseline that predates fingerprints, without touching its
|
|
38
|
+
* screenshot. Lets the next environment change be detected instead of diffing as a regression.
|
|
39
|
+
*/
|
|
40
|
+
backfillFingerprint(baseline: Baseline, infraHash: string, fingerprint: string): Promise<void>;
|
|
41
|
+
/**
|
|
42
|
+
* The content-addressed object a baseline should point at. A source that already is one is
|
|
43
|
+
* reused as is (no copy); a legacy per-build path is stored once by content.
|
|
44
|
+
*/
|
|
45
|
+
private contentFor;
|
|
24
46
|
read(baseline: Baseline): Promise<Buffer>;
|
|
25
|
-
upsert(projectId: string, storyId: string, viewport: string, branch: string, snapshotId: string, sourcePath: string, infraHash?: string | null): Promise<Baseline>;
|
|
47
|
+
upsert(projectId: string, storyId: string, viewport: string, branch: string, snapshotId: string, sourcePath: string, infraHash?: string | null, options?: BaselineUpsertOptions): Promise<Baseline>;
|
|
26
48
|
list(projectId: string): Promise<Baseline[]>;
|
|
49
|
+
/**
|
|
50
|
+
* Delete a baseline's own file. A content-addressed screenshot is shared with snapshots and other
|
|
51
|
+
* baselines, so it is left for the retention sweep instead.
|
|
52
|
+
*/
|
|
53
|
+
private discardFile;
|
|
27
54
|
removeOrphans(projectId: string, validStoryIds: Set<string>): Promise<number>;
|
|
28
55
|
removeForBranch(projectId: string, branch: string): Promise<number>;
|
|
29
56
|
removeStaleBranches(projectId: string, staleBranches: ReadonlySet<string>): Promise<number>;
|
|
30
57
|
}
|
|
31
58
|
//#endregion
|
|
32
|
-
export { BaselineModel, BaselineTables };
|
|
59
|
+
export { BaselineModel, BaselineTables, BaselineUpsertOptions };
|
|
33
60
|
//# sourceMappingURL=baseline.d.mts.map
|
package/dist/models/baseline.mjs
CHANGED
|
@@ -1,19 +1,22 @@
|
|
|
1
1
|
import "node:path";
|
|
2
2
|
import "node:url";
|
|
3
3
|
import.meta.url;
|
|
4
|
+
import { DEFAULT_BROWSER } from "../adapters/capture-target.mjs";
|
|
4
5
|
import { ulid } from "../utils/ulid.mjs";
|
|
6
|
+
import { col } from "./columns.mjs";
|
|
5
7
|
import { emitWebhookEvent } from "../adapter/webhook-events.mjs";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
+
import { isContentPath } from "../paths.mjs";
|
|
9
|
+
import { storeScreenshot, withContentClaim } from "../utils/screenshot-store.mjs";
|
|
10
|
+
import { and, eq } from "drizzle-orm";
|
|
8
11
|
//#region src/models/baseline.ts
|
|
9
12
|
/** Per-branch baseline screenshots with default-branch fallback. */
|
|
10
13
|
/** Data operations for baseline screenshots. */
|
|
11
14
|
var BaselineModel = class {
|
|
12
|
-
db;
|
|
13
15
|
tables;
|
|
14
16
|
storage;
|
|
15
17
|
secret;
|
|
16
18
|
webhookTables;
|
|
19
|
+
db;
|
|
17
20
|
constructor(db, tables, storage, secret, webhookTables) {
|
|
18
21
|
this.db = db;
|
|
19
22
|
this.tables = tables ?? { baselines: db.tables.baselines };
|
|
@@ -21,39 +24,70 @@ var BaselineModel = class {
|
|
|
21
24
|
this.secret = secret;
|
|
22
25
|
this.webhookTables = webhookTables;
|
|
23
26
|
}
|
|
24
|
-
async getFor(projectId, storyId, viewport, branch) {
|
|
27
|
+
async getFor(projectId, storyId, viewport, branch, browser = DEFAULT_BROWSER) {
|
|
25
28
|
return (await this.db.list(this.tables.baselines, {
|
|
26
|
-
where: and(eq(
|
|
29
|
+
where: and(eq(col(this.tables.baselines, "projectId"), projectId), eq(col(this.tables.baselines, "storyId"), storyId), eq(col(this.tables.baselines, "viewportName"), viewport), eq(col(this.tables.baselines, "branch"), branch), eq(col(this.tables.baselines, "browser"), browser)),
|
|
27
30
|
limit: 1
|
|
28
31
|
}))[0] ?? null;
|
|
29
32
|
}
|
|
30
|
-
async resolve(projectId, storyId, viewport, branch, defaultBranch) {
|
|
33
|
+
async resolve(projectId, storyId, viewport, branch, defaultBranch, browser = DEFAULT_BROWSER) {
|
|
31
34
|
if (branch !== defaultBranch) {
|
|
32
|
-
const own = await this.getFor(projectId, storyId, viewport, branch);
|
|
35
|
+
const own = await this.getFor(projectId, storyId, viewport, branch, browser);
|
|
33
36
|
if (own) return own;
|
|
34
37
|
}
|
|
35
|
-
return this.getFor(projectId, storyId, viewport, defaultBranch);
|
|
38
|
+
return this.getFor(projectId, storyId, viewport, defaultBranch, browser);
|
|
39
|
+
}
|
|
40
|
+
/** Whether the project has any baseline captured on this engine. */
|
|
41
|
+
async hasAny(projectId, browser) {
|
|
42
|
+
return (await this.db.list(this.tables.baselines, {
|
|
43
|
+
where: and(eq(col(this.tables.baselines, "projectId"), projectId), eq(col(this.tables.baselines, "browser"), browser)),
|
|
44
|
+
limit: 1
|
|
45
|
+
})).length > 0;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Record the fingerprint of a baseline that predates fingerprints, without touching its
|
|
49
|
+
* screenshot. Lets the next environment change be detected instead of diffing as a regression.
|
|
50
|
+
*/
|
|
51
|
+
async backfillFingerprint(baseline, infraHash, fingerprint) {
|
|
52
|
+
await this.db.update(this.tables.baselines, baseline.id, {
|
|
53
|
+
infraHash,
|
|
54
|
+
fingerprint
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* The content-addressed object a baseline should point at. A source that already is one is
|
|
59
|
+
* reused as is (no copy); a legacy per-build path is stored once by content.
|
|
60
|
+
*/
|
|
61
|
+
async contentFor(projectId, sourcePath) {
|
|
62
|
+
if (isContentPath(projectId, sourcePath)) return sourcePath;
|
|
63
|
+
return await storeScreenshot(this.storage, projectId, await this.storage.read(sourcePath));
|
|
36
64
|
}
|
|
37
65
|
async read(baseline) {
|
|
38
66
|
return await this.storage.read(baseline.screenshotPath);
|
|
39
67
|
}
|
|
40
|
-
async upsert(projectId, storyId, viewport, branch, snapshotId, sourcePath, infraHash) {
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
68
|
+
async upsert(projectId, storyId, viewport, branch, snapshotId, sourcePath, infraHash, options = {}) {
|
|
69
|
+
const write = async () => await this.#upsertAt(projectId, storyId, viewport, branch, snapshotId, await this.contentFor(projectId, sourcePath), infraHash, options);
|
|
70
|
+
return isContentPath(projectId, sourcePath) ? await write() : await withContentClaim(this.storage, projectId, write);
|
|
71
|
+
}
|
|
72
|
+
async #upsertAt(projectId, storyId, viewport, branch, snapshotId, screenshotPath, infraHash, options) {
|
|
73
|
+
const browser = options.browser ?? "chromium";
|
|
74
|
+
const existing = await this.getFor(projectId, storyId, viewport, branch, browser);
|
|
45
75
|
let baseline;
|
|
46
76
|
if (existing) {
|
|
47
77
|
baseline = await this.db.update(this.tables.baselines, existing.id, {
|
|
48
78
|
snapshotId,
|
|
49
79
|
screenshotPath,
|
|
50
80
|
infraHash: infraHash ?? existing.infraHash ?? null,
|
|
81
|
+
fingerprint: options.fingerprint ?? existing.fingerprint ?? null,
|
|
82
|
+
mode: options.mode ?? existing.mode ?? null,
|
|
51
83
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
52
84
|
});
|
|
53
85
|
if (this.webhookTables) await emitWebhookEvent(this.db, this.webhookTables, projectId, "baseline:updated", {
|
|
54
86
|
baselineId: baseline.id,
|
|
55
87
|
storyId,
|
|
56
88
|
viewport,
|
|
89
|
+
browser,
|
|
90
|
+
mode: options.mode ?? null,
|
|
57
91
|
branch,
|
|
58
92
|
snapshotId
|
|
59
93
|
}, this.secret);
|
|
@@ -68,6 +102,9 @@ var BaselineModel = class {
|
|
|
68
102
|
snapshotId,
|
|
69
103
|
screenshotPath,
|
|
70
104
|
infraHash: infraHash ?? null,
|
|
105
|
+
fingerprint: options.fingerprint ?? null,
|
|
106
|
+
mode: options.mode ?? null,
|
|
107
|
+
browser,
|
|
71
108
|
createdAt: now,
|
|
72
109
|
updatedAt: now
|
|
73
110
|
});
|
|
@@ -75,6 +112,8 @@ var BaselineModel = class {
|
|
|
75
112
|
baselineId: baseline.id,
|
|
76
113
|
storyId,
|
|
77
114
|
viewport,
|
|
115
|
+
browser,
|
|
116
|
+
mode: options.mode ?? null,
|
|
78
117
|
branch,
|
|
79
118
|
snapshotId
|
|
80
119
|
}, this.secret);
|
|
@@ -82,14 +121,22 @@ var BaselineModel = class {
|
|
|
82
121
|
return baseline;
|
|
83
122
|
}
|
|
84
123
|
async list(projectId) {
|
|
85
|
-
return await this.db.list(this.tables.baselines, { where: eq(
|
|
124
|
+
return await this.db.list(this.tables.baselines, { where: eq(col(this.tables.baselines, "projectId"), projectId) });
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Delete a baseline's own file. A content-addressed screenshot is shared with snapshots and other
|
|
128
|
+
* baselines, so it is left for the retention sweep instead.
|
|
129
|
+
*/
|
|
130
|
+
async discardFile(baseline) {
|
|
131
|
+
if (isContentPath(baseline.projectId, baseline.screenshotPath)) return;
|
|
132
|
+
try {
|
|
133
|
+
await this.storage.delete(baseline.screenshotPath);
|
|
134
|
+
} catch {}
|
|
86
135
|
}
|
|
87
136
|
async removeOrphans(projectId, validStoryIds) {
|
|
88
137
|
const toRemove = (await this.list(projectId)).filter((baseline) => !validStoryIds.has(baseline.storyId));
|
|
89
138
|
await Promise.all(toRemove.map(async (baseline) => {
|
|
90
|
-
|
|
91
|
-
await this.storage.delete(baseline.screenshotPath);
|
|
92
|
-
} catch {}
|
|
139
|
+
await this.discardFile(baseline);
|
|
93
140
|
await this.db.remove(this.tables.baselines, baseline.id);
|
|
94
141
|
}));
|
|
95
142
|
return toRemove.length;
|
|
@@ -97,9 +144,7 @@ var BaselineModel = class {
|
|
|
97
144
|
async removeForBranch(projectId, branch) {
|
|
98
145
|
const toRemove = (await this.list(projectId)).filter((baseline) => baseline.branch === branch);
|
|
99
146
|
await Promise.all(toRemove.map(async (baseline) => {
|
|
100
|
-
|
|
101
|
-
await this.storage.delete(baseline.screenshotPath);
|
|
102
|
-
} catch {}
|
|
147
|
+
await this.discardFile(baseline);
|
|
103
148
|
await this.db.remove(this.tables.baselines, baseline.id);
|
|
104
149
|
}));
|
|
105
150
|
return toRemove.length;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"baseline.mjs","names":[],"sources":["../../src/models/baseline.ts"],"sourcesContent":["/* oxlint-disable typescript/promise-function-async -- map callbacks return promises from async helpers */\n/** Per-branch baseline screenshots with default-branch fallback. */\nimport { and, eq, getTableColumns } from \"drizzle-orm\";\nimport type { SQLWrapper, Table } from \"drizzle-orm\";\nimport type { DatabaseAdapter } from \"../adapters/database.ts\";\nimport type { StorageAdapter } from \"../adapters/storage.ts\";\nimport { emitWebhookEvent } from \"../adapters/webhook-events.ts\";\nimport type { Baseline } from \"../schema/baseline.ts\";\nimport type { SecretInput } from \"../utils/encrypt.ts\";\nimport { baselinePath } from \"../utils/paths.ts\";\nimport { ulid } from \"../utils/ulid.ts\";\nimport type { WebhookTables } from \"./webhook.ts\";\n\n/** Tables required by {@link BaselineModel}. */\nexport interface BaselineTables {\n baselines: Table;\n}\n\n/** Data operations for baseline screenshots. */\nexport class BaselineModel {\n private readonly tables: BaselineTables;\n private readonly storage: StorageAdapter;\n private readonly secret?: SecretInput;\n private readonly webhookTables?: WebhookTables;\n constructor(\n private readonly db: DatabaseAdapter,\n tables?: BaselineTables,\n storage?: StorageAdapter,\n secret?: SecretInput,\n webhookTables?: WebhookTables,\n ) {\n this.tables = tables ?? { baselines: db.tables.baselines };\n this.storage = storage as StorageAdapter;\n this.secret = secret;\n this.webhookTables = webhookTables;\n }\n\n async getFor(\n projectId: string,\n storyId: string,\n viewport: string,\n branch: string,\n ): Promise<Baseline | null> {\n const rows = (await this.db.list(this.tables.baselines, {\n where: and(\n eq(getTableColumns(this.tables.baselines)[\"projectId\"] as unknown as SQLWrapper, projectId),\n eq(getTableColumns(this.tables.baselines)[\"storyId\"] as unknown as SQLWrapper, storyId),\n eq(\n getTableColumns(this.tables.baselines)[\"viewportName\"] as unknown as SQLWrapper,\n viewport,\n ),\n eq(getTableColumns(this.tables.baselines)[\"branch\"] as unknown as SQLWrapper, branch),\n ),\n limit: 1,\n })) as unknown as Baseline[];\n return rows[0] ?? null;\n }\n\n async resolve(\n projectId: string,\n storyId: string,\n viewport: string,\n branch: string,\n defaultBranch: string,\n ): Promise<Baseline | null> {\n if (branch !== defaultBranch) {\n const own = await this.getFor(projectId, storyId, viewport, branch);\n if (own) {\n return own;\n }\n }\n return this.getFor(projectId, storyId, viewport, defaultBranch);\n }\n\n async read(baseline: Baseline): Promise<Buffer> {\n return await this.storage.read(baseline.screenshotPath);\n }\n\n async upsert(\n projectId: string,\n storyId: string,\n viewport: string,\n branch: string,\n snapshotId: string,\n sourcePath: string,\n infraHash?: string | null,\n ): Promise<Baseline> {\n const screenshotPath = baselinePath(projectId, branch, storyId, viewport);\n const source = await this.storage.read(sourcePath);\n await this.storage.write(screenshotPath, source);\n\n const existing = await this.getFor(projectId, storyId, viewport, branch);\n let baseline: Baseline;\n if (existing) {\n baseline = (await this.db.update(this.tables.baselines, existing.id, {\n snapshotId,\n screenshotPath,\n infraHash: infraHash ?? existing.infraHash ?? null,\n updatedAt: new Date().toISOString(),\n })) as unknown as Baseline;\n if (this.webhookTables) {\n await emitWebhookEvent(\n this.db,\n this.webhookTables,\n projectId,\n \"baseline:updated\",\n {\n baselineId: baseline.id,\n storyId,\n viewport,\n branch,\n snapshotId,\n },\n this.secret,\n );\n }\n } else {\n const now = new Date().toISOString();\n baseline = (await this.db.insert(this.tables.baselines, {\n id: ulid(),\n projectId,\n storyId,\n viewportName: viewport,\n branch,\n snapshotId,\n screenshotPath,\n infraHash: infraHash ?? null,\n createdAt: now,\n updatedAt: now,\n })) as unknown as Baseline;\n if (this.webhookTables) {\n await emitWebhookEvent(\n this.db,\n this.webhookTables,\n projectId,\n \"baseline:created\",\n {\n baselineId: baseline.id,\n storyId,\n viewport,\n branch,\n snapshotId,\n },\n this.secret,\n );\n }\n }\n return baseline;\n }\n\n async list(projectId: string): Promise<Baseline[]> {\n return (await this.db.list(this.tables.baselines, {\n where: eq(\n getTableColumns(this.tables.baselines)[\"projectId\"] as unknown as SQLWrapper,\n projectId,\n ),\n })) as unknown as Baseline[];\n }\n\n async removeOrphans(projectId: string, validStoryIds: Set<string>): Promise<number> {\n const all = await this.list(projectId);\n const toRemove = all.filter((baseline) => !validStoryIds.has(baseline.storyId));\n await Promise.all(\n toRemove.map(async (baseline) => {\n try {\n await this.storage.delete(baseline.screenshotPath);\n } catch {\n // ignore missing file\n }\n await this.db.remove(this.tables.baselines, baseline.id);\n }),\n );\n return toRemove.length;\n }\n\n async removeForBranch(projectId: string, branch: string): Promise<number> {\n const all = await this.list(projectId);\n const toRemove = all.filter((baseline) => baseline.branch === branch);\n await Promise.all(\n toRemove.map(async (baseline) => {\n try {\n await this.storage.delete(baseline.screenshotPath);\n } catch {\n // ignore missing file\n }\n await this.db.remove(this.tables.baselines, baseline.id);\n }),\n );\n return toRemove.length;\n }\n\n // oxlint-disable-next-line eslint/require-await -- delegates to async removeForBranch via Promise.all\n async removeStaleBranches(\n projectId: string,\n staleBranches: ReadonlySet<string>,\n ): Promise<number> {\n if (staleBranches.size === 0) {\n return 0;\n }\n const results = await Promise.all(\n [...staleBranches].map((branch) => this.removeForBranch(projectId, branch)),\n );\n return results.reduce((sum, count) => sum + count, 0);\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"baseline.mjs","names":[],"sources":["../../src/models/baseline.ts"],"sourcesContent":["/* oxlint-disable typescript/promise-function-async -- map callbacks return promises from async helpers */\n/** Per-branch baseline screenshots with default-branch fallback. */\nimport { and, eq } from \"drizzle-orm\";\nimport type { Table } from \"drizzle-orm\";\nimport { DEFAULT_BROWSER } from \"../adapters/capture-runner.ts\";\nimport type { DatabaseAdapter } from \"../adapters/database.ts\";\nimport type { StorageAdapter } from \"../adapters/storage.ts\";\nimport { emitWebhookEvent } from \"../adapters/webhook-events.ts\";\nimport type { Baseline } from \"../schema/baseline.ts\";\nimport type { SecretInput } from \"../utils/encrypt.ts\";\nimport { isContentPath } from \"../utils/paths.ts\";\nimport { storeScreenshot, withContentClaim } from \"../utils/screenshot-store.ts\";\nimport { ulid } from \"../utils/ulid.ts\";\nimport { col } from \"./columns.ts\";\nimport type { WebhookTables } from \"./webhook.ts\";\n\n/** Extra identity/metadata for {@link BaselineModel.upsert}. */\nexport interface BaselineUpsertOptions {\n /** Engine the baseline belongs to (default chromium). */\n browser?: string;\n /** Resolved mode definition as JSON (display only). */\n mode?: string | null;\n /** JSON `Fingerprint` behind the `infraHash` being written. */\n fingerprint?: string | null;\n}\n\n/** Tables required by {@link BaselineModel}. */\nexport interface BaselineTables {\n baselines: Table;\n}\n\n/** Data operations for baseline screenshots. */\nexport class BaselineModel {\n private readonly tables: BaselineTables;\n private readonly storage: StorageAdapter;\n private readonly secret?: SecretInput;\n private readonly webhookTables?: WebhookTables;\n private readonly db: DatabaseAdapter;\n\n constructor(\n db: DatabaseAdapter,\n tables?: BaselineTables,\n storage?: StorageAdapter,\n secret?: SecretInput,\n webhookTables?: WebhookTables,\n ) {\n this.db = db;\n this.tables = tables ?? { baselines: db.tables.baselines };\n this.storage = storage as StorageAdapter;\n this.secret = secret;\n this.webhookTables = webhookTables;\n }\n\n async getFor(\n projectId: string,\n storyId: string,\n viewport: string,\n branch: string,\n browser: string = DEFAULT_BROWSER,\n ): Promise<Baseline | null> {\n const rows = (await this.db.list(this.tables.baselines, {\n where: and(\n eq(col(this.tables.baselines, \"projectId\"), projectId),\n eq(col(this.tables.baselines, \"storyId\"), storyId),\n eq(col(this.tables.baselines, \"viewportName\"), viewport),\n eq(col(this.tables.baselines, \"branch\"), branch),\n eq(col(this.tables.baselines, \"browser\"), browser),\n ),\n limit: 1,\n })) as unknown as Baseline[];\n return rows[0] ?? null;\n }\n\n async resolve(\n projectId: string,\n storyId: string,\n viewport: string,\n branch: string,\n defaultBranch: string,\n browser: string = DEFAULT_BROWSER,\n ): Promise<Baseline | null> {\n if (branch !== defaultBranch) {\n const own = await this.getFor(projectId, storyId, viewport, branch, browser);\n if (own) {\n return own;\n }\n }\n return this.getFor(projectId, storyId, viewport, defaultBranch, browser);\n }\n\n /** Whether the project has any baseline captured on this engine. */\n async hasAny(projectId: string, browser: string): Promise<boolean> {\n const rows = (await this.db.list(this.tables.baselines, {\n where: and(\n eq(col(this.tables.baselines, \"projectId\"), projectId),\n eq(col(this.tables.baselines, \"browser\"), browser),\n ),\n limit: 1,\n })) as unknown as Baseline[];\n return rows.length > 0;\n }\n\n /**\n * Record the fingerprint of a baseline that predates fingerprints, without touching its\n * screenshot. Lets the next environment change be detected instead of diffing as a regression.\n */\n async backfillFingerprint(\n baseline: Baseline,\n infraHash: string,\n fingerprint: string,\n ): Promise<void> {\n await this.db.update(this.tables.baselines, baseline.id, { infraHash, fingerprint });\n }\n\n /**\n * The content-addressed object a baseline should point at. A source that already is one is\n * reused as is (no copy); a legacy per-build path is stored once by content.\n */\n private async contentFor(projectId: string, sourcePath: string): Promise<string> {\n if (isContentPath(projectId, sourcePath)) {\n return sourcePath;\n }\n return await storeScreenshot(this.storage, projectId, await this.storage.read(sourcePath));\n }\n\n async read(baseline: Baseline): Promise<Buffer> {\n return await this.storage.read(baseline.screenshotPath);\n }\n\n async upsert(\n projectId: string,\n storyId: string,\n viewport: string,\n branch: string,\n snapshotId: string,\n sourcePath: string,\n infraHash?: string | null,\n options: BaselineUpsertOptions = {},\n ): Promise<Baseline> {\n const write = async (): Promise<Baseline> =>\n await this.#upsertAt(\n projectId,\n storyId,\n viewport,\n branch,\n snapshotId,\n await this.contentFor(projectId, sourcePath),\n infraHash,\n options,\n );\n // Storing a legacy source may reuse an object the sweep is about to delete: claim it until the\n // row points at it. A content source is already referenced by its snapshot row.\n return isContentPath(projectId, sourcePath)\n ? await write()\n : await withContentClaim(this.storage, projectId, write);\n }\n\n async #upsertAt(\n projectId: string,\n storyId: string,\n viewport: string,\n branch: string,\n snapshotId: string,\n screenshotPath: string,\n infraHash: string | null | undefined,\n options: BaselineUpsertOptions,\n ): Promise<Baseline> {\n const browser = options.browser ?? DEFAULT_BROWSER;\n\n const existing = await this.getFor(projectId, storyId, viewport, branch, browser);\n let baseline: Baseline;\n if (existing) {\n baseline = (await this.db.update(this.tables.baselines, existing.id, {\n snapshotId,\n screenshotPath,\n infraHash: infraHash ?? existing.infraHash ?? null,\n fingerprint: options.fingerprint ?? existing.fingerprint ?? null,\n mode: options.mode ?? existing.mode ?? null,\n updatedAt: new Date().toISOString(),\n })) as unknown as Baseline;\n if (this.webhookTables) {\n await emitWebhookEvent(\n this.db,\n this.webhookTables,\n projectId,\n \"baseline:updated\",\n {\n baselineId: baseline.id,\n storyId,\n viewport,\n browser,\n mode: options.mode ?? null,\n branch,\n snapshotId,\n },\n this.secret,\n );\n }\n } else {\n const now = new Date().toISOString();\n baseline = (await this.db.insert(this.tables.baselines, {\n id: ulid(),\n projectId,\n storyId,\n viewportName: viewport,\n branch,\n snapshotId,\n screenshotPath,\n infraHash: infraHash ?? null,\n fingerprint: options.fingerprint ?? null,\n mode: options.mode ?? null,\n browser,\n createdAt: now,\n updatedAt: now,\n })) as unknown as Baseline;\n if (this.webhookTables) {\n await emitWebhookEvent(\n this.db,\n this.webhookTables,\n projectId,\n \"baseline:created\",\n {\n baselineId: baseline.id,\n storyId,\n viewport,\n browser,\n mode: options.mode ?? null,\n branch,\n snapshotId,\n },\n this.secret,\n );\n }\n }\n return baseline;\n }\n\n async list(projectId: string): Promise<Baseline[]> {\n return (await this.db.list(this.tables.baselines, {\n where: eq(col(this.tables.baselines, \"projectId\"), projectId),\n })) as unknown as Baseline[];\n }\n\n /**\n * Delete a baseline's own file. A content-addressed screenshot is shared with snapshots and other\n * baselines, so it is left for the retention sweep instead.\n */\n private async discardFile(baseline: Baseline): Promise<void> {\n if (isContentPath(baseline.projectId, baseline.screenshotPath)) {\n return;\n }\n try {\n await this.storage.delete(baseline.screenshotPath);\n } catch {\n // ignore missing file\n }\n }\n\n async removeOrphans(projectId: string, validStoryIds: Set<string>): Promise<number> {\n const all = await this.list(projectId);\n const toRemove = all.filter((baseline) => !validStoryIds.has(baseline.storyId));\n await Promise.all(\n toRemove.map(async (baseline) => {\n await this.discardFile(baseline);\n await this.db.remove(this.tables.baselines, baseline.id);\n }),\n );\n return toRemove.length;\n }\n\n async removeForBranch(projectId: string, branch: string): Promise<number> {\n const all = await this.list(projectId);\n const toRemove = all.filter((baseline) => baseline.branch === branch);\n await Promise.all(\n toRemove.map(async (baseline) => {\n await this.discardFile(baseline);\n await this.db.remove(this.tables.baselines, baseline.id);\n }),\n );\n return toRemove.length;\n }\n\n // oxlint-disable-next-line eslint/require-await -- delegates to async removeForBranch via Promise.all\n async removeStaleBranches(\n projectId: string,\n staleBranches: ReadonlySet<string>,\n ): Promise<number> {\n if (staleBranches.size === 0) {\n return 0;\n }\n const results = await Promise.all(\n [...staleBranches].map((branch) => this.removeForBranch(projectId, branch)),\n );\n return results.reduce((sum, count) => sum + count, 0);\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import __tsdown_shims_path from 'node:path';
|
|
2
|
+
import __tsdown_shims_url from 'node:url';
|
|
3
|
+
import { DatabaseAdapter } from "../adapter/database.mjs";
|
|
4
|
+
import { BuildComponent } from "../schema/build-component.mjs";
|
|
5
|
+
import { Table } from "drizzle-orm";
|
|
6
|
+
//#region src/models/build-component.d.ts
|
|
7
|
+
/** Tables required by {@link BuildComponentModel}. */
|
|
8
|
+
interface BuildComponentTables {
|
|
9
|
+
buildComponents: Table;
|
|
10
|
+
}
|
|
11
|
+
/** What a build records of one component. */
|
|
12
|
+
type BuildComponentInput = Pick<BuildComponent, "importPath" | "title" | "storyCount" | "hash">;
|
|
13
|
+
/** Data operations for the components each published build holds. */
|
|
14
|
+
declare class BuildComponentModel {
|
|
15
|
+
#private;
|
|
16
|
+
constructor(db: DatabaseAdapter, tables?: BuildComponentTables);
|
|
17
|
+
/** Components of one build, ordered by title then file. */
|
|
18
|
+
listByBuild(buildId: string): Promise<BuildComponent[]>;
|
|
19
|
+
/** Rows of one story file across the project's builds, newest first: "which builds contain it". */
|
|
20
|
+
listByImportPath(projectId: string, importPath: string): Promise<BuildComponent[]>;
|
|
21
|
+
/** Replace a build's rows with `components`; repeating a publish never duplicates. */
|
|
22
|
+
replaceForBuild(projectId: string, buildId: string, components: readonly BuildComponentInput[]): Promise<void>;
|
|
23
|
+
/** Forget a build's rows (the foreign key cascades too; stub tables have none). */
|
|
24
|
+
removeForBuild(buildId: string): Promise<number>;
|
|
25
|
+
}
|
|
26
|
+
//#endregion
|
|
27
|
+
export { BuildComponentInput, BuildComponentModel, BuildComponentTables };
|
|
28
|
+
//# sourceMappingURL=build-component.d.mts.map
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import "node:path";
|
|
2
|
+
import "node:url";
|
|
3
|
+
import.meta.url;
|
|
4
|
+
import { ulid } from "../utils/ulid.mjs";
|
|
5
|
+
import { col } from "./columns.mjs";
|
|
6
|
+
import { and, asc, desc, eq } from "drizzle-orm";
|
|
7
|
+
//#region src/models/build-component.ts
|
|
8
|
+
/** Component rows of published builds (`build_components`, ADR 0006 decision 9). */
|
|
9
|
+
/** Data operations for the components each published build holds. */
|
|
10
|
+
var BuildComponentModel = class {
|
|
11
|
+
#db;
|
|
12
|
+
#table;
|
|
13
|
+
constructor(db, tables) {
|
|
14
|
+
this.#db = db;
|
|
15
|
+
this.#table = (tables ?? { buildComponents: db.tables.buildComponents }).buildComponents;
|
|
16
|
+
}
|
|
17
|
+
/** Components of one build, ordered by title then file. */
|
|
18
|
+
async listByBuild(buildId) {
|
|
19
|
+
return (await this.#db.list(this.#table, {
|
|
20
|
+
where: eq(col(this.#table, "buildId"), buildId),
|
|
21
|
+
orderBy: asc(col(this.#table, "title"))
|
|
22
|
+
})).toSorted((a, b) => a.title.localeCompare(b.title) || a.importPath.localeCompare(b.importPath));
|
|
23
|
+
}
|
|
24
|
+
/** Rows of one story file across the project's builds, newest first: "which builds contain it". */
|
|
25
|
+
async listByImportPath(projectId, importPath) {
|
|
26
|
+
return await this.#db.list(this.#table, {
|
|
27
|
+
where: and(eq(col(this.#table, "projectId"), projectId), eq(col(this.#table, "importPath"), importPath)),
|
|
28
|
+
orderBy: desc(col(this.#table, "createdAt"))
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
/** Replace a build's rows with `components`; repeating a publish never duplicates. */
|
|
32
|
+
async replaceForBuild(projectId, buildId, components) {
|
|
33
|
+
await this.removeForBuild(buildId);
|
|
34
|
+
const createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
35
|
+
for (const component of components) await this.#db.insert(this.#table, {
|
|
36
|
+
id: ulid(),
|
|
37
|
+
projectId,
|
|
38
|
+
buildId,
|
|
39
|
+
createdAt,
|
|
40
|
+
...component
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
/** Forget a build's rows (the foreign key cascades too; stub tables have none). */
|
|
44
|
+
async removeForBuild(buildId) {
|
|
45
|
+
const rows = await this.#db.list(this.#table, { where: eq(col(this.#table, "buildId"), buildId) });
|
|
46
|
+
await Promise.all(rows.map(async (row) => {
|
|
47
|
+
await this.#db.remove(this.#table, row.id);
|
|
48
|
+
}));
|
|
49
|
+
return rows.length;
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
//#endregion
|
|
53
|
+
export { BuildComponentModel };
|
|
54
|
+
|
|
55
|
+
//# sourceMappingURL=build-component.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build-component.mjs","names":[],"sources":["../../src/models/build-component.ts"],"sourcesContent":["/** Component rows of published builds (`build_components`, ADR 0006 decision 9). */\nimport { and, asc, desc, eq } from \"drizzle-orm\";\nimport type { Table } from \"drizzle-orm\";\nimport type { DatabaseAdapter } from \"../adapters/database.ts\";\nimport type { BuildComponent } from \"../schema/build-component.ts\";\nimport { ulid } from \"../utils/ulid.ts\";\nimport { col } from \"./columns.ts\";\n\n/** Tables required by {@link BuildComponentModel}. */\nexport interface BuildComponentTables {\n buildComponents: Table;\n}\n\n/** What a build records of one component. */\nexport type BuildComponentInput = Pick<\n BuildComponent,\n \"importPath\" | \"title\" | \"storyCount\" | \"hash\"\n>;\n\n/** Data operations for the components each published build holds. */\nexport class BuildComponentModel {\n readonly #db: DatabaseAdapter;\n readonly #table: Table;\n\n constructor(db: DatabaseAdapter, tables?: BuildComponentTables) {\n this.#db = db;\n this.#table = (tables ?? { buildComponents: db.tables.buildComponents }).buildComponents;\n }\n\n /** Components of one build, ordered by title then file. */\n async listByBuild(buildId: string): Promise<BuildComponent[]> {\n const rows = (await this.#db.list(this.#table, {\n where: eq(col(this.#table, \"buildId\"), buildId),\n orderBy: asc(col(this.#table, \"title\")),\n })) as unknown as BuildComponent[];\n return rows.toSorted(\n (a, b) => a.title.localeCompare(b.title) || a.importPath.localeCompare(b.importPath),\n );\n }\n\n /** Rows of one story file across the project's builds, newest first: \"which builds contain it\". */\n async listByImportPath(projectId: string, importPath: string): Promise<BuildComponent[]> {\n const rows = (await this.#db.list(this.#table, {\n where: and(\n eq(col(this.#table, \"projectId\"), projectId),\n eq(col(this.#table, \"importPath\"), importPath),\n ),\n orderBy: desc(col(this.#table, \"createdAt\")),\n })) as unknown as BuildComponent[];\n return rows;\n }\n\n /** Replace a build's rows with `components`; repeating a publish never duplicates. */\n async replaceForBuild(\n projectId: string,\n buildId: string,\n components: readonly BuildComponentInput[],\n ): Promise<void> {\n await this.removeForBuild(buildId);\n const createdAt = new Date().toISOString();\n for (const component of components) {\n // eslint-disable-next-line no-await-in-loop -- rows are few; sequential inserts keep ids ordered\n await this.#db.insert(this.#table, {\n id: ulid(),\n projectId,\n buildId,\n createdAt,\n ...component,\n });\n }\n }\n\n /** Forget a build's rows (the foreign key cascades too; stub tables have none). */\n async removeForBuild(buildId: string): Promise<number> {\n const rows = (await this.#db.list(this.#table, {\n where: eq(col(this.#table, \"buildId\"), buildId),\n })) as unknown as BuildComponent[];\n await Promise.all(\n rows.map(async (row) => {\n await this.#db.remove(this.#table, row.id);\n }),\n );\n return rows.length;\n }\n}\n"],"mappings":";;;;;;;;;AAoBA,IAAa,sBAAb,MAAiC;CAC/B;CACA;CAEA,YAAY,IAAqB,QAA+B;EAC9D,KAAK,MAAM;EACX,KAAK,UAAU,UAAU,EAAE,iBAAiB,GAAG,OAAO,gBAAgB,EAAA,CAAG;CAC3E;;CAGA,MAAM,YAAY,SAA4C;EAK5D,QAAO,MAJa,KAAK,IAAI,KAAK,KAAK,QAAQ;GAC7C,OAAO,GAAG,IAAI,KAAK,QAAQ,SAAS,GAAG,OAAO;GAC9C,SAAS,IAAI,IAAI,KAAK,QAAQ,OAAO,CAAC;EACxC,CAAC,EAAA,CACW,UACT,GAAG,MAAM,EAAE,MAAM,cAAc,EAAE,KAAK,KAAK,EAAE,WAAW,cAAc,EAAE,UAAU,CACrF;CACF;;CAGA,MAAM,iBAAiB,WAAmB,YAA+C;EAQvF,OAAO,MAPa,KAAK,IAAI,KAAK,KAAK,QAAQ;GAC7C,OAAO,IACL,GAAG,IAAI,KAAK,QAAQ,WAAW,GAAG,SAAS,GAC3C,GAAG,IAAI,KAAK,QAAQ,YAAY,GAAG,UAAU,CAC/C;GACA,SAAS,KAAK,IAAI,KAAK,QAAQ,WAAW,CAAC;EAC7C,CAAC;CAEH;;CAGA,MAAM,gBACJ,WACA,SACA,YACe;EACf,MAAM,KAAK,eAAe,OAAO;EACjC,MAAM,6BAAY,IAAI,KAAK,EAAA,CAAE,YAAY;EACzC,KAAK,MAAM,aAAa,YAEtB,MAAM,KAAK,IAAI,OAAO,KAAK,QAAQ;GACjC,IAAI,KAAK;GACT;GACA;GACA;GACA,GAAG;EACL,CAAC;CAEL;;CAGA,MAAM,eAAe,SAAkC;EACrD,MAAM,OAAQ,MAAM,KAAK,IAAI,KAAK,KAAK,QAAQ,EAC7C,OAAO,GAAG,IAAI,KAAK,QAAQ,SAAS,GAAG,OAAO,EAChD,CAAC;EACD,MAAM,QAAQ,IACZ,KAAK,IAAI,OAAO,QAAQ;GACtB,MAAM,KAAK,IAAI,OAAO,KAAK,QAAQ,IAAI,EAAE;EAC3C,CAAC,CACH;EACA,OAAO,KAAK;CACd;AACF"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import __tsdown_shims_path from 'node:path';
|
|
2
|
+
import __tsdown_shims_url from 'node:url';
|
|
3
|
+
import { BuildReview } from "../schema/build-review.mjs";
|
|
4
|
+
//#region src/models/build-review-status.d.ts
|
|
5
|
+
/** What {@link deriveReviewStatus} depends on. */
|
|
6
|
+
interface ReviewStatusInput {
|
|
7
|
+
/** `testing` is on: the snapshot rollup decides and verdicts never gate. */
|
|
8
|
+
testing: boolean;
|
|
9
|
+
/** The project's review switch. */
|
|
10
|
+
review: boolean;
|
|
11
|
+
/** `projects.review_approvals`: approvals a build needs (at least 1). */
|
|
12
|
+
approvalsRequired: number;
|
|
13
|
+
/** Every verdict given to the build, in any order; see {@link latestVerdicts}. */
|
|
14
|
+
reviews: readonly Pick<BuildReview, "id" | "userId" | "verdict" | "createdAt">[];
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* The status the verdicts give the build, or `null` when they do not decide it (`testing` is on,
|
|
18
|
+
* or review is off and the build was accepted when it was published).
|
|
19
|
+
*
|
|
20
|
+
* - `rejected` when any reviewer's latest verdict is `rejected`;
|
|
21
|
+
* - `approved` once the required approvals are present among the latest verdicts;
|
|
22
|
+
* - `pending` otherwise.
|
|
23
|
+
*/
|
|
24
|
+
declare function deriveReviewStatus(input: ReviewStatusInput): "approved" | "rejected" | "pending" | null;
|
|
25
|
+
/** Each reviewer's latest verdict (newest `createdAt`, then newest id), oldest reviewer first. */
|
|
26
|
+
declare function latestVerdicts<T extends Pick<BuildReview, "id" | "userId" | "createdAt">>(reviews: readonly T[]): T[];
|
|
27
|
+
//#endregion
|
|
28
|
+
export { ReviewStatusInput, deriveReviewStatus, latestVerdicts };
|
|
29
|
+
//# sourceMappingURL=build-review-status.d.mts.map
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import "node:path";
|
|
2
|
+
import "node:url";
|
|
3
|
+
import.meta.url;
|
|
4
|
+
//#region src/models/build-review-status.ts
|
|
5
|
+
/** Reviewer key standing for verdicts without a user (auth off or an admin token): one reviewer. */
|
|
6
|
+
const ANONYMOUS_REVIEWER = "";
|
|
7
|
+
/**
|
|
8
|
+
* The status the verdicts give the build, or `null` when they do not decide it (`testing` is on,
|
|
9
|
+
* or review is off and the build was accepted when it was published).
|
|
10
|
+
*
|
|
11
|
+
* - `rejected` when any reviewer's latest verdict is `rejected`;
|
|
12
|
+
* - `approved` once the required approvals are present among the latest verdicts;
|
|
13
|
+
* - `pending` otherwise.
|
|
14
|
+
*/
|
|
15
|
+
function deriveReviewStatus(input) {
|
|
16
|
+
if (input.testing || !input.review) return null;
|
|
17
|
+
const latest = latestVerdicts(input.reviews);
|
|
18
|
+
if (latest.some((review) => review.verdict === "rejected")) return "rejected";
|
|
19
|
+
return latest.filter((review) => review.verdict === "approved").length >= Math.max(1, input.approvalsRequired) ? "approved" : "pending";
|
|
20
|
+
}
|
|
21
|
+
/** Each reviewer's latest verdict (newest `createdAt`, then newest id), oldest reviewer first. */
|
|
22
|
+
function latestVerdicts(reviews) {
|
|
23
|
+
const latest = /* @__PURE__ */ new Map();
|
|
24
|
+
for (const review of reviews) {
|
|
25
|
+
const key = review.userId ?? ANONYMOUS_REVIEWER;
|
|
26
|
+
const current = latest.get(key);
|
|
27
|
+
if (current === void 0 || isNewer(review, current)) latest.set(key, review);
|
|
28
|
+
}
|
|
29
|
+
return [...latest.values()].toSorted((a, b) => isNewer(b, a) ? -1 : 1);
|
|
30
|
+
}
|
|
31
|
+
function isNewer(a, b) {
|
|
32
|
+
return a.createdAt === b.createdAt ? a.id > b.id : a.createdAt > b.createdAt;
|
|
33
|
+
}
|
|
34
|
+
//#endregion
|
|
35
|
+
export { deriveReviewStatus, latestVerdicts };
|
|
36
|
+
|
|
37
|
+
//# sourceMappingURL=build-review-status.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build-review-status.mjs","names":[],"sources":["../../src/models/build-review-status.ts"],"sourcesContent":["/**\n * What the build-level verdicts make of a build (ADR 0013, decision 3). Pure: callers read the\n * reviews and the project, apply the result, and stay free of branching.\n */\nimport type { BuildReview } from \"../schema/build-review.ts\";\n\n/** What {@link deriveReviewStatus} depends on. */\nexport interface ReviewStatusInput {\n /** `testing` is on: the snapshot rollup decides and verdicts never gate. */\n testing: boolean;\n /** The project's review switch. */\n review: boolean;\n /** `projects.review_approvals`: approvals a build needs (at least 1). */\n approvalsRequired: number;\n /** Every verdict given to the build, in any order; see {@link latestVerdicts}. */\n reviews: readonly Pick<BuildReview, \"id\" | \"userId\" | \"verdict\" | \"createdAt\">[];\n}\n\n/** Reviewer key standing for verdicts without a user (auth off or an admin token): one reviewer. */\nconst ANONYMOUS_REVIEWER = \"\";\n\n/**\n * The status the verdicts give the build, or `null` when they do not decide it (`testing` is on,\n * or review is off and the build was accepted when it was published).\n *\n * - `rejected` when any reviewer's latest verdict is `rejected`;\n * - `approved` once the required approvals are present among the latest verdicts;\n * - `pending` otherwise.\n */\nexport function deriveReviewStatus(\n input: ReviewStatusInput,\n): \"approved\" | \"rejected\" | \"pending\" | null {\n if (input.testing || !input.review) {\n return null;\n }\n const latest = latestVerdicts(input.reviews);\n if (latest.some((review) => review.verdict === \"rejected\")) {\n return \"rejected\";\n }\n const approvals = latest.filter((review) => review.verdict === \"approved\").length;\n return approvals >= Math.max(1, input.approvalsRequired) ? \"approved\" : \"pending\";\n}\n\n/** Each reviewer's latest verdict (newest `createdAt`, then newest id), oldest reviewer first. */\nexport function latestVerdicts<T extends Pick<BuildReview, \"id\" | \"userId\" | \"createdAt\">>(\n reviews: readonly T[],\n): T[] {\n const latest = new Map<string, T>();\n for (const review of reviews) {\n const key = review.userId ?? ANONYMOUS_REVIEWER;\n const current = latest.get(key);\n if (current === undefined || isNewer(review, current)) {\n latest.set(key, review);\n }\n }\n return [...latest.values()].toSorted((a, b) => (isNewer(b, a) ? -1 : 1));\n}\n\nfunction isNewer(\n a: Pick<BuildReview, \"id\" | \"createdAt\">,\n b: Pick<BuildReview, \"id\" | \"createdAt\">,\n): boolean {\n return a.createdAt === b.createdAt ? a.id > b.id : a.createdAt > b.createdAt;\n}\n"],"mappings":";;;;;AAmBA,MAAM,qBAAqB;;;;;;;;;AAU3B,SAAgB,mBACd,OAC4C;CAC5C,IAAI,MAAM,WAAW,CAAC,MAAM,QAC1B,OAAO;CAET,MAAM,SAAS,eAAe,MAAM,OAAO;CAC3C,IAAI,OAAO,MAAM,WAAW,OAAO,YAAY,UAAU,GACvD,OAAO;CAGT,OADkB,OAAO,QAAQ,WAAW,OAAO,YAAY,UAAU,CAAC,CAAC,UACvD,KAAK,IAAI,GAAG,MAAM,iBAAiB,IAAI,aAAa;AAC1E;;AAGA,SAAgB,eACd,SACK;CACL,MAAM,yBAAS,IAAI,IAAe;CAClC,KAAK,MAAM,UAAU,SAAS;EAC5B,MAAM,MAAM,OAAO,UAAU;EAC7B,MAAM,UAAU,OAAO,IAAI,GAAG;EAC9B,IAAI,YAAY,KAAA,KAAa,QAAQ,QAAQ,OAAO,GAClD,OAAO,IAAI,KAAK,MAAM;CAE1B;CACA,OAAO,CAAC,GAAG,OAAO,OAAO,CAAC,CAAC,CAAC,UAAU,GAAG,MAAO,QAAQ,GAAG,CAAC,IAAI,KAAK,CAAE;AACzE;AAEA,SAAS,QACP,GACA,GACS;CACT,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,KAAK,EAAE,KAAK,EAAE,YAAY,EAAE;AACrE"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import __tsdown_shims_path from 'node:path';
|
|
2
|
+
import __tsdown_shims_url from 'node:url';
|
|
3
|
+
import { DatabaseAdapter } from "../adapter/database.mjs";
|
|
4
|
+
import { ReviewVerdict } from "../types.mjs";
|
|
5
|
+
import { BuildReview } from "../schema/build-review.mjs";
|
|
6
|
+
import { Table } from "drizzle-orm";
|
|
7
|
+
//#region src/models/build-review.d.ts
|
|
8
|
+
/** Tables required by {@link BuildReviewModel}. */
|
|
9
|
+
interface BuildReviewTables {
|
|
10
|
+
buildReviews: Table;
|
|
11
|
+
}
|
|
12
|
+
/** Data operations for the verdicts reviewers give a build. */
|
|
13
|
+
declare class BuildReviewModel {
|
|
14
|
+
#private;
|
|
15
|
+
constructor(db: DatabaseAdapter, tables?: BuildReviewTables);
|
|
16
|
+
/**
|
|
17
|
+
* Record a verdict. A reviewer rewrites theirs by adding another row; the newest row counts.
|
|
18
|
+
* `createdAt` is strictly increasing within a build, so "newest" never ties.
|
|
19
|
+
*/
|
|
20
|
+
add(buildId: string, userId: string | null, verdict: ReviewVerdict, body?: string): Promise<BuildReview>;
|
|
21
|
+
/** Every verdict of a build, oldest first. */
|
|
22
|
+
listByBuild(buildId: string): Promise<BuildReview[]>;
|
|
23
|
+
/** Each reviewer's latest verdict on a build. */
|
|
24
|
+
latestByBuild(buildId: string): Promise<BuildReview[]>;
|
|
25
|
+
}
|
|
26
|
+
//#endregion
|
|
27
|
+
export { BuildReviewModel, BuildReviewTables };
|
|
28
|
+
//# sourceMappingURL=build-review.d.mts.map
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import "node:path";
|
|
2
|
+
import "node:url";
|
|
3
|
+
import.meta.url;
|
|
4
|
+
import { ulid } from "../utils/ulid.mjs";
|
|
5
|
+
import { col } from "./columns.mjs";
|
|
6
|
+
import { latestVerdicts } from "./build-review-status.mjs";
|
|
7
|
+
import { asc, eq } from "drizzle-orm";
|
|
8
|
+
//#region src/models/build-review.ts
|
|
9
|
+
/** Build-level review verdicts (ADR 0013, decision 3). */
|
|
10
|
+
/** Data operations for the verdicts reviewers give a build. */
|
|
11
|
+
var BuildReviewModel = class {
|
|
12
|
+
#db;
|
|
13
|
+
#table;
|
|
14
|
+
constructor(db, tables) {
|
|
15
|
+
this.#db = db;
|
|
16
|
+
this.#table = (tables ?? { buildReviews: db.tables.buildReviews }).buildReviews;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Record a verdict. A reviewer rewrites theirs by adding another row; the newest row counts.
|
|
20
|
+
* `createdAt` is strictly increasing within a build, so "newest" never ties.
|
|
21
|
+
*/
|
|
22
|
+
async add(buildId, userId, verdict, body) {
|
|
23
|
+
const newest = (await this.listByBuild(buildId)).at(-1)?.createdAt;
|
|
24
|
+
return await this.#db.insert(this.#table, {
|
|
25
|
+
id: ulid(),
|
|
26
|
+
buildId,
|
|
27
|
+
userId,
|
|
28
|
+
verdict,
|
|
29
|
+
body: body === void 0 || body.length === 0 ? null : body,
|
|
30
|
+
createdAt: after(newest)
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
/** Every verdict of a build, oldest first. */
|
|
34
|
+
async listByBuild(buildId) {
|
|
35
|
+
return (await this.#db.list(this.#table, {
|
|
36
|
+
where: eq(col(this.#table, "buildId"), buildId),
|
|
37
|
+
orderBy: asc(col(this.#table, "createdAt"))
|
|
38
|
+
})).toSorted((a, b) => a.createdAt.localeCompare(b.createdAt) || a.id.localeCompare(b.id));
|
|
39
|
+
}
|
|
40
|
+
/** Each reviewer's latest verdict on a build. */
|
|
41
|
+
async latestByBuild(buildId) {
|
|
42
|
+
return latestVerdicts(await this.listByBuild(buildId));
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
/** Now, or one millisecond past `newest` when the clock has not moved on. */
|
|
46
|
+
function after(newest) {
|
|
47
|
+
const now = Date.now();
|
|
48
|
+
const floor = newest === void 0 ? Number.NEGATIVE_INFINITY : Date.parse(newest) + 1;
|
|
49
|
+
return new Date(Math.max(now, floor)).toISOString();
|
|
50
|
+
}
|
|
51
|
+
//#endregion
|
|
52
|
+
export { BuildReviewModel };
|
|
53
|
+
|
|
54
|
+
//# sourceMappingURL=build-review.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build-review.mjs","names":[],"sources":["../../src/models/build-review.ts"],"sourcesContent":["/** Build-level review verdicts (ADR 0013, decision 3). */\nimport { asc, eq } from \"drizzle-orm\";\nimport type { Table } from \"drizzle-orm\";\nimport type { DatabaseAdapter } from \"../adapters/database.ts\";\nimport type { BuildReview } from \"../schema/build-review.ts\";\nimport type { ReviewVerdict } from \"../types.ts\";\nimport { ulid } from \"../utils/ulid.ts\";\nimport { latestVerdicts } from \"./build-review-status.ts\";\nimport { col } from \"./columns.ts\";\n\n/** Tables required by {@link BuildReviewModel}. */\nexport interface BuildReviewTables {\n buildReviews: Table;\n}\n\n/** Data operations for the verdicts reviewers give a build. */\nexport class BuildReviewModel {\n readonly #db: DatabaseAdapter;\n readonly #table: Table;\n\n constructor(db: DatabaseAdapter, tables?: BuildReviewTables) {\n this.#db = db;\n this.#table = (tables ?? { buildReviews: db.tables.buildReviews }).buildReviews;\n }\n\n /**\n * Record a verdict. A reviewer rewrites theirs by adding another row; the newest row counts.\n * `createdAt` is strictly increasing within a build, so \"newest\" never ties.\n */\n async add(\n buildId: string,\n userId: string | null,\n verdict: ReviewVerdict,\n body?: string,\n ): Promise<BuildReview> {\n const newest = (await this.listByBuild(buildId)).at(-1)?.createdAt;\n return (await this.#db.insert(this.#table, {\n id: ulid(),\n buildId,\n userId,\n verdict,\n body: body === undefined || body.length === 0 ? null : body,\n createdAt: after(newest),\n })) as unknown as BuildReview;\n }\n\n /** Every verdict of a build, oldest first. */\n async listByBuild(buildId: string): Promise<BuildReview[]> {\n const rows = (await this.#db.list(this.#table, {\n where: eq(col(this.#table, \"buildId\"), buildId),\n orderBy: asc(col(this.#table, \"createdAt\")),\n })) as unknown as BuildReview[];\n return rows.toSorted(\n (a, b) => a.createdAt.localeCompare(b.createdAt) || a.id.localeCompare(b.id),\n );\n }\n\n /** Each reviewer's latest verdict on a build. */\n async latestByBuild(buildId: string): Promise<BuildReview[]> {\n return latestVerdicts(await this.listByBuild(buildId));\n }\n}\n\n/** Now, or one millisecond past `newest` when the clock has not moved on. */\nfunction after(newest: string | undefined): string {\n const now = Date.now();\n const floor = newest === undefined ? Number.NEGATIVE_INFINITY : Date.parse(newest) + 1;\n return new Date(Math.max(now, floor)).toISOString();\n}\n"],"mappings":";;;;;;;;;;AAgBA,IAAa,mBAAb,MAA8B;CAC5B;CACA;CAEA,YAAY,IAAqB,QAA4B;EAC3D,KAAK,MAAM;EACX,KAAK,UAAU,UAAU,EAAE,cAAc,GAAG,OAAO,aAAa,EAAA,CAAG;CACrE;;;;;CAMA,MAAM,IACJ,SACA,QACA,SACA,MACsB;EACtB,MAAM,UAAU,MAAM,KAAK,YAAY,OAAO,EAAA,CAAG,GAAG,EAAE,CAAC,EAAE;EACzD,OAAQ,MAAM,KAAK,IAAI,OAAO,KAAK,QAAQ;GACzC,IAAI,KAAK;GACT;GACA;GACA;GACA,MAAM,SAAS,KAAA,KAAa,KAAK,WAAW,IAAI,OAAO;GACvD,WAAW,MAAM,MAAM;EACzB,CAAC;CACH;;CAGA,MAAM,YAAY,SAAyC;EAKzD,QAAO,MAJa,KAAK,IAAI,KAAK,KAAK,QAAQ;GAC7C,OAAO,GAAG,IAAI,KAAK,QAAQ,SAAS,GAAG,OAAO;GAC9C,SAAS,IAAI,IAAI,KAAK,QAAQ,WAAW,CAAC;EAC5C,CAAC,EAAA,CACW,UACT,GAAG,MAAM,EAAE,UAAU,cAAc,EAAE,SAAS,KAAK,EAAE,GAAG,cAAc,EAAE,EAAE,CAC7E;CACF;;CAGA,MAAM,cAAc,SAAyC;EAC3D,OAAO,eAAe,MAAM,KAAK,YAAY,OAAO,CAAC;CACvD;AACF;;AAGA,SAAS,MAAM,QAAoC;CACjD,MAAM,MAAM,KAAK,IAAI;CACrB,MAAM,QAAQ,WAAW,KAAA,IAAY,OAAO,oBAAoB,KAAK,MAAM,MAAM,IAAI;CACrF,OAAO,IAAI,KAAK,KAAK,IAAI,KAAK,KAAK,CAAC,CAAC,CAAC,YAAY;AACpD"}
|
package/dist/models/build.d.mts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import __tsdown_shims_path from 'node:path';
|
|
2
2
|
import __tsdown_shims_url from 'node:url';
|
|
3
3
|
import { DatabaseAdapter } from "../adapter/database.mjs";
|
|
4
|
+
import { Logger } from "../logger.mjs";
|
|
4
5
|
import { BuildStatus } from "../types.mjs";
|
|
5
6
|
import { Build } from "../schema/build.mjs";
|
|
6
7
|
import { Table } from "drizzle-orm";
|
|
@@ -13,9 +14,11 @@ interface BuildTables {
|
|
|
13
14
|
}
|
|
14
15
|
/** Data operations for build records. */
|
|
15
16
|
declare class BuildModel {
|
|
16
|
-
private readonly db;
|
|
17
17
|
private readonly tables;
|
|
18
|
-
|
|
18
|
+
private readonly db;
|
|
19
|
+
private readonly logger;
|
|
20
|
+
/** `logger` only reports a status write refused by a `cancelled` build, at debug. */
|
|
21
|
+
constructor(db: DatabaseAdapter, tables?: BuildTables, logger?: Logger);
|
|
19
22
|
create(projectId: string, input: BuildCreateInput): Promise<Build>;
|
|
20
23
|
get(id: string): Promise<Build | null>;
|
|
21
24
|
list(projectId: string, filter?: BuildListFilter): Promise<Build[]>;
|
|
@@ -30,13 +33,30 @@ declare class BuildModel {
|
|
|
30
33
|
id: string;
|
|
31
34
|
gitDefaultBranch: string;
|
|
32
35
|
}): Promise<Build | null>;
|
|
33
|
-
|
|
36
|
+
/**
|
|
37
|
+
* Patch a build. A `cancelled` build is terminal (ADR 0006): a patch that carries another status
|
|
38
|
+
* is refused, the row is returned as it is, so a capture that ends after a cancel cannot move
|
|
39
|
+
* the build. Only {@link BuildModel.resetStatus} leaves `cancelled`.
|
|
40
|
+
*/
|
|
41
|
+
update(id: string, patch: Partial<Pick<Build, "status" | "public" | "message" | "authorEmail" | "authorName">>, options?: {
|
|
42
|
+
reset?: boolean;
|
|
43
|
+
}): Promise<Build>;
|
|
34
44
|
setStatus(id: string, status: BuildStatus): Promise<Build>;
|
|
45
|
+
/**
|
|
46
|
+
* Put a finished build back to `status` for a retry. The one write that leaves `cancelled`
|
|
47
|
+
* (a user chose it), so it skips the terminal guard of {@link BuildModel.update}.
|
|
48
|
+
*/
|
|
49
|
+
resetStatus(id: string, status: BuildStatus): Promise<Build>;
|
|
35
50
|
/**
|
|
36
51
|
* Record the affected-capture computation for a build. Idempotent: the CLI
|
|
37
52
|
* posts this once after creating the build and before uploading the bundle.
|
|
38
53
|
*/
|
|
39
54
|
setAffected(id: string, input: AffectedComputation): Promise<Build>;
|
|
55
|
+
/**
|
|
56
|
+
* Store the results of the build's `.test()` entries (`TestResult[]`, as JSON), or clear them
|
|
57
|
+
* with null. Written once per capture run, by the run that ran the tests.
|
|
58
|
+
*/
|
|
59
|
+
setTestResults(id: string, results: readonly unknown[] | null): Promise<Build>;
|
|
40
60
|
/**
|
|
41
61
|
* Parse a build's affected import paths. Returns `null` for full capture
|
|
42
62
|
* (no computation, or an unreadable payload) — callers render everything.
|
|
@@ -81,6 +101,8 @@ interface BuildListFilter {
|
|
|
81
101
|
branch?: string;
|
|
82
102
|
labelKey?: string;
|
|
83
103
|
labelValue?: string;
|
|
104
|
+
/** Newest builds to return at most. */
|
|
105
|
+
limit?: number;
|
|
84
106
|
}
|
|
85
107
|
//#endregion
|
|
86
108
|
export { AffectedComputation, BuildCreateInput, BuildListFilter, BuildModel, BuildTables, isPublicBuild };
|