@storyshelf/core 0.6.2 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +49 -32
- package/dist/adapter/capture-queue.d.mts +44 -4
- package/dist/adapter/capture-runner.d.mts +134 -12
- package/dist/adapter/capture-runner.mjs +9 -12
- package/dist/adapter/database.d.mts +89 -10
- package/dist/adapter/database.mjs +4 -3
- package/dist/adapter/database.mjs.map +1 -1
- package/dist/adapter/design.d.mts +83 -0
- package/dist/adapter/design.mjs +27 -0
- package/dist/adapter/design.mjs.map +1 -0
- package/dist/adapter/email-sender.d.mts +0 -2
- package/dist/adapter/metadata.d.mts +3 -12
- package/dist/adapter/notifier.d.mts +6 -3
- package/dist/adapter/notifier.mjs +5 -1
- package/dist/adapter/oauth.d.mts +50 -0
- package/dist/adapter/setup.d.mts +4 -7
- package/dist/adapter/setup.mjs +3 -3
- package/dist/adapter/setup.mjs.map +1 -1
- package/dist/adapter/storage.d.mts +1 -3
- package/dist/adapter/webhook-events.d.mts +0 -2
- package/dist/adapter/webhook-events.mjs +13 -14
- package/dist/adapter/webhook-events.mjs.map +1 -1
- package/dist/adapters/capture-target-offers.d.mts +26 -0
- package/dist/adapters/capture-target-offers.mjs +57 -0
- package/dist/adapters/capture-target-offers.mjs.map +1 -0
- package/dist/adapters/capture-target.d.mts +69 -0
- package/dist/adapters/capture-target.mjs +75 -0
- package/dist/adapters/capture-target.mjs.map +1 -0
- package/dist/adapters/notifier/build-comment.d.mts +22 -0
- package/dist/adapters/notifier/build-comment.mjs +23 -0
- package/dist/adapters/notifier/build-comment.mjs.map +1 -0
- package/dist/adapters/notifier/build-state.d.mts +97 -0
- package/dist/adapters/notifier/build-state.mjs +217 -0
- package/dist/adapters/notifier/build-state.mjs.map +1 -0
- package/dist/adapters/notifier/events.d.mts +4 -4
- package/dist/adapters/notifier/events.mjs +32 -20
- package/dist/adapters/notifier/events.mjs.map +1 -1
- package/dist/adapters/notifier/format.d.mts +0 -2
- package/dist/adapters/notifier/format.mjs +1 -2
- package/dist/adapters/notifier/provider.d.mts +53 -9
- package/dist/adapters/notifier/provider.mjs +11 -0
- package/dist/adapters/notifier/provider.mjs.map +1 -0
- package/dist/adapters/notifier/state.d.mts +22 -0
- package/dist/adapters/notifier/state.mjs +65 -0
- package/dist/adapters/notifier/state.mjs.map +1 -0
- package/dist/adapters/notifier/types.d.mts +51 -4
- package/dist/adapters/remote-browser.d.mts +168 -0
- package/dist/adapters/remote-browser.mjs +201 -0
- package/dist/adapters/remote-browser.mjs.map +1 -0
- package/dist/adapters/remote-lease-attempt.mjs +39 -0
- package/dist/adapters/remote-lease-attempt.mjs.map +1 -0
- package/dist/adapters/remote-lease-error.d.mts +25 -0
- package/dist/adapters/remote-lease-error.mjs +54 -0
- package/dist/adapters/remote-lease-error.mjs.map +1 -0
- package/dist/adapters/remote-lease-held.d.mts +15 -0
- package/dist/adapters/remote-lease-held.mjs +86 -0
- package/dist/adapters/remote-lease-held.mjs.map +1 -0
- package/dist/adapters/remote-lease-shutdown.d.mts +12 -0
- package/dist/adapters/remote-lease-shutdown.mjs +32 -0
- package/dist/adapters/remote-lease-shutdown.mjs.map +1 -0
- package/dist/adapters/remote-lease.d.mts +39 -0
- package/dist/adapters/remote-lease.mjs +175 -0
- package/dist/adapters/remote-lease.mjs.map +1 -0
- package/dist/adapters/remote-limiter.mjs +121 -0
- package/dist/adapters/remote-limiter.mjs.map +1 -0
- package/dist/adapters/remote-pool.d.mts +56 -0
- package/dist/adapters/remote-pool.mjs +195 -0
- package/dist/adapters/remote-pool.mjs.map +1 -0
- package/dist/adapters/render-runs.d.mts +23 -0
- package/dist/adapters/render-runs.mjs +40 -0
- package/dist/adapters/render-runs.mjs.map +1 -0
- package/dist/adapters/setup-runner.d.mts +0 -2
- package/dist/adapters/setup-runner.mjs +3 -6
- package/dist/adapters/setup-runner.mjs.map +1 -1
- package/dist/adapters/sources.d.mts +10 -0
- package/dist/adapters/sources.mjs +19 -0
- package/dist/adapters/sources.mjs.map +1 -0
- package/dist/adapters/validate.d.mts +0 -2
- package/dist/adapters/validate.mjs +27 -20
- package/dist/adapters/validate.mjs.map +1 -1
- package/dist/ai.d.mts +103 -0
- package/dist/ai.mjs +18 -0
- package/dist/ai.mjs.map +1 -0
- package/dist/auth.d.mts +0 -2
- package/dist/auth.mjs +1 -2
- package/dist/brand-packs.d.mts +0 -2
- package/dist/brand-packs.mjs +0 -1
- package/dist/capture/a11y/axe-source.d.mts +16 -0
- package/dist/capture/a11y/axe-source.mjs +36 -0
- package/dist/capture/a11y/axe-source.mjs.map +1 -0
- package/dist/capture/a11y/in-page.d.mts +25 -0
- package/dist/capture/a11y/in-page.mjs +59 -0
- package/dist/capture/a11y/in-page.mjs.map +1 -0
- package/dist/capture/a11y/map.d.mts +22 -0
- package/dist/capture/a11y/map.mjs +47 -0
- package/dist/capture/a11y/map.mjs.map +1 -0
- package/dist/capture/a11y/options.d.mts +21 -0
- package/dist/capture/a11y/options.mjs +74 -0
- package/dist/capture/a11y/options.mjs.map +1 -0
- package/dist/capture/adapter.d.mts +46 -5
- package/dist/capture/adapter.mjs +1 -2
- package/dist/capture/adapter.mjs.map +1 -1
- package/dist/capture/affected.d.mts +2 -2
- package/dist/capture/affected.mjs +14 -6
- package/dist/capture/affected.mjs.map +1 -1
- package/dist/capture/attempt-logger.mjs +83 -0
- package/dist/capture/attempt-logger.mjs.map +1 -0
- package/dist/capture/browsers.d.mts +33 -0
- package/dist/capture/browsers.mjs +66 -0
- package/dist/capture/browsers.mjs.map +1 -0
- package/dist/capture/build-state/deliver.d.mts +30 -0
- package/dist/capture/build-state/deliver.mjs +117 -0
- package/dist/capture/build-state/deliver.mjs.map +1 -0
- package/dist/capture/build-state/index.d.mts +8 -0
- package/dist/capture/build-state/notify-job.d.mts +54 -0
- package/dist/capture/build-state/notify-job.mjs +132 -0
- package/dist/capture/build-state/notify-job.mjs.map +1 -0
- package/dist/capture/build-state/publisher.d.mts +21 -0
- package/dist/capture/build-state/publisher.mjs +106 -0
- package/dist/capture/build-state/publisher.mjs.map +1 -0
- package/dist/capture/build-state/resume.d.mts +7 -0
- package/dist/capture/build-state/resume.mjs +46 -0
- package/dist/capture/build-state/resume.mjs.map +1 -0
- package/dist/capture/build-state/types.d.mts +47 -0
- package/dist/capture/catalog/catalogue.d.mts +20 -0
- package/dist/capture/catalog/catalogue.mjs +52 -0
- package/dist/capture/catalog/catalogue.mjs.map +1 -0
- package/dist/capture/catalog/components.d.mts +21 -0
- package/dist/capture/catalog/components.mjs +54 -0
- package/dist/capture/catalog/components.mjs.map +1 -0
- package/dist/capture/catalog/history.d.mts +28 -0
- package/dist/capture/catalog/history.mjs +50 -0
- package/dist/capture/catalog/history.mjs.map +1 -0
- package/dist/capture/catalog/hook.d.mts +12 -0
- package/dist/capture/catalog/hook.mjs +18 -0
- package/dist/capture/catalog/hook.mjs.map +1 -0
- package/dist/capture/catalog/index.d.mts +11 -0
- package/dist/capture/catalog/load.d.mts +24 -0
- package/dist/capture/catalog/load.mjs +48 -0
- package/dist/capture/catalog/load.mjs.map +1 -0
- package/dist/capture/catalog/parse.d.mts +13 -0
- package/dist/capture/catalog/parse.mjs +55 -0
- package/dist/capture/catalog/parse.mjs.map +1 -0
- package/dist/capture/catalog/read.d.mts +15 -0
- package/dist/capture/catalog/read.mjs +39 -0
- package/dist/capture/catalog/read.mjs.map +1 -0
- package/dist/capture/catalog/types.d.mts +31 -0
- package/dist/capture/comparison-reference.d.mts +27 -0
- package/dist/capture/comparison-reference.mjs +75 -0
- package/dist/capture/comparison-reference.mjs.map +1 -0
- package/dist/capture/consumer/capture-handler.d.mts +56 -0
- package/dist/capture/consumer/capture-handler.mjs +117 -0
- package/dist/capture/consumer/capture-handler.mjs.map +1 -0
- package/dist/capture/consumer/idle.d.mts +15 -0
- package/dist/capture/consumer/idle.mjs +24 -0
- package/dist/capture/consumer/idle.mjs.map +1 -0
- package/dist/capture/consumer/index.d.mts +11 -0
- package/dist/capture/consumer/index.mjs +138 -0
- package/dist/capture/consumer/index.mjs.map +1 -0
- package/dist/capture/consumer/loop.mjs +109 -0
- package/dist/capture/consumer/loop.mjs.map +1 -0
- package/dist/capture/consumer/metrics.d.mts +39 -0
- package/dist/capture/consumer/metrics.mjs +37 -0
- package/dist/capture/consumer/metrics.mjs.map +1 -0
- package/dist/capture/consumer/retry.d.mts +31 -0
- package/dist/capture/consumer/retry.mjs +33 -0
- package/dist/capture/consumer/retry.mjs.map +1 -0
- package/dist/capture/consumer/run-job.mjs +194 -0
- package/dist/capture/consumer/run-job.mjs.map +1 -0
- package/dist/capture/consumer/startup.mjs +37 -0
- package/dist/capture/consumer/startup.mjs.map +1 -0
- package/dist/capture/consumer/types.d.mts +95 -0
- package/dist/capture/crash-failure.d.mts +23 -0
- package/dist/capture/crash-failure.mjs +69 -0
- package/dist/capture/crash-failure.mjs.map +1 -0
- package/dist/capture/db-queue/cadence.d.mts +30 -0
- package/dist/capture/db-queue/cadence.mjs +37 -0
- package/dist/capture/db-queue/cadence.mjs.map +1 -0
- package/dist/capture/db-queue/claim.d.mts +7 -0
- package/dist/capture/db-queue/claim.mjs +148 -0
- package/dist/capture/db-queue/claim.mjs.map +1 -0
- package/dist/capture/db-queue/clock.mjs +11 -0
- package/dist/capture/db-queue/clock.mjs.map +1 -0
- package/dist/capture/db-queue/codec.mjs +75 -0
- package/dist/capture/db-queue/codec.mjs.map +1 -0
- package/dist/capture/db-queue/index.d.mts +18 -0
- package/dist/capture/db-queue/index.mjs +89 -0
- package/dist/capture/db-queue/index.mjs.map +1 -0
- package/dist/capture/db-queue/keeper.d.mts +34 -0
- package/dist/capture/db-queue/keeper.mjs +91 -0
- package/dist/capture/db-queue/keeper.mjs.map +1 -0
- package/dist/capture/db-queue/lease.mjs +45 -0
- package/dist/capture/db-queue/lease.mjs.map +1 -0
- package/dist/capture/db-queue/lifecycle.mjs +35 -0
- package/dist/capture/db-queue/lifecycle.mjs.map +1 -0
- package/dist/capture/db-queue/operations.mjs +93 -0
- package/dist/capture/db-queue/operations.mjs.map +1 -0
- package/dist/capture/db-queue/poll-loop.d.mts +28 -0
- package/dist/capture/db-queue/poll-loop.mjs +88 -0
- package/dist/capture/db-queue/poll-loop.mjs.map +1 -0
- package/dist/capture/db-queue/poll.d.mts +8 -0
- package/dist/capture/db-queue/poll.mjs +68 -0
- package/dist/capture/db-queue/poll.mjs.map +1 -0
- package/dist/capture/db-queue/timers.d.mts +10 -0
- package/dist/capture/db-queue/timers.mjs +36 -0
- package/dist/capture/db-queue/timers.mjs.map +1 -0
- package/dist/capture/db-queue/types.d.mts +81 -0
- package/dist/capture/db-queue/types.mjs +11 -0
- package/dist/capture/db-queue/types.mjs.map +1 -0
- package/dist/capture/db-queue/wake.mjs +47 -0
- package/dist/capture/db-queue/wake.mjs.map +1 -0
- package/dist/capture/design.mjs +38 -0
- package/dist/capture/design.mjs.map +1 -0
- package/dist/capture/diff-record.mjs +24 -5
- package/dist/capture/diff-record.mjs.map +1 -1
- package/dist/capture/dispatch.d.mts +13 -10
- package/dist/capture/dispatch.mjs +102 -111
- package/dist/capture/dispatch.mjs.map +1 -1
- package/dist/capture/enrich.mjs +59 -0
- package/dist/capture/enrich.mjs.map +1 -0
- package/dist/capture/failure-sets.mjs +21 -0
- package/dist/capture/failure-sets.mjs.map +1 -0
- package/dist/capture/fingerprint.d.mts +40 -0
- package/dist/capture/fingerprint.mjs +82 -0
- package/dist/capture/fingerprint.mjs.map +1 -0
- package/dist/capture/forked/channel.d.mts +23 -0
- package/dist/capture/forked/child.d.mts +32 -0
- package/dist/capture/forked/child.mjs +201 -0
- package/dist/capture/forked/child.mjs.map +1 -0
- package/dist/capture/forked/crash-tracker.mjs +22 -0
- package/dist/capture/forked/crash-tracker.mjs.map +1 -0
- package/dist/capture/forked/decision.d.mts +24 -0
- package/dist/capture/forked/decision.mjs +26 -0
- package/dist/capture/forked/decision.mjs.map +1 -0
- package/dist/capture/forked/exit.mjs +11 -0
- package/dist/capture/forked/exit.mjs.map +1 -0
- package/dist/capture/forked/fork.d.mts +29 -0
- package/dist/capture/forked/fork.mjs +92 -0
- package/dist/capture/forked/fork.mjs.map +1 -0
- package/dist/capture/forked/forked-capture.d.mts +38 -0
- package/dist/capture/forked/forked-capture.mjs +239 -0
- package/dist/capture/forked/forked-capture.mjs.map +1 -0
- package/dist/capture/forked/lease-book.mjs +45 -0
- package/dist/capture/forked/lease-book.mjs.map +1 -0
- package/dist/capture/forked/protocol.d.mts +36 -0
- package/dist/capture/forked/protocol.mjs +24 -0
- package/dist/capture/forked/protocol.mjs.map +1 -0
- package/dist/capture/forked/runs.mjs +74 -0
- package/dist/capture/forked/runs.mjs.map +1 -0
- package/dist/capture/guards.mjs +1 -2
- package/dist/capture/ignore-regions.d.mts +36 -0
- package/dist/capture/ignore-regions.mjs +65 -0
- package/dist/capture/ignore-regions.mjs.map +1 -0
- package/dist/capture/isolate.mjs +140 -0
- package/dist/capture/isolate.mjs.map +1 -0
- package/dist/capture/job-lease.d.mts +26 -0
- package/dist/capture/job-lease.mjs +56 -0
- package/dist/capture/job-lease.mjs.map +1 -0
- package/dist/capture/matrix.mjs +298 -0
- package/dist/capture/matrix.mjs.map +1 -0
- package/dist/capture/modes.d.mts +23 -0
- package/dist/capture/modes.mjs +106 -0
- package/dist/capture/modes.mjs.map +1 -0
- package/dist/capture/orchestrator.d.mts +37 -7
- package/dist/capture/orchestrator.mjs +120 -110
- package/dist/capture/orchestrator.mjs.map +1 -1
- package/dist/capture/pipeline.d.mts +9 -4
- package/dist/capture/pipeline.mjs +284 -48
- package/dist/capture/pipeline.mjs.map +1 -1
- package/dist/capture/progress.d.mts +9 -0
- package/dist/capture/progress.mjs +27 -0
- package/dist/capture/progress.mjs.map +1 -0
- package/dist/capture/publish/accept.d.mts +37 -0
- package/dist/capture/publish/accept.mjs +27 -0
- package/dist/capture/publish/accept.mjs.map +1 -0
- package/dist/capture/publish/complete.d.mts +44 -0
- package/dist/capture/publish/complete.mjs +63 -0
- package/dist/capture/publish/complete.mjs.map +1 -0
- package/dist/capture/publish/extract.d.mts +18 -0
- package/dist/capture/publish/extract.mjs +120 -0
- package/dist/capture/publish/extract.mjs.map +1 -0
- package/dist/capture/publish/handler.d.mts +26 -0
- package/dist/capture/publish/handler.mjs +61 -0
- package/dist/capture/publish/handler.mjs.map +1 -0
- package/dist/capture/publish/index.d.mts +11 -0
- package/dist/capture/publish/invalid-zip.d.mts +8 -0
- package/dist/capture/publish/invalid-zip.mjs +11 -0
- package/dist/capture/publish/invalid-zip.mjs.map +1 -0
- package/dist/capture/publish/limits.d.mts +35 -0
- package/dist/capture/publish/limits.mjs +38 -0
- package/dist/capture/publish/limits.mjs.map +1 -0
- package/dist/capture/publish/published.d.mts +13 -0
- package/dist/capture/publish/published.mjs +17 -0
- package/dist/capture/publish/published.mjs.map +1 -0
- package/dist/capture/publish/run.d.mts +31 -0
- package/dist/capture/publish/run.mjs +29 -0
- package/dist/capture/publish/run.mjs.map +1 -0
- package/dist/capture/rediff.d.mts +1 -3
- package/dist/capture/rediff.mjs +23 -9
- package/dist/capture/rediff.mjs.map +1 -1
- package/dist/capture/render.mjs +52 -0
- package/dist/capture/render.mjs.map +1 -0
- package/dist/capture/scratch.d.mts +42 -0
- package/dist/capture/scratch.mjs +136 -0
- package/dist/capture/scratch.mjs.map +1 -0
- package/dist/capture/shard-plan.mjs +127 -0
- package/dist/capture/shard-plan.mjs.map +1 -0
- package/dist/capture/shards/context.mjs +51 -0
- package/dist/capture/shards/context.mjs.map +1 -0
- package/dist/capture/shards/execute.mjs +175 -0
- package/dist/capture/shards/execute.mjs.map +1 -0
- package/dist/capture/shards/fan-out.mjs +125 -0
- package/dist/capture/shards/fan-out.mjs.map +1 -0
- package/dist/capture/shards/history.mjs +34 -0
- package/dist/capture/shards/history.mjs.map +1 -0
- package/dist/capture/shards/reduce.mjs +96 -0
- package/dist/capture/shards/reduce.mjs.map +1 -0
- package/dist/capture/sizing.d.mts +0 -2
- package/dist/capture/sizing.mjs +2 -13
- package/dist/capture/sizing.mjs.map +1 -1
- package/dist/capture/skip-checks.mjs +3 -67
- package/dist/capture/skip-checks.mjs.map +1 -1
- package/dist/capture/statics.d.mts +7 -10
- package/dist/capture/statics.mjs +26 -74
- package/dist/capture/statics.mjs.map +1 -1
- package/dist/capture/storybook-urls.d.mts +38 -0
- package/dist/capture/storybook-urls.mjs +41 -0
- package/dist/capture/storybook-urls.mjs.map +1 -0
- package/dist/capture/storybook.d.mts +12 -6
- package/dist/capture/storybook.mjs +63 -19
- package/dist/capture/storybook.mjs.map +1 -1
- package/dist/capture/target.mjs +20 -0
- package/dist/capture/target.mjs.map +1 -0
- package/dist/capture/targets.d.mts +23 -0
- package/dist/capture/targets.mjs +31 -0
- package/dist/capture/targets.mjs.map +1 -0
- package/dist/capture/tests.d.mts +35 -0
- package/dist/capture/tests.mjs +136 -0
- package/dist/capture/tests.mjs.map +1 -0
- package/dist/capture/token-chunks.mjs +91 -0
- package/dist/capture/token-chunks.mjs.map +1 -0
- package/dist/capture.d.mts +63 -7
- package/dist/capture.mjs +51 -3
- package/dist/config.d.mts +136 -14
- package/dist/config.mjs +14 -8
- package/dist/config.mjs.map +1 -1
- package/dist/diff.d.mts +7 -3
- package/dist/diff.mjs +15 -3
- package/dist/diff.mjs.map +1 -0
- package/dist/index.d.mts +3 -3
- package/dist/index.mjs +2 -2
- package/dist/insights/budget.d.mts +18 -0
- package/dist/insights/budget.mjs +32 -0
- package/dist/insights/budget.mjs.map +1 -0
- package/dist/insights/envelope.d.mts +57 -0
- package/dist/insights/envelope.mjs +59 -0
- package/dist/insights/envelope.mjs.map +1 -0
- package/dist/insights/evidence-health.d.mts +30 -0
- package/dist/insights/evidence-health.mjs +27 -0
- package/dist/insights/evidence-health.mjs.map +1 -0
- package/dist/insights/evidence.d.mts +53 -0
- package/dist/insights/evidence.mjs +63 -0
- package/dist/insights/evidence.mjs.map +1 -0
- package/dist/insights/hash.d.mts +16 -0
- package/dist/insights/hash.mjs +20 -0
- package/dist/insights/hash.mjs.map +1 -0
- package/dist/insights/limits.d.mts +22 -0
- package/dist/insights/limits.mjs +23 -0
- package/dist/insights/limits.mjs.map +1 -0
- package/dist/insights/prompts/v1.d.mts +11 -0
- package/dist/insights/prompts/v1.mjs +33 -0
- package/dist/insights/prompts/v1.mjs.map +1 -0
- package/dist/insights/redact.d.mts +7 -0
- package/dist/insights/redact.mjs +23 -0
- package/dist/insights/redact.mjs.map +1 -0
- package/dist/insights/text.mjs +43 -0
- package/dist/insights/text.mjs.map +1 -0
- package/dist/insights.d.mts +11 -0
- package/dist/insights.mjs +12 -0
- package/dist/logger.d.mts +0 -2
- package/dist/logger.mjs +18 -3
- package/dist/logger.mjs.map +1 -1
- package/dist/models/ai-budget-alert.d.mts +21 -0
- package/dist/models/ai-budget-alert.mjs +37 -0
- package/dist/models/ai-budget-alert.mjs.map +1 -0
- package/dist/models/ai-usage.d.mts +38 -0
- package/dist/models/ai-usage.mjs +60 -0
- package/dist/models/ai-usage.mjs.map +1 -0
- package/dist/models/baseline-approval.d.mts +18 -0
- package/dist/models/baseline-approval.mjs +41 -0
- package/dist/models/baseline-approval.mjs.map +1 -0
- package/dist/models/baseline-archive.d.mts +82 -0
- package/dist/models/baseline-archive.mjs +105 -0
- package/dist/models/baseline-archive.mjs.map +1 -0
- package/dist/models/baseline-atomic.mjs +66 -0
- package/dist/models/baseline-atomic.mjs.map +1 -0
- package/dist/models/baseline-conflict.d.mts +6 -0
- package/dist/models/baseline-conflict.mjs +9 -0
- package/dist/models/baseline-conflict.mjs.map +1 -0
- package/dist/models/baseline-export.d.mts +17 -0
- package/dist/models/baseline-export.mjs +135 -0
- package/dist/models/baseline-export.mjs.map +1 -0
- package/dist/models/baseline-import.mjs +117 -0
- package/dist/models/baseline-import.mjs.map +1 -0
- package/dist/models/baseline-origin.d.mts +16 -0
- package/dist/models/baseline-origin.mjs +35 -0
- package/dist/models/baseline-origin.mjs.map +1 -0
- package/dist/models/baseline-status.d.mts +0 -2
- package/dist/models/baseline-status.mjs +1 -2
- package/dist/models/baseline-transfer.d.mts +48 -0
- package/dist/models/baseline-transfer.mjs +149 -0
- package/dist/models/baseline-transfer.mjs.map +1 -0
- package/dist/models/baseline.d.mts +37 -7
- package/dist/models/baseline.mjs +152 -28
- package/dist/models/baseline.mjs.map +1 -1
- package/dist/models/build-component.d.mts +26 -0
- package/dist/models/build-component.mjs +54 -0
- package/dist/models/build-component.mjs.map +1 -0
- package/dist/models/build-review-status.d.mts +27 -0
- package/dist/models/build-review-status.mjs +36 -0
- package/dist/models/build-review-status.mjs.map +1 -0
- package/dist/models/build-review.d.mts +26 -0
- package/dist/models/build-review.mjs +53 -0
- package/dist/models/build-review.mjs.map +1 -0
- package/dist/models/build.d.mts +25 -5
- package/dist/models/build.mjs +53 -15
- package/dist/models/build.mjs.map +1 -1
- package/dist/models/capture-attempt.d.mts +7 -3
- package/dist/models/capture-attempt.mjs +30 -6
- package/dist/models/capture-attempt.mjs.map +1 -1
- package/dist/models/capture-job.d.mts +104 -0
- package/dist/models/capture-job.mjs +287 -0
- package/dist/models/capture-job.mjs.map +1 -0
- package/dist/models/capture-log.d.mts +1 -3
- package/dist/models/capture-log.mjs +5 -5
- package/dist/models/capture-log.mjs.map +1 -1
- package/dist/models/capture-shard.d.mts +60 -0
- package/dist/models/capture-shard.mjs +189 -0
- package/dist/models/capture-shard.mjs.map +1 -0
- package/dist/models/changeset.d.mts +46 -0
- package/dist/models/changeset.mjs +80 -0
- package/dist/models/changeset.mjs.map +1 -0
- package/dist/models/columns.mjs +18 -0
- package/dist/models/columns.mjs.map +1 -0
- package/dist/models/comment.d.mts +4 -3
- package/dist/models/comment.mjs +8 -6
- package/dist/models/comment.mjs.map +1 -1
- package/dist/models/component.d.mts +41 -0
- package/dist/models/component.mjs +59 -0
- package/dist/models/component.mjs.map +1 -0
- package/dist/models/connections.d.mts +39 -0
- package/dist/models/connections.mjs +111 -0
- package/dist/models/connections.mjs.map +1 -0
- package/dist/models/content-ref.d.mts +1 -3
- package/dist/models/content-ref.mjs +2 -3
- package/dist/models/content-ref.mjs.map +1 -1
- package/dist/models/credentials.d.mts +0 -2
- package/dist/models/credentials.mjs +13 -4
- package/dist/models/credentials.mjs.map +1 -1
- package/dist/models/design-credentials.d.mts +39 -0
- package/dist/models/design-credentials.mjs +85 -0
- package/dist/models/design-credentials.mjs.map +1 -0
- package/dist/models/design-health.d.mts +14 -0
- package/dist/models/design-health.mjs +24 -0
- package/dist/models/design-health.mjs.map +1 -0
- package/dist/models/design-nodes.d.mts +81 -0
- package/dist/models/design-nodes.mjs +221 -0
- package/dist/models/design-nodes.mjs.map +1 -0
- package/dist/models/design-renders.mjs +73 -0
- package/dist/models/design-renders.mjs.map +1 -0
- package/dist/models/design-resolve.d.mts +27 -0
- package/dist/models/design-resolve.mjs +40 -0
- package/dist/models/design-resolve.mjs.map +1 -0
- package/dist/models/design.d.mts +37 -0
- package/dist/models/design.mjs +89 -0
- package/dist/models/design.mjs.map +1 -0
- package/dist/models/inherited-ratio.d.mts +19 -0
- package/dist/models/inherited-ratio.mjs +21 -0
- package/dist/models/inherited-ratio.mjs.map +1 -0
- package/dist/models/insight.d.mts +68 -0
- package/dist/models/insight.mjs +161 -0
- package/dist/models/insight.mjs.map +1 -0
- package/dist/models/label.d.mts +4 -4
- package/dist/models/label.mjs +26 -12
- package/dist/models/label.mjs.map +1 -1
- package/dist/models/member.d.mts +1 -3
- package/dist/models/member.mjs +6 -6
- package/dist/models/member.mjs.map +1 -1
- package/dist/models/notification-channel.d.mts +37 -5
- package/dist/models/notification-channel.mjs +89 -11
- package/dist/models/notification-channel.mjs.map +1 -1
- package/dist/models/notification-subscription.d.mts +1 -4
- package/dist/models/notification-subscription.mjs +8 -11
- package/dist/models/notification-subscription.mjs.map +1 -1
- package/dist/models/project-group-mapping.d.mts +6 -3
- package/dist/models/project-group-mapping.mjs +15 -6
- package/dist/models/project-group-mapping.mjs.map +1 -1
- package/dist/models/project.d.mts +9 -3
- package/dist/models/project.mjs +10 -6
- package/dist/models/project.mjs.map +1 -1
- package/dist/models/public-origin.d.mts +61 -0
- package/dist/models/public-origin.mjs +113 -0
- package/dist/models/public-origin.mjs.map +1 -0
- package/dist/models/review-switch-off.d.mts +12 -0
- package/dist/models/review-switch-off.mjs +25 -0
- package/dist/models/review-switch-off.mjs.map +1 -0
- package/dist/models/site-settings.d.mts +21 -0
- package/dist/models/site-settings.mjs +47 -0
- package/dist/models/site-settings.mjs.map +1 -0
- package/dist/models/snapshot.d.mts +39 -3
- package/dist/models/snapshot.mjs +54 -5
- package/dist/models/snapshot.mjs.map +1 -1
- package/dist/models/token.d.mts +13 -3
- package/dist/models/token.mjs +24 -7
- package/dist/models/token.mjs.map +1 -1
- package/dist/models/usage.d.mts +74 -0
- package/dist/models/usage.mjs +148 -0
- package/dist/models/usage.mjs.map +1 -0
- package/dist/models/user.d.mts +1 -3
- package/dist/models/user.mjs +5 -5
- package/dist/models/user.mjs.map +1 -1
- package/dist/models/webhook.d.mts +15 -3
- package/dist/models/webhook.mjs +34 -6
- package/dist/models/webhook.mjs.map +1 -1
- package/dist/models.d.mts +30 -4
- package/dist/models.mjs +31 -6
- package/dist/options.d.mts +16 -0
- package/dist/options.mjs +88 -0
- package/dist/options.mjs.map +1 -0
- package/dist/paths.d.mts +21 -6
- package/dist/paths.mjs +57 -12
- package/dist/paths.mjs.map +1 -1
- package/dist/retention/ai.d.mts +16 -0
- package/dist/retention/ai.mjs +22 -0
- package/dist/retention/ai.mjs.map +1 -0
- package/dist/retention/pending.mjs +46 -0
- package/dist/retention/pending.mjs.map +1 -0
- package/dist/retention/published-gzip-sweep.mjs +28 -0
- package/dist/retention/published-gzip-sweep.mjs.map +1 -0
- package/dist/retention/screenshot-sweep.d.mts +49 -0
- package/dist/retention/screenshot-sweep.mjs +127 -0
- package/dist/retention/screenshot-sweep.mjs.map +1 -0
- package/dist/retention.d.mts +16 -4
- package/dist/retention.mjs +76 -16
- package/dist/retention.mjs.map +1 -1
- package/dist/schema/baseline.d.mts +8 -2
- package/dist/schema/build-component.d.mts +23 -0
- package/dist/schema/build-review.d.mts +15 -0
- package/dist/schema/build.d.mts +7 -2
- package/dist/schema/capture-attempt.d.mts +0 -2
- package/dist/schema/capture-job.d.mts +58 -0
- package/dist/schema/capture-shard.d.mts +33 -0
- package/dist/schema/comment.d.mts +2 -2
- package/dist/schema/connection.d.mts +22 -0
- package/dist/schema/content-ref.d.mts +0 -2
- package/dist/schema/design.d.mts +56 -0
- package/dist/schema/insight.d.mts +52 -0
- package/dist/schema/label.d.mts +0 -2
- package/dist/schema/member.d.mts +0 -2
- package/dist/schema/notification.d.mts +4 -2
- package/dist/schema/project-group-mapping.d.mts +0 -2
- package/dist/schema/project.d.mts +21 -2
- package/dist/schema/site-setting.d.mts +19 -0
- package/dist/schema/snapshot.d.mts +17 -2
- package/dist/schema/token.d.mts +0 -2
- package/dist/schema/usage.d.mts +28 -0
- package/dist/schema/user.d.mts +0 -2
- package/dist/schema/webhook.d.mts +0 -2
- package/dist/schema.d.mts +12 -6
- package/dist/serverless.d.mts +19 -0
- package/dist/serverless.mjs +36 -0
- package/dist/serverless.mjs.map +1 -0
- package/dist/test-helpers/build-reference-contract.d.mts +14 -0
- package/dist/test-helpers/build-reference-contract.mjs +66 -0
- package/dist/test-helpers/build-reference-contract.mjs.map +1 -0
- package/dist/test-helpers/create-project.d.mts +1 -3
- package/dist/test-helpers/create-project.mjs +1 -2
- package/dist/test-helpers/database-contract.d.mts +0 -2
- package/dist/test-helpers/database-contract.mjs +37 -2
- package/dist/test-helpers/database-contract.mjs.map +1 -1
- package/dist/test-helpers/deferred.d.mts +37 -0
- package/dist/test-helpers/deferred.mjs +60 -0
- package/dist/test-helpers/deferred.mjs.map +1 -0
- package/dist/test-helpers/design-contract.d.mts +19 -0
- package/dist/test-helpers/design-contract.mjs +39 -0
- package/dist/test-helpers/design-contract.mjs.map +1 -0
- package/dist/test-helpers/fake-adapters.d.mts +0 -2
- package/dist/test-helpers/fake-adapters.mjs +1 -2
- package/dist/test-helpers/fake-database.d.mts +0 -2
- package/dist/test-helpers/fake-database.mjs +21 -4
- package/dist/test-helpers/fake-database.mjs.map +1 -1
- package/dist/test-helpers/fake-design.d.mts +41 -0
- package/dist/test-helpers/fake-design.mjs +113 -0
- package/dist/test-helpers/fake-design.mjs.map +1 -0
- package/dist/test-helpers/fake-notifier.d.mts +19 -0
- package/dist/test-helpers/fake-notifier.mjs +43 -0
- package/dist/test-helpers/fake-notifier.mjs.map +1 -0
- package/dist/test-helpers/fake-storage.d.mts +0 -2
- package/dist/test-helpers/fake-storage.mjs +1 -2
- package/dist/test-helpers/fake-tables.mjs +260 -69
- package/dist/test-helpers/fake-tables.mjs.map +1 -1
- package/dist/test-helpers/manual-clock.d.mts +25 -0
- package/dist/test-helpers/manual-clock.mjs +55 -0
- package/dist/test-helpers/manual-clock.mjs.map +1 -0
- package/dist/test-helpers/notifier-contract.d.mts +7 -3
- package/dist/test-helpers/notifier-contract.mjs +33 -5
- package/dist/test-helpers/notifier-contract.mjs.map +1 -1
- package/dist/test-helpers/queue-contract.d.mts +16 -4
- package/dist/test-helpers/queue-contract.mjs +196 -3
- package/dist/test-helpers/queue-contract.mjs.map +1 -1
- package/dist/test-helpers/queue-multi-instance.d.mts +18 -0
- package/dist/test-helpers/queue-multi-instance.mjs +205 -0
- package/dist/test-helpers/queue-multi-instance.mjs.map +1 -0
- package/dist/test-helpers/runner-contract.d.mts +0 -2
- package/dist/test-helpers/runner-contract.mjs +12 -2
- package/dist/test-helpers/runner-contract.mjs.map +1 -1
- package/dist/test-helpers/sql-chunks.mjs +25 -2
- package/dist/test-helpers/sql-chunks.mjs.map +1 -1
- package/dist/test-helpers/storage-contract.d.mts +0 -2
- package/dist/test-helpers/storage-contract.mjs +2 -3
- package/dist/test-helpers.d.mts +10 -4
- package/dist/test-helpers.mjs +9 -3
- package/dist/tracing.mjs +36 -6
- package/dist/tracing.mjs.map +1 -1
- package/dist/types.d.mts +14 -5
- package/dist/types.mjs +22 -6
- package/dist/types.mjs.map +1 -1
- package/dist/urls.d.mts +4 -2
- package/dist/urls.mjs +12 -4
- package/dist/urls.mjs.map +1 -1
- package/dist/utils/adaptive-concurrency.d.mts +64 -0
- package/dist/utils/adaptive-concurrency.mjs +148 -0
- package/dist/utils/adaptive-concurrency.mjs.map +1 -0
- package/dist/utils/connect-retry.d.mts +26 -0
- package/dist/utils/connect-retry.mjs +42 -0
- package/dist/utils/connect-retry.mjs.map +1 -0
- package/dist/utils/encrypt.d.mts +0 -2
- package/dist/utils/encrypt.mjs +1 -2
- package/dist/utils/environment.d.mts +17 -0
- package/dist/utils/environment.mjs +58 -0
- package/dist/utils/environment.mjs.map +1 -0
- package/dist/utils/hash.d.mts +0 -2
- package/dist/utils/hash.mjs +1 -2
- package/dist/utils/http.d.mts +29 -3
- package/dist/utils/http.mjs +100 -19
- package/dist/utils/http.mjs.map +1 -1
- package/dist/utils/map-adaptive.d.mts +13 -0
- package/dist/utils/map-adaptive.mjs +71 -0
- package/dist/utils/map-adaptive.mjs.map +1 -0
- package/dist/utils/map-limit.d.mts +8 -0
- package/dist/utils/map-limit.mjs +31 -0
- package/dist/utils/map-limit.mjs.map +1 -0
- package/dist/utils/mime.d.mts +0 -2
- package/dist/utils/mime.mjs +1 -2
- package/dist/utils/page-limiter.d.mts +35 -0
- package/dist/utils/page-limiter.mjs +55 -0
- package/dist/utils/page-limiter.mjs.map +1 -0
- package/dist/utils/path-security.d.mts +0 -2
- package/dist/utils/path-security.mjs +2 -4
- package/dist/utils/path-security.mjs.map +1 -1
- package/dist/utils/public-origin.d.mts +18 -0
- package/dist/utils/public-origin.mjs +74 -0
- package/dist/utils/public-origin.mjs.map +1 -0
- package/dist/utils/redact.d.mts +0 -2
- package/dist/utils/redact.mjs +4 -2
- package/dist/utils/redact.mjs.map +1 -1
- package/dist/utils/remote-environment.d.mts +76 -0
- package/dist/utils/remote-environment.mjs +163 -0
- package/dist/utils/remote-environment.mjs.map +1 -0
- package/dist/utils/resources.d.mts +62 -0
- package/dist/utils/resources.mjs +121 -0
- package/dist/utils/resources.mjs.map +1 -0
- package/dist/utils/screenshot-store.d.mts +13 -0
- package/dist/utils/screenshot-store.mjs +40 -0
- package/dist/utils/screenshot-store.mjs.map +1 -0
- package/dist/utils/session-gate.d.mts +23 -0
- package/dist/utils/session-gate.mjs +69 -0
- package/dist/utils/session-gate.mjs.map +1 -0
- package/dist/utils/storybook-token.d.mts +89 -0
- package/dist/utils/storybook-token.mjs +135 -0
- package/dist/utils/storybook-token.mjs.map +1 -0
- package/dist/utils/storybook-url.d.mts +13 -0
- package/dist/utils/storybook-url.mjs +20 -0
- package/dist/utils/storybook-url.mjs.map +1 -0
- package/dist/utils/timing.d.mts +0 -2
- package/dist/utils/timing.mjs +2 -3
- package/dist/utils/timing.mjs.map +1 -1
- package/dist/utils/ulid.d.mts +0 -2
- package/dist/utils/ulid.mjs +1 -2
- package/dist/utils/zip-read.mjs +128 -0
- package/dist/utils/zip-read.mjs.map +1 -0
- package/dist/utils/zip-write.d.mts +16 -0
- package/dist/utils/zip-write.mjs +187 -0
- package/dist/utils/zip-write.mjs.map +1 -0
- package/dist/utils.d.mts +17 -3
- package/dist/utils.mjs +18 -4
- package/package.json +35 -26
- package/dist/adapter/capture-runner.mjs.map +0 -1
- package/dist/adapter/git-host/comments.d.mts +0 -41
- package/dist/adapter/git-host/comments.mjs +0 -54
- package/dist/adapter/git-host/comments.mjs.map +0 -1
- package/dist/adapter/git-host/helpers.d.mts +0 -13
- package/dist/adapter/git-host/helpers.mjs +0 -30
- package/dist/adapter/git-host/helpers.mjs.map +0 -1
- package/dist/adapter/git-host.d.mts +0 -42
- package/dist/capture/queue.d.mts +0 -38
- package/dist/capture/queue.mjs +0 -98
- package/dist/capture/queue.mjs.map +0 -1
- package/dist/capture/recovery.d.mts +0 -21
- package/dist/capture/recovery.mjs +0 -83
- package/dist/capture/recovery.mjs.map +0 -1
- package/dist/capture/status-fanout.mjs +0 -72
- package/dist/capture/status-fanout.mjs.map +0 -1
- package/dist/diff/engine.d.mts +0 -16
- package/dist/diff/engine.mjs +0 -87
- package/dist/diff/engine.mjs.map +0 -1
- package/dist/diff/options.d.mts +0 -42
- package/dist/diff/options.mjs +0 -15
- package/dist/diff/options.mjs.map +0 -1
- package/dist/models/status-config.d.mts +0 -38
- package/dist/models/status-config.mjs +0 -63
- package/dist/models/status-config.mjs.map +0 -1
- package/dist/schema/status-config.d.mts +0 -16
- package/dist/test-helpers/git-contract.d.mts +0 -11
- package/dist/test-helpers/git-contract.mjs +0 -34
- package/dist/test-helpers/git-contract.mjs.map +0 -1
- /package/dist/adapter/{git-host.mjs → oauth.mjs} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capture-shard.mjs","names":[],"sources":["../../src/models/capture-shard.ts"],"sourcesContent":["/** Shard rows of a sharded capture: the plan, the render slices, and the finalize claim. */\nimport { asc, eq } from \"drizzle-orm\";\nimport type { Table } from \"drizzle-orm\";\nimport type { DatabaseAdapter } from \"../adapters/database.ts\";\nimport type { CaptureShard, CaptureShardKind } from \"../schema/capture-shard.ts\";\nimport { ulid } from \"../utils/ulid.ts\";\nimport { col } from \"./columns.ts\";\n\n/** Tables required by {@link CaptureShardModel}. */\nexport interface CaptureShardTables {\n captureShards: Table;\n}\n\n/** A render shard to create: its JSON payload (the slice of stories and engines). */\nexport interface NewRenderShard {\n payload: string;\n}\n\n/** Data operations for the stages of a sharded capture. */\nexport class CaptureShardModel {\n readonly #db: DatabaseAdapter;\n readonly #table: Table;\n\n constructor(db: DatabaseAdapter, tables?: CaptureShardTables) {\n this.#db = db;\n this.#table = (tables ?? { captureShards: db.tables.captureShards }).captureShards;\n }\n\n async get(id: string): Promise<CaptureShard | null> {\n return (await this.#db.get(this.#table, id)) as unknown as CaptureShard | null;\n }\n\n /** Every row of a build, in plan, render, finalize and ordinal order. */\n async listForBuild(buildId: string): Promise<CaptureShard[]> {\n const rows = (await this.#db.list(this.#table, {\n where: eq(col(this.#table, \"buildId\"), buildId),\n orderBy: asc(col(this.#table, \"ordinal\")),\n })) as unknown as CaptureShard[];\n return rows.toSorted((a, b) => kindRank(a.kind) - kindRank(b.kind) || a.ordinal - b.ordinal);\n }\n\n async renderShards(buildId: string): Promise<CaptureShard[]> {\n return (await this.listForBuild(buildId)).filter((row) => row.kind === \"render\");\n }\n\n async plan(buildId: string): Promise<CaptureShard | null> {\n return (await this.listForBuild(buildId)).find((row) => row.kind === \"plan\") ?? null;\n }\n\n /** Forget every stage of a build: a new plan (retry, recovery) starts clean. */\n async clearBuild(buildId: string): Promise<void> {\n const rows = await this.listForBuild(buildId);\n await Promise.all(\n rows.map(async (row) => {\n await this.#db.remove(this.#table, row.id);\n }),\n );\n }\n\n /**\n * Record a plan: the plan row (already `done`, it ran) and one `pending` render shard per slice.\n * Returns the render shards in ordinal order.\n */\n async createPlan(\n projectId: string,\n buildId: string,\n plan: { attemptId: string | null; payload: string; result: string },\n shards: NewRenderShard[],\n ): Promise<CaptureShard[]> {\n const now = new Date().toISOString();\n await this.#db.insert(this.#table, {\n ...base(projectId, buildId, \"plan\", 0, now),\n id: `${buildId}:plan`,\n status: \"done\",\n attemptId: plan.attemptId,\n payload: plan.payload,\n result: plan.result,\n startedAt: now,\n finishedAt: now,\n });\n return await Promise.all(\n shards.map(\n async (shard, index) =>\n (await this.#db.insert(this.#table, {\n ...base(projectId, buildId, \"render\", index + 1, now),\n id: ulid(),\n payload: shard.payload,\n })) as unknown as CaptureShard,\n ),\n );\n }\n\n /** Mark a shard running. A shard already done stays done: this run is a redelivered duplicate. */\n async markRunning(id: string): Promise<void> {\n const now = new Date().toISOString();\n await this.#unlessDone(id, { status: \"running\", startedAt: now, updatedAt: now });\n }\n\n /** Finish a shard with its outcome (JSON). Idempotent: a redelivered shard overwrites its own result. */\n async complete(id: string, result: string): Promise<void> {\n const now = new Date().toISOString();\n await this.#db.update(this.#table, id, {\n status: \"done\",\n result,\n error: null,\n finishedAt: now,\n updatedAt: now,\n });\n }\n\n /**\n * A failed run that will be retried: back to `pending`, remembering why. Ignored for a shard\n * already done, whose result a failing duplicate must not undo.\n */\n async retry(id: string, error: string): Promise<void> {\n await this.#unlessDone(id, {\n status: \"pending\",\n error,\n updatedAt: new Date().toISOString(),\n });\n }\n\n /** The shard's last try failed. Ignored for a shard already done, like {@link retry}. */\n async fail(id: string, error: string): Promise<void> {\n const now = new Date().toISOString();\n await this.#unlessDone(id, { status: \"failed\", error, finishedAt: now, updatedAt: now });\n }\n\n /**\n * A queue that redelivers (at-least-once) can run one shard twice. Whichever finishes first wins;\n * the other must not move the row out of `done`. Read-then-write, not atomic, but the window is\n * the time between two statements, and the outcome of losing it is one extra identical write.\n */\n async #unlessDone(id: string, patch: Record<string, unknown>): Promise<void> {\n if ((await this.get(id))?.status === \"done\") {\n return;\n }\n await this.#db.update(this.#table, id, patch);\n }\n\n /**\n * Try to become the one worker that finalizes the build. The claim row's primary key is\n * `<buildId>:finalize`, so inserting it succeeds for exactly one caller on every database;\n * everyone else gets a key conflict, which this reports as `false`.\n *\n * A claim still `running` after {@link FINALIZE_STALE_MS} belonged to a worker that died\n * mid-finalize, and is taken over. Two late takers can both win; finalizing is idempotent.\n */\n async claimFinalize(projectId: string, buildId: string, now = new Date()): Promise<boolean> {\n const id = finalizeId(buildId);\n try {\n await this.#db.insert(this.#table, {\n ...base(projectId, buildId, \"finalize\", 0, now.toISOString()),\n id,\n status: \"running\",\n startedAt: now.toISOString(),\n });\n return true;\n } catch (error) {\n // A conflict means someone else holds the claim; anything else is a real failure.\n const held = await this.get(id);\n if (held === null) {\n throw error;\n }\n return await this.#takeOverStale(held, now);\n }\n }\n\n /** The finalize ran to the end: its claim is closed for good. */\n async finishFinalize(buildId: string): Promise<void> {\n const now = new Date().toISOString();\n await this.#db.update(this.#table, finalizeId(buildId), {\n status: \"done\",\n finishedAt: now,\n updatedAt: now,\n });\n }\n\n /** The finalize failed: drop its claim, so the next run to reduce the build can finalize it. */\n async releaseFinalize(buildId: string): Promise<void> {\n await this.#db.remove(this.#table, finalizeId(buildId));\n }\n\n async #takeOverStale(held: CaptureShard, now: Date): Promise<boolean> {\n const since = Date.parse(held.startedAt ?? held.createdAt);\n if (held.status !== \"running\" || now.getTime() - since < FINALIZE_STALE_MS) {\n return false;\n }\n await this.#db.update(this.#table, held.id, {\n startedAt: now.toISOString(),\n updatedAt: now.toISOString(),\n });\n return true;\n }\n}\n\n/**\n * How long a finalize claim may stay `running` before it counts as abandoned. Finalizing is a\n * handful of queries over the build's snapshots, so minutes is long past any healthy run.\n */\nexport const FINALIZE_STALE_MS = 5 * 60_000;\n\nfunction finalizeId(buildId: string): string {\n return `${buildId}:finalize`;\n}\n\ntype NewShardRow = Omit<CaptureShard, \"id\">;\n\nfunction base(\n projectId: string,\n buildId: string,\n kind: CaptureShardKind,\n ordinal: number,\n now: string,\n): NewShardRow {\n return {\n projectId,\n buildId,\n kind,\n ordinal,\n status: \"pending\",\n attemptId: null,\n payload: null,\n result: null,\n error: null,\n startedAt: null,\n finishedAt: null,\n createdAt: now,\n updatedAt: now,\n };\n}\n\nfunction kindRank(kind: CaptureShardKind): number {\n return kind === \"plan\" ? 0 : kind === \"render\" ? 1 : 2;\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,46 @@
|
|
|
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 { Build } from "../schema/build.mjs";
|
|
5
|
+
import { BuildComponent } from "../schema/build-component.mjs";
|
|
6
|
+
/** What the changeset shows of a component. */
|
|
7
|
+
type ChangesetComponent = Pick<BuildComponent, "importPath" | "title" | "storyCount">;
|
|
8
|
+
/** A component present in both builds whose hash differs. */
|
|
9
|
+
interface ChangedComponent extends ChangesetComponent {
|
|
10
|
+
/** Stories in the baseline build's version. */
|
|
11
|
+
previousStoryCount: number;
|
|
12
|
+
}
|
|
13
|
+
/** Components added, removed and changed from a baseline build to a build. */
|
|
14
|
+
interface Changeset {
|
|
15
|
+
added: ChangesetComponent[];
|
|
16
|
+
removed: ChangesetComponent[];
|
|
17
|
+
changed: ChangedComponent[];
|
|
18
|
+
/** Components with the same hash in both builds. */
|
|
19
|
+
unchanged: number;
|
|
20
|
+
}
|
|
21
|
+
/** A build's changeset and what it was measured against. */
|
|
22
|
+
interface BuildChangeset {
|
|
23
|
+
/** The baseline build; null when there is none (every component then counts as added). */
|
|
24
|
+
baseline: Build | null;
|
|
25
|
+
changeset: Changeset;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Compare a build's components with the baseline's. Components match by story file, or by title
|
|
29
|
+
* when the index carries no file. With no baseline (`null`) every component is `added`.
|
|
30
|
+
*/
|
|
31
|
+
declare function computeChangeset(current: readonly BuildComponent[], baseline: readonly BuildComponent[] | null): Changeset;
|
|
32
|
+
/**
|
|
33
|
+
* The build a build is compared with: the latest accepted build of its branch before it, else the
|
|
34
|
+
* latest accepted default-branch build (the rule baselines use). Null for the first build.
|
|
35
|
+
*/
|
|
36
|
+
declare function findChangesetBaseline(db: DatabaseAdapter, project: {
|
|
37
|
+
id: string;
|
|
38
|
+
gitDefaultBranch: string;
|
|
39
|
+
}, build: Build): Promise<Build | null>;
|
|
40
|
+
/** The changeset of `build` against its baseline; empty rows on both sides when it has no catalog. */
|
|
41
|
+
declare function changesetOf(db: DatabaseAdapter, project: {
|
|
42
|
+
id: string;
|
|
43
|
+
gitDefaultBranch: string;
|
|
44
|
+
}, build: Build): Promise<BuildChangeset>;
|
|
45
|
+
export { BuildChangeset, ChangedComponent, Changeset, ChangesetComponent, changesetOf, computeChangeset, findChangesetBaseline };
|
|
46
|
+
//# sourceMappingURL=changeset.d.mts.map
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import "node:path";
|
|
2
|
+
import "node:url";
|
|
3
|
+
import.meta.url;
|
|
4
|
+
import { BuildModel } from "./build.mjs";
|
|
5
|
+
import { BuildComponentModel } from "./build-component.mjs";
|
|
6
|
+
// src/models/changeset.ts
|
|
7
|
+
/**
|
|
8
|
+
* Compare a build's components with the baseline's. Components match by story file, or by title
|
|
9
|
+
* when the index carries no file. With no baseline (`null`) every component is `added`.
|
|
10
|
+
*/
|
|
11
|
+
function computeChangeset(current, baseline) {
|
|
12
|
+
const was = new Map((baseline ?? []).map((row) => [keyOf(row), row]));
|
|
13
|
+
const now = new Map(current.map((row) => [keyOf(row), row]));
|
|
14
|
+
const changeset = {
|
|
15
|
+
added: [],
|
|
16
|
+
removed: [],
|
|
17
|
+
changed: [],
|
|
18
|
+
unchanged: 0
|
|
19
|
+
};
|
|
20
|
+
for (const [key, row] of now) place(changeset, row, was.get(key));
|
|
21
|
+
for (const [key, row] of was) if (!now.has(key)) changeset.removed.push(summary(row));
|
|
22
|
+
return {
|
|
23
|
+
...changeset,
|
|
24
|
+
added: changeset.added.toSorted(byTitle),
|
|
25
|
+
removed: changeset.removed.toSorted(byTitle),
|
|
26
|
+
changed: changeset.changed.toSorted(byTitle)
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/** File `row` under added, changed or unchanged, given the baseline's version of it. */
|
|
30
|
+
function place(changeset, row, before) {
|
|
31
|
+
if (before === void 0) changeset.added.push(summary(row));
|
|
32
|
+
else if (before.hash === row.hash) changeset.unchanged += 1;
|
|
33
|
+
else changeset.changed.push({
|
|
34
|
+
...summary(row),
|
|
35
|
+
previousStoryCount: before.storyCount
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* The build a build is compared with: the latest accepted build of its branch before it, else the
|
|
40
|
+
* latest accepted default-branch build (the rule baselines use). Null for the first build.
|
|
41
|
+
*/
|
|
42
|
+
async function findChangesetBaseline(db, project, build) {
|
|
43
|
+
const builds = new BuildModel(db);
|
|
44
|
+
const branches = [.../* @__PURE__ */ new Set([build.gitBranch, project.gitDefaultBranch])];
|
|
45
|
+
const lists = await Promise.all(branches.map(async (branch) => await builds.list(project.id, {
|
|
46
|
+
branch,
|
|
47
|
+
status: "approved"
|
|
48
|
+
})));
|
|
49
|
+
for (const accepted of lists) {
|
|
50
|
+
const baseline = accepted.find((candidate) => candidate.id !== build.id && candidate.createdAt < build.createdAt);
|
|
51
|
+
if (baseline) return baseline;
|
|
52
|
+
}
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
/** The changeset of `build` against its baseline; empty rows on both sides when it has no catalog. */
|
|
56
|
+
async function changesetOf(db, project, build) {
|
|
57
|
+
const components = new BuildComponentModel(db);
|
|
58
|
+
const baseline = await findChangesetBaseline(db, project, build);
|
|
59
|
+
const [current, before] = await Promise.all([components.listByBuild(build.id), baseline ? components.listByBuild(baseline.id) : Promise.resolve(null)]);
|
|
60
|
+
return {
|
|
61
|
+
baseline,
|
|
62
|
+
changeset: computeChangeset(current, before)
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function keyOf(row) {
|
|
66
|
+
return row.importPath === "" ? `title:${row.title}` : row.importPath;
|
|
67
|
+
}
|
|
68
|
+
function summary(row) {
|
|
69
|
+
return {
|
|
70
|
+
importPath: row.importPath,
|
|
71
|
+
title: row.title,
|
|
72
|
+
storyCount: row.storyCount
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
function byTitle(a, b) {
|
|
76
|
+
return a.title.localeCompare(b.title) || a.importPath.localeCompare(b.importPath);
|
|
77
|
+
}
|
|
78
|
+
export { changesetOf, computeChangeset, findChangesetBaseline };
|
|
79
|
+
|
|
80
|
+
//# sourceMappingURL=changeset.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"changeset.mjs","names":[],"sources":["../../src/models/changeset.ts"],"sourcesContent":["/**\n * The changeset of a build without screenshots (ADR 0013, decision 8): which components the build\n * added, removed or changed against its baseline build, from the `build_components` hashes. A\n * changed hash means the component's stories changed, not that anything looks different.\n */\nimport type { DatabaseAdapter } from \"../adapters/database.ts\";\nimport type { BuildComponent } from \"../schema/build-component.ts\";\nimport type { Build } from \"../schema/build.ts\";\nimport { BuildComponentModel } from \"./build-component.ts\";\nimport { BuildModel } from \"./build.ts\";\n\n/** What the changeset shows of a component. */\nexport type ChangesetComponent = Pick<BuildComponent, \"importPath\" | \"title\" | \"storyCount\">;\n\n/** A component present in both builds whose hash differs. */\nexport interface ChangedComponent extends ChangesetComponent {\n /** Stories in the baseline build's version. */\n previousStoryCount: number;\n}\n\n/** Components added, removed and changed from a baseline build to a build. */\nexport interface Changeset {\n added: ChangesetComponent[];\n removed: ChangesetComponent[];\n changed: ChangedComponent[];\n /** Components with the same hash in both builds. */\n unchanged: number;\n}\n\n/** A build's changeset and what it was measured against. */\nexport interface BuildChangeset {\n /** The baseline build; null when there is none (every component then counts as added). */\n baseline: Build | null;\n changeset: Changeset;\n}\n\n/**\n * Compare a build's components with the baseline's. Components match by story file, or by title\n * when the index carries no file. With no baseline (`null`) every component is `added`.\n */\nexport function computeChangeset(\n current: readonly BuildComponent[],\n baseline: readonly BuildComponent[] | null,\n): Changeset {\n const was = new Map((baseline ?? []).map((row) => [keyOf(row), row] as const));\n const now = new Map(current.map((row) => [keyOf(row), row] as const));\n const changeset: Changeset = { added: [], removed: [], changed: [], unchanged: 0 };\n for (const [key, row] of now) {\n place(changeset, row, was.get(key));\n }\n for (const [key, row] of was) {\n if (!now.has(key)) {\n changeset.removed.push(summary(row));\n }\n }\n return {\n ...changeset,\n added: changeset.added.toSorted(byTitle),\n removed: changeset.removed.toSorted(byTitle),\n changed: changeset.changed.toSorted(byTitle),\n };\n}\n\n/** File `row` under added, changed or unchanged, given the baseline's version of it. */\nfunction place(\n changeset: Changeset,\n row: BuildComponent,\n before: BuildComponent | undefined,\n): void {\n if (before === undefined) {\n changeset.added.push(summary(row));\n } else if (before.hash === row.hash) {\n changeset.unchanged += 1;\n } else {\n changeset.changed.push({ ...summary(row), previousStoryCount: before.storyCount });\n }\n}\n\n/**\n * The build a build is compared with: the latest accepted build of its branch before it, else the\n * latest accepted default-branch build (the rule baselines use). Null for the first build.\n */\nexport async function findChangesetBaseline(\n db: DatabaseAdapter,\n project: { id: string; gitDefaultBranch: string },\n build: Build,\n): Promise<Build | null> {\n const builds = new BuildModel(db);\n const branches = [...new Set([build.gitBranch, project.gitDefaultBranch])];\n const lists = await Promise.all(\n branches.map(async (branch) => await builds.list(project.id, { branch, status: \"approved\" })),\n );\n for (const accepted of lists) {\n const baseline = accepted.find(\n (candidate) => candidate.id !== build.id && candidate.createdAt < build.createdAt,\n );\n if (baseline) {\n return baseline;\n }\n }\n return null;\n}\n\n/** The changeset of `build` against its baseline; empty rows on both sides when it has no catalog. */\nexport async function changesetOf(\n db: DatabaseAdapter,\n project: { id: string; gitDefaultBranch: string },\n build: Build,\n): Promise<BuildChangeset> {\n const components = new BuildComponentModel(db);\n const baseline = await findChangesetBaseline(db, project, build);\n const [current, before] = await Promise.all([\n components.listByBuild(build.id),\n baseline ? components.listByBuild(baseline.id) : Promise.resolve(null),\n ]);\n return { baseline, changeset: computeChangeset(current, before) };\n}\n\nfunction keyOf(row: Pick<BuildComponent, \"importPath\" | \"title\">): string {\n return row.importPath === \"\" ? `title:${row.title}` : row.importPath;\n}\n\nfunction summary(row: BuildComponent): ChangesetComponent {\n return { importPath: row.importPath, title: row.title, storyCount: row.storyCount };\n}\n\nfunction byTitle(a: ChangesetComponent, b: ChangesetComponent): number {\n return a.title.localeCompare(b.title) || a.importPath.localeCompare(b.importPath);\n}\n"],"mappings":";;;;;;;;;;AAwCA,SAAgB,iBACd,SACA,UACW;CACX,MAAM,MAAM,IAAI,KAAK,YAAY,CAAC,EAAA,CAAG,KAAK,QAAQ,CAAC,MAAM,GAAG,GAAG,GAAG,CAAU,CAAC;CAC7E,MAAM,MAAM,IAAI,IAAI,QAAQ,KAAK,QAAQ,CAAC,MAAM,GAAG,GAAG,GAAG,CAAU,CAAC;CACpE,MAAM,YAAuB;EAAE,OAAO,CAAC;EAAG,SAAS,CAAC;EAAG,SAAS,CAAC;EAAG,WAAW;CAAE;CACjF,KAAK,MAAM,CAAC,KAAK,QAAQ,KACvB,MAAM,WAAW,KAAK,IAAI,IAAI,GAAG,CAAC;CAEpC,KAAK,MAAM,CAAC,KAAK,QAAQ,KACvB,IAAI,CAAC,IAAI,IAAI,GAAG,GACd,UAAU,QAAQ,KAAK,QAAQ,GAAG,CAAC;CAGvC,OAAO;EACL,GAAG;EACH,OAAO,UAAU,MAAM,SAAS,OAAO;EACvC,SAAS,UAAU,QAAQ,SAAS,OAAO;EAC3C,SAAS,UAAU,QAAQ,SAAS,OAAO;CAC7C;AACF;;AAGA,SAAS,MACP,WACA,KACA,QACM;CACN,IAAI,WAAW,KAAA,GACb,UAAU,MAAM,KAAK,QAAQ,GAAG,CAAC;MAC5B,IAAI,OAAO,SAAS,IAAI,MAC7B,UAAU,aAAa;MAEvB,UAAU,QAAQ,KAAK;EAAE,GAAG,QAAQ,GAAG;EAAG,oBAAoB,OAAO;CAAW,CAAC;AAErF;;;;;AAMA,eAAsB,sBACpB,IACA,SACA,OACuB;CACvB,MAAM,SAAS,IAAI,WAAW,EAAE;CAChC,MAAM,WAAW,CAAC,mBAAG,IAAI,IAAI,CAAC,MAAM,WAAW,QAAQ,gBAAgB,CAAC,CAAC;CACzE,MAAM,QAAQ,MAAM,QAAQ,IAC1B,SAAS,IAAI,OAAO,WAAW,MAAM,OAAO,KAAK,QAAQ,IAAI;EAAE;EAAQ,QAAQ;CAAW,CAAC,CAAC,CAC9F;CACA,KAAK,MAAM,YAAY,OAAO;EAC5B,MAAM,WAAW,SAAS,MACvB,cAAc,UAAU,OAAO,MAAM,MAAM,UAAU,YAAY,MAAM,SAC1E;EACA,IAAI,UACF,OAAO;CAEX;CACA,OAAO;AACT;;AAGA,eAAsB,YACpB,IACA,SACA,OACyB;CACzB,MAAM,aAAa,IAAI,oBAAoB,EAAE;CAC7C,MAAM,WAAW,MAAM,sBAAsB,IAAI,SAAS,KAAK;CAC/D,MAAM,CAAC,SAAS,UAAU,MAAM,QAAQ,IAAI,CAC1C,WAAW,YAAY,MAAM,EAAE,GAC/B,WAAW,WAAW,YAAY,SAAS,EAAE,IAAI,QAAQ,QAAQ,IAAI,CACvE,CAAC;CACD,OAAO;EAAE;EAAU,WAAW,iBAAiB,SAAS,MAAM;CAAE;AAClE;AAEA,SAAS,MAAM,KAA2D;CACxE,OAAO,IAAI,eAAe,KAAK,SAAS,IAAI,UAAU,IAAI;AAC5D;AAEA,SAAS,QAAQ,KAAyC;CACxD,OAAO;EAAE,YAAY,IAAI;EAAY,OAAO,IAAI;EAAO,YAAY,IAAI;CAAW;AACpF;AAEA,SAAS,QAAQ,GAAuB,GAA+B;CACrE,OAAO,EAAE,MAAM,cAAc,EAAE,KAAK,KAAK,EAAE,WAAW,cAAc,EAAE,UAAU;AAClF"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import "node:path";
|
|
2
|
+
import "node:url";
|
|
3
|
+
import.meta.url;
|
|
4
|
+
import { getTableColumns } from "drizzle-orm";
|
|
5
|
+
// src/models/columns.ts
|
|
6
|
+
/**
|
|
7
|
+
* Column handle for query builders (`eq`, `inArray`, `desc`, …) from a table
|
|
8
|
+
* handle and a property name. Throws on an unknown name instead of letting
|
|
9
|
+
* `undefined` reach the query builder.
|
|
10
|
+
*/
|
|
11
|
+
function col(table, name) {
|
|
12
|
+
const column = getTableColumns(table)[name];
|
|
13
|
+
if (!column) throw new Error(`Unknown column "${name}"`);
|
|
14
|
+
return column;
|
|
15
|
+
}
|
|
16
|
+
export { col };
|
|
17
|
+
|
|
18
|
+
//# sourceMappingURL=columns.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"columns.mjs","names":[],"sources":["../../src/models/columns.ts"],"sourcesContent":["import { getTableColumns } from \"drizzle-orm\";\nimport type { Column, Table } from \"drizzle-orm\";\n\n/**\n * Column handle for query builders (`eq`, `inArray`, `desc`, …) from a table\n * handle and a property name. Throws on an unknown name instead of letting\n * `undefined` reach the query builder.\n */\nexport function col(table: Table, name: string): Column {\n const column = getTableColumns(table)[name];\n if (!column) {\n throw new Error(`Unknown column \"${name}\"`);\n }\n return column as Column;\n}\n"],"mappings":";;;;;;;;;;AAQA,SAAgB,IAAI,OAAc,MAAsB;CACtD,MAAM,SAAS,gBAAgB,KAAK,CAAC,CAAC;CACtC,IAAI,CAAC,QACH,MAAM,IAAI,MAAM,mBAAmB,KAAK,EAAE;CAE5C,OAAO;AACT"}
|
|
@@ -3,7 +3,6 @@ import __tsdown_shims_url from 'node:url';
|
|
|
3
3
|
import { DatabaseAdapter } from "../adapter/database.mjs";
|
|
4
4
|
import { Comment } from "../schema/comment.mjs";
|
|
5
5
|
import { Table } from "drizzle-orm";
|
|
6
|
-
//#region src/models/comment.d.ts
|
|
7
6
|
/** Tables required by {@link CommentModel}. */
|
|
8
7
|
interface CommentTables {
|
|
9
8
|
comments: Table;
|
|
@@ -11,8 +10,8 @@ interface CommentTables {
|
|
|
11
10
|
}
|
|
12
11
|
/** Data operations for review comments. */
|
|
13
12
|
declare class CommentModel {
|
|
14
|
-
private readonly db;
|
|
15
13
|
private readonly tables;
|
|
14
|
+
private readonly db;
|
|
16
15
|
constructor(db: DatabaseAdapter, tables?: CommentTables);
|
|
17
16
|
listByBuild(buildId: string): Promise<Comment[]>;
|
|
18
17
|
create(projectId: string, buildId: string, userId: string | null, input: CommentCreateInput): Promise<Comment>;
|
|
@@ -21,9 +20,11 @@ declare class CommentModel {
|
|
|
21
20
|
/** Input for creating a review comment. */
|
|
22
21
|
interface CommentCreateInput {
|
|
23
22
|
body: string;
|
|
23
|
+
/** Anchor to a snapshot (builds with snapshots). */
|
|
24
24
|
snapshotId?: string;
|
|
25
|
+
/** Anchor to a story of the build's catalog; not together with `snapshotId` (ADR 0013, decision 2). */
|
|
26
|
+
storyId?: string;
|
|
25
27
|
parentId?: string;
|
|
26
28
|
}
|
|
27
|
-
//#endregion
|
|
28
29
|
export { CommentCreateInput, CommentModel, CommentTables };
|
|
29
30
|
//# sourceMappingURL=comment.d.mts.map
|
package/dist/models/comment.mjs
CHANGED
|
@@ -2,13 +2,14 @@ import "node:path";
|
|
|
2
2
|
import "node:url";
|
|
3
3
|
import.meta.url;
|
|
4
4
|
import { ulid } from "../utils/ulid.mjs";
|
|
5
|
-
import {
|
|
6
|
-
|
|
5
|
+
import { col } from "./columns.mjs";
|
|
6
|
+
import { eq } from "drizzle-orm";
|
|
7
|
+
// src/models/comment.ts
|
|
7
8
|
/** Build and snapshot review comments. */
|
|
8
9
|
/** Data operations for review comments. */
|
|
9
10
|
var CommentModel = class {
|
|
10
|
-
db;
|
|
11
11
|
tables;
|
|
12
|
+
db;
|
|
12
13
|
constructor(db, tables) {
|
|
13
14
|
this.db = db;
|
|
14
15
|
this.tables = tables ?? {
|
|
@@ -17,16 +18,18 @@ var CommentModel = class {
|
|
|
17
18
|
};
|
|
18
19
|
}
|
|
19
20
|
async listByBuild(buildId) {
|
|
20
|
-
return await this.db.list(this.tables.comments, { where: eq(
|
|
21
|
+
return await this.db.list(this.tables.comments, { where: eq(col(this.tables.comments, "buildId"), buildId) });
|
|
21
22
|
}
|
|
22
23
|
async create(projectId, buildId, userId, input) {
|
|
23
24
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
24
25
|
if (!await this.db.get(this.tables.projects, projectId)) throw new Error(`Project not found: ${projectId}`);
|
|
26
|
+
if (input.snapshotId !== void 0 && input.storyId !== void 0) throw new Error("A comment anchors to a build, a story or a snapshot, not several");
|
|
25
27
|
return await this.db.insert(this.tables.comments, {
|
|
26
28
|
id: ulid(),
|
|
27
29
|
projectId,
|
|
28
30
|
buildId,
|
|
29
|
-
snapshotId: input.snapshotId,
|
|
31
|
+
snapshotId: input.snapshotId ?? null,
|
|
32
|
+
storyId: input.storyId ?? null,
|
|
30
33
|
userId,
|
|
31
34
|
body: input.body,
|
|
32
35
|
parentId: input.parentId,
|
|
@@ -42,7 +45,6 @@ var CommentModel = class {
|
|
|
42
45
|
});
|
|
43
46
|
}
|
|
44
47
|
};
|
|
45
|
-
//#endregion
|
|
46
48
|
export { CommentModel };
|
|
47
49
|
|
|
48
50
|
//# sourceMappingURL=comment.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"comment.mjs","names":[],"sources":["../../src/models/comment.ts"],"sourcesContent":["/** Build and snapshot review comments. */\nimport { eq
|
|
1
|
+
{"version":3,"file":"comment.mjs","names":[],"sources":["../../src/models/comment.ts"],"sourcesContent":["/** Build and snapshot review comments. */\nimport { eq } from \"drizzle-orm\";\nimport type { Table } from \"drizzle-orm\";\nimport type { DatabaseAdapter } from \"../adapters/database.ts\";\nimport type { Comment } from \"../schema/comment.ts\";\nimport { ulid } from \"../utils/ulid.ts\";\nimport { col } from \"./columns.ts\";\n\n/** Tables required by {@link CommentModel}. */\nexport interface CommentTables {\n comments: Table;\n projects: Table;\n}\n\n/** Data operations for review comments. */\nexport class CommentModel {\n private readonly tables: CommentTables;\n private readonly db: DatabaseAdapter;\n\n constructor(db: DatabaseAdapter, tables?: CommentTables) {\n this.db = db;\n this.tables = tables ?? {\n comments: db.tables.comments,\n projects: db.tables.projects,\n };\n }\n\n async listByBuild(buildId: string): Promise<Comment[]> {\n return (await this.db.list(this.tables.comments, {\n where: eq(col(this.tables.comments, \"buildId\"), buildId),\n })) as unknown as Comment[];\n }\n\n async create(\n projectId: string,\n buildId: string,\n userId: string | null,\n input: CommentCreateInput,\n ): Promise<Comment> {\n const now = new Date().toISOString();\n const project = (await this.db.get(this.tables.projects, projectId)) as unknown as {\n id: string;\n } | null;\n if (!project) {\n throw new Error(`Project not found: ${projectId}`);\n }\n if (input.snapshotId !== undefined && input.storyId !== undefined) {\n throw new Error(\"A comment anchors to a build, a story or a snapshot, not several\");\n }\n return (await this.db.insert(this.tables.comments, {\n id: ulid(),\n projectId,\n buildId,\n snapshotId: input.snapshotId ?? null,\n storyId: input.storyId ?? null,\n userId,\n body: input.body,\n parentId: input.parentId,\n resolved: false,\n createdAt: now,\n updatedAt: now,\n })) as unknown as Comment;\n }\n\n async resolve(id: string): Promise<Comment> {\n return (await this.db.update(this.tables.comments, id, {\n resolved: true,\n updatedAt: new Date().toISOString(),\n })) as unknown as Comment;\n }\n}\n\n/** Input for creating a review comment. */\nexport interface CommentCreateInput {\n body: string;\n /** Anchor to a snapshot (builds with snapshots). */\n snapshotId?: string;\n /** Anchor to a story of the build's catalog; not together with `snapshotId` (ADR 0013, decision 2). */\n storyId?: string;\n parentId?: string;\n}\n"],"mappings":";;;;;;;;;AAeA,IAAa,eAAb,MAA0B;CACxB;CACA;CAEA,YAAY,IAAqB,QAAwB;EACvD,KAAK,KAAK;EACV,KAAK,SAAS,UAAU;GACtB,UAAU,GAAG,OAAO;GACpB,UAAU,GAAG,OAAO;EACtB;CACF;CAEA,MAAM,YAAY,SAAqC;EACrD,OAAQ,MAAM,KAAK,GAAG,KAAK,KAAK,OAAO,UAAU,EAC/C,OAAO,GAAG,IAAI,KAAK,OAAO,UAAU,SAAS,GAAG,OAAO,EACzD,CAAC;CACH;CAEA,MAAM,OACJ,WACA,SACA,QACA,OACkB;EAClB,MAAM,uBAAM,IAAI,KAAK,EAAA,CAAE,YAAY;EAInC,IAAI,CAAC,MAHkB,KAAK,GAAG,IAAI,KAAK,OAAO,UAAU,SAAS,GAIhE,MAAM,IAAI,MAAM,sBAAsB,WAAW;EAEnD,IAAI,MAAM,eAAe,KAAA,KAAa,MAAM,YAAY,KAAA,GACtD,MAAM,IAAI,MAAM,kEAAkE;EAEpF,OAAQ,MAAM,KAAK,GAAG,OAAO,KAAK,OAAO,UAAU;GACjD,IAAI,KAAK;GACT;GACA;GACA,YAAY,MAAM,cAAc;GAChC,SAAS,MAAM,WAAW;GAC1B;GACA,MAAM,MAAM;GACZ,UAAU,MAAM;GAChB,UAAU;GACV,WAAW;GACX,WAAW;EACb,CAAC;CACH;CAEA,MAAM,QAAQ,IAA8B;EAC1C,OAAQ,MAAM,KAAK,GAAG,OAAO,KAAK,OAAO,UAAU,IAAI;GACrD,UAAU;GACV,4BAAW,IAAI,KAAK,EAAA,CAAE,YAAY;EACpC,CAAC;CACH;AACF"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import __tsdown_shims_path from 'node:path';
|
|
2
|
+
import __tsdown_shims_url from 'node:url';
|
|
3
|
+
import { SnapshotStatus } from "../types.mjs";
|
|
4
|
+
import { Build } from "../schema/build.mjs";
|
|
5
|
+
import { Snapshot } from "../schema/snapshot.mjs";
|
|
6
|
+
/** The most attention-worthy status of a set (unchanged when empty). */
|
|
7
|
+
declare function worstStatus(statuses: readonly SnapshotStatus[]): SnapshotStatus;
|
|
8
|
+
/** One build's view of a component. */
|
|
9
|
+
interface ComponentHistoryRow {
|
|
10
|
+
build: Build;
|
|
11
|
+
/** Distinct stories (states) of the file captured in the build. */
|
|
12
|
+
states: number;
|
|
13
|
+
/** Distinct stories that are new or changed in the build. */
|
|
14
|
+
changedStates: number;
|
|
15
|
+
status: SnapshotStatus;
|
|
16
|
+
/** Snapshot the row links to: the first needing attention, else the first. */
|
|
17
|
+
snapshotId: string;
|
|
18
|
+
}
|
|
19
|
+
/** Per-build history of a component, newest build first; builds without a snapshot of it are skipped. */
|
|
20
|
+
declare function componentHistory(snapshots: readonly Snapshot[], builds: readonly Build[]): ComponentHistoryRow[];
|
|
21
|
+
/** Where a component page stands relative to the project's latest build. */
|
|
22
|
+
type ComponentPresence = {
|
|
23
|
+
kind: "present";
|
|
24
|
+
} | {
|
|
25
|
+
kind: "moved";
|
|
26
|
+
to: string;
|
|
27
|
+
title: string;
|
|
28
|
+
} | {
|
|
29
|
+
kind: "deleted";
|
|
30
|
+
lastSeen: Build;
|
|
31
|
+
} | {
|
|
32
|
+
kind: "unknown";
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Classify a component against the latest build. `history` is the component's
|
|
36
|
+
* past builds (newest first); `latest` holds the latest build's snapshots of
|
|
37
|
+
* every file, used to find a moved file by its last-seen title.
|
|
38
|
+
*/
|
|
39
|
+
declare function componentPresence(history: readonly ComponentHistoryRow[], latestBuildId: string | null, latest: readonly Pick<Snapshot, "storyTitle" | "storyImportPath">[], lastTitle: string | null): ComponentPresence;
|
|
40
|
+
export { ComponentHistoryRow, ComponentPresence, componentHistory, componentPresence, worstStatus };
|
|
41
|
+
//# sourceMappingURL=component.d.mts.map
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import "node:path";
|
|
2
|
+
import "node:url";
|
|
3
|
+
import.meta.url;
|
|
4
|
+
// src/models/component.ts
|
|
5
|
+
/** Worst-first order used for status roll-ups. */
|
|
6
|
+
const STATUS_PRIORITY = [
|
|
7
|
+
"rejected",
|
|
8
|
+
"changed",
|
|
9
|
+
"new",
|
|
10
|
+
"pending",
|
|
11
|
+
"approved",
|
|
12
|
+
"unchanged"
|
|
13
|
+
];
|
|
14
|
+
/** Statuses that count as "state changed" in a build. */
|
|
15
|
+
const CHANGED = /* @__PURE__ */ new Set(["changed", "new"]);
|
|
16
|
+
/** The most attention-worthy status of a set (unchanged when empty). */
|
|
17
|
+
function worstStatus(statuses) {
|
|
18
|
+
return STATUS_PRIORITY.find((status) => statuses.includes(status)) ?? "unchanged";
|
|
19
|
+
}
|
|
20
|
+
/** Per-build history of a component, newest build first; builds without a snapshot of it are skipped. */
|
|
21
|
+
function componentHistory(snapshots, builds) {
|
|
22
|
+
const byBuild = Map.groupBy(snapshots, (snapshot) => snapshot.buildId);
|
|
23
|
+
return builds.toSorted((a, b) => b.createdAt.localeCompare(a.createdAt)).flatMap((build) => {
|
|
24
|
+
const own = byBuild.get(build.id) ?? [];
|
|
25
|
+
const representative = own.find((snap) => snap.status === "changed" || snap.status === "new") ?? own[0];
|
|
26
|
+
if (!representative) return [];
|
|
27
|
+
const changed = own.filter((snap) => CHANGED.has(snap.status));
|
|
28
|
+
return [{
|
|
29
|
+
build,
|
|
30
|
+
states: new Set(own.map((snap) => snap.storyId)).size,
|
|
31
|
+
changedStates: new Set(changed.map((snap) => snap.storyId)).size,
|
|
32
|
+
status: worstStatus(own.map((snap) => snap.status)),
|
|
33
|
+
snapshotId: representative.id
|
|
34
|
+
}];
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Classify a component against the latest build. `history` is the component's
|
|
39
|
+
* past builds (newest first); `latest` holds the latest build's snapshots of
|
|
40
|
+
* every file, used to find a moved file by its last-seen title.
|
|
41
|
+
*/
|
|
42
|
+
function componentPresence(history, latestBuildId, latest, lastTitle) {
|
|
43
|
+
const [newest] = history;
|
|
44
|
+
if (!newest) return { kind: "unknown" };
|
|
45
|
+
if (newest.build.id === latestBuildId) return { kind: "present" };
|
|
46
|
+
const moved = latest.find((snap) => snap.storyTitle === lastTitle && snap.storyImportPath);
|
|
47
|
+
if (moved?.storyImportPath) return {
|
|
48
|
+
kind: "moved",
|
|
49
|
+
to: moved.storyImportPath,
|
|
50
|
+
title: moved.storyTitle
|
|
51
|
+
};
|
|
52
|
+
return {
|
|
53
|
+
kind: "deleted",
|
|
54
|
+
lastSeen: newest.build
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
export { componentHistory, componentPresence, worstStatus };
|
|
58
|
+
|
|
59
|
+
//# sourceMappingURL=component.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"component.mjs","names":[],"sources":["../../src/models/component.ts"],"sourcesContent":["/** Component (story file) roll-ups: status priority, per-build history, and tombstone lookup. */\nimport type { Build } from \"../schema/build.ts\";\nimport type { Snapshot } from \"../schema/snapshot.ts\";\nimport type { SnapshotStatus } from \"../types.ts\";\n\n/** Worst-first order used for status roll-ups. */\nconst STATUS_PRIORITY: readonly SnapshotStatus[] = [\n \"rejected\",\n \"changed\",\n \"new\",\n \"pending\",\n \"approved\",\n \"unchanged\",\n];\n\n/** Statuses that count as \"state changed\" in a build. */\nconst CHANGED = new Set<SnapshotStatus>([\"changed\", \"new\"]);\n\n/** The most attention-worthy status of a set (unchanged when empty). */\nexport function worstStatus(statuses: readonly SnapshotStatus[]): SnapshotStatus {\n return STATUS_PRIORITY.find((status) => statuses.includes(status)) ?? \"unchanged\";\n}\n\n/** One build's view of a component. */\nexport interface ComponentHistoryRow {\n build: Build;\n /** Distinct stories (states) of the file captured in the build. */\n states: number;\n /** Distinct stories that are new or changed in the build. */\n changedStates: number;\n status: SnapshotStatus;\n /** Snapshot the row links to: the first needing attention, else the first. */\n snapshotId: string;\n}\n\n/** Per-build history of a component, newest build first; builds without a snapshot of it are skipped. */\nexport function componentHistory(\n snapshots: readonly Snapshot[],\n builds: readonly Build[],\n): ComponentHistoryRow[] {\n const byBuild = Map.groupBy(snapshots, (snapshot) => snapshot.buildId);\n return builds\n .toSorted((a, b) => b.createdAt.localeCompare(a.createdAt))\n .flatMap((build): ComponentHistoryRow[] => {\n const own = byBuild.get(build.id) ?? [];\n const representative =\n own.find((snap) => snap.status === \"changed\" || snap.status === \"new\") ?? own[0];\n if (!representative) return [];\n const changed = own.filter((snap) => CHANGED.has(snap.status));\n return [\n {\n build,\n states: new Set(own.map((snap) => snap.storyId)).size,\n changedStates: new Set(changed.map((snap) => snap.storyId)).size,\n status: worstStatus(own.map((snap) => snap.status)),\n snapshotId: representative.id,\n },\n ];\n });\n}\n\n/** Where a component page stands relative to the project's latest build. */\nexport type ComponentPresence =\n | { kind: \"present\" }\n | { kind: \"moved\"; to: string; title: string }\n | { kind: \"deleted\"; lastSeen: Build }\n | { kind: \"unknown\" };\n\n/**\n * Classify a component against the latest build. `history` is the component's\n * past builds (newest first); `latest` holds the latest build's snapshots of\n * every file, used to find a moved file by its last-seen title.\n */\nexport function componentPresence(\n history: readonly ComponentHistoryRow[],\n latestBuildId: string | null,\n latest: readonly Pick<Snapshot, \"storyTitle\" | \"storyImportPath\">[],\n lastTitle: string | null,\n): ComponentPresence {\n const [newest] = history;\n if (!newest) return { kind: \"unknown\" };\n if (newest.build.id === latestBuildId) return { kind: \"present\" };\n const moved = latest.find((snap) => snap.storyTitle === lastTitle && snap.storyImportPath);\n if (moved?.storyImportPath) {\n return { kind: \"moved\", to: moved.storyImportPath, title: moved.storyTitle };\n }\n return { kind: \"deleted\", lastSeen: newest.build };\n}\n"],"mappings":";;;;;AAMA,MAAM,kBAA6C;CACjD;CACA;CACA;CACA;CACA;CACA;AACF;;AAGA,MAAM,0BAAU,IAAI,IAAoB,CAAC,WAAW,KAAK,CAAC;;AAG1D,SAAgB,YAAY,UAAqD;CAC/E,OAAO,gBAAgB,MAAM,WAAW,SAAS,SAAS,MAAM,CAAC,KAAK;AACxE;;AAeA,SAAgB,iBACd,WACA,QACuB;CACvB,MAAM,UAAU,IAAI,QAAQ,YAAY,aAAa,SAAS,OAAO;CACrE,OAAO,OACJ,UAAU,GAAG,MAAM,EAAE,UAAU,cAAc,EAAE,SAAS,CAAC,CAAC,CAC1D,SAAS,UAAiC;EACzC,MAAM,MAAM,QAAQ,IAAI,MAAM,EAAE,KAAK,CAAC;EACtC,MAAM,iBACJ,IAAI,MAAM,SAAS,KAAK,WAAW,aAAa,KAAK,WAAW,KAAK,KAAK,IAAI;EAChF,IAAI,CAAC,gBAAgB,OAAO,CAAC;EAC7B,MAAM,UAAU,IAAI,QAAQ,SAAS,QAAQ,IAAI,KAAK,MAAM,CAAC;EAC7D,OAAO,CACL;GACE;GACA,QAAQ,IAAI,IAAI,IAAI,KAAK,SAAS,KAAK,OAAO,CAAC,CAAC,CAAC;GACjD,eAAe,IAAI,IAAI,QAAQ,KAAK,SAAS,KAAK,OAAO,CAAC,CAAC,CAAC;GAC5D,QAAQ,YAAY,IAAI,KAAK,SAAS,KAAK,MAAM,CAAC;GAClD,YAAY,eAAe;EAC7B,CACF;CACF,CAAC;AACL;;;;;;AAcA,SAAgB,kBACd,SACA,eACA,QACA,WACmB;CACnB,MAAM,CAAC,UAAU;CACjB,IAAI,CAAC,QAAQ,OAAO,EAAE,MAAM,UAAU;CACtC,IAAI,OAAO,MAAM,OAAO,eAAe,OAAO,EAAE,MAAM,UAAU;CAChE,MAAM,QAAQ,OAAO,MAAM,SAAS,KAAK,eAAe,aAAa,KAAK,eAAe;CACzF,IAAI,OAAO,iBACT,OAAO;EAAE,MAAM;EAAS,IAAI,MAAM;EAAiB,OAAO,MAAM;CAAW;CAE7E,OAAO;EAAE,MAAM;EAAW,UAAU,OAAO;CAAM;AACnD"}
|
|
@@ -0,0 +1,39 @@
|
|
|
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 { OAuthClient, OAuthTokens } from "../adapter/oauth.mjs";
|
|
5
|
+
import { SecretInput } from "../utils/encrypt.mjs";
|
|
6
|
+
import { UserConnection } from "../schema/connection.mjs";
|
|
7
|
+
import { Table } from "drizzle-orm";
|
|
8
|
+
/** Tables required by {@link ConnectionModel}. */
|
|
9
|
+
interface ConnectionTables {
|
|
10
|
+
userConnections: Table;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Data operations for user OAuth connections, addressed by `(user, service)`.
|
|
14
|
+
* Plaintext only leaves through {@link accessToken}; a token is refreshed lazily,
|
|
15
|
+
* when its owner (or a request on their behalf) needs it — never in the background.
|
|
16
|
+
*/
|
|
17
|
+
declare class ConnectionModel {
|
|
18
|
+
private readonly tables;
|
|
19
|
+
private readonly db;
|
|
20
|
+
private readonly secret?;
|
|
21
|
+
constructor(db: DatabaseAdapter, secret?: SecretInput, tables?: ConnectionTables);
|
|
22
|
+
/** The connection row (never the plaintext), or null. */
|
|
23
|
+
info(userId: string, service: string): Promise<UserConnection | null>;
|
|
24
|
+
/** Every connection of a user (no plaintext), for a "connected apps" list. */
|
|
25
|
+
listForUser(userId: string): Promise<UserConnection[]>;
|
|
26
|
+
/** Store (or replace) a connection from fresh tokens; resets it to healthy. */
|
|
27
|
+
connect(userId: string, service: string, tokens: OAuthTokens): Promise<void>;
|
|
28
|
+
/** Remove a connection. */
|
|
29
|
+
remove(userId: string, service: string): Promise<void>;
|
|
30
|
+
/**
|
|
31
|
+
* A usable access token, refreshing it (once, under concurrent calls) when it is
|
|
32
|
+
* about to expire. A failed refresh marks the connection `expired` and returns
|
|
33
|
+
* null until the user reconnects.
|
|
34
|
+
*/
|
|
35
|
+
accessToken(userId: string, service: string, oauth?: Pick<OAuthClient, "refresh">, now?: Date): Promise<string | null>;
|
|
36
|
+
private refreshOnce;
|
|
37
|
+
}
|
|
38
|
+
export { ConnectionModel, ConnectionTables };
|
|
39
|
+
//# sourceMappingURL=connections.d.mts.map
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import "node:path";
|
|
2
|
+
import "node:url";
|
|
3
|
+
import.meta.url;
|
|
4
|
+
import { decryptCredential, encrypt } from "../utils/encrypt.mjs";
|
|
5
|
+
import { ulid } from "../utils/ulid.mjs";
|
|
6
|
+
import { col } from "./columns.mjs";
|
|
7
|
+
import { and, eq } from "drizzle-orm";
|
|
8
|
+
// src/models/connections.ts
|
|
9
|
+
/** A user's own OAuth connections to external services: encrypted tokens, lazy refresh on use. */
|
|
10
|
+
/** Refresh this long before expiry so a call never starts with a token about to lapse. */
|
|
11
|
+
const REFRESH_MARGIN_MS = 6e4;
|
|
12
|
+
/** In-flight refreshes by connection, so concurrent page views refresh once (per process). */
|
|
13
|
+
const refreshing = /* @__PURE__ */ new Map();
|
|
14
|
+
/**
|
|
15
|
+
* Data operations for user OAuth connections, addressed by `(user, service)`.
|
|
16
|
+
* Plaintext only leaves through {@link accessToken}; a token is refreshed lazily,
|
|
17
|
+
* when its owner (or a request on their behalf) needs it — never in the background.
|
|
18
|
+
*/
|
|
19
|
+
var ConnectionModel = class {
|
|
20
|
+
tables;
|
|
21
|
+
db;
|
|
22
|
+
secret;
|
|
23
|
+
constructor(db, secret, tables) {
|
|
24
|
+
this.db = db;
|
|
25
|
+
this.secret = secret;
|
|
26
|
+
this.tables = tables ?? { userConnections: db.tables.userConnections };
|
|
27
|
+
}
|
|
28
|
+
/** The connection row (never the plaintext), or null. */
|
|
29
|
+
async info(userId, service) {
|
|
30
|
+
return (await this.db.list(this.tables.userConnections, {
|
|
31
|
+
where: and(eq(col(this.tables.userConnections, "userId"), userId), eq(col(this.tables.userConnections, "service"), service)),
|
|
32
|
+
limit: 1
|
|
33
|
+
}))[0] ?? null;
|
|
34
|
+
}
|
|
35
|
+
/** Every connection of a user (no plaintext), for a "connected apps" list. */
|
|
36
|
+
async listForUser(userId) {
|
|
37
|
+
return await this.db.list(this.tables.userConnections, { where: eq(col(this.tables.userConnections, "userId"), userId) });
|
|
38
|
+
}
|
|
39
|
+
/** Store (or replace) a connection from fresh tokens; resets it to healthy. */
|
|
40
|
+
async connect(userId, service, tokens) {
|
|
41
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
42
|
+
const values = {
|
|
43
|
+
tokenEncrypted: encrypt(this.secret, tokens.accessToken),
|
|
44
|
+
refreshTokenEncrypted: tokens.refreshToken ? encrypt(this.secret, tokens.refreshToken) : null,
|
|
45
|
+
expiresAt: tokens.expiresAt,
|
|
46
|
+
status: "ok",
|
|
47
|
+
updatedAt: now
|
|
48
|
+
};
|
|
49
|
+
const existing = await this.info(userId, service);
|
|
50
|
+
if (existing) {
|
|
51
|
+
await this.db.update(this.tables.userConnections, existing.id, values);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
await this.db.insert(this.tables.userConnections, {
|
|
55
|
+
id: ulid(),
|
|
56
|
+
userId,
|
|
57
|
+
service,
|
|
58
|
+
createdAt: now,
|
|
59
|
+
...values
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
/** Remove a connection. */
|
|
63
|
+
async remove(userId, service) {
|
|
64
|
+
const existing = await this.info(userId, service);
|
|
65
|
+
if (existing) await this.db.remove(this.tables.userConnections, existing.id);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* A usable access token, refreshing it (once, under concurrent calls) when it is
|
|
69
|
+
* about to expire. A failed refresh marks the connection `expired` and returns
|
|
70
|
+
* null until the user reconnects.
|
|
71
|
+
*/
|
|
72
|
+
async accessToken(userId, service, oauth, now = /* @__PURE__ */ new Date()) {
|
|
73
|
+
const row = await this.info(userId, service);
|
|
74
|
+
if (!row || row.status === "expired") return null;
|
|
75
|
+
if (!isExpiring(row.expiresAt, now)) return decryptCredential(this.secret, row.tokenEncrypted);
|
|
76
|
+
const inFlight = refreshing.get(row.id);
|
|
77
|
+
if (inFlight) return await inFlight;
|
|
78
|
+
const pending = this.refreshOnce(row, oauth).finally(() => {
|
|
79
|
+
refreshing.delete(row.id);
|
|
80
|
+
});
|
|
81
|
+
refreshing.set(row.id, pending);
|
|
82
|
+
return await pending;
|
|
83
|
+
}
|
|
84
|
+
async refreshOnce(row, oauth) {
|
|
85
|
+
try {
|
|
86
|
+
if (!oauth || !row.refreshTokenEncrypted) throw new Error("connection cannot be refreshed");
|
|
87
|
+
const tokens = await oauth.refresh({ refreshToken: decryptCredential(this.secret, row.refreshTokenEncrypted) });
|
|
88
|
+
await this.db.update(this.tables.userConnections, row.id, {
|
|
89
|
+
tokenEncrypted: encrypt(this.secret, tokens.accessToken),
|
|
90
|
+
refreshTokenEncrypted: tokens.refreshToken ? encrypt(this.secret, tokens.refreshToken) : row.refreshTokenEncrypted,
|
|
91
|
+
expiresAt: tokens.expiresAt,
|
|
92
|
+
status: "ok",
|
|
93
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
94
|
+
});
|
|
95
|
+
return tokens.accessToken;
|
|
96
|
+
} catch {
|
|
97
|
+
await this.db.update(this.tables.userConnections, row.id, {
|
|
98
|
+
status: "expired",
|
|
99
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
100
|
+
});
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
/** Expiring within the margin (null never expires). */
|
|
106
|
+
function isExpiring(expiresAt, now) {
|
|
107
|
+
return expiresAt !== null && Date.parse(expiresAt) - now.getTime() < REFRESH_MARGIN_MS;
|
|
108
|
+
}
|
|
109
|
+
export { ConnectionModel };
|
|
110
|
+
|
|
111
|
+
//# sourceMappingURL=connections.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connections.mjs","names":[],"sources":["../../src/models/connections.ts"],"sourcesContent":["/** A user's own OAuth connections to external services: encrypted tokens, lazy refresh on use. */\nimport { and, eq } from \"drizzle-orm\";\nimport type { Table } from \"drizzle-orm\";\nimport type { DatabaseAdapter } from \"../adapters/database.ts\";\nimport type { OAuthClient, OAuthTokens } from \"../adapters/oauth.ts\";\nimport type { UserConnection } from \"../schema/connection.ts\";\nimport { decryptCredential, encrypt, type SecretInput } from \"../utils/encrypt.ts\";\nimport { ulid } from \"../utils/ulid.ts\";\nimport { col } from \"./columns.ts\";\n\n/** Tables required by {@link ConnectionModel}. */\nexport interface ConnectionTables {\n userConnections: Table;\n}\n\n/** Refresh this long before expiry so a call never starts with a token about to lapse. */\nconst REFRESH_MARGIN_MS = 60_000;\n\n/** In-flight refreshes by connection, so concurrent page views refresh once (per process). */\nconst refreshing = new Map<string, Promise<string | null>>();\n\n/**\n * Data operations for user OAuth connections, addressed by `(user, service)`.\n * Plaintext only leaves through {@link accessToken}; a token is refreshed lazily,\n * when its owner (or a request on their behalf) needs it — never in the background.\n */\nexport class ConnectionModel {\n private readonly tables: ConnectionTables;\n private readonly db: DatabaseAdapter;\n private readonly secret?: SecretInput;\n\n constructor(db: DatabaseAdapter, secret?: SecretInput, tables?: ConnectionTables) {\n this.db = db;\n this.secret = secret;\n this.tables = tables ?? { userConnections: db.tables.userConnections };\n }\n\n /** The connection row (never the plaintext), or null. */\n async info(userId: string, service: string): Promise<UserConnection | null> {\n const rows = (await this.db.list(this.tables.userConnections, {\n where: and(\n eq(col(this.tables.userConnections, \"userId\"), userId),\n eq(col(this.tables.userConnections, \"service\"), service),\n ),\n limit: 1,\n })) as unknown as UserConnection[];\n return rows[0] ?? null;\n }\n\n /** Every connection of a user (no plaintext), for a \"connected apps\" list. */\n async listForUser(userId: string): Promise<UserConnection[]> {\n return (await this.db.list(this.tables.userConnections, {\n where: eq(col(this.tables.userConnections, \"userId\"), userId),\n })) as unknown as UserConnection[];\n }\n\n /** Store (or replace) a connection from fresh tokens; resets it to healthy. */\n async connect(userId: string, service: string, tokens: OAuthTokens): Promise<void> {\n const now = new Date().toISOString();\n const values = {\n tokenEncrypted: encrypt(this.secret, tokens.accessToken),\n refreshTokenEncrypted: tokens.refreshToken ? encrypt(this.secret, tokens.refreshToken) : null,\n expiresAt: tokens.expiresAt,\n status: \"ok\",\n updatedAt: now,\n };\n const existing = await this.info(userId, service);\n if (existing) {\n await this.db.update(this.tables.userConnections, existing.id, values);\n return;\n }\n await this.db.insert(this.tables.userConnections, {\n id: ulid(),\n userId,\n service,\n createdAt: now,\n ...values,\n });\n }\n\n /** Remove a connection. */\n async remove(userId: string, service: string): Promise<void> {\n const existing = await this.info(userId, service);\n if (existing) await this.db.remove(this.tables.userConnections, existing.id);\n }\n\n /**\n * A usable access token, refreshing it (once, under concurrent calls) when it is\n * about to expire. A failed refresh marks the connection `expired` and returns\n * null until the user reconnects.\n */\n async accessToken(\n userId: string,\n service: string,\n oauth?: Pick<OAuthClient, \"refresh\">,\n now = new Date(),\n ): Promise<string | null> {\n const row = await this.info(userId, service);\n if (!row || row.status === \"expired\") return null;\n if (!isExpiring(row.expiresAt, now)) return decryptCredential(this.secret, row.tokenEncrypted);\n const inFlight = refreshing.get(row.id);\n if (inFlight) return await inFlight;\n const pending = this.refreshOnce(row, oauth).finally(() => {\n refreshing.delete(row.id);\n });\n refreshing.set(row.id, pending);\n return await pending;\n }\n\n private async refreshOnce(\n row: UserConnection,\n oauth: Pick<OAuthClient, \"refresh\"> | undefined,\n ): Promise<string | null> {\n try {\n if (!oauth || !row.refreshTokenEncrypted) throw new Error(\"connection cannot be refreshed\");\n const tokens = await oauth.refresh({\n refreshToken: decryptCredential(this.secret, row.refreshTokenEncrypted),\n });\n await this.db.update(this.tables.userConnections, row.id, {\n tokenEncrypted: encrypt(this.secret, tokens.accessToken),\n refreshTokenEncrypted: tokens.refreshToken\n ? encrypt(this.secret, tokens.refreshToken)\n : row.refreshTokenEncrypted,\n expiresAt: tokens.expiresAt,\n status: \"ok\",\n updatedAt: new Date().toISOString(),\n });\n return tokens.accessToken;\n } catch {\n await this.db.update(this.tables.userConnections, row.id, {\n status: \"expired\",\n updatedAt: new Date().toISOString(),\n });\n return null;\n }\n }\n}\n\n/** Expiring within the margin (null never expires). */\nfunction isExpiring(expiresAt: string | null, now: Date): boolean {\n return expiresAt !== null && Date.parse(expiresAt) - now.getTime() < REFRESH_MARGIN_MS;\n}\n"],"mappings":"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"}
|
|
@@ -3,11 +3,10 @@ import __tsdown_shims_url from 'node:url';
|
|
|
3
3
|
import { DatabaseAdapter } from "../adapter/database.mjs";
|
|
4
4
|
import { ContentRef } from "../schema/content-ref.mjs";
|
|
5
5
|
import { Table } from "drizzle-orm";
|
|
6
|
-
//#region src/models/content-ref.d.ts
|
|
7
6
|
/** Content refs for deduplicated Storybook assets. */
|
|
8
7
|
declare class ContentRefModel {
|
|
9
|
-
private readonly db;
|
|
10
8
|
private readonly tables;
|
|
9
|
+
private readonly db;
|
|
11
10
|
constructor(db: DatabaseAdapter, tables?: {
|
|
12
11
|
contentRefs: Table;
|
|
13
12
|
});
|
|
@@ -16,6 +15,5 @@ declare class ContentRefModel {
|
|
|
16
15
|
decrement(hash: string): Promise<ContentRef | null>;
|
|
17
16
|
listStale(cutoff: string): Promise<ContentRef[]>;
|
|
18
17
|
}
|
|
19
|
-
//#endregion
|
|
20
18
|
export { ContentRefModel };
|
|
21
19
|
//# sourceMappingURL=content-ref.d.mts.map
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import "node:path";
|
|
2
2
|
import "node:url";
|
|
3
3
|
import.meta.url;
|
|
4
|
-
|
|
4
|
+
// src/models/content-ref.ts
|
|
5
5
|
/** Content refs for deduplicated Storybook assets. */
|
|
6
6
|
var ContentRefModel = class {
|
|
7
|
-
db;
|
|
8
7
|
tables;
|
|
8
|
+
db;
|
|
9
9
|
constructor(db, tables) {
|
|
10
10
|
this.db = db;
|
|
11
11
|
this.tables = tables ?? { contentRefs: db.tables.contentRefs };
|
|
@@ -43,7 +43,6 @@ var ContentRefModel = class {
|
|
|
43
43
|
return (await this.db.list(this.tables.contentRefs)).filter((r) => r.refCount === 0 && r.lastSeenAt < cutoff);
|
|
44
44
|
}
|
|
45
45
|
};
|
|
46
|
-
//#endregion
|
|
47
46
|
export { ContentRefModel };
|
|
48
47
|
|
|
49
48
|
//# sourceMappingURL=content-ref.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"content-ref.mjs","names":[],"sources":["../../src/models/content-ref.ts"],"sourcesContent":["import type { Table } from \"drizzle-orm\";\nimport type { DatabaseAdapter } from \"../adapters/database.ts\";\nimport type { ContentRef } from \"../schema/content-ref.ts\";\n\n/** Content refs for deduplicated Storybook assets. */\nexport class ContentRefModel {\n private readonly tables: { contentRefs: Table };\n
|
|
1
|
+
{"version":3,"file":"content-ref.mjs","names":[],"sources":["../../src/models/content-ref.ts"],"sourcesContent":["import type { Table } from \"drizzle-orm\";\nimport type { DatabaseAdapter } from \"../adapters/database.ts\";\nimport type { ContentRef } from \"../schema/content-ref.ts\";\n\n/** Content refs for deduplicated Storybook assets. */\nexport class ContentRefModel {\n private readonly tables: { contentRefs: Table };\n private readonly db: DatabaseAdapter;\n\n constructor(db: DatabaseAdapter, tables?: { contentRefs: Table }) {\n this.db = db;\n this.tables = tables ?? { contentRefs: db.tables.contentRefs };\n }\n\n async get(hash: string): Promise<ContentRef | null> {\n return (await this.db.get(this.tables.contentRefs, hash)) as unknown as ContentRef | null;\n }\n\n async upsert(hash: string): Promise<ContentRef> {\n const now = new Date().toISOString();\n const existing = await this.get(hash);\n if (existing) {\n return (await this.db.update(this.tables.contentRefs, hash, {\n refCount: existing.refCount + 1,\n lastSeenAt: now,\n })) as unknown as ContentRef;\n }\n return (await this.db.insert(this.tables.contentRefs, {\n hash,\n refCount: 1,\n lastSeenAt: now,\n createdAt: now,\n })) as unknown as ContentRef;\n }\n\n async decrement(hash: string): Promise<ContentRef | null> {\n const existing = await this.get(hash);\n if (!existing) return null;\n if (existing.refCount <= 1) {\n await this.db.remove(this.tables.contentRefs, hash);\n return null;\n }\n return (await this.db.update(this.tables.contentRefs, hash, {\n refCount: existing.refCount - 1,\n lastSeenAt: new Date().toISOString(),\n })) as unknown as ContentRef;\n }\n\n async listStale(cutoff: string): Promise<ContentRef[]> {\n const all = (await this.db.list(this.tables.contentRefs)) as unknown as ContentRef[];\n return all.filter((r) => r.refCount === 0 && r.lastSeenAt < cutoff);\n }\n}\n"],"mappings":";;;;;AAKA,IAAa,kBAAb,MAA6B;CAC3B;CACA;CAEA,YAAY,IAAqB,QAAiC;EAChE,KAAK,KAAK;EACV,KAAK,SAAS,UAAU,EAAE,aAAa,GAAG,OAAO,YAAY;CAC/D;CAEA,MAAM,IAAI,MAA0C;EAClD,OAAQ,MAAM,KAAK,GAAG,IAAI,KAAK,OAAO,aAAa,IAAI;CACzD;CAEA,MAAM,OAAO,MAAmC;EAC9C,MAAM,uBAAM,IAAI,KAAK,EAAA,CAAE,YAAY;EACnC,MAAM,WAAW,MAAM,KAAK,IAAI,IAAI;EACpC,IAAI,UACF,OAAQ,MAAM,KAAK,GAAG,OAAO,KAAK,OAAO,aAAa,MAAM;GAC1D,UAAU,SAAS,WAAW;GAC9B,YAAY;EACd,CAAC;EAEH,OAAQ,MAAM,KAAK,GAAG,OAAO,KAAK,OAAO,aAAa;GACpD;GACA,UAAU;GACV,YAAY;GACZ,WAAW;EACb,CAAC;CACH;CAEA,MAAM,UAAU,MAA0C;EACxD,MAAM,WAAW,MAAM,KAAK,IAAI,IAAI;EACpC,IAAI,CAAC,UAAU,OAAO;EACtB,IAAI,SAAS,YAAY,GAAG;GAC1B,MAAM,KAAK,GAAG,OAAO,KAAK,OAAO,aAAa,IAAI;GAClD,OAAO;EACT;EACA,OAAQ,MAAM,KAAK,GAAG,OAAO,KAAK,OAAO,aAAa,MAAM;GAC1D,UAAU,SAAS,WAAW;GAC9B,6BAAY,IAAI,KAAK,EAAA,CAAE,YAAY;EACrC,CAAC;CACH;CAEA,MAAM,UAAU,QAAuC;EAErD,QAAO,MADY,KAAK,GAAG,KAAK,KAAK,OAAO,WAAW,EAAA,CAC5C,QAAQ,MAAM,EAAE,aAAa,KAAK,EAAE,aAAa,MAAM;CACpE;AACF"}
|