@storyshelf/core 0.6.1 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +49 -32
- package/dist/adapter/capture-queue.d.mts +44 -2
- package/dist/adapter/capture-runner.d.mts +127 -10
- package/dist/adapter/capture-runner.mjs +5 -12
- package/dist/adapter/database.d.mts +27 -8
- package/dist/adapter/database.mjs.map +1 -1
- package/dist/adapter/design.d.mts +85 -0
- package/dist/adapter/design.mjs +28 -0
- package/dist/adapter/design.mjs.map +1 -0
- package/dist/adapter/metadata.d.mts +3 -10
- package/dist/adapter/notifier.d.mts +6 -3
- package/dist/adapter/notifier.mjs +5 -1
- package/dist/adapter/oauth.d.mts +52 -0
- package/dist/adapter/setup.d.mts +4 -5
- package/dist/adapter/setup.mjs +2 -1
- package/dist/adapter/setup.mjs.map +1 -1
- package/dist/adapter/webhook-events.mjs +1 -1
- package/dist/adapters/capture-target-offers.d.mts +28 -0
- package/dist/adapters/capture-target-offers.mjs +58 -0
- package/dist/adapters/capture-target-offers.mjs.map +1 -0
- package/dist/adapters/capture-target.d.mts +71 -0
- package/dist/adapters/capture-target.mjs +76 -0
- package/dist/adapters/capture-target.mjs.map +1 -0
- package/dist/adapters/notifier/build-comment.d.mts +24 -0
- package/dist/adapters/notifier/build-comment.mjs +24 -0
- package/dist/adapters/notifier/build-comment.mjs.map +1 -0
- package/dist/adapters/notifier/build-state.d.mts +99 -0
- package/dist/adapters/notifier/build-state.mjs +218 -0
- package/dist/adapters/notifier/build-state.mjs.map +1 -0
- package/dist/adapters/notifier/events.d.mts +4 -2
- package/dist/adapters/notifier/events.mjs +31 -18
- package/dist/adapters/notifier/events.mjs.map +1 -1
- package/dist/adapters/notifier/provider.d.mts +53 -7
- package/dist/adapters/notifier/provider.mjs +12 -0
- package/dist/adapters/notifier/provider.mjs.map +1 -0
- package/dist/adapters/notifier/state.d.mts +24 -0
- package/dist/adapters/notifier/state.mjs +66 -0
- package/dist/adapters/notifier/state.mjs.map +1 -0
- package/dist/adapters/notifier/types.d.mts +51 -2
- package/dist/adapters/remote-browser.d.mts +87 -0
- package/dist/adapters/remote-browser.mjs +103 -0
- package/dist/adapters/remote-browser.mjs.map +1 -0
- package/dist/adapters/render-runs.d.mts +25 -0
- package/dist/adapters/render-runs.mjs +41 -0
- package/dist/adapters/render-runs.mjs.map +1 -0
- package/dist/adapters/setup-runner.mjs +2 -4
- package/dist/adapters/setup-runner.mjs.map +1 -1
- package/dist/adapters/sources.d.mts +12 -0
- package/dist/adapters/sources.mjs +20 -0
- package/dist/adapters/sources.mjs.map +1 -0
- package/dist/adapters/validate.mjs +25 -18
- package/dist/adapters/validate.mjs.map +1 -1
- package/dist/ai.d.mts +105 -0
- package/dist/ai.mjs +19 -0
- package/dist/ai.mjs.map +1 -0
- package/dist/auth.d.mts +8 -1
- package/dist/auth.mjs +14 -1
- package/dist/auth.mjs.map +1 -0
- package/dist/capture/a11y/axe-source.d.mts +18 -0
- package/dist/capture/a11y/axe-source.mjs +37 -0
- package/dist/capture/a11y/axe-source.mjs.map +1 -0
- package/dist/capture/a11y/in-page.d.mts +27 -0
- package/dist/capture/a11y/in-page.mjs +60 -0
- package/dist/capture/a11y/in-page.mjs.map +1 -0
- package/dist/capture/a11y/map.d.mts +24 -0
- package/dist/capture/a11y/map.mjs +48 -0
- package/dist/capture/a11y/map.mjs.map +1 -0
- package/dist/capture/a11y/options.d.mts +23 -0
- package/dist/capture/a11y/options.mjs +75 -0
- package/dist/capture/a11y/options.mjs.map +1 -0
- package/dist/capture/adapter.d.mts +41 -3
- package/dist/capture/adapter.mjs.map +1 -1
- package/dist/capture/affected.d.mts +2 -0
- package/dist/capture/affected.mjs +13 -4
- package/dist/capture/affected.mjs.map +1 -1
- package/dist/capture/attempt-logger.mjs +84 -0
- package/dist/capture/attempt-logger.mjs.map +1 -0
- package/dist/capture/browsers.d.mts +35 -0
- package/dist/capture/browsers.mjs +67 -0
- package/dist/capture/browsers.mjs.map +1 -0
- package/dist/capture/build-state/deliver.d.mts +32 -0
- package/dist/capture/build-state/deliver.mjs +118 -0
- package/dist/capture/build-state/deliver.mjs.map +1 -0
- package/dist/capture/build-state/index.d.mts +8 -0
- package/dist/capture/build-state/notify-job.d.mts +56 -0
- package/dist/capture/build-state/notify-job.mjs +133 -0
- package/dist/capture/build-state/notify-job.mjs.map +1 -0
- package/dist/capture/build-state/publisher.d.mts +23 -0
- package/dist/capture/build-state/publisher.mjs +107 -0
- package/dist/capture/build-state/publisher.mjs.map +1 -0
- package/dist/capture/build-state/resume.d.mts +9 -0
- package/dist/capture/build-state/resume.mjs +47 -0
- package/dist/capture/build-state/resume.mjs.map +1 -0
- package/dist/capture/build-state/types.d.mts +49 -0
- package/dist/capture/catalog/catalogue.d.mts +22 -0
- package/dist/capture/catalog/catalogue.mjs +53 -0
- package/dist/capture/catalog/catalogue.mjs.map +1 -0
- package/dist/capture/catalog/components.d.mts +23 -0
- package/dist/capture/catalog/components.mjs +55 -0
- package/dist/capture/catalog/components.mjs.map +1 -0
- package/dist/capture/catalog/history.d.mts +30 -0
- package/dist/capture/catalog/history.mjs +51 -0
- package/dist/capture/catalog/history.mjs.map +1 -0
- package/dist/capture/catalog/hook.d.mts +14 -0
- package/dist/capture/catalog/hook.mjs +19 -0
- package/dist/capture/catalog/hook.mjs.map +1 -0
- package/dist/capture/catalog/index.d.mts +11 -0
- package/dist/capture/catalog/load.d.mts +26 -0
- package/dist/capture/catalog/load.mjs +49 -0
- package/dist/capture/catalog/load.mjs.map +1 -0
- package/dist/capture/catalog/parse.d.mts +15 -0
- package/dist/capture/catalog/parse.mjs +56 -0
- package/dist/capture/catalog/parse.mjs.map +1 -0
- package/dist/capture/catalog/read.d.mts +12 -0
- package/dist/capture/catalog/read.mjs +33 -0
- package/dist/capture/catalog/read.mjs.map +1 -0
- package/dist/capture/catalog/types.d.mts +33 -0
- package/dist/capture/consumer/capture-handler.d.mts +58 -0
- package/dist/capture/consumer/capture-handler.mjs +118 -0
- package/dist/capture/consumer/capture-handler.mjs.map +1 -0
- package/dist/capture/consumer/idle.d.mts +17 -0
- package/dist/capture/consumer/idle.mjs +25 -0
- package/dist/capture/consumer/idle.mjs.map +1 -0
- package/dist/capture/consumer/index.d.mts +13 -0
- package/dist/capture/consumer/index.mjs +139 -0
- package/dist/capture/consumer/index.mjs.map +1 -0
- package/dist/capture/consumer/loop.mjs +110 -0
- package/dist/capture/consumer/loop.mjs.map +1 -0
- package/dist/capture/consumer/metrics.d.mts +41 -0
- package/dist/capture/consumer/metrics.mjs +38 -0
- package/dist/capture/consumer/metrics.mjs.map +1 -0
- package/dist/capture/consumer/retry.d.mts +33 -0
- package/dist/capture/consumer/retry.mjs +34 -0
- package/dist/capture/consumer/retry.mjs.map +1 -0
- package/dist/capture/consumer/run-job.mjs +195 -0
- package/dist/capture/consumer/run-job.mjs.map +1 -0
- package/dist/capture/consumer/startup.mjs +38 -0
- package/dist/capture/consumer/startup.mjs.map +1 -0
- package/dist/capture/consumer/types.d.mts +97 -0
- package/dist/capture/crash-failure.d.mts +25 -0
- package/dist/capture/crash-failure.mjs +70 -0
- package/dist/capture/crash-failure.mjs.map +1 -0
- package/dist/capture/db-queue/cadence.d.mts +32 -0
- package/dist/capture/db-queue/cadence.mjs +38 -0
- package/dist/capture/db-queue/cadence.mjs.map +1 -0
- package/dist/capture/db-queue/claim.d.mts +9 -0
- package/dist/capture/db-queue/claim.mjs +132 -0
- package/dist/capture/db-queue/claim.mjs.map +1 -0
- package/dist/capture/db-queue/clock.mjs +12 -0
- package/dist/capture/db-queue/clock.mjs.map +1 -0
- package/dist/capture/db-queue/codec.mjs +76 -0
- package/dist/capture/db-queue/codec.mjs.map +1 -0
- package/dist/capture/db-queue/index.d.mts +20 -0
- package/dist/capture/db-queue/index.mjs +89 -0
- package/dist/capture/db-queue/index.mjs.map +1 -0
- package/dist/capture/db-queue/keeper.d.mts +36 -0
- package/dist/capture/db-queue/keeper.mjs +92 -0
- package/dist/capture/db-queue/keeper.mjs.map +1 -0
- package/dist/capture/db-queue/lease.mjs +46 -0
- package/dist/capture/db-queue/lease.mjs.map +1 -0
- package/dist/capture/db-queue/lifecycle.mjs +36 -0
- package/dist/capture/db-queue/lifecycle.mjs.map +1 -0
- package/dist/capture/db-queue/operations.mjs +94 -0
- package/dist/capture/db-queue/operations.mjs.map +1 -0
- package/dist/capture/db-queue/poll-loop.d.mts +30 -0
- package/dist/capture/db-queue/poll-loop.mjs +89 -0
- package/dist/capture/db-queue/poll-loop.mjs.map +1 -0
- package/dist/capture/db-queue/poll.d.mts +10 -0
- package/dist/capture/db-queue/poll.mjs +69 -0
- package/dist/capture/db-queue/poll.mjs.map +1 -0
- package/dist/capture/db-queue/timers.d.mts +12 -0
- package/dist/capture/db-queue/timers.mjs +37 -0
- package/dist/capture/db-queue/timers.mjs.map +1 -0
- package/dist/capture/db-queue/types.d.mts +83 -0
- package/dist/capture/db-queue/types.mjs +12 -0
- package/dist/capture/db-queue/types.mjs.map +1 -0
- package/dist/capture/db-queue/wake.mjs +48 -0
- package/dist/capture/db-queue/wake.mjs.map +1 -0
- package/dist/capture/design.mjs +39 -0
- package/dist/capture/design.mjs.map +1 -0
- package/dist/capture/diff-record.mjs +23 -3
- package/dist/capture/diff-record.mjs.map +1 -1
- package/dist/capture/dispatch.d.mts +12 -7
- package/dist/capture/dispatch.mjs +86 -107
- package/dist/capture/dispatch.mjs.map +1 -1
- package/dist/capture/enrich.mjs +60 -0
- package/dist/capture/enrich.mjs.map +1 -0
- package/dist/capture/failure-sets.mjs +22 -0
- package/dist/capture/failure-sets.mjs.map +1 -0
- package/dist/capture/fingerprint.d.mts +42 -0
- package/dist/capture/fingerprint.mjs +83 -0
- package/dist/capture/fingerprint.mjs.map +1 -0
- package/dist/capture/forked/channel.d.mts +25 -0
- package/dist/capture/forked/child.d.mts +34 -0
- package/dist/capture/forked/child.mjs +202 -0
- package/dist/capture/forked/child.mjs.map +1 -0
- package/dist/capture/forked/crash-tracker.mjs +23 -0
- package/dist/capture/forked/crash-tracker.mjs.map +1 -0
- package/dist/capture/forked/decision.d.mts +26 -0
- package/dist/capture/forked/decision.mjs +27 -0
- package/dist/capture/forked/decision.mjs.map +1 -0
- package/dist/capture/forked/exit.mjs +12 -0
- package/dist/capture/forked/exit.mjs.map +1 -0
- package/dist/capture/forked/fork.d.mts +31 -0
- package/dist/capture/forked/fork.mjs +93 -0
- package/dist/capture/forked/fork.mjs.map +1 -0
- package/dist/capture/forked/forked-capture.d.mts +40 -0
- package/dist/capture/forked/forked-capture.mjs +240 -0
- package/dist/capture/forked/forked-capture.mjs.map +1 -0
- package/dist/capture/forked/lease-book.mjs +46 -0
- package/dist/capture/forked/lease-book.mjs.map +1 -0
- package/dist/capture/forked/protocol.d.mts +38 -0
- package/dist/capture/forked/protocol.mjs +25 -0
- package/dist/capture/forked/protocol.mjs.map +1 -0
- package/dist/capture/forked/runs.mjs +75 -0
- package/dist/capture/forked/runs.mjs.map +1 -0
- package/dist/capture/isolate.mjs +141 -0
- package/dist/capture/isolate.mjs.map +1 -0
- package/dist/capture/job-lease.d.mts +28 -0
- package/dist/capture/job-lease.mjs +57 -0
- package/dist/capture/job-lease.mjs.map +1 -0
- package/dist/capture/matrix.mjs +298 -0
- package/dist/capture/matrix.mjs.map +1 -0
- package/dist/capture/modes.d.mts +25 -0
- package/dist/capture/modes.mjs +107 -0
- package/dist/capture/modes.mjs.map +1 -0
- package/dist/capture/orchestrator.d.mts +37 -5
- package/dist/capture/orchestrator.mjs +118 -107
- package/dist/capture/orchestrator.mjs.map +1 -1
- package/dist/capture/pipeline.d.mts +9 -2
- package/dist/capture/pipeline.mjs +250 -45
- package/dist/capture/pipeline.mjs.map +1 -1
- package/dist/capture/progress.d.mts +11 -0
- package/dist/capture/progress.mjs +28 -0
- package/dist/capture/progress.mjs.map +1 -0
- package/dist/capture/publish/accept.d.mts +39 -0
- package/dist/capture/publish/accept.mjs +28 -0
- package/dist/capture/publish/accept.mjs.map +1 -0
- package/dist/capture/publish/complete.d.mts +46 -0
- package/dist/capture/publish/complete.mjs +64 -0
- package/dist/capture/publish/complete.mjs.map +1 -0
- package/dist/capture/publish/extract.d.mts +20 -0
- package/dist/capture/publish/extract.mjs +119 -0
- package/dist/capture/publish/extract.mjs.map +1 -0
- package/dist/capture/publish/handler.d.mts +28 -0
- package/dist/capture/publish/handler.mjs +62 -0
- package/dist/capture/publish/handler.mjs.map +1 -0
- package/dist/capture/publish/index.d.mts +10 -0
- package/dist/capture/publish/limits.d.mts +37 -0
- package/dist/capture/publish/limits.mjs +39 -0
- package/dist/capture/publish/limits.mjs.map +1 -0
- package/dist/capture/publish/published.d.mts +15 -0
- package/dist/capture/publish/published.mjs +18 -0
- package/dist/capture/publish/published.mjs.map +1 -0
- package/dist/capture/publish/run.d.mts +33 -0
- package/dist/capture/publish/run.mjs +30 -0
- package/dist/capture/publish/run.mjs.map +1 -0
- package/dist/capture/rediff.d.mts +1 -1
- package/dist/capture/rediff.mjs +4 -3
- package/dist/capture/rediff.mjs.map +1 -1
- package/dist/capture/render.mjs +53 -0
- package/dist/capture/render.mjs.map +1 -0
- package/dist/capture/scratch.d.mts +44 -0
- package/dist/capture/scratch.mjs +138 -0
- package/dist/capture/scratch.mjs.map +1 -0
- package/dist/capture/shard-plan.mjs +128 -0
- package/dist/capture/shard-plan.mjs.map +1 -0
- package/dist/capture/shards/context.mjs +51 -0
- package/dist/capture/shards/context.mjs.map +1 -0
- package/dist/capture/shards/execute.mjs +174 -0
- package/dist/capture/shards/execute.mjs.map +1 -0
- package/dist/capture/shards/fan-out.mjs +126 -0
- package/dist/capture/shards/fan-out.mjs.map +1 -0
- package/dist/capture/shards/history.mjs +35 -0
- package/dist/capture/shards/history.mjs.map +1 -0
- package/dist/capture/shards/reduce.mjs +97 -0
- package/dist/capture/shards/reduce.mjs.map +1 -0
- package/dist/capture/sizing.mjs +1 -11
- package/dist/capture/sizing.mjs.map +1 -1
- package/dist/capture/skip-checks.mjs +2 -65
- package/dist/capture/skip-checks.mjs.map +1 -1
- package/dist/capture/statics.d.mts +7 -8
- package/dist/capture/statics.mjs +25 -71
- package/dist/capture/statics.mjs.map +1 -1
- package/dist/capture/storybook-urls.d.mts +40 -0
- package/dist/capture/storybook-urls.mjs +41 -0
- package/dist/capture/storybook-urls.mjs.map +1 -0
- package/dist/capture/storybook.d.mts +12 -4
- package/dist/capture/storybook.mjs +56 -16
- package/dist/capture/storybook.mjs.map +1 -1
- package/dist/capture/target.mjs +21 -0
- package/dist/capture/target.mjs.map +1 -0
- package/dist/capture/targets.d.mts +25 -0
- package/dist/capture/targets.mjs +32 -0
- package/dist/capture/targets.mjs.map +1 -0
- package/dist/capture/tests.d.mts +37 -0
- package/dist/capture/tests.mjs +126 -0
- package/dist/capture/tests.mjs.map +1 -0
- package/dist/capture/token-chunks.mjs +74 -0
- package/dist/capture/token-chunks.mjs.map +1 -0
- package/dist/capture.d.mts +60 -7
- package/dist/capture.mjs +48 -3
- package/dist/config.d.mts +121 -12
- package/dist/config.mjs +9 -6
- package/dist/config.mjs.map +1 -1
- package/dist/diff.d.mts +9 -3
- package/dist/diff.mjs +16 -3
- package/dist/diff.mjs.map +1 -0
- package/dist/index.d.mts +1 -1
- package/dist/insights/budget.d.mts +20 -0
- package/dist/insights/budget.mjs +33 -0
- package/dist/insights/budget.mjs.map +1 -0
- package/dist/insights/envelope.d.mts +59 -0
- package/dist/insights/envelope.mjs +60 -0
- package/dist/insights/envelope.mjs.map +1 -0
- package/dist/insights/evidence-health.d.mts +32 -0
- package/dist/insights/evidence-health.mjs +28 -0
- package/dist/insights/evidence-health.mjs.map +1 -0
- package/dist/insights/evidence.d.mts +55 -0
- package/dist/insights/evidence.mjs +64 -0
- package/dist/insights/evidence.mjs.map +1 -0
- package/dist/insights/hash.d.mts +18 -0
- package/dist/insights/hash.mjs +21 -0
- package/dist/insights/hash.mjs.map +1 -0
- package/dist/insights/limits.d.mts +24 -0
- package/dist/insights/limits.mjs +24 -0
- package/dist/insights/limits.mjs.map +1 -0
- package/dist/insights/prompts/v1.d.mts +13 -0
- package/dist/insights/prompts/v1.mjs +34 -0
- package/dist/insights/prompts/v1.mjs.map +1 -0
- package/dist/insights/redact.d.mts +9 -0
- package/dist/insights/redact.mjs +24 -0
- package/dist/insights/redact.mjs.map +1 -0
- package/dist/insights/text.mjs +44 -0
- package/dist/insights/text.mjs.map +1 -0
- package/dist/insights.d.mts +11 -0
- package/dist/insights.mjs +12 -0
- package/dist/logger.mjs +17 -1
- package/dist/logger.mjs.map +1 -1
- package/dist/models/ai-budget-alert.d.mts +23 -0
- package/dist/models/ai-budget-alert.mjs +38 -0
- package/dist/models/ai-budget-alert.mjs.map +1 -0
- package/dist/models/ai-usage.d.mts +40 -0
- package/dist/models/ai-usage.mjs +61 -0
- package/dist/models/ai-usage.mjs.map +1 -0
- package/dist/models/baseline-archive.d.mts +84 -0
- package/dist/models/baseline-archive.mjs +106 -0
- package/dist/models/baseline-archive.mjs.map +1 -0
- package/dist/models/baseline-export.d.mts +19 -0
- package/dist/models/baseline-export.mjs +136 -0
- package/dist/models/baseline-export.mjs.map +1 -0
- package/dist/models/baseline-import.mjs +118 -0
- package/dist/models/baseline-import.mjs.map +1 -0
- package/dist/models/baseline-transfer.d.mts +49 -0
- package/dist/models/baseline-transfer.mjs +121 -0
- package/dist/models/baseline-transfer.mjs.map +1 -0
- package/dist/models/baseline.d.mts +32 -5
- package/dist/models/baseline.mjs +65 -20
- package/dist/models/baseline.mjs.map +1 -1
- package/dist/models/build-component.d.mts +28 -0
- package/dist/models/build-component.mjs +55 -0
- package/dist/models/build-component.mjs.map +1 -0
- package/dist/models/build-review-status.d.mts +29 -0
- package/dist/models/build-review-status.mjs +37 -0
- package/dist/models/build-review-status.mjs.map +1 -0
- package/dist/models/build-review.d.mts +28 -0
- package/dist/models/build-review.mjs +54 -0
- package/dist/models/build-review.mjs.map +1 -0
- package/dist/models/build.d.mts +25 -3
- package/dist/models/build.mjs +50 -11
- package/dist/models/build.mjs.map +1 -1
- package/dist/models/capture-attempt.d.mts +7 -1
- package/dist/models/capture-attempt.mjs +29 -4
- package/dist/models/capture-attempt.mjs.map +1 -1
- package/dist/models/capture-job.d.mts +106 -0
- package/dist/models/capture-job.mjs +288 -0
- package/dist/models/capture-job.mjs.map +1 -0
- package/dist/models/capture-log.d.mts +1 -1
- package/dist/models/capture-log.mjs +4 -3
- package/dist/models/capture-log.mjs.map +1 -1
- package/dist/models/capture-shard.d.mts +62 -0
- package/dist/models/capture-shard.mjs +190 -0
- package/dist/models/capture-shard.mjs.map +1 -0
- package/dist/models/changeset.d.mts +48 -0
- package/dist/models/changeset.mjs +81 -0
- package/dist/models/changeset.mjs.map +1 -0
- package/dist/models/columns.mjs +19 -0
- package/dist/models/columns.mjs.map +1 -0
- package/dist/models/comment.d.mts +4 -1
- package/dist/models/comment.mjs +7 -4
- package/dist/models/comment.mjs.map +1 -1
- package/dist/models/component.d.mts +43 -0
- package/dist/models/component.mjs +60 -0
- package/dist/models/component.mjs.map +1 -0
- package/dist/models/connections.d.mts +41 -0
- package/dist/models/connections.mjs +112 -0
- package/dist/models/connections.mjs.map +1 -0
- package/dist/models/content-ref.d.mts +1 -1
- package/dist/models/content-ref.mjs +1 -1
- package/dist/models/content-ref.mjs.map +1 -1
- package/dist/models/credentials.mjs +12 -2
- package/dist/models/credentials.mjs.map +1 -1
- package/dist/models/design-credentials.d.mts +41 -0
- package/dist/models/design-credentials.mjs +86 -0
- package/dist/models/design-credentials.mjs.map +1 -0
- package/dist/models/design-health.d.mts +16 -0
- package/dist/models/design-health.mjs +25 -0
- package/dist/models/design-health.mjs.map +1 -0
- package/dist/models/design-nodes.d.mts +83 -0
- package/dist/models/design-nodes.mjs +222 -0
- package/dist/models/design-nodes.mjs.map +1 -0
- package/dist/models/design-renders.mjs +74 -0
- package/dist/models/design-renders.mjs.map +1 -0
- package/dist/models/design-resolve.d.mts +29 -0
- package/dist/models/design-resolve.mjs +41 -0
- package/dist/models/design-resolve.mjs.map +1 -0
- package/dist/models/design.d.mts +39 -0
- package/dist/models/design.mjs +90 -0
- package/dist/models/design.mjs.map +1 -0
- package/dist/models/inherited-ratio.d.mts +21 -0
- package/dist/models/inherited-ratio.mjs +22 -0
- package/dist/models/inherited-ratio.mjs.map +1 -0
- package/dist/models/insight.d.mts +70 -0
- package/dist/models/insight.mjs +162 -0
- package/dist/models/insight.mjs.map +1 -0
- package/dist/models/label.d.mts +4 -2
- package/dist/models/label.mjs +25 -10
- package/dist/models/label.mjs.map +1 -1
- package/dist/models/member.d.mts +1 -1
- package/dist/models/member.mjs +5 -4
- package/dist/models/member.mjs.map +1 -1
- package/dist/models/notification-channel.d.mts +37 -3
- package/dist/models/notification-channel.mjs +88 -9
- package/dist/models/notification-channel.mjs.map +1 -1
- package/dist/models/notification-subscription.d.mts +1 -2
- package/dist/models/notification-subscription.mjs +7 -9
- package/dist/models/notification-subscription.mjs.map +1 -1
- package/dist/models/project-group-mapping.d.mts +6 -1
- package/dist/models/project-group-mapping.mjs +14 -4
- package/dist/models/project-group-mapping.mjs.map +1 -1
- package/dist/models/project.d.mts +9 -1
- package/dist/models/project.mjs +9 -4
- package/dist/models/project.mjs.map +1 -1
- package/dist/models/public-origin.d.mts +63 -0
- package/dist/models/public-origin.mjs +114 -0
- package/dist/models/public-origin.mjs.map +1 -0
- package/dist/models/review-switch-off.d.mts +14 -0
- package/dist/models/review-switch-off.mjs +26 -0
- package/dist/models/review-switch-off.mjs.map +1 -0
- package/dist/models/site-settings.d.mts +23 -0
- package/dist/models/site-settings.mjs +48 -0
- package/dist/models/site-settings.mjs.map +1 -0
- package/dist/models/snapshot.d.mts +36 -1
- package/dist/models/snapshot.mjs +50 -3
- package/dist/models/snapshot.mjs.map +1 -1
- package/dist/models/token.d.mts +13 -1
- package/dist/models/token.mjs +23 -5
- package/dist/models/token.mjs.map +1 -1
- package/dist/models/usage.d.mts +76 -0
- package/dist/models/usage.mjs +149 -0
- package/dist/models/usage.mjs.map +1 -0
- package/dist/models/user.d.mts +1 -1
- package/dist/models/user.mjs +4 -3
- package/dist/models/user.mjs.map +1 -1
- package/dist/models/webhook.d.mts +15 -1
- package/dist/models/webhook.mjs +33 -4
- package/dist/models/webhook.mjs.map +1 -1
- package/dist/models.d.mts +26 -3
- package/dist/models.mjs +27 -5
- package/dist/options.d.mts +18 -0
- package/dist/options.mjs +90 -0
- package/dist/options.mjs.map +1 -0
- package/dist/paths.d.mts +19 -4
- package/dist/paths.mjs +51 -10
- package/dist/paths.mjs.map +1 -1
- package/dist/retention/ai.d.mts +18 -0
- package/dist/retention/ai.mjs +23 -0
- package/dist/retention/ai.mjs.map +1 -0
- package/dist/retention/pending.mjs +47 -0
- package/dist/retention/pending.mjs.map +1 -0
- package/dist/retention/screenshot-sweep.d.mts +51 -0
- package/dist/retention/screenshot-sweep.mjs +128 -0
- package/dist/retention/screenshot-sweep.mjs.map +1 -0
- package/dist/retention.d.mts +16 -2
- package/dist/retention.mjs +71 -14
- package/dist/retention.mjs.map +1 -1
- package/dist/schema/baseline.d.mts +6 -0
- package/dist/schema/build-component.d.mts +25 -0
- package/dist/schema/build-review.d.mts +17 -0
- package/dist/schema/build.d.mts +7 -0
- package/dist/schema/capture-job.d.mts +60 -0
- package/dist/schema/capture-shard.d.mts +35 -0
- package/dist/schema/comment.d.mts +2 -0
- package/dist/schema/connection.d.mts +24 -0
- package/dist/schema/design.d.mts +58 -0
- package/dist/schema/insight.d.mts +54 -0
- package/dist/schema/notification.d.mts +4 -0
- package/dist/schema/project.d.mts +21 -0
- package/dist/schema/site-setting.d.mts +21 -0
- package/dist/schema/snapshot.d.mts +12 -0
- package/dist/schema/usage.d.mts +30 -0
- package/dist/schema.d.mts +11 -3
- package/dist/serverless.d.mts +21 -0
- package/dist/serverless.mjs +37 -0
- package/dist/serverless.mjs.map +1 -0
- package/dist/test-helpers/create-project.d.mts +1 -1
- package/dist/test-helpers/deferred.d.mts +39 -0
- package/dist/test-helpers/deferred.mjs +61 -0
- package/dist/test-helpers/deferred.mjs.map +1 -0
- package/dist/test-helpers/design-contract.d.mts +21 -0
- package/dist/test-helpers/design-contract.mjs +40 -0
- package/dist/test-helpers/design-contract.mjs.map +1 -0
- package/dist/test-helpers/fake-database.mjs +14 -2
- package/dist/test-helpers/fake-database.mjs.map +1 -1
- package/dist/test-helpers/fake-design.d.mts +43 -0
- package/dist/test-helpers/fake-design.mjs +114 -0
- package/dist/test-helpers/fake-design.mjs.map +1 -0
- package/dist/test-helpers/fake-notifier.d.mts +21 -0
- package/dist/test-helpers/fake-notifier.mjs +44 -0
- package/dist/test-helpers/fake-notifier.mjs.map +1 -0
- package/dist/test-helpers/fake-tables.mjs +255 -67
- package/dist/test-helpers/fake-tables.mjs.map +1 -1
- package/dist/test-helpers/manual-clock.d.mts +27 -0
- package/dist/test-helpers/manual-clock.mjs +56 -0
- package/dist/test-helpers/manual-clock.mjs.map +1 -0
- package/dist/test-helpers/notifier-contract.d.mts +7 -1
- package/dist/test-helpers/notifier-contract.mjs +32 -3
- package/dist/test-helpers/notifier-contract.mjs.map +1 -1
- package/dist/test-helpers/queue-contract.d.mts +16 -2
- package/dist/test-helpers/queue-contract.mjs +195 -1
- package/dist/test-helpers/queue-contract.mjs.map +1 -1
- package/dist/test-helpers/queue-multi-instance.d.mts +20 -0
- package/dist/test-helpers/queue-multi-instance.mjs +206 -0
- package/dist/test-helpers/queue-multi-instance.mjs.map +1 -0
- package/dist/test-helpers/runner-contract.mjs +11 -0
- package/dist/test-helpers/runner-contract.mjs.map +1 -1
- package/dist/test-helpers/sql-chunks.mjs +23 -0
- package/dist/test-helpers/sql-chunks.mjs.map +1 -1
- package/dist/test-helpers/storage-contract.mjs +1 -1
- package/dist/test-helpers.d.mts +9 -4
- package/dist/test-helpers.mjs +8 -3
- package/dist/types.d.mts +14 -3
- package/dist/types.mjs +21 -4
- package/dist/types.mjs.map +1 -1
- package/dist/urls.d.mts +4 -0
- package/dist/urls.mjs +11 -2
- package/dist/urls.mjs.map +1 -1
- package/dist/utils/adaptive-concurrency.d.mts +66 -0
- package/dist/utils/adaptive-concurrency.mjs +149 -0
- package/dist/utils/adaptive-concurrency.mjs.map +1 -0
- package/dist/utils/connect-retry.d.mts +26 -0
- package/dist/utils/connect-retry.mjs +43 -0
- package/dist/utils/connect-retry.mjs.map +1 -0
- package/dist/utils/environment.d.mts +19 -0
- package/dist/utils/environment.mjs +59 -0
- package/dist/utils/environment.mjs.map +1 -0
- package/dist/utils/http.d.mts +17 -1
- package/dist/utils/http.mjs +22 -3
- package/dist/utils/http.mjs.map +1 -1
- package/dist/utils/map-adaptive.d.mts +15 -0
- package/dist/utils/map-adaptive.mjs +72 -0
- package/dist/utils/map-adaptive.mjs.map +1 -0
- package/dist/utils/map-limit.d.mts +10 -0
- package/dist/utils/map-limit.mjs +32 -0
- package/dist/utils/map-limit.mjs.map +1 -0
- package/dist/utils/page-limiter.d.mts +37 -0
- package/dist/utils/page-limiter.mjs +56 -0
- package/dist/utils/page-limiter.mjs.map +1 -0
- package/dist/utils/public-origin.d.mts +20 -0
- package/dist/utils/public-origin.mjs +75 -0
- package/dist/utils/public-origin.mjs.map +1 -0
- package/dist/utils/redact.mjs +3 -0
- package/dist/utils/redact.mjs.map +1 -1
- package/dist/utils/remote-environment.d.mts +39 -0
- package/dist/utils/remote-environment.mjs +114 -0
- package/dist/utils/remote-environment.mjs.map +1 -0
- package/dist/utils/resources.d.mts +64 -0
- package/dist/utils/resources.mjs +122 -0
- package/dist/utils/resources.mjs.map +1 -0
- package/dist/utils/screenshot-store.d.mts +15 -0
- package/dist/utils/screenshot-store.mjs +41 -0
- package/dist/utils/screenshot-store.mjs.map +1 -0
- package/dist/utils/session-gate.d.mts +22 -0
- package/dist/utils/session-gate.mjs +53 -0
- package/dist/utils/session-gate.mjs.map +1 -0
- package/dist/utils/storybook-token.d.mts +81 -0
- package/dist/utils/storybook-token.mjs +134 -0
- package/dist/utils/storybook-token.mjs.map +1 -0
- package/dist/utils/storybook-url.d.mts +15 -0
- package/dist/utils/storybook-url.mjs +21 -0
- package/dist/utils/storybook-url.mjs.map +1 -0
- package/dist/utils/timing.mjs +1 -1
- package/dist/utils/timing.mjs.map +1 -1
- package/dist/utils/zip-read.mjs +129 -0
- package/dist/utils/zip-read.mjs.map +1 -0
- package/dist/utils/zip-write.d.mts +18 -0
- package/dist/utils/zip-write.mjs +188 -0
- package/dist/utils/zip-write.mjs.map +1 -0
- package/dist/utils.d.mts +17 -3
- package/dist/utils.mjs +18 -4
- package/package.json +32 -20
- package/dist/adapter/capture-runner.mjs.map +0 -1
- package/dist/adapter/git-host/comments.d.mts +0 -41
- package/dist/adapter/git-host/comments.mjs +0 -54
- package/dist/adapter/git-host/comments.mjs.map +0 -1
- package/dist/adapter/git-host/helpers.d.mts +0 -13
- package/dist/adapter/git-host/helpers.mjs +0 -30
- package/dist/adapter/git-host/helpers.mjs.map +0 -1
- package/dist/adapter/git-host.d.mts +0 -42
- package/dist/capture/queue.d.mts +0 -38
- package/dist/capture/queue.mjs +0 -98
- package/dist/capture/queue.mjs.map +0 -1
- package/dist/capture/recovery.d.mts +0 -21
- package/dist/capture/recovery.mjs +0 -83
- package/dist/capture/recovery.mjs.map +0 -1
- package/dist/capture/status-fanout.mjs +0 -72
- package/dist/capture/status-fanout.mjs.map +0 -1
- package/dist/diff/engine.d.mts +0 -16
- package/dist/diff/engine.mjs +0 -87
- package/dist/diff/engine.mjs.map +0 -1
- package/dist/diff/options.d.mts +0 -42
- package/dist/diff/options.mjs +0 -15
- package/dist/diff/options.mjs.map +0 -1
- package/dist/models/status-config.d.mts +0 -38
- package/dist/models/status-config.mjs +0 -63
- package/dist/models/status-config.mjs.map +0 -1
- package/dist/schema/status-config.d.mts +0 -16
- package/dist/test-helpers/git-contract.d.mts +0 -11
- package/dist/test-helpers/git-contract.mjs +0 -34
- package/dist/test-helpers/git-contract.mjs.map +0 -1
- /package/dist/adapter/{git-host.mjs → oauth.mjs} +0 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import "node:path";
|
|
2
|
+
import "node:url";
|
|
3
|
+
import.meta.url;
|
|
4
|
+
import { col } from "../models/columns.mjs";
|
|
5
|
+
import { contentDir } from "../paths.mjs";
|
|
6
|
+
import { CLAIMS_DIR } from "../utils/screenshot-store.mjs";
|
|
7
|
+
import { and, eq, gte, inArray, like } from "drizzle-orm";
|
|
8
|
+
//#region src/retention/screenshot-sweep.ts
|
|
9
|
+
/** Mark-and-sweep for a project's content-addressed screenshots. */
|
|
10
|
+
/** Longest an unreferenced screenshot is kept after the sweep first sees it (default 24 h). */
|
|
11
|
+
const SWEEP_GRACE_MS = 864e5;
|
|
12
|
+
/** Marker object remembering when each unreferenced screenshot was first seen. */
|
|
13
|
+
const MARKER = ".sweep.json";
|
|
14
|
+
/**
|
|
15
|
+
* Build statuses during which a capture may have reused an existing object (the "already exists,
|
|
16
|
+
* skip the write" path) without yet writing the snapshot rows that reference it.
|
|
17
|
+
*/
|
|
18
|
+
const ACTIVE_BUILD_STATUSES = [
|
|
19
|
+
"pending",
|
|
20
|
+
"capturing",
|
|
21
|
+
"comparing"
|
|
22
|
+
];
|
|
23
|
+
/**
|
|
24
|
+
* Delete content-addressed screenshots that no snapshot or baseline points at any more.
|
|
25
|
+
*
|
|
26
|
+
* An object is deleted only after it has been unreferenced across two sweeps at least `graceMs`
|
|
27
|
+
* apart (first-seen times live in a marker object, so no schema is needed and the storage
|
|
28
|
+
* adapter needs no modification times). A capture writes its screenshot before the rows that
|
|
29
|
+
* reference it, so a single look is never enough; each candidate is also re-checked against the
|
|
30
|
+
* database immediately before it is deleted.
|
|
31
|
+
*
|
|
32
|
+
* Reusing an object that already exists writes nothing, so the sweep cannot see it by looking at
|
|
33
|
+
* storage. Deletion is therefore deferred (objects stay pending, first-seen times kept) while the
|
|
34
|
+
* project has reuse in flight: a build still capturing, or a reuse claim from a baseline import or
|
|
35
|
+
* approval ({@link withContentClaim}). Both checks are bounded by the grace period, so a build
|
|
36
|
+
* stuck in `capturing` or a claim left by a crash delays reclaiming storage, never forever.
|
|
37
|
+
*/
|
|
38
|
+
async function sweepScreenshotContent(deps, projectId, options = {}) {
|
|
39
|
+
const dir = `${contentDir(projectId)}/`;
|
|
40
|
+
const unreferenced = await findUnreferenced(deps, projectId, dir);
|
|
41
|
+
const firstSeen = await readMarker(deps.storage, dir);
|
|
42
|
+
const outcome = await deleteDue(deps, projectId, unreferenced, firstSeen, {
|
|
43
|
+
now: options.now ?? Date.now(),
|
|
44
|
+
graceMs: options.graceMs ?? 864e5
|
|
45
|
+
});
|
|
46
|
+
await writeMarker(deps.storage, dir, outcome.pending, firstSeen);
|
|
47
|
+
if (outcome.removed > 0) deps.logger?.info({
|
|
48
|
+
projectId,
|
|
49
|
+
removed: outcome.removed
|
|
50
|
+
}, "removed unreferenced screenshots");
|
|
51
|
+
return {
|
|
52
|
+
removed: outcome.removed,
|
|
53
|
+
pending: Object.keys(outcome.pending).length
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
async function findUnreferenced(deps, projectId, dir) {
|
|
57
|
+
const objects = (await deps.storage.list(dir)).filter((path) => path.startsWith(dir) && path.endsWith(".png"));
|
|
58
|
+
const referenced = await referencedPaths(deps, projectId);
|
|
59
|
+
return objects.filter((path) => !referenced.has(path));
|
|
60
|
+
}
|
|
61
|
+
/** Delete what has been unreferenced for the whole grace period; return the rest with first-seen times. */
|
|
62
|
+
async function deleteDue(deps, projectId, unreferenced, firstSeen, clock) {
|
|
63
|
+
const pending = {};
|
|
64
|
+
let removed = 0;
|
|
65
|
+
for (const path of unreferenced) {
|
|
66
|
+
const seen = firstSeen[path] ?? clock.now;
|
|
67
|
+
if (clock.now - seen >= clock.graceMs && !await isHeld(deps, projectId, path, clock)) {
|
|
68
|
+
await deps.storage.delete(path);
|
|
69
|
+
removed += 1;
|
|
70
|
+
} else pending[path] = seen;
|
|
71
|
+
}
|
|
72
|
+
return {
|
|
73
|
+
removed,
|
|
74
|
+
pending
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
async function referencedPaths(deps, projectId) {
|
|
78
|
+
const rows = await Promise.all([deps.tables.snapshots, deps.tables.baselines].map(async (table) => await deps.db.list(table, { where: and(eq(col(table, "projectId"), projectId), like(col(table, "screenshotPath"), `${contentDir(projectId)}/%`)) })));
|
|
79
|
+
return new Set(rows.flat().map((row) => row.screenshotPath));
|
|
80
|
+
}
|
|
81
|
+
/** Whether `path` must be kept now: referenced by a row, or reuse may be in flight. */
|
|
82
|
+
async function isHeld(deps, projectId, path, clock) {
|
|
83
|
+
return await isReferenced(deps, projectId, path) || await hasActiveBuild(deps, projectId, clock) || await hasActiveClaim(deps.storage, projectId, clock);
|
|
84
|
+
}
|
|
85
|
+
/** A build of the project that is still capturing and was updated within the grace period. */
|
|
86
|
+
async function hasActiveBuild(deps, projectId, clock) {
|
|
87
|
+
const table = deps.tables.builds;
|
|
88
|
+
return (await deps.db.list(table, {
|
|
89
|
+
where: and(eq(col(table, "projectId"), projectId), inArray(col(table, "status"), ACTIVE_BUILD_STATUSES), gte(col(table, "updatedAt"), new Date(clock.now - clock.graceMs).toISOString())),
|
|
90
|
+
limit: 1
|
|
91
|
+
})).length > 0;
|
|
92
|
+
}
|
|
93
|
+
/** A reuse claim younger than the grace period; older ones (left by a crash) are ignored. */
|
|
94
|
+
async function hasActiveClaim(storage, projectId, clock) {
|
|
95
|
+
const claims = await storage.list(`${contentDir(projectId)}/${CLAIMS_DIR}/`);
|
|
96
|
+
return (await Promise.all(claims.map(async (path) => await claimTime(storage, path)))).some((at) => at === null || clock.now - at < clock.graceMs);
|
|
97
|
+
}
|
|
98
|
+
/** When a claim was made; null when it cannot be read (treated as active: fail safe). */
|
|
99
|
+
async function claimTime(storage, path) {
|
|
100
|
+
try {
|
|
101
|
+
const parsed = JSON.parse((await storage.read(path)).toString("utf8"));
|
|
102
|
+
return typeof parsed.at === "number" ? parsed.at : null;
|
|
103
|
+
} catch {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
async function isReferenced(deps, projectId, path) {
|
|
108
|
+
return (await Promise.all([deps.tables.snapshots, deps.tables.baselines].map(async (table) => (await deps.db.list(table, {
|
|
109
|
+
where: and(eq(col(table, "projectId"), projectId), eq(col(table, "screenshotPath"), path)),
|
|
110
|
+
limit: 1
|
|
111
|
+
})).length > 0))).some(Boolean);
|
|
112
|
+
}
|
|
113
|
+
async function readMarker(storage, dir) {
|
|
114
|
+
try {
|
|
115
|
+
if (!await storage.exists(`${dir}${MARKER}`)) return {};
|
|
116
|
+
const parsed = JSON.parse((await storage.read(`${dir}${MARKER}`)).toString("utf8"));
|
|
117
|
+
return parsed && typeof parsed === "object" ? parsed : {};
|
|
118
|
+
} catch {
|
|
119
|
+
return {};
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
async function writeMarker(storage, dir, next, previous) {
|
|
123
|
+
if (JSON.stringify(next) !== JSON.stringify(previous)) await storage.write(`${dir}${MARKER}`, Buffer.from(JSON.stringify(next)));
|
|
124
|
+
}
|
|
125
|
+
//#endregion
|
|
126
|
+
export { SWEEP_GRACE_MS, sweepScreenshotContent };
|
|
127
|
+
|
|
128
|
+
//# sourceMappingURL=screenshot-sweep.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"screenshot-sweep.mjs","names":[],"sources":["../../src/retention/screenshot-sweep.ts"],"sourcesContent":["/** Mark-and-sweep for a project's content-addressed screenshots. */\nimport { and, eq, gte, inArray, like } from \"drizzle-orm\";\nimport type { Table } from \"drizzle-orm\";\nimport type { Logger } from \"pino\";\nimport type { DatabaseAdapter } from \"../adapters/database.ts\";\nimport type { StorageAdapter } from \"../adapters/storage.ts\";\nimport { col } from \"../models/columns.ts\";\nimport { contentDir } from \"../utils/paths.ts\";\nimport { CLAIMS_DIR } from \"../utils/screenshot-store.ts\";\n\n/** Longest an unreferenced screenshot is kept after the sweep first sees it (default 24 h). */\nexport const SWEEP_GRACE_MS = 24 * 60 * 60 * 1000;\n\n/** Marker object remembering when each unreferenced screenshot was first seen. */\nconst MARKER = \".sweep.json\";\n\n/**\n * Build statuses during which a capture may have reused an existing object (the \"already exists,\n * skip the write\" path) without yet writing the snapshot rows that reference it.\n */\nconst ACTIVE_BUILD_STATUSES = [\"pending\", \"capturing\", \"comparing\"];\n\n/** Tables the sweep reads references and in-flight builds from. */\nexport interface SweepTables {\n snapshots: Table;\n baselines: Table;\n builds: Table;\n}\n\n/** Inputs for {@link sweepScreenshotContent}. */\nexport interface SweepDeps {\n db: DatabaseAdapter;\n storage: StorageAdapter;\n tables: SweepTables;\n logger?: Logger;\n}\n\n/** What one sweep did. */\nexport interface SweepResult {\n /** Objects deleted. */\n removed: number;\n /** Unreferenced objects still inside their grace period. */\n pending: number;\n}\n\n/**\n * Delete content-addressed screenshots that no snapshot or baseline points at any more.\n *\n * An object is deleted only after it has been unreferenced across two sweeps at least `graceMs`\n * apart (first-seen times live in a marker object, so no schema is needed and the storage\n * adapter needs no modification times). A capture writes its screenshot before the rows that\n * reference it, so a single look is never enough; each candidate is also re-checked against the\n * database immediately before it is deleted.\n *\n * Reusing an object that already exists writes nothing, so the sweep cannot see it by looking at\n * storage. Deletion is therefore deferred (objects stay pending, first-seen times kept) while the\n * project has reuse in flight: a build still capturing, or a reuse claim from a baseline import or\n * approval ({@link withContentClaim}). Both checks are bounded by the grace period, so a build\n * stuck in `capturing` or a claim left by a crash delays reclaiming storage, never forever.\n */\nexport async function sweepScreenshotContent(\n deps: SweepDeps,\n projectId: string,\n options: { now?: number; graceMs?: number } = {},\n): Promise<SweepResult> {\n const dir = `${contentDir(projectId)}/`;\n const unreferenced = await findUnreferenced(deps, projectId, dir);\n const firstSeen = await readMarker(deps.storage, dir);\n const outcome = await deleteDue(deps, projectId, unreferenced, firstSeen, {\n now: options.now ?? Date.now(),\n graceMs: options.graceMs ?? SWEEP_GRACE_MS,\n });\n await writeMarker(deps.storage, dir, outcome.pending, firstSeen);\n if (outcome.removed > 0) {\n deps.logger?.info({ projectId, removed: outcome.removed }, \"removed unreferenced screenshots\");\n }\n return { removed: outcome.removed, pending: Object.keys(outcome.pending).length };\n}\n\nasync function findUnreferenced(\n deps: SweepDeps,\n projectId: string,\n dir: string,\n): Promise<string[]> {\n // Only this project's content screenshots: the prefix guards against an adapter listing keys\n // in another form (such as OS-separated paths), which would otherwise match no reference.\n const objects = (await deps.storage.list(dir)).filter(\n (path) => path.startsWith(dir) && path.endsWith(\".png\"),\n );\n const referenced = await referencedPaths(deps, projectId);\n return objects.filter((path) => !referenced.has(path));\n}\n\n/** Delete what has been unreferenced for the whole grace period; return the rest with first-seen times. */\nasync function deleteDue(\n deps: SweepDeps,\n projectId: string,\n unreferenced: string[],\n firstSeen: Record<string, number>,\n clock: { now: number; graceMs: number },\n): Promise<{ removed: number; pending: Record<string, number> }> {\n const pending: Record<string, number> = {};\n let removed = 0;\n for (const path of unreferenced) {\n const seen = firstSeen[path] ?? clock.now;\n // oxlint-disable-next-line eslint/no-await-in-loop -- re-check and delete one object at a time\n if (clock.now - seen >= clock.graceMs && !(await isHeld(deps, projectId, path, clock))) {\n // oxlint-disable-next-line eslint/no-await-in-loop -- see above\n await deps.storage.delete(path);\n removed += 1;\n } else {\n pending[path] = seen;\n }\n }\n return { removed, pending };\n}\n\nasync function referencedPaths(deps: SweepDeps, projectId: string): Promise<Set<string>> {\n const rows = await Promise.all(\n [deps.tables.snapshots, deps.tables.baselines].map(\n async (table) =>\n (await deps.db.list(table, {\n where: and(\n eq(col(table, \"projectId\"), projectId),\n like(col(table, \"screenshotPath\"), `${contentDir(projectId)}/%`),\n ),\n })) as unknown as { screenshotPath: string }[],\n ),\n );\n return new Set(rows.flat().map((row) => row.screenshotPath));\n}\n\n/** Whether `path` must be kept now: referenced by a row, or reuse may be in flight. */\nasync function isHeld(\n deps: SweepDeps,\n projectId: string,\n path: string,\n clock: { now: number; graceMs: number },\n): Promise<boolean> {\n return (\n (await isReferenced(deps, projectId, path)) ||\n (await hasActiveBuild(deps, projectId, clock)) ||\n (await hasActiveClaim(deps.storage, projectId, clock))\n );\n}\n\n/** A build of the project that is still capturing and was updated within the grace period. */\nasync function hasActiveBuild(\n deps: SweepDeps,\n projectId: string,\n clock: { now: number; graceMs: number },\n): Promise<boolean> {\n const table = deps.tables.builds;\n const rows = await deps.db.list(table, {\n where: and(\n eq(col(table, \"projectId\"), projectId),\n inArray(col(table, \"status\"), ACTIVE_BUILD_STATUSES),\n gte(col(table, \"updatedAt\"), new Date(clock.now - clock.graceMs).toISOString()),\n ),\n limit: 1,\n });\n return rows.length > 0;\n}\n\n/** A reuse claim younger than the grace period; older ones (left by a crash) are ignored. */\nasync function hasActiveClaim(\n storage: StorageAdapter,\n projectId: string,\n clock: { now: number; graceMs: number },\n): Promise<boolean> {\n const claims = await storage.list(`${contentDir(projectId)}/${CLAIMS_DIR}/`);\n const times = await Promise.all(claims.map(async (path) => await claimTime(storage, path)));\n return times.some((at) => at === null || clock.now - at < clock.graceMs);\n}\n\n/** When a claim was made; null when it cannot be read (treated as active: fail safe). */\nasync function claimTime(storage: StorageAdapter, path: string): Promise<number | null> {\n try {\n const parsed = JSON.parse((await storage.read(path)).toString(\"utf8\")) as { at?: unknown };\n return typeof parsed.at === \"number\" ? parsed.at : null;\n } catch {\n return null;\n }\n}\n\nasync function isReferenced(deps: SweepDeps, projectId: string, path: string): Promise<boolean> {\n const hits = await Promise.all(\n [deps.tables.snapshots, deps.tables.baselines].map(\n async (table) =>\n (\n await deps.db.list(table, {\n where: and(\n eq(col(table, \"projectId\"), projectId),\n eq(col(table, \"screenshotPath\"), path),\n ),\n limit: 1,\n })\n ).length > 0,\n ),\n );\n return hits.some(Boolean);\n}\n\nasync function readMarker(storage: StorageAdapter, dir: string): Promise<Record<string, number>> {\n try {\n if (!(await storage.exists(`${dir}${MARKER}`))) {\n return {};\n }\n const parsed = JSON.parse((await storage.read(`${dir}${MARKER}`)).toString(\"utf8\")) as unknown;\n return parsed && typeof parsed === \"object\" ? (parsed as Record<string, number>) : {};\n } catch {\n return {};\n }\n}\n\nasync function writeMarker(\n storage: StorageAdapter,\n dir: string,\n next: Record<string, number>,\n previous: Record<string, number>,\n): Promise<void> {\n if (JSON.stringify(next) !== JSON.stringify(previous)) {\n await storage.write(`${dir}${MARKER}`, Buffer.from(JSON.stringify(next)));\n }\n}\n"],"mappings":"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"}
|
package/dist/retention.d.mts
CHANGED
|
@@ -3,6 +3,8 @@ import __tsdown_shims_url from 'node:url';
|
|
|
3
3
|
import { DatabaseAdapter } from "./adapter/database.mjs";
|
|
4
4
|
import { StorageAdapter } from "./adapter/storage.mjs";
|
|
5
5
|
import { Project } from "./schema/project.mjs";
|
|
6
|
+
import { SWEEP_GRACE_MS, SweepDeps, SweepResult, SweepTables, sweepScreenshotContent } from "./retention/screenshot-sweep.mjs";
|
|
7
|
+
import { AiPurgeResult, STALE_RUN_MS, purgeAiData } from "./retention/ai.mjs";
|
|
6
8
|
import { Logger } from "pino";
|
|
7
9
|
import { Table } from "drizzle-orm";
|
|
8
10
|
//#region src/retention/purge.d.ts
|
|
@@ -18,8 +20,20 @@ declare class Retention {
|
|
|
18
20
|
private readonly storage;
|
|
19
21
|
private readonly tables;
|
|
20
22
|
private readonly logger?;
|
|
21
|
-
constructor(db: DatabaseAdapter, storage: StorageAdapter, tables: RetentionTables, logger?: Logger
|
|
23
|
+
constructor(db: DatabaseAdapter, storage: StorageAdapter, tables: RetentionTables, logger?: Logger);
|
|
22
24
|
purge(project: Project, options: PurgeOptions): Promise<PurgeResult>;
|
|
25
|
+
/**
|
|
26
|
+
* The builds a purge removes: terminal builds past the window (minus the latest per branch when
|
|
27
|
+
* asked), and pending builds idle past it (ADR 0013, decision 9). Persistent builds stay.
|
|
28
|
+
*/
|
|
29
|
+
private selectBuilds;
|
|
30
|
+
/** Reclaim screenshots no snapshot or baseline references any more; never blocks the purge. */
|
|
31
|
+
private sweepScreenshots;
|
|
32
|
+
/**
|
|
33
|
+
* Drop finished queue jobs older than the build cutoff (they have no foreign key to a build, and
|
|
34
|
+
* a finished job is only history); never blocks the purge. Queued and running jobs are kept.
|
|
35
|
+
*/
|
|
36
|
+
private purgeFinishedJobs;
|
|
23
37
|
private removeBuild;
|
|
24
38
|
private latestPerBranch;
|
|
25
39
|
private deleteBuildFiles;
|
|
@@ -41,5 +55,5 @@ interface BranchGcResult {
|
|
|
41
55
|
removedBaselines: number;
|
|
42
56
|
}
|
|
43
57
|
//#endregion
|
|
44
|
-
export { BranchGcResult, PurgeOptions, PurgeResult, Retention, RetentionTables };
|
|
58
|
+
export { type AiPurgeResult, BranchGcResult, PurgeOptions, PurgeResult, Retention, RetentionTables, STALE_RUN_MS, SWEEP_GRACE_MS, type SweepDeps, type SweepResult, type SweepTables, purgeAiData, sweepScreenshotContent };
|
|
45
59
|
//# sourceMappingURL=retention.d.mts.map
|
package/dist/retention.mjs
CHANGED
|
@@ -2,10 +2,18 @@ import "node:path";
|
|
|
2
2
|
import "node:url";
|
|
3
3
|
import.meta.url;
|
|
4
4
|
import { TERMINAL_BUILD_STATUSES } from "./types.mjs";
|
|
5
|
+
import { col } from "./models/columns.mjs";
|
|
5
6
|
import { BuildModel } from "./models/build.mjs";
|
|
6
7
|
import { BaselineModel } from "./models/baseline.mjs";
|
|
8
|
+
import { CaptureJobModel } from "./models/capture-job.mjs";
|
|
9
|
+
import { BuildComponentModel } from "./models/build-component.mjs";
|
|
10
|
+
import { InsightModel } from "./models/insight.mjs";
|
|
7
11
|
import { LabelModel } from "./models/label.mjs";
|
|
8
|
-
import {
|
|
12
|
+
import { PackageUsageModel } from "./models/usage.mjs";
|
|
13
|
+
import { pendingCandidates } from "./retention/pending.mjs";
|
|
14
|
+
import { SWEEP_GRACE_MS, sweepScreenshotContent } from "./retention/screenshot-sweep.mjs";
|
|
15
|
+
import { STALE_RUN_MS, purgeAiData } from "./retention/ai.mjs";
|
|
16
|
+
import { and, desc, eq, inArray, lt } from "drizzle-orm";
|
|
9
17
|
//#region src/retention/purge.ts
|
|
10
18
|
/** Removes expired transient builds while keeping baselines and persistent builds. */
|
|
11
19
|
var Retention = class {
|
|
@@ -21,38 +29,86 @@ var Retention = class {
|
|
|
21
29
|
}
|
|
22
30
|
async purge(project, options) {
|
|
23
31
|
const cutoff = (/* @__PURE__ */ new Date(Date.now() - options.ttlDays * 864e5)).toISOString();
|
|
24
|
-
const
|
|
25
|
-
const keep = options.keepLatestPerBranch ? await this.latestPerBranch(project.id) : /* @__PURE__ */ new Set();
|
|
26
|
-
const labelModel = new LabelModel(this.db, this.tables);
|
|
27
|
-
const buildIds = (await Promise.all(candidates.map(async (build) => {
|
|
28
|
-
const buildId = build.id;
|
|
29
|
-
if (keep.has(buildId) || await labelModel.hasPersistent(project.id, buildId)) return null;
|
|
30
|
-
return buildId;
|
|
31
|
-
}))).filter((id) => id !== null);
|
|
32
|
+
const { buildIds, pending } = await this.selectBuilds(project, options, cutoff);
|
|
32
33
|
const results = await Promise.all(buildIds.map(async (buildId) => ({
|
|
33
34
|
files: await this.deleteBuildFiles(project.id, buildId),
|
|
34
35
|
removed: await this.removeBuild(buildId)
|
|
35
36
|
})));
|
|
36
37
|
const removedBuilds = results.filter((r) => r.removed).length;
|
|
37
38
|
const removedFiles = results.reduce((sum, r) => sum + r.files, 0);
|
|
39
|
+
await this.sweepScreenshots(project);
|
|
40
|
+
const removedJobs = await this.purgeFinishedJobs(cutoff);
|
|
38
41
|
this.logger?.info({
|
|
39
42
|
projectId: project.id,
|
|
40
43
|
removedBuilds,
|
|
41
|
-
removedFiles
|
|
44
|
+
removedFiles,
|
|
45
|
+
removedJobs,
|
|
46
|
+
removedPending: pending
|
|
42
47
|
}, "build retention purge complete");
|
|
43
48
|
return {
|
|
44
49
|
removedBuilds,
|
|
45
50
|
removedFiles
|
|
46
51
|
};
|
|
47
52
|
}
|
|
53
|
+
/**
|
|
54
|
+
* The builds a purge removes: terminal builds past the window (minus the latest per branch when
|
|
55
|
+
* asked), and pending builds idle past it (ADR 0013, decision 9). Persistent builds stay.
|
|
56
|
+
*/
|
|
57
|
+
async selectBuilds(project, options, cutoff) {
|
|
58
|
+
const candidates = await this.db.list(this.tables.builds, { where: and(eq(col(this.tables.builds, "projectId"), project.id), inArray(col(this.tables.builds, "status"), [...TERMINAL_BUILD_STATUSES]), lt(col(this.tables.builds, "updatedAt"), cutoff)) });
|
|
59
|
+
const keep = options.keepLatestPerBranch ? await this.latestPerBranch(project.id) : /* @__PURE__ */ new Set();
|
|
60
|
+
const persistent = await new LabelModel(this.db, this.tables).persistentBuildIds(project.id);
|
|
61
|
+
const terminal = candidates.map((build) => build.id).filter((buildId) => !keep.has(buildId));
|
|
62
|
+
const pending = (await pendingCandidates(this.db, project.id, cutoff)).map((candidate) => candidate.buildId);
|
|
63
|
+
return {
|
|
64
|
+
buildIds: [...terminal, ...pending].filter((buildId) => !persistent.has(buildId)),
|
|
65
|
+
pending: pending.length
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
/** Reclaim screenshots no snapshot or baseline references any more; never blocks the purge. */
|
|
69
|
+
async sweepScreenshots(project) {
|
|
70
|
+
try {
|
|
71
|
+
await sweepScreenshotContent({
|
|
72
|
+
db: this.db,
|
|
73
|
+
storage: this.storage,
|
|
74
|
+
tables: {
|
|
75
|
+
snapshots: this.db.tables.snapshots,
|
|
76
|
+
baselines: this.tables.baselines,
|
|
77
|
+
builds: this.tables.builds
|
|
78
|
+
},
|
|
79
|
+
logger: this.logger
|
|
80
|
+
}, project.id);
|
|
81
|
+
} catch (error) {
|
|
82
|
+
this.logger?.warn({
|
|
83
|
+
err: error,
|
|
84
|
+
projectId: project.id
|
|
85
|
+
}, "screenshot sweep failed");
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Drop finished queue jobs older than the build cutoff (they have no foreign key to a build, and
|
|
90
|
+
* a finished job is only history); never blocks the purge. Queued and running jobs are kept.
|
|
91
|
+
*/
|
|
92
|
+
async purgeFinishedJobs(cutoff) {
|
|
93
|
+
try {
|
|
94
|
+
return await new CaptureJobModel(this.db).purgeFinished(cutoff);
|
|
95
|
+
} catch (error) {
|
|
96
|
+
this.logger?.warn({ err: error }, "capture job purge failed");
|
|
97
|
+
return 0;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
48
100
|
async removeBuild(buildId) {
|
|
101
|
+
await new InsightModel(this.db).removeForBuild(buildId);
|
|
102
|
+
await new CaptureJobModel(this.db).removeForBuild(buildId);
|
|
103
|
+
await new BuildComponentModel(this.db).removeForBuild(buildId);
|
|
104
|
+
await new PackageUsageModel(this.db).removeForBuild(buildId);
|
|
49
105
|
await new BuildModel(this.db, this.tables).remove(buildId);
|
|
50
106
|
return true;
|
|
51
107
|
}
|
|
52
108
|
async latestPerBranch(projectId) {
|
|
53
109
|
const rows = await this.db.list(this.tables.builds, {
|
|
54
|
-
where: eq(
|
|
55
|
-
orderBy: desc(
|
|
110
|
+
where: eq(col(this.tables.builds, "projectId"), projectId),
|
|
111
|
+
orderBy: desc(col(this.tables.builds, "updatedAt"))
|
|
56
112
|
});
|
|
57
113
|
const latest = /* @__PURE__ */ new Map();
|
|
58
114
|
for (const row of rows) {
|
|
@@ -108,12 +164,13 @@ var Retention = class {
|
|
|
108
164
|
removedBaselines: 0
|
|
109
165
|
};
|
|
110
166
|
const cutoff = (/* @__PURE__ */ new Date(Date.now() - ttlDays * 864e5)).toISOString();
|
|
111
|
-
const stale = collectStaleBranches(await this.db.list(this.tables.builds, { where: eq(
|
|
167
|
+
const stale = collectStaleBranches(await this.db.list(this.tables.builds, { where: eq(col(this.tables.builds, "projectId"), project.id) }), project.gitDefaultBranch, cutoff);
|
|
112
168
|
if (stale.size === 0) return {
|
|
113
169
|
removedBranches: 0,
|
|
114
170
|
removedBaselines: 0
|
|
115
171
|
};
|
|
116
172
|
const removedBaselines = await new BaselineModel(this.db, this.tables, this.storage).removeStaleBranches(project.id, stale);
|
|
173
|
+
await this.sweepScreenshots(project);
|
|
117
174
|
this.logger?.info({
|
|
118
175
|
projectId: project.id,
|
|
119
176
|
removedBranches: stale.size,
|
|
@@ -140,6 +197,6 @@ function branchLatestAt(rows) {
|
|
|
140
197
|
return latestAt;
|
|
141
198
|
}
|
|
142
199
|
//#endregion
|
|
143
|
-
export { Retention };
|
|
200
|
+
export { Retention, STALE_RUN_MS, SWEEP_GRACE_MS, purgeAiData, sweepScreenshotContent };
|
|
144
201
|
|
|
145
202
|
//# sourceMappingURL=retention.mjs.map
|
package/dist/retention.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"retention.mjs","names":[],"sources":["../src/retention/purge.ts"],"sourcesContent":["import { and, desc, eq, getTableColumns, inArray, lt } from \"drizzle-orm\";\nimport type { SQLWrapper, Table } from \"drizzle-orm\";\nimport type { Logger } from \"pino\";\nimport type { DatabaseAdapter } from \"../adapters/database.ts\";\nimport type { StorageAdapter } from \"../adapters/storage.ts\";\nimport { BaselineModel } from \"../models/baseline.ts\";\nimport { BuildModel, type BuildTables } from \"../models/build.ts\";\nimport { LabelModel, type LabelTables } from \"../models/label.ts\";\nimport type { Project } from \"../schema/project.ts\";\nimport { TERMINAL_BUILD_STATUSES } from \"../types.ts\";\n\n/** Table handles for retention queries. */\nexport interface RetentionTables {\n builds: Table;\n buildLabels: Table;\n baselines: Table;\n}\n\n/** Removes expired transient builds while keeping baselines and persistent builds. */\nexport class Retention {\n constructor(\n private readonly db: DatabaseAdapter,\n private readonly storage: StorageAdapter,\n private readonly tables: RetentionTables,\n private readonly logger?: Logger,\n ) {}\n\n async purge(project: Project, options: PurgeOptions): Promise<PurgeResult> {\n const cutoff = new Date(Date.now() - options.ttlDays * 86_400_000).toISOString();\n const candidates = await this.db.list(this.tables.builds, {\n where: and(\n eq(getTableColumns(this.tables.builds)[\"projectId\"] as unknown as SQLWrapper, project.id),\n inArray(getTableColumns(this.tables.builds)[\"status\"] as unknown as SQLWrapper, [\n ...TERMINAL_BUILD_STATUSES,\n ]),\n lt(getTableColumns(this.tables.builds)[\"updatedAt\"] as unknown as SQLWrapper, cutoff),\n ),\n });\n\n const keep = options.keepLatestPerBranch\n ? await this.latestPerBranch(project.id)\n : new Set<string>();\n const labelModel = new LabelModel(this.db, this.tables as unknown as LabelTables);\n const target = await Promise.all(\n candidates.map(async (build) => {\n const buildId = (build as { id: string }).id;\n if (keep.has(buildId) || (await labelModel.hasPersistent(project.id, buildId))) {\n return null;\n }\n return buildId;\n }),\n );\n\n const buildIds = target.filter((id): id is string => id !== null);\n const results = await Promise.all(\n buildIds.map(async (buildId) => ({\n files: await this.deleteBuildFiles(project.id, buildId),\n removed: await this.removeBuild(buildId),\n })),\n );\n const removedBuilds = results.filter((r: { removed: boolean }) => r.removed).length;\n const removedFiles = results.reduce((sum: number, r: { files: number }) => sum + r.files, 0);\n this.logger?.info(\n { projectId: project.id, removedBuilds, removedFiles },\n \"build retention purge complete\",\n );\n return { removedBuilds, removedFiles };\n }\n\n private async removeBuild(buildId: string): Promise<boolean> {\n await new BuildModel(this.db, this.tables as unknown as BuildTables).remove(buildId);\n return true;\n }\n\n private async latestPerBranch(projectId: string): Promise<Set<string>> {\n const rows = (await this.db.list(this.tables.builds, {\n where: eq(\n getTableColumns(this.tables.builds)[\"projectId\"] as unknown as SQLWrapper,\n projectId,\n ),\n orderBy: desc(getTableColumns(this.tables.builds)[\"updatedAt\"] as unknown as SQLWrapper),\n })) as unknown as { gitBranch: string; id: string }[];\n const latest = new Map<string, string>();\n for (const row of rows) {\n const branch = row.gitBranch;\n const current = latest.get(branch);\n if (!current) {\n latest.set(branch, row.id);\n }\n }\n return new Set(latest.values());\n }\n\n private async deleteBuildFiles(projectId: string, buildId: string): Promise<number> {\n const prefix = `${projectId}/builds/${buildId}/`;\n const manifestPath = `${prefix}storybook/manifest.json`;\n let manifestFiles: string[] = [];\n try {\n if (await this.storage.exists(manifestPath)) {\n const raw = await this.storage.read(manifestPath);\n const manifest = JSON.parse(raw.toString(\"utf8\")) as Record<string, string>;\n manifestFiles = Object.values(manifest);\n }\n } catch {\n // ignore — manifest may not exist for old builds\n }\n const files = await this.storage.list(prefix);\n await Promise.all(\n files.map(async (file) => {\n await this.storage.delete(file);\n }),\n );\n // Decrement content refs for this build's manifest\n for (const hash of manifestFiles) {\n try {\n const contentRefs = this.db.tables.contentRefs;\n const existing = (await this.db.get(contentRefs, hash)) as unknown as {\n refCount: number;\n } | null;\n // oxlint-disable-next-line unicorn/prefer-ternary -- refCount check is clearer as if/else\n if (existing) {\n const refCount = existing.refCount;\n // oxlint-disable-next-line unicorn/prefer-ternary -- grace logic is clearer as if/else\n if (refCount <= 1) {\n // Keep for 7-day grace: set refCount 0 and update lastSeenAt, don't delete yet\n await this.db.update(contentRefs, hash, {\n refCount: 0,\n lastSeenAt: new Date().toISOString(),\n });\n } else {\n await this.db.update(contentRefs, hash, {\n refCount: refCount - 1,\n lastSeenAt: new Date().toISOString(),\n });\n }\n }\n } catch {\n // ignore — content_refs may not exist\n }\n }\n // GC stale content_refs with 7-day grace\n try {\n const contentRefs = this.db.tables.contentRefs;\n const cutoff = new Date(Date.now() - 7 * 86_400_000).toISOString();\n const all = (await this.db.list(contentRefs)) as unknown as {\n hash: string;\n refCount: number;\n lastSeenAt: string;\n }[];\n for (const row of all) {\n if (row.refCount === 0 && row.lastSeenAt < cutoff) {\n await this.storage.delete(`content/${row.hash}`).catch(() => {\n // ignore storage delete failure\n });\n await this.db.remove(contentRefs, row.hash);\n }\n }\n } catch {\n // ignore — content_refs may not exist\n }\n return files.length;\n }\n\n async purgeStaleBranches(project: Project, ttlDays: number): Promise<BranchGcResult> {\n if (ttlDays <= 0) {\n return { removedBranches: 0, removedBaselines: 0 };\n }\n const cutoff = new Date(Date.now() - ttlDays * 86_400_000).toISOString();\n const rows = (await this.db.list(this.tables.builds, {\n where: eq(\n getTableColumns(this.tables.builds)[\"projectId\"] as unknown as SQLWrapper,\n project.id,\n ),\n })) as unknown as { gitBranch: string; updatedAt: string }[];\n const stale = collectStaleBranches(rows, project.gitDefaultBranch, cutoff);\n if (stale.size === 0) {\n return { removedBranches: 0, removedBaselines: 0 };\n }\n const baselines = new BaselineModel(this.db, this.tables, this.storage);\n const removedBaselines = await baselines.removeStaleBranches(project.id, stale);\n this.logger?.info(\n { projectId: project.id, removedBranches: stale.size, removedBaselines },\n \"branch GC complete\",\n );\n return { removedBranches: stale.size, removedBaselines };\n }\n}\n\n/** Options controlling which builds a retention purge removes. */\nexport interface PurgeOptions {\n ttlDays: number;\n keepLatestPerBranch: boolean;\n}\n\n/** Counts of builds and files removed by a retention purge. */\nexport interface PurgeResult {\n removedBuilds: number;\n removedFiles: number;\n}\n\n/** Counts of stale branches and baselines removed. */\nexport interface BranchGcResult {\n removedBranches: number;\n removedBaselines: number;\n}\n\nfunction collectStaleBranches(\n rows: { gitBranch: string; updatedAt: string }[],\n defaultBranch: string,\n cutoff: string,\n): Set<string> {\n const latestAt = branchLatestAt(rows);\n const stale = new Set<string>();\n for (const [branch, updatedAt] of latestAt) {\n if (branch !== defaultBranch && updatedAt < cutoff) {\n stale.add(branch);\n }\n }\n return stale;\n}\n\nfunction branchLatestAt(rows: { gitBranch: string; updatedAt: string }[]): Map<string, string> {\n const latestAt = new Map<string, string>();\n for (const row of rows) {\n const current = latestAt.get(row.gitBranch);\n if (!current || row.updatedAt > current) {\n latestAt.set(row.gitBranch, row.updatedAt);\n }\n }\n return latestAt;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"retention.mjs","names":[],"sources":["../src/retention/purge.ts"],"sourcesContent":["import { and, desc, eq, inArray, lt } from \"drizzle-orm\";\nimport type { Table } from \"drizzle-orm\";\nimport type { Logger } from \"pino\";\nimport type { DatabaseAdapter } from \"../adapters/database.ts\";\nimport type { StorageAdapter } from \"../adapters/storage.ts\";\nimport { BaselineModel } from \"../models/baseline.ts\";\nimport { BuildComponentModel } from \"../models/build-component.ts\";\nimport { BuildModel, type BuildTables } from \"../models/build.ts\";\nimport { CaptureJobModel } from \"../models/capture-job.ts\";\nimport { InsightModel } from \"../models/insight.ts\";\nimport { LabelModel, type LabelTables } from \"../models/label.ts\";\nimport { PackageUsageModel } from \"../models/usage.ts\";\nimport type { Project } from \"../schema/project.ts\";\nimport { TERMINAL_BUILD_STATUSES } from \"../types.ts\";\nimport { pendingCandidates } from \"./pending.ts\";\nimport { sweepScreenshotContent } from \"./screenshot-sweep.ts\";\n\nexport {\n sweepScreenshotContent,\n SWEEP_GRACE_MS,\n type SweepDeps,\n type SweepResult,\n type SweepTables,\n} from \"./screenshot-sweep.ts\";\nimport { col } from \"../models/columns.ts\";\nexport { purgeAiData, STALE_RUN_MS, type AiPurgeResult } from \"./ai.ts\";\n\n/** Table handles for retention queries. */\nexport interface RetentionTables {\n builds: Table;\n buildLabels: Table;\n baselines: Table;\n}\n\n/** Removes expired transient builds while keeping baselines and persistent builds. */\nexport class Retention {\n private readonly db: DatabaseAdapter;\n private readonly storage: StorageAdapter;\n private readonly tables: RetentionTables;\n private readonly logger?: Logger;\n\n constructor(\n db: DatabaseAdapter,\n storage: StorageAdapter,\n tables: RetentionTables,\n logger?: Logger,\n ) {\n this.db = db;\n this.storage = storage;\n this.tables = tables;\n this.logger = logger;\n }\n\n async purge(project: Project, options: PurgeOptions): Promise<PurgeResult> {\n const cutoff = new Date(Date.now() - options.ttlDays * 86_400_000).toISOString();\n const { buildIds, pending } = await this.selectBuilds(project, options, cutoff);\n const results = await Promise.all(\n buildIds.map(async (buildId) => ({\n files: await this.deleteBuildFiles(project.id, buildId),\n removed: await this.removeBuild(buildId),\n })),\n );\n const removedBuilds = results.filter((r: { removed: boolean }) => r.removed).length;\n const removedFiles = results.reduce((sum: number, r: { files: number }) => sum + r.files, 0);\n await this.sweepScreenshots(project);\n const removedJobs = await this.purgeFinishedJobs(cutoff);\n this.logger?.info(\n { projectId: project.id, removedBuilds, removedFiles, removedJobs, removedPending: pending },\n \"build retention purge complete\",\n );\n return { removedBuilds, removedFiles };\n }\n\n /**\n * The builds a purge removes: terminal builds past the window (minus the latest per branch when\n * asked), and pending builds idle past it (ADR 0013, decision 9). Persistent builds stay.\n */\n private async selectBuilds(\n project: Project,\n options: PurgeOptions,\n cutoff: string,\n ): Promise<{ buildIds: string[]; pending: number }> {\n const candidates = await this.db.list(this.tables.builds, {\n where: and(\n eq(col(this.tables.builds, \"projectId\"), project.id),\n inArray(col(this.tables.builds, \"status\"), [...TERMINAL_BUILD_STATUSES]),\n lt(col(this.tables.builds, \"updatedAt\"), cutoff),\n ),\n });\n const keep = options.keepLatestPerBranch\n ? await this.latestPerBranch(project.id)\n : new Set<string>();\n const persistent = await new LabelModel(\n this.db,\n this.tables as unknown as LabelTables,\n ).persistentBuildIds(project.id);\n const terminal = candidates\n .map((build) => (build as { id: string }).id)\n .filter((buildId) => !keep.has(buildId));\n // Pending builds leave on their own clock; \"the latest on a branch\" does not shield them.\n const pending = (await pendingCandidates(this.db, project.id, cutoff)).map(\n (candidate) => candidate.buildId,\n );\n return {\n buildIds: [...terminal, ...pending].filter((buildId) => !persistent.has(buildId)),\n pending: pending.length,\n };\n }\n\n /** Reclaim screenshots no snapshot or baseline references any more; never blocks the purge. */\n private async sweepScreenshots(project: Project): Promise<void> {\n try {\n await sweepScreenshotContent(\n {\n db: this.db,\n storage: this.storage,\n tables: {\n snapshots: this.db.tables.snapshots,\n baselines: this.tables.baselines,\n builds: this.tables.builds,\n },\n logger: this.logger,\n },\n project.id,\n );\n } catch (error) {\n this.logger?.warn({ err: error, projectId: project.id }, \"screenshot sweep failed\");\n }\n }\n\n /**\n * Drop finished queue jobs older than the build cutoff (they have no foreign key to a build, and\n * a finished job is only history); never blocks the purge. Queued and running jobs are kept.\n */\n private async purgeFinishedJobs(cutoff: string): Promise<number> {\n try {\n return await new CaptureJobModel(this.db).purgeFinished(cutoff);\n } catch (error) {\n this.logger?.warn({ err: error }, \"capture job purge failed\");\n return 0;\n }\n }\n\n private async removeBuild(buildId: string): Promise<boolean> {\n await new InsightModel(this.db).removeForBuild(buildId);\n await new CaptureJobModel(this.db).removeForBuild(buildId);\n await new BuildComponentModel(this.db).removeForBuild(buildId);\n await new PackageUsageModel(this.db).removeForBuild(buildId);\n await new BuildModel(this.db, this.tables as unknown as BuildTables).remove(buildId);\n return true;\n }\n\n private async latestPerBranch(projectId: string): Promise<Set<string>> {\n const rows = (await this.db.list(this.tables.builds, {\n where: eq(col(this.tables.builds, \"projectId\"), projectId),\n orderBy: desc(col(this.tables.builds, \"updatedAt\")),\n })) as unknown as { gitBranch: string; id: string }[];\n const latest = new Map<string, string>();\n for (const row of rows) {\n const branch = row.gitBranch;\n const current = latest.get(branch);\n if (!current) {\n latest.set(branch, row.id);\n }\n }\n return new Set(latest.values());\n }\n\n private async deleteBuildFiles(projectId: string, buildId: string): Promise<number> {\n const prefix = `${projectId}/builds/${buildId}/`;\n const manifestPath = `${prefix}storybook/manifest.json`;\n let manifestFiles: string[] = [];\n try {\n if (await this.storage.exists(manifestPath)) {\n const raw = await this.storage.read(manifestPath);\n const manifest = JSON.parse(raw.toString(\"utf8\")) as Record<string, string>;\n manifestFiles = Object.values(manifest);\n }\n } catch {\n // ignore — manifest may not exist for old builds\n }\n const files = await this.storage.list(prefix);\n await Promise.all(\n files.map(async (file) => {\n await this.storage.delete(file);\n }),\n );\n // Decrement content refs for this build's manifest\n for (const hash of manifestFiles) {\n try {\n const contentRefs = this.db.tables.contentRefs;\n const existing = (await this.db.get(contentRefs, hash)) as unknown as {\n refCount: number;\n } | null;\n // oxlint-disable-next-line unicorn/prefer-ternary -- refCount check is clearer as if/else\n if (existing) {\n const refCount = existing.refCount;\n // oxlint-disable-next-line unicorn/prefer-ternary -- grace logic is clearer as if/else\n if (refCount <= 1) {\n // Keep for 7-day grace: set refCount 0 and update lastSeenAt, don't delete yet\n await this.db.update(contentRefs, hash, {\n refCount: 0,\n lastSeenAt: new Date().toISOString(),\n });\n } else {\n await this.db.update(contentRefs, hash, {\n refCount: refCount - 1,\n lastSeenAt: new Date().toISOString(),\n });\n }\n }\n } catch {\n // ignore — content_refs may not exist\n }\n }\n // GC stale content_refs with 7-day grace\n try {\n const contentRefs = this.db.tables.contentRefs;\n const cutoff = new Date(Date.now() - 7 * 86_400_000).toISOString();\n const all = (await this.db.list(contentRefs)) as unknown as {\n hash: string;\n refCount: number;\n lastSeenAt: string;\n }[];\n for (const row of all) {\n if (row.refCount === 0 && row.lastSeenAt < cutoff) {\n await this.storage.delete(`content/${row.hash}`).catch(() => {\n // ignore storage delete failure\n });\n await this.db.remove(contentRefs, row.hash);\n }\n }\n } catch {\n // ignore — content_refs may not exist\n }\n return files.length;\n }\n\n async purgeStaleBranches(project: Project, ttlDays: number): Promise<BranchGcResult> {\n if (ttlDays <= 0) {\n return { removedBranches: 0, removedBaselines: 0 };\n }\n const cutoff = new Date(Date.now() - ttlDays * 86_400_000).toISOString();\n const rows = (await this.db.list(this.tables.builds, {\n where: eq(col(this.tables.builds, \"projectId\"), project.id),\n })) as unknown as { gitBranch: string; updatedAt: string }[];\n const stale = collectStaleBranches(rows, project.gitDefaultBranch, cutoff);\n if (stale.size === 0) {\n return { removedBranches: 0, removedBaselines: 0 };\n }\n const baselines = new BaselineModel(this.db, this.tables, this.storage);\n const removedBaselines = await baselines.removeStaleBranches(project.id, stale);\n await this.sweepScreenshots(project);\n this.logger?.info(\n { projectId: project.id, removedBranches: stale.size, removedBaselines },\n \"branch GC complete\",\n );\n return { removedBranches: stale.size, removedBaselines };\n }\n}\n\n/** Options controlling which builds a retention purge removes. */\nexport interface PurgeOptions {\n ttlDays: number;\n keepLatestPerBranch: boolean;\n}\n\n/** Counts of builds and files removed by a retention purge. */\nexport interface PurgeResult {\n removedBuilds: number;\n removedFiles: number;\n}\n\n/** Counts of stale branches and baselines removed. */\nexport interface BranchGcResult {\n removedBranches: number;\n removedBaselines: number;\n}\n\nfunction collectStaleBranches(\n rows: { gitBranch: string; updatedAt: string }[],\n defaultBranch: string,\n cutoff: string,\n): Set<string> {\n const latestAt = branchLatestAt(rows);\n const stale = new Set<string>();\n for (const [branch, updatedAt] of latestAt) {\n if (branch !== defaultBranch && updatedAt < cutoff) {\n stale.add(branch);\n }\n }\n return stale;\n}\n\nfunction branchLatestAt(rows: { gitBranch: string; updatedAt: string }[]): Map<string, string> {\n const latestAt = new Map<string, string>();\n for (const row of rows) {\n const current = latestAt.get(row.gitBranch);\n if (!current || row.updatedAt > current) {\n latestAt.set(row.gitBranch, row.updatedAt);\n }\n }\n return latestAt;\n}\n"],"mappings":"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"}
|
|
@@ -11,6 +11,12 @@ interface Baseline {
|
|
|
11
11
|
snapshotId: string | null;
|
|
12
12
|
screenshotPath: string;
|
|
13
13
|
infraHash: string | null;
|
|
14
|
+
/** JSON `Fingerprint` behind `infraHash`; null on baselines recorded before fingerprints. */
|
|
15
|
+
fingerprint: string | null;
|
|
16
|
+
/** Resolved mode definition (JSON) — display/capture only; identity is `viewportName`. */
|
|
17
|
+
mode?: string | null;
|
|
18
|
+
/** Engine the image was captured with. */
|
|
19
|
+
browser?: string;
|
|
14
20
|
createdAt: string;
|
|
15
21
|
updatedAt: string;
|
|
16
22
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import __tsdown_shims_path from 'node:path';
|
|
2
|
+
import __tsdown_shims_url from 'node:url';
|
|
3
|
+
//#region src/schema/build-component.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Row type of `build_components` (ADR 0006, decision 9): one row per component of a published
|
|
6
|
+
* build, written from the build's Storybook index when publishing ends. A component is the
|
|
7
|
+
* stories and docs entries that share a story file (`importPath`).
|
|
8
|
+
*/
|
|
9
|
+
/** One component of one build, as persisted from the Storybook index. */
|
|
10
|
+
interface BuildComponent {
|
|
11
|
+
id: string;
|
|
12
|
+
projectId: string;
|
|
13
|
+
buildId: string;
|
|
14
|
+
/** Story file the component's entries come from, without a leading `./`; empty when the index has none. */
|
|
15
|
+
importPath: string;
|
|
16
|
+
title: string;
|
|
17
|
+
/** Story entries (docs entries are not counted). */
|
|
18
|
+
storyCount: number;
|
|
19
|
+
/** SHA-256 of the component's entries (id, name, type), so two builds compare with one string. */
|
|
20
|
+
hash: string;
|
|
21
|
+
createdAt: string;
|
|
22
|
+
}
|
|
23
|
+
//#endregion
|
|
24
|
+
export { BuildComponent };
|
|
25
|
+
//# sourceMappingURL=build-component.d.mts.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import __tsdown_shims_path from 'node:path';
|
|
2
|
+
import __tsdown_shims_url from 'node:url';
|
|
3
|
+
import { ReviewVerdict } from "../types.mjs";
|
|
4
|
+
//#region src/schema/build-review.d.ts
|
|
5
|
+
/** One verdict on one build. */
|
|
6
|
+
interface BuildReview {
|
|
7
|
+
id: string;
|
|
8
|
+
buildId: string;
|
|
9
|
+
/** The reviewer; null when auth is off or an admin token wrote it. */
|
|
10
|
+
userId: string | null;
|
|
11
|
+
verdict: ReviewVerdict;
|
|
12
|
+
body: string | null;
|
|
13
|
+
createdAt: string;
|
|
14
|
+
}
|
|
15
|
+
//#endregion
|
|
16
|
+
export { BuildReview };
|
|
17
|
+
//# sourceMappingURL=build-review.d.mts.map
|
package/dist/schema/build.d.mts
CHANGED
|
@@ -18,6 +18,8 @@ interface Build {
|
|
|
18
18
|
changedCount: number;
|
|
19
19
|
approvedCount: number;
|
|
20
20
|
rejectedCount: number;
|
|
21
|
+
/** Snapshots inherited from the baseline without rendering (affected capture); a subset of `snapshotCount`. */
|
|
22
|
+
inheritedCount: number;
|
|
21
23
|
/** Affected capture enabled for this build (default true). */
|
|
22
24
|
affectedOnly: boolean;
|
|
23
25
|
/** Ancestor commit the affected set was computed against, if any. */
|
|
@@ -26,6 +28,11 @@ interface Build {
|
|
|
26
28
|
changedFiles: string | null;
|
|
27
29
|
/** JSON array of affected story import paths; null means full capture. */
|
|
28
30
|
affectedImportPaths: string | null;
|
|
31
|
+
/**
|
|
32
|
+
* JSON array of the results of the build's Storybook `.test()` entries (`TestResult`); null when
|
|
33
|
+
* the project does not run tests or the Storybook has none.
|
|
34
|
+
*/
|
|
35
|
+
testResults?: string | null;
|
|
29
36
|
createdAt: string;
|
|
30
37
|
updatedAt: string;
|
|
31
38
|
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import __tsdown_shims_path from 'node:path';
|
|
2
|
+
import __tsdown_shims_url from 'node:url';
|
|
3
|
+
//#region src/schema/capture-job.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Row types of `capture_jobs`, the database queue (ADR 0006, decision 4).
|
|
6
|
+
*
|
|
7
|
+
* One table holds two kinds of row, told apart by `entry`:
|
|
8
|
+
*
|
|
9
|
+
* - a **job** row (`id` = job id, `jobId` = its own id) carries delivery state: `status` is
|
|
10
|
+
* `queued | running | done | failed | cancelled`, `attempt` counts the attempts started so far,
|
|
11
|
+
* and `visibleAt` is when a queued job may next be claimed;
|
|
12
|
+
* - an **attempt** row (`id` = `<jobId>:<attempt>`) is one claim of a job, created by a unique-key
|
|
13
|
+
* insert. It holds the `owner`, the lease expiry (in `visibleAt`) and, once over, its outcome:
|
|
14
|
+
* `status` is `running | done | failed | expired`.
|
|
15
|
+
*
|
|
16
|
+
* `visibleAt` is non-null only while a row is a poll candidate (a queued job row, a running attempt
|
|
17
|
+
* row), so the poll is one indexed range query that reads at most one row.
|
|
18
|
+
*/
|
|
19
|
+
/** Which of the two row kinds of `capture_jobs` a row is. */
|
|
20
|
+
type CaptureJobEntry = "job" | "attempt";
|
|
21
|
+
/** What a job does: render a build or shard, publish a zip, or deliver a notification. */
|
|
22
|
+
type CaptureJobKind = "capture" | "publish" | "notify";
|
|
23
|
+
/** Lifecycle of a job row. */
|
|
24
|
+
type CaptureJobStatus = "queued" | "running" | "done" | "failed" | "cancelled";
|
|
25
|
+
/** Lifecycle of an attempt row; `expired` is a lease another instance took over. */
|
|
26
|
+
type CaptureAttemptStatus = "running" | "done" | "failed" | "expired";
|
|
27
|
+
/** One row of `capture_jobs`: a job, or one attempt at it. */
|
|
28
|
+
interface CaptureJobRow {
|
|
29
|
+
id: string;
|
|
30
|
+
entry: CaptureJobEntry;
|
|
31
|
+
/** The job this row belongs to (a job row's own id). */
|
|
32
|
+
jobId: string;
|
|
33
|
+
kind: CaptureJobKind;
|
|
34
|
+
/** Build the job works on; notify jobs may have none. */
|
|
35
|
+
buildId: string | null;
|
|
36
|
+
/** `capture_shards` row a shard job renders; null for the build's own job. */
|
|
37
|
+
shardId: string | null;
|
|
38
|
+
/** Job row: attempts started so far. Attempt row: its number, from 1. */
|
|
39
|
+
attempt: number;
|
|
40
|
+
/** Attempts allowed in total (`testing.retries` + 1); copied onto attempt rows. */
|
|
41
|
+
maxAttempts: number;
|
|
42
|
+
status: CaptureJobStatus | CaptureAttemptStatus;
|
|
43
|
+
/** Queued job: when it may be claimed. Running attempt: its lease expiry. Otherwise null. */
|
|
44
|
+
visibleAt: string | null;
|
|
45
|
+
/** Instance holding an attempt; null on job rows. */
|
|
46
|
+
owner: string | null;
|
|
47
|
+
/** JSON: request id, traceparent and kind-specific fields. */
|
|
48
|
+
payload: string | null;
|
|
49
|
+
/** Set once cancel was asked for; the holder turns it into `runner.cancel`. */
|
|
50
|
+
cancelRequestedAt: string | null;
|
|
51
|
+
/** Job row: the last attempt's error. Attempt row: its own. */
|
|
52
|
+
error: string | null;
|
|
53
|
+
startedAt: string | null;
|
|
54
|
+
finishedAt: string | null;
|
|
55
|
+
createdAt: string;
|
|
56
|
+
updatedAt: string;
|
|
57
|
+
}
|
|
58
|
+
//#endregion
|
|
59
|
+
export { CaptureAttemptStatus, CaptureJobEntry, CaptureJobKind, CaptureJobRow, CaptureJobStatus };
|
|
60
|
+
//# sourceMappingURL=capture-job.d.mts.map
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import __tsdown_shims_path from 'node:path';
|
|
2
|
+
import __tsdown_shims_url from 'node:url';
|
|
3
|
+
//#region src/schema/capture-shard.d.ts
|
|
4
|
+
/** What a row of `capture_shards` is: a stage of a sharded capture, or the claim that finalizes it. */
|
|
5
|
+
type CaptureShardKind = "plan" | "render" | "finalize";
|
|
6
|
+
/** Lifecycle of one shard row. */
|
|
7
|
+
type CaptureShardStatus = "pending" | "running" | "done" | "failed";
|
|
8
|
+
/**
|
|
9
|
+
* One row per stage of a sharded capture: the `plan` (ordinal 0) records the build-level plan, each
|
|
10
|
+
* `render` shard (ordinal 1..n) is a slice of stories, and a `finalize` row (ordinal 0) is the
|
|
11
|
+
* claim that makes exactly one worker finish the build. `(buildId, kind, ordinal)` is unique, so
|
|
12
|
+
* inserting a row is an atomic claim on every database.
|
|
13
|
+
*/
|
|
14
|
+
interface CaptureShard {
|
|
15
|
+
id: string;
|
|
16
|
+
projectId: string;
|
|
17
|
+
buildId: string;
|
|
18
|
+
kind: CaptureShardKind;
|
|
19
|
+
ordinal: number;
|
|
20
|
+
status: CaptureShardStatus;
|
|
21
|
+
/** Attempt row the build's log history and result belong to (plan row only). */
|
|
22
|
+
attemptId: string | null;
|
|
23
|
+
/** JSON: the stories and engines of a render shard, or the plan's settings. */
|
|
24
|
+
payload: string | null;
|
|
25
|
+
/** JSON outcome once the stage ended (failed, flaky and a11y story ids, counts, duration). */
|
|
26
|
+
result: string | null;
|
|
27
|
+
error: string | null;
|
|
28
|
+
startedAt: string | null;
|
|
29
|
+
finishedAt: string | null;
|
|
30
|
+
createdAt: string;
|
|
31
|
+
updatedAt: string;
|
|
32
|
+
}
|
|
33
|
+
//#endregion
|
|
34
|
+
export { CaptureShard, CaptureShardKind, CaptureShardStatus };
|
|
35
|
+
//# sourceMappingURL=capture-shard.d.mts.map
|
|
@@ -7,6 +7,8 @@ interface Comment {
|
|
|
7
7
|
projectId: string;
|
|
8
8
|
buildId: string;
|
|
9
9
|
snapshotId: string | null;
|
|
10
|
+
/** Storybook story id of a story-anchored comment (ADR 0013, decision 2); null otherwise. */
|
|
11
|
+
storyId: string | null;
|
|
10
12
|
userId: string | null;
|
|
11
13
|
body: string;
|
|
12
14
|
parentId: string | null;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import __tsdown_shims_path from 'node:path';
|
|
2
|
+
import __tsdown_shims_url from 'node:url';
|
|
3
|
+
//#region src/schema/connection.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* A user's own OAuth connection to an external service (`design` providers today,
|
|
6
|
+
* issue trackers later). One per user and service; tokens are encrypted at rest.
|
|
7
|
+
*/
|
|
8
|
+
interface UserConnection {
|
|
9
|
+
id: string;
|
|
10
|
+
userId: string;
|
|
11
|
+
/** Service `metadata.kind`, e.g. `figma`. */
|
|
12
|
+
service: string;
|
|
13
|
+
tokenEncrypted: string;
|
|
14
|
+
refreshTokenEncrypted: string | null;
|
|
15
|
+
/** When the access token expires; null when it never does. */
|
|
16
|
+
expiresAt: string | null;
|
|
17
|
+
/** `ok`, or `expired` once a refresh failed and the user must reconnect. */
|
|
18
|
+
status: string;
|
|
19
|
+
createdAt: string;
|
|
20
|
+
updatedAt: string;
|
|
21
|
+
}
|
|
22
|
+
//#endregion
|
|
23
|
+
export { UserConnection };
|
|
24
|
+
//# sourceMappingURL=connection.d.mts.map
|