@storyshelf/core 0.3.2 → 0.5.3
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 +38 -31
- package/dist/adapter/capture-queue.d.mts +29 -1
- package/dist/adapter/capture-runner.d.mts +18 -2
- package/dist/adapter/capture-runner.mjs +12 -1
- package/dist/adapter/capture-runner.mjs.map +1 -0
- package/dist/adapter/database.d.mts +67 -17
- package/dist/adapter/database.mjs +22 -55
- package/dist/adapter/database.mjs.map +1 -1
- package/dist/adapter/email-sender.d.mts +27 -0
- package/dist/adapter/email-sender.mjs +4 -0
- package/dist/adapter/metadata.d.mts +21 -15
- package/dist/adapter/notifier.d.mts +7 -0
- package/dist/adapter/notifier.mjs +6 -0
- package/dist/adapter/setup.d.mts +61 -0
- package/dist/adapter/setup.mjs +29 -0
- package/dist/adapter/setup.mjs.map +1 -0
- package/dist/adapter/webhook-events.d.mts +29 -0
- package/dist/adapter/webhook-events.mjs +83 -0
- package/dist/adapter/webhook-events.mjs.map +1 -0
- package/dist/adapters/notifier/events.d.mts +26 -0
- package/dist/adapters/notifier/events.mjs +66 -0
- package/dist/adapters/notifier/events.mjs.map +1 -0
- package/dist/adapters/notifier/format.d.mts +14 -0
- package/dist/adapters/notifier/format.mjs +96 -0
- package/dist/adapters/notifier/format.mjs.map +1 -0
- package/dist/adapters/notifier/provider.d.mts +32 -0
- package/dist/adapters/notifier/types.d.mts +56 -0
- package/dist/adapters/setup-runner.d.mts +23 -0
- package/dist/adapters/setup-runner.mjs +149 -0
- package/dist/adapters/setup-runner.mjs.map +1 -0
- package/dist/adapters/validate.d.mts +30 -0
- package/dist/adapters/validate.mjs +313 -0
- package/dist/adapters/validate.mjs.map +1 -0
- package/dist/auth.d.mts +94 -0
- package/dist/auth.mjs +4 -0
- package/dist/brand-packs.d.mts +16 -0
- package/dist/brand-packs.mjs +130 -0
- package/dist/brand-packs.mjs.map +1 -0
- package/dist/capture/adapter.d.mts +51 -1
- package/dist/capture/adapter.mjs.map +1 -1
- package/dist/capture/affected.d.mts +16 -0
- package/dist/capture/affected.mjs +64 -0
- package/dist/capture/affected.mjs.map +1 -0
- package/dist/capture/diff-record.mjs +30 -0
- package/dist/capture/diff-record.mjs.map +1 -0
- package/dist/capture/dispatch.d.mts +36 -0
- package/dist/capture/dispatch.mjs +88 -31
- package/dist/capture/dispatch.mjs.map +1 -1
- package/dist/capture/guards.mjs +31 -0
- package/dist/capture/guards.mjs.map +1 -0
- package/dist/capture/orchestrator.d.mts +41 -6
- package/dist/capture/orchestrator.mjs +151 -28
- package/dist/capture/orchestrator.mjs.map +1 -1
- package/dist/capture/pipeline.d.mts +30 -16
- package/dist/capture/pipeline.mjs +143 -39
- package/dist/capture/pipeline.mjs.map +1 -1
- package/dist/capture/recovery.d.mts +21 -0
- package/dist/capture/recovery.mjs +83 -0
- package/dist/capture/recovery.mjs.map +1 -0
- package/dist/capture/rediff.d.mts +35 -0
- package/dist/capture/rediff.mjs +77 -0
- package/dist/capture/rediff.mjs.map +1 -0
- package/dist/capture/sizing.d.mts +58 -0
- package/dist/capture/sizing.mjs +119 -0
- package/dist/capture/sizing.mjs.map +1 -0
- package/dist/capture/skip-checks.mjs +3 -3
- package/dist/capture/skip-checks.mjs.map +1 -1
- package/dist/capture/statics.d.mts +38 -0
- package/dist/capture/statics.mjs +38 -4
- package/dist/capture/statics.mjs.map +1 -1
- package/dist/capture/status-fanout.mjs +7 -2
- package/dist/capture/status-fanout.mjs.map +1 -1
- package/dist/capture/storybook.d.mts +31 -2
- package/dist/capture/storybook.mjs +77 -2
- package/dist/capture/storybook.mjs.map +1 -1
- package/dist/capture.d.mts +10 -4
- package/dist/capture.mjs +7 -2
- package/dist/config.d.mts +121 -4
- package/dist/config.mjs +55 -8
- package/dist/config.mjs.map +1 -1
- package/dist/index.d.mts +5 -56
- package/dist/index.mjs +4 -284
- package/dist/logger.d.mts +26 -17
- package/dist/logger.mjs +10 -5
- package/dist/logger.mjs.map +1 -1
- package/dist/models/baseline-status.d.mts +29 -0
- package/dist/models/baseline-status.mjs +26 -0
- package/dist/models/baseline-status.mjs.map +1 -0
- package/dist/models/baseline.d.mts +33 -0
- package/dist/models/baseline.mjs +35 -13
- package/dist/models/baseline.mjs.map +1 -1
- package/dist/models/build.d.mts +87 -0
- package/dist/models/build.mjs +83 -35
- package/dist/models/build.mjs.map +1 -1
- package/dist/models/capture-attempt.d.mts +40 -0
- package/dist/models/capture-attempt.mjs +85 -0
- package/dist/models/capture-attempt.mjs.map +1 -0
- package/dist/models/capture-log.d.mts +21 -0
- package/dist/models/capture-log.mjs +44 -0
- package/dist/models/capture-log.mjs.map +1 -0
- package/dist/models/comment.d.mts +29 -0
- package/dist/models/comment.mjs +11 -8
- package/dist/models/comment.mjs.map +1 -1
- package/dist/models/content-ref.d.mts +21 -0
- package/dist/models/content-ref.mjs +49 -0
- package/dist/models/content-ref.mjs.map +1 -0
- package/dist/models/credentials.d.mts +36 -0
- package/dist/models/credentials.mjs +103 -0
- package/dist/models/credentials.mjs.map +1 -0
- package/dist/models/label.d.mts +58 -0
- package/dist/models/label.mjs +32 -27
- package/dist/models/label.mjs.map +1 -1
- package/dist/models/member.d.mts +34 -0
- package/dist/models/member.mjs +20 -11
- package/dist/models/member.mjs.map +1 -1
- package/dist/models/notification-channel.d.mts +52 -0
- package/dist/models/notification-channel.mjs +93 -0
- package/dist/models/notification-channel.mjs.map +1 -0
- package/dist/models/notification-subscription.d.mts +44 -0
- package/dist/models/notification-subscription.mjs +89 -0
- package/dist/models/notification-subscription.mjs.map +1 -0
- package/dist/models/project-group-mapping.d.mts +30 -0
- package/dist/models/project-group-mapping.mjs +46 -0
- package/dist/models/project-group-mapping.mjs.map +1 -0
- package/dist/models/project.d.mts +54 -0
- package/dist/models/project.mjs +15 -11
- package/dist/models/project.mjs.map +1 -1
- package/dist/models/snapshot.d.mts +58 -0
- package/dist/models/snapshot.mjs +13 -7
- package/dist/models/snapshot.mjs.map +1 -1
- package/dist/models/status-config.d.mts +38 -0
- package/dist/models/status-config.mjs +12 -11
- package/dist/models/status-config.mjs.map +1 -1
- package/dist/models/token.d.mts +44 -0
- package/dist/models/token.mjs +18 -15
- package/dist/models/token.mjs.map +1 -1
- package/dist/models/user.d.mts +42 -0
- package/dist/models/user.mjs +65 -0
- package/dist/models/user.mjs.map +1 -0
- package/dist/models/webhook.d.mts +50 -0
- package/dist/models/webhook.mjs +12 -10
- package/dist/models/webhook.mjs.map +1 -1
- package/dist/models.d.mts +22 -0
- package/dist/models.mjs +23 -0
- package/dist/paths.mjs +29 -0
- package/dist/paths.mjs.map +1 -1
- package/dist/retention.d.mts +45 -0
- package/dist/retention.mjs +145 -0
- package/dist/retention.mjs.map +1 -0
- package/dist/schema/baseline.d.mts +1 -0
- package/dist/schema/build.d.mts +8 -0
- package/dist/schema/capture-attempt.d.mts +38 -0
- package/dist/schema/comment.d.mts +1 -1
- package/dist/schema/content-ref.d.mts +13 -0
- package/dist/schema/member.d.mts +2 -0
- package/dist/schema/notification.d.mts +35 -0
- package/dist/schema/project-group-mapping.d.mts +15 -0
- package/dist/schema/project.d.mts +4 -0
- package/dist/schema/snapshot.d.mts +7 -0
- package/dist/schema/token.d.mts +2 -0
- package/dist/schema/user.d.mts +11 -1
- package/dist/schema.d.mts +9 -46
- package/dist/schema.mjs +1 -55
- package/dist/test-helpers/create-project.d.mts +15 -0
- package/dist/test-helpers/create-project.mjs +28 -0
- package/dist/test-helpers/create-project.mjs.map +1 -0
- package/dist/test-helpers/database-contract.d.mts +13 -0
- package/dist/test-helpers/database-contract.mjs +58 -0
- package/dist/test-helpers/database-contract.mjs.map +1 -0
- package/dist/test-helpers/fake-adapters.d.mts +14 -0
- package/dist/test-helpers/fake-adapters.mjs +20 -0
- package/dist/test-helpers/fake-adapters.mjs.map +1 -0
- package/dist/test-helpers/fake-database.d.mts +11 -0
- package/dist/test-helpers/fake-database.mjs +104 -0
- package/dist/test-helpers/fake-database.mjs.map +1 -0
- package/dist/test-helpers/fake-storage.d.mts +14 -0
- package/dist/test-helpers/fake-storage.mjs +49 -0
- package/dist/test-helpers/fake-storage.mjs.map +1 -0
- package/dist/test-helpers/fake-tables.mjs +276 -0
- package/dist/test-helpers/fake-tables.mjs.map +1 -0
- package/dist/test-helpers/git-contract.d.mts +11 -0
- package/dist/test-helpers/git-contract.mjs +34 -0
- package/dist/test-helpers/git-contract.mjs.map +1 -0
- package/dist/test-helpers/notifier-contract.d.mts +11 -0
- package/dist/test-helpers/notifier-contract.mjs +34 -0
- package/dist/test-helpers/notifier-contract.mjs.map +1 -0
- package/dist/test-helpers/queue-contract.d.mts +11 -0
- package/dist/test-helpers/queue-contract.mjs +64 -0
- package/dist/test-helpers/queue-contract.mjs.map +1 -0
- package/dist/test-helpers/runner-contract.d.mts +11 -0
- package/dist/test-helpers/runner-contract.mjs +32 -0
- package/dist/test-helpers/runner-contract.mjs.map +1 -0
- package/dist/test-helpers/sql-chunks.mjs +138 -0
- package/dist/test-helpers/sql-chunks.mjs.map +1 -0
- package/dist/test-helpers/storage-contract.d.mts +11 -0
- package/dist/test-helpers/storage-contract.mjs +87 -0
- package/dist/test-helpers/storage-contract.mjs.map +1 -0
- package/dist/test-helpers.d.mts +13 -0
- package/dist/test-helpers.mjs +14 -0
- package/dist/tracing.mjs +63 -0
- package/dist/tracing.mjs.map +1 -0
- package/dist/types.d.mts +38 -5
- package/dist/types.mjs +18 -5
- package/dist/types.mjs.map +1 -1
- package/dist/urls.d.mts +12 -1
- package/dist/urls.mjs +22 -3
- package/dist/urls.mjs.map +1 -1
- package/dist/utils/encrypt.d.mts +48 -0
- package/dist/utils/encrypt.mjs +67 -13
- package/dist/utils/encrypt.mjs.map +1 -1
- package/dist/utils/hash.d.mts +17 -0
- package/dist/utils/hash.mjs +8 -2
- package/dist/utils/hash.mjs.map +1 -1
- package/dist/utils/http.d.mts +58 -0
- package/dist/utils/http.mjs +147 -0
- package/dist/utils/http.mjs.map +1 -0
- package/dist/utils/mime.d.mts +9 -0
- package/dist/utils/mime.mjs +33 -0
- package/dist/utils/mime.mjs.map +1 -0
- package/dist/utils/path-security.d.mts +12 -0
- package/dist/utils/path-security.mjs +24 -0
- package/dist/utils/path-security.mjs.map +1 -0
- package/dist/utils/redact.d.mts +17 -0
- package/dist/utils/redact.mjs +42 -0
- package/dist/utils/redact.mjs.map +1 -0
- package/dist/utils/timing.d.mts +19 -0
- package/dist/utils/timing.mjs +46 -0
- package/dist/utils/timing.mjs.map +1 -0
- package/dist/utils/ulid.d.mts +10 -0
- package/dist/utils/ulid.mjs +8 -8
- package/dist/utils/ulid.mjs.map +1 -1
- package/dist/utils.d.mts +12 -0
- package/dist/utils.mjs +13 -0
- package/package.json +69 -28
- package/dist/adapter/auth.d.mts +0 -50
- package/dist/adapter/auth.mjs +0 -10
- package/dist/adapter/auth.mjs.map +0 -1
- package/dist/adapter/init.d.mts +0 -58
- package/dist/adapter/init.mjs +0 -93
- package/dist/adapter/init.mjs.map +0 -1
- package/dist/adapters/webhook-events.mjs +0 -55
- package/dist/adapters/webhook-events.mjs.map +0 -1
- package/dist/ddl.d.mts +0 -8
- package/dist/ddl.mjs +0 -158
- package/dist/ddl.mjs.map +0 -1
- package/dist/index.mjs.map +0 -1
- package/dist/middleware/auth-gate.mjs +0 -36
- package/dist/middleware/auth-gate.mjs.map +0 -1
- package/dist/middleware/csrf.mjs +0 -40
- package/dist/middleware/csrf.mjs.map +0 -1
- package/dist/middleware/rate-limit.mjs +0 -39
- package/dist/middleware/rate-limit.mjs.map +0 -1
- package/dist/middleware/request-log.mjs +0 -28
- package/dist/middleware/request-log.mjs.map +0 -1
- package/dist/middleware/store-scope.mjs +0 -29
- package/dist/middleware/store-scope.mjs.map +0 -1
- package/dist/pages/build-detail.mjs +0 -339
- package/dist/pages/build-detail.mjs.map +0 -1
- package/dist/pages/build-diff-comments.mjs +0 -113
- package/dist/pages/build-diff-comments.mjs.map +0 -1
- package/dist/pages/build-diff-header.mjs +0 -163
- package/dist/pages/build-diff-header.mjs.map +0 -1
- package/dist/pages/build-diff-nav.mjs +0 -66
- package/dist/pages/build-diff-nav.mjs.map +0 -1
- package/dist/pages/build-diff-viewer.mjs +0 -197
- package/dist/pages/build-diff-viewer.mjs.map +0 -1
- package/dist/pages/build-diff.mjs +0 -90
- package/dist/pages/build-diff.mjs.map +0 -1
- package/dist/pages/compute-jobs.mjs +0 -198
- package/dist/pages/compute-jobs.mjs.map +0 -1
- package/dist/pages/label-detail.mjs +0 -228
- package/dist/pages/label-detail.mjs.map +0 -1
- package/dist/pages/login.mjs +0 -78
- package/dist/pages/login.mjs.map +0 -1
- package/dist/pages/project-builds.mjs +0 -231
- package/dist/pages/project-builds.mjs.map +0 -1
- package/dist/pages/project-create.mjs +0 -169
- package/dist/pages/project-create.mjs.map +0 -1
- package/dist/pages/project-settings.mjs +0 -102
- package/dist/pages/project-settings.mjs.map +0 -1
- package/dist/pages/projects.mjs +0 -184
- package/dist/pages/projects.mjs.map +0 -1
- package/dist/pages/root.mjs +0 -94
- package/dist/pages/root.mjs.map +0 -1
- package/dist/pages/settings-general.mjs +0 -192
- package/dist/pages/settings-general.mjs.map +0 -1
- package/dist/pages/settings-labels.mjs +0 -150
- package/dist/pages/settings-labels.mjs.map +0 -1
- package/dist/pages/settings-members.mjs +0 -125
- package/dist/pages/settings-members.mjs.map +0 -1
- package/dist/pages/settings-status.mjs +0 -152
- package/dist/pages/settings-status.mjs.map +0 -1
- package/dist/pages/settings-tests.mjs +0 -149
- package/dist/pages/settings-tests.mjs.map +0 -1
- package/dist/pages/settings-tokens.mjs +0 -96
- package/dist/pages/settings-tokens.mjs.map +0 -1
- package/dist/pages/settings-webhooks.mjs +0 -150
- package/dist/pages/settings-webhooks.mjs.map +0 -1
- package/dist/pages/storybook.mjs +0 -96
- package/dist/pages/storybook.mjs.map +0 -1
- package/dist/retention/purge.mjs +0 -70
- package/dist/retention/purge.mjs.map +0 -1
- package/dist/routers/admin.mjs +0 -42
- package/dist/routers/admin.mjs.map +0 -1
- package/dist/routers/assets.mjs +0 -25
- package/dist/routers/assets.mjs.map +0 -1
- package/dist/routers/auth.mjs +0 -78
- package/dist/routers/auth.mjs.map +0 -1
- package/dist/routers/builds.handlers.mjs +0 -184
- package/dist/routers/builds.handlers.mjs.map +0 -1
- package/dist/routers/builds.mjs +0 -185
- package/dist/routers/builds.mjs.map +0 -1
- package/dist/routers/comments.mjs +0 -90
- package/dist/routers/comments.mjs.map +0 -1
- package/dist/routers/health.mjs +0 -137
- package/dist/routers/health.mjs.map +0 -1
- package/dist/routers/helpers.mjs +0 -82
- package/dist/routers/helpers.mjs.map +0 -1
- package/dist/routers/htmx.mjs +0 -20
- package/dist/routers/htmx.mjs.map +0 -1
- package/dist/routers/labels.mjs +0 -110
- package/dist/routers/labels.mjs.map +0 -1
- package/dist/routers/media.mjs +0 -55
- package/dist/routers/media.mjs.map +0 -1
- package/dist/routers/members.mjs +0 -102
- package/dist/routers/members.mjs.map +0 -1
- package/dist/routers/projects.mjs +0 -122
- package/dist/routers/projects.mjs.map +0 -1
- package/dist/routers/schemas.mjs +0 -224
- package/dist/routers/schemas.mjs.map +0 -1
- package/dist/routers/settings-general.mjs +0 -95
- package/dist/routers/settings-general.mjs.map +0 -1
- package/dist/routers/settings-labels.mjs +0 -49
- package/dist/routers/settings-labels.mjs.map +0 -1
- package/dist/routers/settings-members.mjs +0 -30
- package/dist/routers/settings-members.mjs.map +0 -1
- package/dist/routers/settings-status.mjs +0 -73
- package/dist/routers/settings-status.mjs.map +0 -1
- package/dist/routers/settings-tokens.mjs +0 -40
- package/dist/routers/settings-tokens.mjs.map +0 -1
- package/dist/routers/settings-webhooks.mjs +0 -63
- package/dist/routers/settings-webhooks.mjs.map +0 -1
- package/dist/routers/settings.handlers.mjs +0 -93
- package/dist/routers/settings.handlers.mjs.map +0 -1
- package/dist/routers/settings.mjs +0 -23
- package/dist/routers/settings.mjs.map +0 -1
- package/dist/routers/snapshots.mjs +0 -144
- package/dist/routers/snapshots.mjs.map +0 -1
- package/dist/routers/status-configs.mjs +0 -101
- package/dist/routers/status-configs.mjs.map +0 -1
- package/dist/routers/storybook.mjs +0 -120
- package/dist/routers/storybook.mjs.map +0 -1
- package/dist/routers/tokens.mjs +0 -81
- package/dist/routers/tokens.mjs.map +0 -1
- package/dist/routers/ui.mjs +0 -165
- package/dist/routers/ui.mjs.map +0 -1
- package/dist/routers/webhooks.mjs +0 -90
- package/dist/routers/webhooks.mjs.map +0 -1
- package/dist/schema/baseline.mjs +0 -22
- package/dist/schema/baseline.mjs.map +0 -1
- package/dist/schema/build.mjs +0 -29
- package/dist/schema/build.mjs.map +0 -1
- package/dist/schema/comment.mjs +0 -26
- package/dist/schema/comment.mjs.map +0 -1
- package/dist/schema/label.mjs +0 -30
- package/dist/schema/label.mjs.map +0 -1
- package/dist/schema/member.mjs +0 -19
- package/dist/schema/member.mjs.map +0 -1
- package/dist/schema/project.mjs +0 -25
- package/dist/schema/project.mjs.map +0 -1
- package/dist/schema/snapshot.mjs +0 -34
- package/dist/schema/snapshot.mjs.map +0 -1
- package/dist/schema/status-config.mjs +0 -20
- package/dist/schema/status-config.mjs.map +0 -1
- package/dist/schema/token.mjs +0 -19
- package/dist/schema/token.mjs.map +0 -1
- package/dist/schema/user.mjs +0 -19
- package/dist/schema/user.mjs.map +0 -1
- package/dist/schema/webhook.mjs +0 -20
- package/dist/schema/webhook.mjs.map +0 -1
- package/dist/schema.mjs.map +0 -1
- package/dist/store.mjs +0 -20
- package/dist/store.mjs.map +0 -1
- package/dist/ui/document.mjs +0 -348
- package/dist/ui/document.mjs.map +0 -1
- package/dist/ui/feedback.mjs +0 -24
- package/dist/ui/feedback.mjs.map +0 -1
- package/dist/ui/forms.mjs +0 -117
- package/dist/ui/forms.mjs.map +0 -1
- package/dist/ui/styles.mjs +0 -174
- package/dist/ui/styles.mjs.map +0 -1
- package/dist/ui/theme.mjs +0 -44
- package/dist/ui/theme.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @storyshelf/core
|
|
2
2
|
|
|
3
|
-
The
|
|
3
|
+
The domain layer of StoryShelf: adapter interfaces, models, capture pipeline, diff engine, and retention logic — with no HTTP dependency, so workers import it without pulling a server. The Hono router lives in `@storyshelf/app`. Compose pluggable adapters into a complete self-hosted visual testing server.
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
@@ -17,17 +17,22 @@ npm install @storyshelf/core
|
|
|
17
17
|
## Quick start
|
|
18
18
|
|
|
19
19
|
```ts
|
|
20
|
-
import {
|
|
20
|
+
import { createShelfApp } from "@storyshelf/app";
|
|
21
21
|
|
|
22
|
-
const app =
|
|
22
|
+
const app = createShelfApp({
|
|
23
23
|
database, // DatabaseAdapter
|
|
24
24
|
storage, // StorageAdapter
|
|
25
25
|
captureRunner, // CaptureRunner (optional)
|
|
26
|
-
auth, //
|
|
26
|
+
auth, // Auth from @storyshelf/auth (optional)
|
|
27
27
|
gitHosts, // GitHostProvider[] (optional)
|
|
28
28
|
logger, // pino Logger (optional; built internally if omitted)
|
|
29
29
|
ui: { name: "My Shelf" }, // UIConfig (optional)
|
|
30
|
-
config: {
|
|
30
|
+
config: {
|
|
31
|
+
captureConcurrency: 2,
|
|
32
|
+
purgeTtlDays: 30,
|
|
33
|
+
branchTtlDays: 30,
|
|
34
|
+
branchGcIntervalMs: 86_400_000,
|
|
35
|
+
}, // ShelfConfig (optional)
|
|
31
36
|
});
|
|
32
37
|
|
|
33
38
|
// The returned app is a Hono instance; serve it with any Hono adapter.
|
|
@@ -36,21 +41,21 @@ serve({ fetch: app.fetch, port: 3000 });
|
|
|
36
41
|
|
|
37
42
|
## API
|
|
38
43
|
|
|
39
|
-
### `
|
|
44
|
+
### `createShelfApp(options: ShelfOptions): Hono`
|
|
40
45
|
|
|
41
46
|
Assembles the router from the provided adapters. `ShelfOptions`:
|
|
42
47
|
|
|
43
|
-
| Option | Type
|
|
44
|
-
| --------------- |
|
|
45
|
-
| `database` | `DatabaseAdapter`
|
|
46
|
-
| `storage` | `StorageAdapter`
|
|
47
|
-
| `captureRunner` | `CaptureRunner`
|
|
48
|
-
| `captureQueue` | `CaptureQueue`
|
|
49
|
-
| `auth` | `
|
|
50
|
-
| `gitHosts` | `GitHostProvider[]`
|
|
51
|
-
| `logger` | `Logger` (pino)
|
|
52
|
-
| `ui` | `UIConfig`
|
|
53
|
-
| `config` | `ShelfConfig`
|
|
48
|
+
| Option | Type | Description |
|
|
49
|
+
| --------------- | --------------------------- | ---------------------------------------------------------------------------------------- |
|
|
50
|
+
| `database` | `DatabaseAdapter` | **Required.** Data access. |
|
|
51
|
+
| `storage` | `StorageAdapter` | **Required.** Blob storage for screenshots, diffs, storybook archives. |
|
|
52
|
+
| `captureRunner` | `CaptureRunner` | Optional. Enables the async capture pipeline (pure renderer). |
|
|
53
|
+
| `captureQueue` | `CaptureQueue` | Optional. Queue adapter; defaults to `InMemoryCaptureQueue`. |
|
|
54
|
+
| `auth` | `Auth` (`@storyshelf/auth`) | Optional. Enables auth and the login UI. |
|
|
55
|
+
| `gitHosts` | `GitHostProvider[]` | Optional. Git-host adapters (GitHub/GitLab) for status checks, merge gates, PR comments. |
|
|
56
|
+
| `logger` | `Logger` (pino) | Optional. Shared logger. Construct a fallback via `createShelfLogger()`. |
|
|
57
|
+
| `ui` | `UIConfig` | Optional. UI branding. |
|
|
58
|
+
| `config` | `ShelfConfig` | Optional. Server behavior. |
|
|
54
59
|
|
|
55
60
|
### `ShelfConfig`
|
|
56
61
|
|
|
@@ -61,6 +66,8 @@ interface ShelfConfig {
|
|
|
61
66
|
captureConcurrency?: number; // concurrent capture jobs (default 2)
|
|
62
67
|
scratchDir?: string; // capture working directory (required with captureRunner)
|
|
63
68
|
purgeTtlDays?: number; // purge builds older than N days
|
|
69
|
+
branchTtlDays?: number | null; // branch baseline TTL, days; null disables GC (default 30)
|
|
70
|
+
branchGcIntervalMs?: number; // branch GC interval, ms; daily via interval clock (default 86_400_000)
|
|
64
71
|
maxUploadBytes?: number; // single-zip upload cap (default 1 GiB)
|
|
65
72
|
maxInlineUnzipSize?: number; // inline statics extraction cap; unset = capture-only
|
|
66
73
|
viewports?: Viewport[]; // capture viewports
|
|
@@ -73,7 +80,7 @@ interface ShelfConfig {
|
|
|
73
80
|
interface UIConfig {
|
|
74
81
|
name?: string;
|
|
75
82
|
logo?: string;
|
|
76
|
-
favicon?: string;
|
|
83
|
+
favicon?: string; // deprecated: ignored, the app serves its own mark
|
|
77
84
|
lightTheme?: BrandTheme;
|
|
78
85
|
darkTheme?: BrandTheme;
|
|
79
86
|
}
|
|
@@ -85,25 +92,25 @@ All adapters are constructor-injected (no AsyncLocalStorage). See `docs/architec
|
|
|
85
92
|
|
|
86
93
|
- `DatabaseAdapter` (`core/adapter/database`) — `insert`, `update`, `get`, `remove`, `list`, `count`, `all`, `migrate`, `close`. Also exports `ListOptions` and the `createDrizzleAdapter` factory.
|
|
87
94
|
- `StorageAdapter` (`core/adapter/storage`) — `read`, `write`, `delete`, `exists`, `list(prefix)`.
|
|
88
|
-
- `
|
|
95
|
+
- `Auth` (`@storyshelf/auth`) — the shelf authentication singleton: `check(request)`, `handler` (mount at `/api/auth/*`), `loginMethods()`, invites, sessions, passkeys. `AuthUser` and the shared `SESSION_COOKIE` live in `core/types`.
|
|
89
96
|
- `CaptureRunner` (`core/adapter/capture-runner`) — a **pure capture renderer**: `render(input) => RenderResult`, `cancel(buildId)`. Also exports `RenderedSnapshot`, `RenderResult`, `StoryEntry`, `StorySourceAdapter`, `Viewport`.
|
|
90
97
|
- `CaptureQueue` (`core/adapter/capture-queue`) — `enqueue({ buildId, reqId? })`, plus `status`, `active`, `recent`. Also exports `CaptureJob`, `QueueEntry`, `JobStatus`.
|
|
91
98
|
- `GitHostProvider` / `GitHostAdapter` (`core/adapter/git-host`) — set commit status checks, detect merges, and upsert PR comments. Real providers ship in `@storyshelf/git-github` and `@storyshelf/git-gitlab`. Also exports `CheckStatus`.
|
|
92
99
|
|
|
93
100
|
### Logging
|
|
94
101
|
|
|
95
|
-
`core` uses **pino** for structured JSON logging. `createShelfLogger({ level, transports, env })` (from `core/logger`) builds a logger writing to stdout by default, with optional extra pino worker transports (Sentry, PostHog, Datadog, GCP, OTEL collector, etc.). Pass the resulting `Logger` to `
|
|
102
|
+
`core` uses **pino** for structured JSON logging. `createShelfLogger({ level, transports, env })` (from `core/logger`) builds a logger writing to stdout by default, with optional extra pino worker transports (Sentry, PostHog, Datadog, GCP, OTEL collector, etc.). Pass the resulting `Logger` to `createShelfApp({ logger })` (or construct it at your composition root) so request and background logs share one stream. The capture orchestrator derives a `reqId`-scoped child for background capture work, correlating each capture back to the triggering HTTP request. See ADR 0014.
|
|
96
103
|
|
|
97
104
|
### Capture, diff, and retention
|
|
98
105
|
|
|
99
106
|
Import from `core/capture`, `core/diff`, and the model entries — never from the barrel:
|
|
100
107
|
|
|
101
|
-
- `executeCaptureJob({ buildId, reqId }, deps)` (`core/capture`) — the capture **orchestrator**: loads the build, marks it `capturing`, extracts the uploaded archive into `scratchDir`, discovers stories, delegates rendering to a pure `CaptureRunner`, and persists. `
|
|
108
|
+
- `executeCaptureJob({ buildId, reqId }, deps)` (`core/capture`) — the capture **orchestrator**: loads the build, marks it `capturing`, extracts the uploaded archive into `scratchDir`, discovers stories, delegates rendering to a pure `CaptureRunner`, and persists. `createShelfApp` wires it into a `CaptureQueue` when `capture` is supplied (and requires `ShelfConfig.scratchDir`). Also exports `CaptureJobOptions`.
|
|
102
109
|
- `persistCapture(ctx: CaptureContext)` (`core/capture`) — writes screenshots, diffs against the branch baseline, creates snapshots, and finalizes a build from a pure renderer's `captures`. Also exports `CaptureContext`.
|
|
103
110
|
- `StorybookAdapter` (`core/capture`) — reads a built Storybook's `index.json`/`stories.json`.
|
|
104
111
|
- `InMemoryCaptureQueue` (`core/capture`) — in-process, concurrency-limited queue for long-lived hosts; supply a remote queue with a separate worker for serverless.
|
|
105
112
|
- `diffImages(baseline: Buffer, current: Buffer, options: DiffOptions): DiffResult` (`core/diff`) — pixelmatch-based diff. Also exports `DiffOptions`, `DiffResult`.
|
|
106
|
-
- Models, schema, and row types back every entity; retention runs inside the router. (Models are private implementation details with no public entry — reach row types via `core/schema`.)
|
|
113
|
+
- Models, schema, and row types back every entity; retention runs inside the router (`Retention.purge` for builds + `Retention.purgeStaleBranches` for branch GC via `retention-timer.ts` daily sweep). (Models are private implementation details with no public entry — reach row types via `core/schema`.)
|
|
107
114
|
|
|
108
115
|
### Helpers
|
|
109
116
|
|
|
@@ -112,7 +119,7 @@ Import from `core/capture`, `core/diff`, and the model entries — never from th
|
|
|
112
119
|
|
|
113
120
|
## How it fits in
|
|
114
121
|
|
|
115
|
-
`core` is the framework everything else plugs into: `
|
|
122
|
+
`core` is the framework everything else plugs into: `createShelfApp` takes database, storage, capture, and auth adapters (from the `db-*`, `storage-*`, and `auth-*` packages) and produces a complete Hono server. The web UI is server-rendered `hono/jsx` + HTMX, and custom UIs can consume the JSON API under `/api/v1`.
|
|
116
123
|
|
|
117
124
|
See `docs/architecture.md` and the ADRs in `docs/adr/`.
|
|
118
125
|
|
|
@@ -120,13 +127,13 @@ See `docs/architecture.md` and the ADRs in `docs/adr/`.
|
|
|
120
127
|
|
|
121
128
|
The core router is runtime-agnostic (Web `Request`/`Response`, `fetch`, `crypto`, `URL`). The only Node-specific piece is the in-process `InMemoryCaptureQueue`, which suits long-lived Node servers; serverless runtimes swap in a remote `CaptureQueue` (e.g. `@storyshelf/queue-sqs`) plus a separate worker. You assemble a server for any platform — `storyshelf server init` generates a scaffold with the adapters you choose:
|
|
122
129
|
|
|
123
|
-
| Platform | Database
|
|
124
|
-
| ------------------------------------------ |
|
|
125
|
-
| **Vercel** | `@storyshelf/db-turso`
|
|
126
|
-
| **Cloudflare Workers** | `@storyshelf/db-turso`
|
|
127
|
-
| **Azure Functions** | `@storyshelf/db-turso`
|
|
128
|
-
| **AWS Lambda** | `@storyshelf/db-turso`
|
|
129
|
-
| **Deno Deploy** | `@storyshelf/db-turso`
|
|
130
|
-
| **Bun / Node (VPS, Fly, Railway, Render)** | `db-sqlite`
|
|
130
|
+
| Platform | Database | Storage | Capture queue | Server entry |
|
|
131
|
+
| ------------------------------------------ | ------------------------------------- | -------------------------------- | ------------------------------- | -------------------------- |
|
|
132
|
+
| **Vercel** | `@storyshelf/db-sqlite/turso` | `@storyshelf/storage-s3` (R2/S3) | Remote `CaptureQueue` + worker | Hono + `@hono/vercel-edge` |
|
|
133
|
+
| **Cloudflare Workers** | `@storyshelf/db-sqlite/turso` | `@storyshelf/storage-s3` (R2) | Workers Queues `CaptureQueue` | Hono + Workers entry |
|
|
134
|
+
| **Azure Functions** | `@storyshelf/db-sqlite/turso` | `@storyshelf/storage-s3` (Blob) | Azure Queues `CaptureQueue` | Hono + Azure handler |
|
|
135
|
+
| **AWS Lambda** | `@storyshelf/db-sqlite/turso` | `@storyshelf/storage-s3` | SQS `CaptureQueue` | Hono + Lambda handler |
|
|
136
|
+
| **Deno Deploy** | `@storyshelf/db-sqlite/turso` | `@storyshelf/storage-s3` | Custom `CaptureQueue` (Deno KV) | Hono + Deno entry |
|
|
137
|
+
| **Bun / Node (VPS, Fly, Railway, Render)** | `db-sqlite` (+ `./bun-sqlite` on Bun) | `storage-local` / `storage-s3` | `InMemoryCaptureQueue` | `storyshelf-server serve` |
|
|
131
138
|
|
|
132
139
|
All clouds are equal — pick the adapters that match your infrastructure. See the **Deployment** guide for recipes including a minimal Turso + S3 + `InMemoryCaptureQueue` example.
|
|
@@ -18,6 +18,20 @@ interface CaptureJob {
|
|
|
18
18
|
buildId: string;
|
|
19
19
|
/** Request id used to correlate the background job with the request that queued it. */
|
|
20
20
|
reqId?: string;
|
|
21
|
+
/**
|
|
22
|
+
* W3C `traceparent` of the enqueueing request. Workers continue this trace
|
|
23
|
+
* instead of starting a root span; absent means start a new trace.
|
|
24
|
+
*/
|
|
25
|
+
traceparent?: string;
|
|
26
|
+
}
|
|
27
|
+
/** A job polled from a queue with transport metadata. */
|
|
28
|
+
interface PollableJob extends CaptureJob {
|
|
29
|
+
/** Transport receipt handle (SQS, Redis pop token, etc.). */
|
|
30
|
+
receipt?: string;
|
|
31
|
+
/** Number of delivery attempts already made (0 = first delivery). */
|
|
32
|
+
attempts?: number;
|
|
33
|
+
/** Raw transport message for debugging. */
|
|
34
|
+
raw?: unknown;
|
|
21
35
|
}
|
|
22
36
|
/**
|
|
23
37
|
* A capture job queue, decoupled from the runtime.
|
|
@@ -46,6 +60,20 @@ interface CaptureQueue extends Adapter<{
|
|
|
46
60
|
/** The most recent queue entries, newest first. */
|
|
47
61
|
recent(limit: number): Promise<QueueEntry[]>;
|
|
48
62
|
}
|
|
63
|
+
/** Extension for queues that support worker-side polling. */
|
|
64
|
+
interface PollableCaptureQueue extends CaptureQueue {
|
|
65
|
+
/** Poll for a single job; returns null if none available within waitMs. */
|
|
66
|
+
poll(options?: {
|
|
67
|
+
waitMs?: number;
|
|
68
|
+
}): Promise<PollableJob | null>;
|
|
69
|
+
/** Acknowledge successful processing of a polled job. */
|
|
70
|
+
ack(job: PollableJob): Promise<void>;
|
|
71
|
+
/** Negatively acknowledge; requeue with optional delay when possible. */
|
|
72
|
+
nack(job: PollableJob, options?: {
|
|
73
|
+
requeue?: boolean;
|
|
74
|
+
delayMs?: number;
|
|
75
|
+
}): Promise<void>;
|
|
76
|
+
}
|
|
49
77
|
//#endregion
|
|
50
|
-
export { CaptureJob, CaptureQueue, JobStatus, QueueEntry };
|
|
78
|
+
export { CaptureJob, CaptureQueue, JobStatus, PollableCaptureQueue, PollableJob, QueueEntry };
|
|
51
79
|
//# sourceMappingURL=capture-queue.d.mts.map
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import __tsdown_shims_path from 'node:path';
|
|
2
2
|
import __tsdown_shims_url from 'node:url';
|
|
3
|
-
import { JobStatus } from "./capture-queue.mjs";
|
|
4
3
|
import { StoryEntry, StorySourceAdapter, Viewport } from "../capture/adapter.mjs";
|
|
5
4
|
import { Adapter } from "./metadata.mjs";
|
|
5
|
+
import { JobStatus } from "./capture-queue.mjs";
|
|
6
6
|
import { Logger } from "pino";
|
|
7
7
|
//#region src/adapters/capture-runner.d.ts
|
|
8
8
|
/** A screenshot produced by a capture renderer. */
|
|
@@ -11,6 +11,8 @@ interface RenderedSnapshot {
|
|
|
11
11
|
story: StoryEntry;
|
|
12
12
|
/** Viewport name the screenshot was captured at. */
|
|
13
13
|
viewportName: string;
|
|
14
|
+
/** Exact viewport dimensions captured at; falls back to the project list lookup in the pipeline. */
|
|
15
|
+
viewport?: Viewport;
|
|
14
16
|
/** PNG screenshot bytes. */
|
|
15
17
|
screenshot: Buffer;
|
|
16
18
|
}
|
|
@@ -41,8 +43,12 @@ interface RenderResult {
|
|
|
41
43
|
* orchestrator's job (see `capture/orchestrator.ts`), keeping every adapter
|
|
42
44
|
* implementation free of server concerns.
|
|
43
45
|
*/
|
|
46
|
+
type BrowserName = "chromium" | "firefox" | "webkit" | "chrome";
|
|
47
|
+
/** Browser names accepted for capture rendering (Chromium default). */
|
|
48
|
+
declare const BROWSER_NAMES: readonly ["chromium", "firefox", "webkit", "chrome"];
|
|
44
49
|
interface CaptureRunner extends Adapter<{
|
|
45
50
|
readonly category: "capture-runner";
|
|
51
|
+
readonly supportedBrowsers?: readonly BrowserName[];
|
|
46
52
|
}> {
|
|
47
53
|
/**
|
|
48
54
|
* Render configured viewports for the given stories of an extracted
|
|
@@ -65,10 +71,20 @@ interface CaptureRunner extends Adapter<{
|
|
|
65
71
|
executePlay?: boolean;
|
|
66
72
|
/** Timeout for play function execution in ms. */
|
|
67
73
|
playTimeoutMs?: number;
|
|
74
|
+
/** Whether to run a11y checks (axe) before screenshot; annotated as non-blocking. */
|
|
75
|
+
runA11y?: boolean;
|
|
76
|
+
/** Browser to use for rendering (chromium default). */
|
|
77
|
+
browser?: BrowserName;
|
|
78
|
+
/**
|
|
79
|
+
* Test/extension seam: override the story source adapter (defaults to
|
|
80
|
+
* `StorybookAdapter`). Only the renderer uses it; discovery stays the
|
|
81
|
+
* orchestrator's job.
|
|
82
|
+
*/
|
|
83
|
+
adapter?: StorySourceAdapter;
|
|
68
84
|
}): Promise<RenderResult>;
|
|
69
85
|
/** Cancel a pending or in-flight render for a build. */
|
|
70
86
|
cancel(buildId: string): Promise<void>;
|
|
71
87
|
}
|
|
72
88
|
//#endregion
|
|
73
|
-
export { CaptureRunner, type JobStatus, RenderFailure, RenderResult, RenderedSnapshot, type StoryEntry, type StorySourceAdapter, type Viewport };
|
|
89
|
+
export { BROWSER_NAMES, BrowserName, CaptureRunner, type JobStatus, RenderFailure, RenderResult, RenderedSnapshot, type StoryEntry, type StorySourceAdapter, type Viewport };
|
|
74
90
|
//# sourceMappingURL=capture-runner.d.mts.map
|
|
@@ -1,4 +1,15 @@
|
|
|
1
1
|
import "node:path";
|
|
2
2
|
import "node:url";
|
|
3
3
|
import.meta.url;
|
|
4
|
-
|
|
4
|
+
//#region src/adapters/capture-runner.ts
|
|
5
|
+
/** Browser names accepted for capture rendering (Chromium default). */
|
|
6
|
+
const BROWSER_NAMES = [
|
|
7
|
+
"chromium",
|
|
8
|
+
"firefox",
|
|
9
|
+
"webkit",
|
|
10
|
+
"chrome"
|
|
11
|
+
];
|
|
12
|
+
//#endregion
|
|
13
|
+
export { BROWSER_NAMES };
|
|
14
|
+
|
|
15
|
+
//# sourceMappingURL=capture-runner.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capture-runner.mjs","names":[],"sources":["../../src/adapters/capture-runner.ts"],"sourcesContent":["/**\n * Capture-runner adapter interface: render story screenshots for a build.\n */\nimport type { Logger } from \"pino\";\nimport type { StoryEntry, StorySourceAdapter, Viewport } from \"../capture/adapter.ts\";\nimport type { Adapter } from \"./metadata.ts\";\n\nexport type { JobStatus } from \"./capture-queue.ts\";\nexport type { StoryEntry, StorySourceAdapter, Viewport } from \"../capture/adapter.ts\";\n\n/** A screenshot produced by a capture renderer. */\nexport interface RenderedSnapshot {\n /** The rendered story (echoed back from the render request). */\n story: StoryEntry;\n /** Viewport name the screenshot was captured at. */\n viewportName: string;\n /** Exact viewport dimensions captured at; falls back to the project list lookup in the pipeline. */\n viewport?: Viewport;\n /** PNG screenshot bytes. */\n screenshot: Buffer;\n}\n\n/** A story/viewport that a renderer could not capture. */\nexport interface RenderFailure {\n /** Story that failed to render. */\n storyId: string;\n /** Viewport that failed to render. */\n viewportName: string;\n /** Error message. */\n error: string;\n}\n\n/** The outcome of a render request. */\nexport interface RenderResult {\n /** Screenshots the renderer produced successfully. */\n captures: RenderedSnapshot[];\n /** Story/viewport pairs the renderer could not capture. */\n failures: RenderFailure[];\n}\n\n/**\n * A pure capture renderer.\n *\n * Adapters implementing `CaptureRunner` render screenshots from an\n * already-extracted Storybook directory and return PNG buffers. They are\n * intentionally pure: they perform **no** database, storage, or build-state\n * management. Loading the build, extracting the uploaded archive, persisting\n * snapshots/baselines/diffs, and advancing build status are the capture\n * orchestrator's job (see `capture/orchestrator.ts`), keeping every adapter\n * implementation free of server concerns.\n */\nexport type BrowserName = \"chromium\" | \"firefox\" | \"webkit\" | \"chrome\";\n\n/** Browser names accepted for capture rendering (Chromium default). */\nexport const BROWSER_NAMES = [\"chromium\", \"firefox\", \"webkit\", \"chrome\"] as const;\n\nexport interface CaptureRunner extends Adapter<{\n readonly category: \"capture-runner\";\n readonly supportedBrowsers?: readonly BrowserName[];\n}> {\n /**\n * Render configured viewports for the given stories of an extracted\n * Storybook and return the screenshot buffers.\n *\n * @param input - Render request with the extracted Storybook directory.\n */\n render(input: {\n /** Build being captured, used to correlate cancels with in-flight work. */\n buildId: string;\n /** Root of the already-extracted Storybook build. */\n storybookDir: string;\n /** Stories to render. */\n stories: StoryEntry[];\n /** Viewports to capture. */\n viewports: Viewport[];\n /** Optional logger for render-time diagnostics. */\n logger?: Logger;\n /** Whether to execute Storybook play functions before screenshot. */\n executePlay?: boolean;\n /** Timeout for play function execution in ms. */\n playTimeoutMs?: number;\n /** Whether to run a11y checks (axe) before screenshot; annotated as non-blocking. */\n runA11y?: boolean;\n /** Browser to use for rendering (chromium default). */\n browser?: BrowserName;\n /**\n * Test/extension seam: override the story source adapter (defaults to\n * `StorybookAdapter`). Only the renderer uses it; discovery stays the\n * orchestrator's job.\n */\n adapter?: StorySourceAdapter;\n }): Promise<RenderResult>;\n\n /** Cancel a pending or in-flight render for a build. */\n cancel(buildId: string): Promise<void>;\n}\n"],"mappings":";;;;;AAsDA,MAAa,gBAAgB;CAAC;CAAY;CAAW;CAAU;AAAQ"}
|
|
@@ -1,9 +1,36 @@
|
|
|
1
1
|
import __tsdown_shims_path from 'node:path';
|
|
2
2
|
import __tsdown_shims_url from 'node:url';
|
|
3
3
|
import { Adapter, AdapterMetadata } from "./metadata.mjs";
|
|
4
|
-
import { SQL } from "drizzle-orm";
|
|
5
|
-
import { AnySQLiteTable } from "drizzle-orm/sqlite-core";
|
|
4
|
+
import { SQL, Table } from "drizzle-orm";
|
|
6
5
|
//#region src/adapters/database.d.ts
|
|
6
|
+
/** Type-safe table map enforced on every DatabaseAdapter. */
|
|
7
|
+
interface Tables {
|
|
8
|
+
projects: Table;
|
|
9
|
+
projectStatusConfigs: Table;
|
|
10
|
+
builds: Table;
|
|
11
|
+
captureAttempts: Table;
|
|
12
|
+
captureLogs: Table;
|
|
13
|
+
snapshots: Table;
|
|
14
|
+
baselines: Table;
|
|
15
|
+
comments: Table;
|
|
16
|
+
labelTypes: Table;
|
|
17
|
+
buildLabels: Table;
|
|
18
|
+
tokens: Table;
|
|
19
|
+
webhooks: Table;
|
|
20
|
+
notificationChannels: Table;
|
|
21
|
+
notificationSubscriptions: Table;
|
|
22
|
+
users: Table;
|
|
23
|
+
userInviteTokens: Table;
|
|
24
|
+
projectMembers: Table;
|
|
25
|
+
projectGroupMappings: Table;
|
|
26
|
+
contentRefs: Table;
|
|
27
|
+
}
|
|
28
|
+
/** Table keys every DatabaseAdapter must expose (contract + validation share this). */
|
|
29
|
+
declare const REQUIRED_TABLE_KEYS: string[];
|
|
30
|
+
/** Largest page any adapter serves in one list call. */
|
|
31
|
+
declare const MAX_LIST_LIMIT = 1000;
|
|
32
|
+
/** Clamp a list limit into [1, MAX_LIST_LIMIT]; undefined stays undefined. */
|
|
33
|
+
declare function clampListLimit(limit?: number): number | undefined;
|
|
7
34
|
/** Options that narrow and page a list query. */
|
|
8
35
|
interface ListOptions {
|
|
9
36
|
/** SQL WHERE condition. */
|
|
@@ -15,40 +42,63 @@ interface ListOptions {
|
|
|
15
42
|
/** Number of rows to skip. */
|
|
16
43
|
offset?: number;
|
|
17
44
|
}
|
|
18
|
-
/**
|
|
45
|
+
/**
|
|
46
|
+
* Transactional CRUD subset available inside {@link DatabaseAdapter.transact}.
|
|
47
|
+
* Mirrors the adapter surface minus metadata, lifecycle, and raw `all`
|
|
48
|
+
* (opaque to transactions on some drivers).
|
|
49
|
+
*/
|
|
50
|
+
interface TxStore {
|
|
51
|
+
/** Insert a row and return the inserted record. */
|
|
52
|
+
insert<T extends Table>(table: T, values: T["$inferInsert"]): Promise<T["$inferSelect"]>;
|
|
53
|
+
/** Update a row by id and return the updated record. */
|
|
54
|
+
update<T extends Table>(table: T, id: string, values: Partial<T["$inferInsert"]>): Promise<T["$inferSelect"]>;
|
|
55
|
+
/** Fetch a single row by id, or null if not found. */
|
|
56
|
+
get<T extends Table>(table: T, id: string): Promise<T["$inferSelect"] | null>;
|
|
57
|
+
/** Delete a row by id. */
|
|
58
|
+
remove(table: Table, id: string): Promise<void>;
|
|
59
|
+
/** List rows matching the given options. */
|
|
60
|
+
list<T extends Table>(table: T, opts?: ListOptions): Promise<T["$inferSelect"][]>;
|
|
61
|
+
/** Count rows matching an optional where condition. */
|
|
62
|
+
count(table: Table, where?: SQL): Promise<number>;
|
|
63
|
+
}
|
|
64
|
+
/** Database abstraction over Drizzle tables, agnostic of dialect and driver. */
|
|
19
65
|
interface DatabaseAdapter extends Adapter<{
|
|
20
66
|
readonly category: "database";
|
|
21
67
|
}> {
|
|
68
|
+
/** Table handles for this adapter's dialect, enforced with type safety. */
|
|
69
|
+
readonly tables: Tables;
|
|
22
70
|
/** Insert a row and return the inserted record. */
|
|
23
|
-
insert<T extends
|
|
71
|
+
insert<T extends Table>(table: T, values: T["$inferInsert"]): Promise<T["$inferSelect"]>;
|
|
24
72
|
/** Update a row by id and return the updated record. */
|
|
25
|
-
update<T extends
|
|
73
|
+
update<T extends Table>(table: T, id: string, values: Partial<T["$inferInsert"]>): Promise<T["$inferSelect"]>;
|
|
26
74
|
/** Fetch a single row by id, or null if not found. */
|
|
27
|
-
get<T extends
|
|
75
|
+
get<T extends Table>(table: T, id: string): Promise<T["$inferSelect"] | null>;
|
|
28
76
|
/** Delete a row by id. */
|
|
29
|
-
remove(table:
|
|
77
|
+
remove(table: Table, id: string): Promise<void>;
|
|
30
78
|
/** List rows matching the given options. */
|
|
31
|
-
list<T extends
|
|
79
|
+
list<T extends Table>(table: T, opts?: ListOptions): Promise<T["$inferSelect"][]>;
|
|
32
80
|
/** Count rows matching an optional where condition. */
|
|
33
|
-
count(table:
|
|
81
|
+
count(table: Table, where?: SQL): Promise<number>;
|
|
34
82
|
/** Run an arbitrary SQL query and return typed rows. */
|
|
35
83
|
all<T>(query: SQL): Promise<T[]>;
|
|
84
|
+
/**
|
|
85
|
+
* Run `fn` inside a transaction when the driver supports one.
|
|
86
|
+
* Commits on resolve, rolls back on throw. Drivers without transaction
|
|
87
|
+
* support omit this; callers must use `?.` and accept sequential execution.
|
|
88
|
+
*/
|
|
89
|
+
transact?<R>(fn: (tx: TxStore) => Promise<R>): Promise<R>;
|
|
36
90
|
}
|
|
37
91
|
/** Driver-supplied identity plus lifecycle hooks. */
|
|
38
92
|
interface DrizzleAdapterOptions {
|
|
39
93
|
metadata: AdapterMetadata & {
|
|
40
94
|
readonly category: "database";
|
|
41
95
|
};
|
|
96
|
+
tables: Tables;
|
|
42
97
|
migrate: () => Promise<void> | void;
|
|
43
98
|
close: () => Promise<void> | void;
|
|
99
|
+
/** Cheap liveness probe (e.g. `SELECT 1`); omitted when the driver has none. */
|
|
100
|
+
ping?: () => Promise<void> | void;
|
|
44
101
|
}
|
|
45
|
-
/**
|
|
46
|
-
* Build a {@link DatabaseAdapter} over any SQLite-compatible Drizzle
|
|
47
|
-
* dialect. Sync results (node:sqlite) and promises (libSQL) are both
|
|
48
|
-
* awaited, so drivers only supply connection setup, metadata, and the
|
|
49
|
-
* driver-specific migrate/close.
|
|
50
|
-
*/
|
|
51
|
-
declare function createDrizzleAdapter(db: unknown, options: DrizzleAdapterOptions): DatabaseAdapter;
|
|
52
102
|
//#endregion
|
|
53
|
-
export { DatabaseAdapter, DrizzleAdapterOptions, ListOptions,
|
|
103
|
+
export { DatabaseAdapter, DrizzleAdapterOptions, ListOptions, MAX_LIST_LIMIT, REQUIRED_TABLE_KEYS, Tables, TxStore, clampListLimit };
|
|
54
104
|
//# sourceMappingURL=database.d.mts.map
|
|
@@ -1,63 +1,30 @@
|
|
|
1
1
|
import "node:path";
|
|
2
2
|
import "node:url";
|
|
3
3
|
import.meta.url;
|
|
4
|
-
import { eq, getTableColumns } from "drizzle-orm";
|
|
5
4
|
//#region src/adapters/database.ts
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
*/
|
|
23
|
-
function
|
|
24
|
-
|
|
25
|
-
return
|
|
26
|
-
|
|
27
|
-
await options.migrate();
|
|
28
|
-
},
|
|
29
|
-
close: async () => {
|
|
30
|
-
if (closed) return;
|
|
31
|
-
closed = true;
|
|
32
|
-
await options.close();
|
|
33
|
-
}
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
/**
|
|
37
|
-
* Build a {@link DatabaseAdapter} over any SQLite-compatible Drizzle
|
|
38
|
-
* dialect. Sync results (node:sqlite) and promises (libSQL) are both
|
|
39
|
-
* awaited, so drivers only supply connection setup, metadata, and the
|
|
40
|
-
* driver-specific migrate/close.
|
|
41
|
-
*/
|
|
42
|
-
function createDrizzleAdapter(db, options) {
|
|
43
|
-
const drizzle = db;
|
|
44
|
-
return {
|
|
45
|
-
metadata: options.metadata,
|
|
46
|
-
lifecycle: buildLifecycle(options),
|
|
47
|
-
insert: async (table, values) => await drizzle.insert(table).values(values).returning().get(),
|
|
48
|
-
update: async (table, id, values) => await drizzle.update(table).set(values).where(eq(idOf(table), id)).returning().get(),
|
|
49
|
-
get: async (table, id) => await drizzle.select().from(table).where(eq(idOf(table), id)).limit(1).get() ?? null,
|
|
50
|
-
remove: async (table, id) => {
|
|
51
|
-
await drizzle.delete(table).where(eq(idOf(table), id)).run();
|
|
52
|
-
},
|
|
53
|
-
list: async (table, opts = {}) => {
|
|
54
|
-
return await applyListOptions(drizzle.select().from(table), opts).all();
|
|
55
|
-
},
|
|
56
|
-
count: async (table, where) => await drizzle.$count(table, where),
|
|
57
|
-
all: async (query) => await drizzle.all(query)
|
|
58
|
-
};
|
|
5
|
+
/** Table keys every DatabaseAdapter must expose (contract + validation share this). */
|
|
6
|
+
const REQUIRED_TABLE_KEYS = [
|
|
7
|
+
"projects",
|
|
8
|
+
"builds",
|
|
9
|
+
"snapshots",
|
|
10
|
+
"baselines",
|
|
11
|
+
"comments",
|
|
12
|
+
"labelTypes",
|
|
13
|
+
"buildLabels",
|
|
14
|
+
"tokens",
|
|
15
|
+
"webhooks",
|
|
16
|
+
"users",
|
|
17
|
+
"projectMembers"
|
|
18
|
+
];
|
|
19
|
+
/** Largest page any adapter serves in one list call. */
|
|
20
|
+
const MAX_LIST_LIMIT = 1e3;
|
|
21
|
+
/** Clamp a list limit into [1, MAX_LIST_LIMIT]; undefined stays undefined. */
|
|
22
|
+
function clampListLimit(limit) {
|
|
23
|
+
if (limit === void 0) return;
|
|
24
|
+
if (!Number.isFinite(limit)) return MAX_LIST_LIMIT;
|
|
25
|
+
return Math.min(Math.max(Math.floor(limit), 1), MAX_LIST_LIMIT);
|
|
59
26
|
}
|
|
60
27
|
//#endregion
|
|
61
|
-
export {
|
|
28
|
+
export { MAX_LIST_LIMIT, REQUIRED_TABLE_KEYS, clampListLimit };
|
|
62
29
|
|
|
63
30
|
//# sourceMappingURL=database.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"database.mjs","names":[],"sources":["../../src/adapters/database.ts"],"sourcesContent":["/**\n * Database adapter interface: query builders over explicitly typed tables.\n */\nimport type { SQL } from \"drizzle-orm\";\nimport {
|
|
1
|
+
{"version":3,"file":"database.mjs","names":[],"sources":["../../src/adapters/database.ts"],"sourcesContent":["/**\n * Database adapter interface: query builders over explicitly typed tables.\n *\n * Canonical home of the database contract — {@link DatabaseAdapter},\n * {@link Tables}, {@link TxStore}, {@link ListOptions}, and\n * {@link DrizzleAdapterOptions}. Driver machinery lives in the database\n * packages (`@storyshelf/db-sqlite`, `@storyshelf/db-postgres`).\n */\nimport type { SQL } from \"drizzle-orm\";\nimport type { Table } from \"drizzle-orm\";\nimport type { Adapter, AdapterMetadata } from \"./metadata.ts\";\n\n/** Type-safe table map enforced on every DatabaseAdapter. */\nexport interface Tables {\n projects: Table;\n projectStatusConfigs: Table;\n builds: Table;\n captureAttempts: Table;\n captureLogs: Table;\n snapshots: Table;\n baselines: Table;\n comments: Table;\n labelTypes: Table;\n buildLabels: Table;\n tokens: Table;\n webhooks: Table;\n notificationChannels: Table;\n notificationSubscriptions: Table;\n users: Table;\n userInviteTokens: Table;\n projectMembers: Table;\n projectGroupMappings: Table;\n contentRefs: Table;\n}\n\n/** Table keys every DatabaseAdapter must expose (contract + validation share this). */\nexport const REQUIRED_TABLE_KEYS = [\n \"projects\",\n \"builds\",\n \"snapshots\",\n \"baselines\",\n \"comments\",\n \"labelTypes\",\n \"buildLabels\",\n \"tokens\",\n \"webhooks\",\n \"users\",\n \"projectMembers\",\n];\n\n/** Largest page any adapter serves in one list call. */\nexport const MAX_LIST_LIMIT = 1000;\n\n/** Clamp a list limit into [1, MAX_LIST_LIMIT]; undefined stays undefined. */\nexport function clampListLimit(limit?: number): number | undefined {\n if (limit === undefined) {\n return undefined;\n }\n if (!Number.isFinite(limit)) {\n return MAX_LIST_LIMIT;\n }\n return Math.min(Math.max(Math.floor(limit), 1), MAX_LIST_LIMIT);\n}\n\n/** Options that narrow and page a list query. */\nexport interface ListOptions {\n /** SQL WHERE condition. */\n where?: SQL;\n /** SQL ORDER BY condition. */\n orderBy?: SQL;\n /** Maximum number of rows to return. */\n limit?: number;\n /** Number of rows to skip. */\n offset?: number;\n}\n\n/**\n * Transactional CRUD subset available inside {@link DatabaseAdapter.transact}.\n * Mirrors the adapter surface minus metadata, lifecycle, and raw `all`\n * (opaque to transactions on some drivers).\n */\nexport interface TxStore {\n /** Insert a row and return the inserted record. */\n insert<T extends Table>(table: T, values: T[\"$inferInsert\"]): Promise<T[\"$inferSelect\"]>;\n /** Update a row by id and return the updated record. */\n update<T extends Table>(\n table: T,\n id: string,\n values: Partial<T[\"$inferInsert\"]>,\n ): Promise<T[\"$inferSelect\"]>;\n /** Fetch a single row by id, or null if not found. */\n get<T extends Table>(table: T, id: string): Promise<T[\"$inferSelect\"] | null>;\n /** Delete a row by id. */\n remove(table: Table, id: string): Promise<void>;\n /** List rows matching the given options. */\n list<T extends Table>(table: T, opts?: ListOptions): Promise<T[\"$inferSelect\"][]>;\n /** Count rows matching an optional where condition. */\n count(table: Table, where?: SQL): Promise<number>;\n}\n\n/** Database abstraction over Drizzle tables, agnostic of dialect and driver. */\nexport interface DatabaseAdapter extends Adapter<{ readonly category: \"database\" }> {\n /** Table handles for this adapter's dialect, enforced with type safety. */\n readonly tables: Tables;\n /** Insert a row and return the inserted record. */\n insert<T extends Table>(table: T, values: T[\"$inferInsert\"]): Promise<T[\"$inferSelect\"]>;\n /** Update a row by id and return the updated record. */\n update<T extends Table>(\n table: T,\n id: string,\n values: Partial<T[\"$inferInsert\"]>,\n ): Promise<T[\"$inferSelect\"]>;\n /** Fetch a single row by id, or null if not found. */\n get<T extends Table>(table: T, id: string): Promise<T[\"$inferSelect\"] | null>;\n /** Delete a row by id. */\n remove(table: Table, id: string): Promise<void>;\n /** List rows matching the given options. */\n list<T extends Table>(table: T, opts?: ListOptions): Promise<T[\"$inferSelect\"][]>;\n /** Count rows matching an optional where condition. */\n count(table: Table, where?: SQL): Promise<number>;\n /** Run an arbitrary SQL query and return typed rows. */\n all<T>(query: SQL): Promise<T[]>;\n /**\n * Run `fn` inside a transaction when the driver supports one.\n * Commits on resolve, rolls back on throw. Drivers without transaction\n * support omit this; callers must use `?.` and accept sequential execution.\n */\n transact?<R>(fn: (tx: TxStore) => Promise<R>): Promise<R>;\n}\n\n/** Driver-supplied identity plus lifecycle hooks. */\nexport interface DrizzleAdapterOptions {\n metadata: AdapterMetadata & { readonly category: \"database\" };\n tables: Tables;\n migrate: () => Promise<void> | void;\n close: () => Promise<void> | void;\n /** Cheap liveness probe (e.g. `SELECT 1`); omitted when the driver has none. */\n ping?: () => Promise<void> | void;\n}\n"],"mappings":";;;;;AAoCA,MAAa,sBAAsB;CACjC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;;AAGA,MAAa,iBAAiB;;AAG9B,SAAgB,eAAe,OAAoC;CACjE,IAAI,UAAU,KAAA,GACZ;CAEF,IAAI,CAAC,OAAO,SAAS,KAAK,GACxB,OAAO;CAET,OAAO,KAAK,IAAI,KAAK,IAAI,KAAK,MAAM,KAAK,GAAG,CAAC,GAAG,cAAc;AAChE"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import __tsdown_shims_path from 'node:path';
|
|
2
|
+
import __tsdown_shims_url from 'node:url';
|
|
3
|
+
import { Adapter } from "./metadata.mjs";
|
|
4
|
+
//#region src/adapters/email-sender.d.ts
|
|
5
|
+
/** A single email message (transport-agnostic). */
|
|
6
|
+
interface EmailMessage {
|
|
7
|
+
to: string;
|
|
8
|
+
subject: string;
|
|
9
|
+
text: string;
|
|
10
|
+
html?: string;
|
|
11
|
+
from?: string;
|
|
12
|
+
replyTo?: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Minimal email transport: implemented once in `@storyshelf/notify-email`,
|
|
16
|
+
* consumed by notification fan-out and (optionally) the auth engine for
|
|
17
|
+
* invites and password resets. Best-effort delivery; throws on failure so
|
|
18
|
+
* callers can log and continue.
|
|
19
|
+
*/
|
|
20
|
+
interface EmailSender extends Adapter<{
|
|
21
|
+
readonly category: "notifier";
|
|
22
|
+
}> {
|
|
23
|
+
send(message: EmailMessage): Promise<void>;
|
|
24
|
+
}
|
|
25
|
+
//#endregion
|
|
26
|
+
export { EmailMessage, EmailSender };
|
|
27
|
+
//# sourceMappingURL=email-sender.d.mts.map
|