@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
|
@@ -0,0 +1,87 @@
|
|
|
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 { BuildStatus } from "../types.mjs";
|
|
5
|
+
import { Build } from "../schema/build.mjs";
|
|
6
|
+
import { Table } from "drizzle-orm";
|
|
7
|
+
//#region src/models/build.d.ts
|
|
8
|
+
/** Tables required by {@link BuildModel}. */
|
|
9
|
+
interface BuildTables {
|
|
10
|
+
builds: Table;
|
|
11
|
+
buildLabels: Table;
|
|
12
|
+
snapshots: Table;
|
|
13
|
+
}
|
|
14
|
+
/** Data operations for build records. */
|
|
15
|
+
declare class BuildModel {
|
|
16
|
+
private readonly db;
|
|
17
|
+
private readonly tables;
|
|
18
|
+
constructor(db: DatabaseAdapter, tables?: BuildTables);
|
|
19
|
+
create(projectId: string, input: BuildCreateInput): Promise<Build>;
|
|
20
|
+
get(id: string): Promise<Build | null>;
|
|
21
|
+
list(projectId: string, filter?: BuildListFilter): Promise<Build[]>;
|
|
22
|
+
/**
|
|
23
|
+
* Return the build a project-slug Storybook link resolves to: the most recent
|
|
24
|
+
* `approved` build on the project's default branch. Approved builds on other
|
|
25
|
+
* branches never qualify (their PR can still be dismissed), and neither do
|
|
26
|
+
* pending, failed, or unreviewed default-branch builds. Visibility (public or
|
|
27
|
+
* authenticated) is checked separately by the caller.
|
|
28
|
+
*/
|
|
29
|
+
latestApprovedDefault(project: {
|
|
30
|
+
id: string;
|
|
31
|
+
gitDefaultBranch: string;
|
|
32
|
+
}): Promise<Build | null>;
|
|
33
|
+
update(id: string, patch: Partial<Pick<Build, "status" | "public" | "message" | "authorEmail" | "authorName">>): Promise<Build>;
|
|
34
|
+
setStatus(id: string, status: BuildStatus): Promise<Build>;
|
|
35
|
+
/**
|
|
36
|
+
* Record the affected-capture computation for a build. Idempotent: the CLI
|
|
37
|
+
* posts this once after creating the build and before uploading the bundle.
|
|
38
|
+
*/
|
|
39
|
+
setAffected(id: string, input: AffectedComputation): Promise<Build>;
|
|
40
|
+
/**
|
|
41
|
+
* Parse a build's affected import paths. Returns `null` for full capture
|
|
42
|
+
* (no computation, or an unreadable payload) — callers render everything.
|
|
43
|
+
*/
|
|
44
|
+
static affectedPaths(build: Pick<Build, "affectedImportPaths">): string[] | null;
|
|
45
|
+
updateCounts(id: string): Promise<Build>;
|
|
46
|
+
remove(id: string): Promise<void>;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Return whether a build is publicly viewable without a session.
|
|
50
|
+
*
|
|
51
|
+
* A build is public iff `builds.public` is set or its branch matches the
|
|
52
|
+
* project's `public_branch_regex` (ADR 0011). Supplying an empty/nonexistent
|
|
53
|
+
* regex makes every build require auth.
|
|
54
|
+
*/
|
|
55
|
+
declare function isPublicBuild(project: {
|
|
56
|
+
publicBranchRegex: string | null;
|
|
57
|
+
}, build: Pick<Build, "public" | "gitBranch">): boolean;
|
|
58
|
+
/** Input for creating a build. */
|
|
59
|
+
interface BuildCreateInput {
|
|
60
|
+
gitSha: string;
|
|
61
|
+
gitBranch: string;
|
|
62
|
+
isDefault?: boolean;
|
|
63
|
+
authorEmail?: string;
|
|
64
|
+
authorName?: string;
|
|
65
|
+
message?: string;
|
|
66
|
+
public?: boolean;
|
|
67
|
+
affectedOnly?: boolean;
|
|
68
|
+
baselineSha?: string | null;
|
|
69
|
+
changedFiles?: string[] | null;
|
|
70
|
+
affectedImportPaths?: string[] | null;
|
|
71
|
+
}
|
|
72
|
+
/** Affected-capture computation posted after build creation. */
|
|
73
|
+
interface AffectedComputation {
|
|
74
|
+
baselineSha: string | null;
|
|
75
|
+
changedFiles: string[];
|
|
76
|
+
affectedImportPaths: string[] | null;
|
|
77
|
+
}
|
|
78
|
+
/** Filter options for listing builds. */
|
|
79
|
+
interface BuildListFilter {
|
|
80
|
+
status?: BuildStatus;
|
|
81
|
+
branch?: string;
|
|
82
|
+
labelKey?: string;
|
|
83
|
+
labelValue?: string;
|
|
84
|
+
}
|
|
85
|
+
//#endregion
|
|
86
|
+
export { AffectedComputation, BuildCreateInput, BuildListFilter, BuildModel, BuildTables, isPublicBuild };
|
|
87
|
+
//# sourceMappingURL=build.d.mts.map
|
package/dist/models/build.mjs
CHANGED
|
@@ -1,34 +1,25 @@
|
|
|
1
1
|
import "node:path";
|
|
2
2
|
import "node:url";
|
|
3
3
|
import.meta.url;
|
|
4
|
-
import { builds } from "../schema/build.mjs";
|
|
5
|
-
import { buildLabels } from "../schema/label.mjs";
|
|
6
|
-
import { snapshots } from "../schema/snapshot.mjs";
|
|
7
4
|
import { ulid } from "../utils/ulid.mjs";
|
|
8
|
-
import { and, desc, eq, inArray } from "drizzle-orm";
|
|
5
|
+
import { and, desc, eq, getTableColumns, inArray } from "drizzle-orm";
|
|
9
6
|
//#region src/models/build.ts
|
|
10
7
|
/** Build records, status transitions, and publication helpers. */
|
|
11
|
-
/**
|
|
12
|
-
* Return whether a build is publicly viewable without a session.
|
|
13
|
-
*
|
|
14
|
-
* A build is public iff `builds.public` is set or its branch matches the
|
|
15
|
-
* project's `public_branch_regex` (ADR 0011). Supplying an empty/nonexistent
|
|
16
|
-
* regex makes every build require auth.
|
|
17
|
-
*/
|
|
18
|
-
function isPublicBuild(project, build) {
|
|
19
|
-
if (build.public) return true;
|
|
20
|
-
if (!project.publicBranchRegex) return false;
|
|
21
|
-
return new RegExp(project.publicBranchRegex, "u").test(build.gitBranch);
|
|
22
|
-
}
|
|
23
8
|
/** Data operations for build records. */
|
|
24
9
|
var BuildModel = class {
|
|
25
10
|
db;
|
|
26
|
-
|
|
11
|
+
tables;
|
|
12
|
+
constructor(db, tables) {
|
|
27
13
|
this.db = db;
|
|
14
|
+
this.tables = tables ?? {
|
|
15
|
+
builds: db.tables.builds,
|
|
16
|
+
buildLabels: db.tables.buildLabels,
|
|
17
|
+
snapshots: db.tables.snapshots
|
|
18
|
+
};
|
|
28
19
|
}
|
|
29
20
|
async create(projectId, input) {
|
|
30
21
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
31
|
-
return await this.db.insert(builds, {
|
|
22
|
+
return await this.db.insert(this.tables.builds, {
|
|
32
23
|
id: ulid(),
|
|
33
24
|
projectId,
|
|
34
25
|
gitSha: input.gitSha,
|
|
@@ -38,36 +29,47 @@ var BuildModel = class {
|
|
|
38
29
|
authorName: input.authorName,
|
|
39
30
|
message: input.message,
|
|
40
31
|
public: input.public ?? false,
|
|
32
|
+
affectedOnly: input.affectedOnly ?? true,
|
|
33
|
+
baselineSha: input.baselineSha ?? null,
|
|
34
|
+
changedFiles: serializePaths(input.changedFiles),
|
|
35
|
+
affectedImportPaths: serializePaths(input.affectedImportPaths),
|
|
41
36
|
status: "pending",
|
|
42
37
|
createdAt: now,
|
|
43
38
|
updatedAt: now
|
|
44
39
|
});
|
|
45
40
|
}
|
|
46
41
|
async get(id) {
|
|
47
|
-
return await this.db.get(builds, id);
|
|
42
|
+
return await this.db.get(this.tables.builds, id);
|
|
48
43
|
}
|
|
49
44
|
async list(projectId, filter = {}) {
|
|
50
|
-
const conditions = [eq(builds
|
|
51
|
-
if (filter.status) conditions.push(eq(builds
|
|
52
|
-
if (filter.branch) conditions.push(eq(builds
|
|
45
|
+
const conditions = [eq(getTableColumns(this.tables.builds)["projectId"], projectId)];
|
|
46
|
+
if (filter.status) conditions.push(eq(getTableColumns(this.tables.builds)["status"], filter.status));
|
|
47
|
+
if (filter.branch) conditions.push(eq(getTableColumns(this.tables.builds)["gitBranch"], filter.branch));
|
|
53
48
|
if (filter.labelKey && filter.labelValue) {
|
|
54
|
-
const ids = (await this.db.list(buildLabels, { where: and(eq(buildLabels
|
|
49
|
+
const ids = (await this.db.list(this.tables.buildLabels, { where: and(eq(getTableColumns(this.tables.buildLabels)["projectId"], projectId), eq(getTableColumns(this.tables.buildLabels)["typeKey"], filter.labelKey), eq(getTableColumns(this.tables.buildLabels)["value"], filter.labelValue)) })).map((l) => l.buildId);
|
|
55
50
|
if (ids.length === 0) return [];
|
|
56
|
-
conditions.push(inArray(builds
|
|
51
|
+
conditions.push(inArray(getTableColumns(this.tables.builds)["id"], ids));
|
|
57
52
|
}
|
|
58
|
-
return this.db.list(builds, {
|
|
53
|
+
return await this.db.list(this.tables.builds, {
|
|
59
54
|
where: and(...conditions),
|
|
60
|
-
orderBy: desc(builds
|
|
55
|
+
orderBy: desc(getTableColumns(this.tables.builds)["createdAt"])
|
|
61
56
|
});
|
|
62
57
|
}
|
|
63
|
-
/**
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
58
|
+
/**
|
|
59
|
+
* Return the build a project-slug Storybook link resolves to: the most recent
|
|
60
|
+
* `approved` build on the project's default branch. Approved builds on other
|
|
61
|
+
* branches never qualify (their PR can still be dismissed), and neither do
|
|
62
|
+
* pending, failed, or unreviewed default-branch builds. Visibility (public or
|
|
63
|
+
* authenticated) is checked separately by the caller.
|
|
64
|
+
*/
|
|
65
|
+
async latestApprovedDefault(project) {
|
|
66
|
+
return (await this.list(project.id, {
|
|
67
|
+
branch: project.gitDefaultBranch,
|
|
68
|
+
status: "approved"
|
|
69
|
+
}))[0] ?? null;
|
|
68
70
|
}
|
|
69
71
|
async update(id, patch) {
|
|
70
|
-
return await this.db.update(builds, id, {
|
|
72
|
+
return await this.db.update(this.tables.builds, id, {
|
|
71
73
|
...patch,
|
|
72
74
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
73
75
|
});
|
|
@@ -75,13 +77,32 @@ var BuildModel = class {
|
|
|
75
77
|
async setStatus(id, status) {
|
|
76
78
|
return await this.update(id, { status });
|
|
77
79
|
}
|
|
80
|
+
/**
|
|
81
|
+
* Record the affected-capture computation for a build. Idempotent: the CLI
|
|
82
|
+
* posts this once after creating the build and before uploading the bundle.
|
|
83
|
+
*/
|
|
84
|
+
async setAffected(id, input) {
|
|
85
|
+
return await this.db.update(this.tables.builds, id, {
|
|
86
|
+
baselineSha: input.baselineSha,
|
|
87
|
+
changedFiles: serializePaths(input.changedFiles),
|
|
88
|
+
affectedImportPaths: serializePaths(input.affectedImportPaths),
|
|
89
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Parse a build's affected import paths. Returns `null` for full capture
|
|
94
|
+
* (no computation, or an unreadable payload) — callers render everything.
|
|
95
|
+
*/
|
|
96
|
+
static affectedPaths(build) {
|
|
97
|
+
return parsePaths(build.affectedImportPaths);
|
|
98
|
+
}
|
|
78
99
|
async updateCounts(id) {
|
|
79
|
-
const rows = await this.db.list(snapshots, { where: eq(snapshots
|
|
100
|
+
const rows = await this.db.list(this.tables.snapshots, { where: eq(getTableColumns(this.tables.snapshots)["buildId"], id) });
|
|
80
101
|
const snapshotCount = rows.length;
|
|
81
102
|
const changedCount = rows.filter((s) => s.status === "changed" || s.status === "new").length;
|
|
82
103
|
const approvedCount = rows.filter((s) => s.status === "approved" || s.status === "unchanged").length;
|
|
83
104
|
const rejectedCount = rows.filter((s) => s.status === "rejected").length;
|
|
84
|
-
return this.db.update(builds, id, {
|
|
105
|
+
return await this.db.update(this.tables.builds, id, {
|
|
85
106
|
snapshotCount,
|
|
86
107
|
changedCount,
|
|
87
108
|
approvedCount,
|
|
@@ -90,9 +111,36 @@ var BuildModel = class {
|
|
|
90
111
|
});
|
|
91
112
|
}
|
|
92
113
|
async remove(id) {
|
|
93
|
-
await this.db.remove(builds, id);
|
|
114
|
+
await this.db.remove(this.tables.builds, id);
|
|
94
115
|
}
|
|
95
116
|
};
|
|
117
|
+
/**
|
|
118
|
+
* Return whether a build is publicly viewable without a session.
|
|
119
|
+
*
|
|
120
|
+
* A build is public iff `builds.public` is set or its branch matches the
|
|
121
|
+
* project's `public_branch_regex` (ADR 0011). Supplying an empty/nonexistent
|
|
122
|
+
* regex makes every build require auth.
|
|
123
|
+
*/
|
|
124
|
+
function isPublicBuild(project, build) {
|
|
125
|
+
if (build.public) return true;
|
|
126
|
+
if (!project.publicBranchRegex) return false;
|
|
127
|
+
return new RegExp(project.publicBranchRegex, "u").test(build.gitBranch);
|
|
128
|
+
}
|
|
129
|
+
/** Serialize an optional path list to its JSON text form (or null). */
|
|
130
|
+
function serializePaths(paths) {
|
|
131
|
+
if (!paths) return null;
|
|
132
|
+
return JSON.stringify(paths);
|
|
133
|
+
}
|
|
134
|
+
/** Parse a JSON path list; null (full capture) on missing/invalid input. */
|
|
135
|
+
function parsePaths(raw) {
|
|
136
|
+
if (!raw) return null;
|
|
137
|
+
try {
|
|
138
|
+
const parsed = JSON.parse(raw);
|
|
139
|
+
return Array.isArray(parsed) && parsed.every((entry) => typeof entry === "string") ? parsed : null;
|
|
140
|
+
} catch {
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
96
144
|
//#endregion
|
|
97
145
|
export { BuildModel, isPublicBuild };
|
|
98
146
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build.mjs","names":[],"sources":["../../src/models/build.ts"],"sourcesContent":["/** Build records, status transitions, and publication helpers. */\nimport { and, desc, eq, inArray } from \"drizzle-orm\";\nimport type { DatabaseAdapter } from \"../adapters/database.ts\";\nimport { builds } from \"../schema/build.ts\";\nimport type { Build } from \"../schema/build.ts\";\nimport { buildLabels } from \"../schema/label.ts\";\nimport { snapshots } from \"../schema/snapshot.ts\";\nimport type { BuildStatus } from \"../types.ts\";\nimport { ulid } from \"../utils/ulid.ts\";\n\n/**\n * Return whether a build is publicly viewable without a session.\n *\n * A build is public iff `builds.public` is set or its branch matches the\n * project's `public_branch_regex` (ADR 0011). Supplying an empty/nonexistent\n * regex makes every build require auth.\n */\nexport function isPublicBuild(\n project: { publicBranchRegex: string | null },\n build: Pick<Build, \"public\" | \"gitBranch\">,\n): boolean {\n if (build.public) {\n return true;\n }\n if (!project.publicBranchRegex) {\n return false;\n }\n return new RegExp(project.publicBranchRegex, \"u\").test(build.gitBranch);\n}\n\n/** Input for creating a build. */\nexport interface BuildCreateInput {\n gitSha: string;\n gitBranch: string;\n isDefault?: boolean;\n authorEmail?: string;\n authorName?: string;\n message?: string;\n public?: boolean;\n}\n\n/** Filter options for listing builds. */\nexport interface BuildListFilter {\n status?: BuildStatus;\n branch?: string;\n labelKey?: string;\n labelValue?: string;\n}\n\n/** Data operations for build records. */\nexport class BuildModel {\n constructor(private readonly db: DatabaseAdapter) {}\n\n async create(projectId: string, input: BuildCreateInput): Promise<Build> {\n const now = new Date().toISOString();\n return await this.db.insert(builds, {\n id: ulid(),\n projectId,\n gitSha: input.gitSha,\n gitBranch: input.gitBranch,\n isDefault: input.isDefault ?? false,\n authorEmail: input.authorEmail,\n authorName: input.authorName,\n message: input.message,\n public: input.public ?? false,\n status: \"pending\",\n createdAt: now,\n updatedAt: now,\n });\n }\n\n async get(id: string): Promise<Build | null> {\n return await this.db.get(builds, id);\n }\n\n async list(projectId: string, filter: BuildListFilter = {}): Promise<Build[]> {\n const conditions = [eq(builds.projectId, projectId)];\n if (filter.status) {\n conditions.push(eq(builds.status, filter.status));\n }\n if (filter.branch) {\n conditions.push(eq(builds.gitBranch, filter.branch));\n }\n if (filter.labelKey && filter.labelValue) {\n const labels = await this.db.list(buildLabels, {\n where: and(\n eq(buildLabels.projectId, projectId),\n eq(buildLabels.typeKey, filter.labelKey),\n eq(buildLabels.value, filter.labelValue),\n ),\n });\n const ids = labels.map((l) => l.buildId);\n if (ids.length === 0) {\n return [];\n }\n conditions.push(inArray(builds.id, ids));\n }\n return this.db.list(builds, { where: and(...conditions), orderBy: desc(builds.createdAt) });\n }\n\n /** Return the most recent build intended for publishing, if any. */\n async latestPublished(project: {\n id: string;\n publicBranchRegex: string | null;\n }): Promise<Build | null> {\n const rows = await this.list(project.id);\n for (const build of rows) {\n if (isPublicBuild(project, build)) {\n return build;\n }\n }\n return null;\n }\n\n async update(\n id: string,\n patch: Partial<Pick<Build, \"status\" | \"public\" | \"message\" | \"authorEmail\" | \"authorName\">>,\n ): Promise<Build> {\n return await this.db.update(builds, id, { ...patch, updatedAt: new Date().toISOString() });\n }\n\n async setStatus(id: string, status: BuildStatus): Promise<Build> {\n return await this.update(id, { status });\n }\n\n async updateCounts(id: string): Promise<Build> {\n const rows = await this.db.list(snapshots, { where: eq(snapshots.buildId, id) });\n const snapshotCount = rows.length;\n const changedCount = rows.filter((s) => s.status === \"changed\" || s.status === \"new\").length;\n const approvedCount = rows.filter(\n (s) => s.status === \"approved\" || s.status === \"unchanged\",\n ).length;\n const rejectedCount = rows.filter((s) => s.status === \"rejected\").length;\n return this.db.update(builds, id, {\n snapshotCount,\n changedCount,\n approvedCount,\n rejectedCount,\n updatedAt: new Date().toISOString(),\n });\n }\n\n async remove(id: string): Promise<void> {\n await this.db.remove(builds, id);\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"build.mjs","names":[],"sources":["../../src/models/build.ts"],"sourcesContent":["/** Build records, status transitions, and publication helpers. */\nimport { and, desc, eq, getTableColumns, inArray } from \"drizzle-orm\";\nimport type { SQLWrapper, Table } from \"drizzle-orm\";\nimport type { DatabaseAdapter } from \"../adapters/database.ts\";\nimport type { Build } from \"../schema/build.ts\";\nimport type { BuildStatus } from \"../types.ts\";\nimport { ulid } from \"../utils/ulid.ts\";\n\n/** Tables required by {@link BuildModel}. */\nexport interface BuildTables {\n builds: Table;\n buildLabels: Table;\n snapshots: Table;\n}\n\n/** Data operations for build records. */\nexport class BuildModel {\n private readonly tables: BuildTables;\n constructor(\n private readonly db: DatabaseAdapter,\n tables?: BuildTables,\n ) {\n this.tables = tables ?? {\n builds: db.tables.builds,\n buildLabels: db.tables.buildLabels,\n snapshots: db.tables.snapshots,\n };\n }\n\n async create(projectId: string, input: BuildCreateInput): Promise<Build> {\n const now = new Date().toISOString();\n return (await this.db.insert(this.tables.builds, {\n id: ulid(),\n projectId,\n gitSha: input.gitSha,\n gitBranch: input.gitBranch,\n isDefault: input.isDefault ?? false,\n authorEmail: input.authorEmail,\n authorName: input.authorName,\n message: input.message,\n public: input.public ?? false,\n affectedOnly: input.affectedOnly ?? true,\n baselineSha: input.baselineSha ?? null,\n changedFiles: serializePaths(input.changedFiles),\n affectedImportPaths: serializePaths(input.affectedImportPaths),\n status: \"pending\",\n createdAt: now,\n updatedAt: now,\n })) as unknown as Build;\n }\n\n async get(id: string): Promise<Build | null> {\n return (await this.db.get(this.tables.builds, id)) as unknown as Build | null;\n }\n\n async list(projectId: string, filter: BuildListFilter = {}): Promise<Build[]> {\n const conditions = [\n eq(getTableColumns(this.tables.builds)[\"projectId\"] as unknown as SQLWrapper, projectId),\n ];\n if (filter.status) {\n conditions.push(\n eq(getTableColumns(this.tables.builds)[\"status\"] as unknown as SQLWrapper, filter.status),\n );\n }\n if (filter.branch) {\n conditions.push(\n eq(\n getTableColumns(this.tables.builds)[\"gitBranch\"] as unknown as SQLWrapper,\n filter.branch,\n ),\n );\n }\n if (filter.labelKey && filter.labelValue) {\n const labels = (await this.db.list(this.tables.buildLabels, {\n where: and(\n eq(\n getTableColumns(this.tables.buildLabels)[\"projectId\"] as unknown as SQLWrapper,\n projectId,\n ),\n eq(\n getTableColumns(this.tables.buildLabels)[\"typeKey\"] as unknown as SQLWrapper,\n filter.labelKey,\n ),\n eq(\n getTableColumns(this.tables.buildLabels)[\"value\"] as unknown as SQLWrapper,\n filter.labelValue,\n ),\n ),\n })) as unknown as { buildId: string }[];\n const ids = labels.map((l) => l.buildId);\n if (ids.length === 0) {\n return [];\n }\n conditions.push(\n inArray(getTableColumns(this.tables.builds)[\"id\"] as unknown as SQLWrapper, ids),\n );\n }\n return (await this.db.list(this.tables.builds, {\n where: and(...conditions),\n orderBy: desc(getTableColumns(this.tables.builds)[\"createdAt\"] as unknown as SQLWrapper),\n })) as unknown as Build[];\n }\n\n /**\n * Return the build a project-slug Storybook link resolves to: the most recent\n * `approved` build on the project's default branch. Approved builds on other\n * branches never qualify (their PR can still be dismissed), and neither do\n * pending, failed, or unreviewed default-branch builds. Visibility (public or\n * authenticated) is checked separately by the caller.\n */\n async latestApprovedDefault(project: {\n id: string;\n gitDefaultBranch: string;\n }): Promise<Build | null> {\n const rows = await this.list(project.id, {\n branch: project.gitDefaultBranch,\n status: \"approved\",\n });\n return rows[0] ?? null;\n }\n\n async update(\n id: string,\n patch: Partial<Pick<Build, \"status\" | \"public\" | \"message\" | \"authorEmail\" | \"authorName\">>,\n ): Promise<Build> {\n return (await this.db.update(this.tables.builds, id, {\n ...patch,\n updatedAt: new Date().toISOString(),\n })) as unknown as Build;\n }\n\n async setStatus(id: string, status: BuildStatus): Promise<Build> {\n return await this.update(id, { status });\n }\n\n /**\n * Record the affected-capture computation for a build. Idempotent: the CLI\n * posts this once after creating the build and before uploading the bundle.\n */\n async setAffected(id: string, input: AffectedComputation): Promise<Build> {\n return (await this.db.update(this.tables.builds, id, {\n baselineSha: input.baselineSha,\n changedFiles: serializePaths(input.changedFiles),\n affectedImportPaths: serializePaths(input.affectedImportPaths),\n updatedAt: new Date().toISOString(),\n })) as unknown as Build;\n }\n\n /**\n * Parse a build's affected import paths. Returns `null` for full capture\n * (no computation, or an unreadable payload) — callers render everything.\n */\n static affectedPaths(build: Pick<Build, \"affectedImportPaths\">): string[] | null {\n return parsePaths(build.affectedImportPaths);\n }\n\n async updateCounts(id: string): Promise<Build> {\n const rows = (await this.db.list(this.tables.snapshots, {\n where: eq(getTableColumns(this.tables.snapshots)[\"buildId\"] as unknown as SQLWrapper, id),\n })) as unknown as { status: string }[];\n const snapshotCount = rows.length;\n const changedCount = rows.filter((s) => s.status === \"changed\" || s.status === \"new\").length;\n const approvedCount = rows.filter(\n (s) => s.status === \"approved\" || s.status === \"unchanged\",\n ).length;\n const rejectedCount = rows.filter((s) => s.status === \"rejected\").length;\n return (await this.db.update(this.tables.builds, id, {\n snapshotCount,\n changedCount,\n approvedCount,\n rejectedCount,\n updatedAt: new Date().toISOString(),\n })) as unknown as Build;\n }\n\n async remove(id: string): Promise<void> {\n await this.db.remove(this.tables.builds, id);\n }\n}\n\n/**\n * Return whether a build is publicly viewable without a session.\n *\n * A build is public iff `builds.public` is set or its branch matches the\n * project's `public_branch_regex` (ADR 0011). Supplying an empty/nonexistent\n * regex makes every build require auth.\n */\nexport function isPublicBuild(\n project: { publicBranchRegex: string | null },\n build: Pick<Build, \"public\" | \"gitBranch\">,\n): boolean {\n if (build.public) {\n return true;\n }\n if (!project.publicBranchRegex) {\n return false;\n }\n return new RegExp(project.publicBranchRegex, \"u\").test(build.gitBranch);\n}\n\n/** Input for creating a build. */\nexport interface BuildCreateInput {\n gitSha: string;\n gitBranch: string;\n isDefault?: boolean;\n authorEmail?: string;\n authorName?: string;\n message?: string;\n public?: boolean;\n affectedOnly?: boolean;\n baselineSha?: string | null;\n changedFiles?: string[] | null;\n affectedImportPaths?: string[] | null;\n}\n\n/** Affected-capture computation posted after build creation. */\nexport interface AffectedComputation {\n baselineSha: string | null;\n changedFiles: string[];\n affectedImportPaths: string[] | null;\n}\n\n/** Serialize an optional path list to its JSON text form (or null). */\nfunction serializePaths(paths: string[] | null | undefined): string | null {\n if (!paths) {\n return null;\n }\n return JSON.stringify(paths);\n}\n\n/** Parse a JSON path list; null (full capture) on missing/invalid input. */\nfunction parsePaths(raw: string | null): string[] | null {\n if (!raw) {\n return null;\n }\n try {\n const parsed: unknown = JSON.parse(raw);\n return Array.isArray(parsed) && parsed.every((entry) => typeof entry === \"string\")\n ? (parsed as string[])\n : null;\n } catch {\n return null;\n }\n}\n\n/** Filter options for listing builds. */\nexport interface BuildListFilter {\n status?: BuildStatus;\n branch?: string;\n labelKey?: string;\n labelValue?: string;\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,40 @@
|
|
|
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 { JobStatus } from "../adapter/capture-queue.mjs";
|
|
5
|
+
import { CaptureAttempt, CaptureLogLevel } from "../schema/capture-attempt.mjs";
|
|
6
|
+
import { Logger } from "pino";
|
|
7
|
+
import { Table } from "drizzle-orm";
|
|
8
|
+
//#region src/models/capture-attempt.d.ts
|
|
9
|
+
/** Tables required by {@link CaptureAttemptModel}. */
|
|
10
|
+
interface CaptureAttemptTables {
|
|
11
|
+
captureAttempts: Table;
|
|
12
|
+
}
|
|
13
|
+
/** Data operations for capture attempts (one row per build run). */
|
|
14
|
+
declare class CaptureAttemptModel {
|
|
15
|
+
private readonly db;
|
|
16
|
+
private readonly tables;
|
|
17
|
+
constructor(db: DatabaseAdapter, tables?: CaptureAttemptTables);
|
|
18
|
+
listByBuild(buildId: string): Promise<CaptureAttempt[]>;
|
|
19
|
+
getByNo(buildId: string, attemptNo: number): Promise<CaptureAttempt | null>;
|
|
20
|
+
startAttempt(projectId: string, buildId: string, reqId?: string): Promise<CaptureAttempt>;
|
|
21
|
+
markRunning(id: string): Promise<CaptureAttempt>;
|
|
22
|
+
markFinished(id: string, status: Extract<JobStatus, "completed" | "failed" | "cancelled">, error?: string, counts?: {
|
|
23
|
+
storyCount: number;
|
|
24
|
+
failedCount: number;
|
|
25
|
+
}): Promise<CaptureAttempt>;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Recorder writing one attempt log line to the `capture_logs` table.
|
|
29
|
+
* Dispatch builds it per attempt; the orchestrator and pipeline call it
|
|
30
|
+
* alongside every pino log so the UI can replay the run later.
|
|
31
|
+
*/
|
|
32
|
+
type AttemptLogRecorder = (level: CaptureLogLevel, message: string, fields?: unknown) => Promise<void>;
|
|
33
|
+
/**
|
|
34
|
+
* Record one attempt log line without breaking the capture on write failure.
|
|
35
|
+
* Log-write failures are reported through pino and never thrown.
|
|
36
|
+
*/
|
|
37
|
+
declare function emitAttemptLog(record: AttemptLogRecorder | undefined, logger: Logger | undefined, level: CaptureLogLevel, message: string, fields?: unknown): Promise<void>;
|
|
38
|
+
//#endregion
|
|
39
|
+
export { AttemptLogRecorder, CaptureAttemptModel, CaptureAttemptTables, emitAttemptLog };
|
|
40
|
+
//# sourceMappingURL=capture-attempt.d.mts.map
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import "node:path";
|
|
2
|
+
import "node:url";
|
|
3
|
+
import.meta.url;
|
|
4
|
+
import { ulid } from "../utils/ulid.mjs";
|
|
5
|
+
import { and, asc, eq, getTableColumns } from "drizzle-orm";
|
|
6
|
+
//#region src/models/capture-attempt.ts
|
|
7
|
+
/** Capture attempts and their log lines: per-run history for a build. */
|
|
8
|
+
/** Data operations for capture attempts (one row per build run). */
|
|
9
|
+
var CaptureAttemptModel = class {
|
|
10
|
+
db;
|
|
11
|
+
tables;
|
|
12
|
+
constructor(db, tables) {
|
|
13
|
+
this.db = db;
|
|
14
|
+
this.tables = tables ?? { captureAttempts: db.tables.captureAttempts };
|
|
15
|
+
}
|
|
16
|
+
async listByBuild(buildId) {
|
|
17
|
+
return await this.db.list(this.tables.captureAttempts, {
|
|
18
|
+
where: eq(columnsOf(this.tables.captureAttempts, "buildId"), buildId),
|
|
19
|
+
orderBy: asc(columnsOf(this.tables.captureAttempts, "attemptNo"))
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
async getByNo(buildId, attemptNo) {
|
|
23
|
+
return (await this.db.list(this.tables.captureAttempts, { where: and(eq(columnsOf(this.tables.captureAttempts, "buildId"), buildId), eq(columnsOf(this.tables.captureAttempts, "attemptNo"), attemptNo)) }))[0] ?? null;
|
|
24
|
+
}
|
|
25
|
+
async startAttempt(projectId, buildId, reqId) {
|
|
26
|
+
const existing = await this.listByBuild(buildId);
|
|
27
|
+
let attemptNo = 1;
|
|
28
|
+
for (const row of existing) if (row.attemptNo >= attemptNo) attemptNo = row.attemptNo + 1;
|
|
29
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
30
|
+
return await this.db.insert(this.tables.captureAttempts, {
|
|
31
|
+
id: ulid(),
|
|
32
|
+
projectId,
|
|
33
|
+
buildId,
|
|
34
|
+
attemptNo,
|
|
35
|
+
status: "queued",
|
|
36
|
+
error: null,
|
|
37
|
+
reqId: reqId ?? null,
|
|
38
|
+
storyCount: 0,
|
|
39
|
+
failedCount: 0,
|
|
40
|
+
queuedAt: now,
|
|
41
|
+
startedAt: null,
|
|
42
|
+
finishedAt: null,
|
|
43
|
+
createdAt: now,
|
|
44
|
+
updatedAt: now
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
async markRunning(id) {
|
|
48
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
49
|
+
return await this.db.update(this.tables.captureAttempts, id, {
|
|
50
|
+
status: "running",
|
|
51
|
+
startedAt: now,
|
|
52
|
+
updatedAt: now
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
async markFinished(id, status, error, counts) {
|
|
56
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
57
|
+
return await this.db.update(this.tables.captureAttempts, id, {
|
|
58
|
+
status,
|
|
59
|
+
error: error ?? null,
|
|
60
|
+
storyCount: counts?.storyCount ?? 0,
|
|
61
|
+
failedCount: counts?.failedCount ?? 0,
|
|
62
|
+
finishedAt: now,
|
|
63
|
+
updatedAt: now
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
function columnsOf(table, column) {
|
|
68
|
+
return getTableColumns(table)[column];
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Record one attempt log line without breaking the capture on write failure.
|
|
72
|
+
* Log-write failures are reported through pino and never thrown.
|
|
73
|
+
*/
|
|
74
|
+
async function emitAttemptLog(record, logger, level, message, fields) {
|
|
75
|
+
if (!record) return;
|
|
76
|
+
try {
|
|
77
|
+
await record(level, message, fields);
|
|
78
|
+
} catch (error) {
|
|
79
|
+
logger?.warn({ err: error }, "attempt log write failed");
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
//#endregion
|
|
83
|
+
export { CaptureAttemptModel, emitAttemptLog };
|
|
84
|
+
|
|
85
|
+
//# sourceMappingURL=capture-attempt.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capture-attempt.mjs","names":[],"sources":["../../src/models/capture-attempt.ts"],"sourcesContent":["/** Capture attempts and their log lines: per-run history for a build. */\nimport { and, asc, eq, getTableColumns } from \"drizzle-orm\";\nimport type { SQLWrapper, Table } from \"drizzle-orm\";\nimport type { Logger } from \"pino\";\nimport type { JobStatus } from \"../adapters/capture-queue.ts\";\nimport type { DatabaseAdapter } from \"../adapters/database.ts\";\nimport type { CaptureAttempt, CaptureLogLevel } from \"../schema/capture-attempt.ts\";\nimport { ulid } from \"../utils/ulid.ts\";\n\n/** Tables required by {@link CaptureAttemptModel}. */\nexport interface CaptureAttemptTables {\n captureAttempts: Table;\n}\n\n/** Data operations for capture attempts (one row per build run). */\nexport class CaptureAttemptModel {\n private readonly tables: CaptureAttemptTables;\n constructor(\n private readonly db: DatabaseAdapter,\n tables?: CaptureAttemptTables,\n ) {\n this.tables = tables ?? { captureAttempts: db.tables.captureAttempts };\n }\n\n async listByBuild(buildId: string): Promise<CaptureAttempt[]> {\n return (await this.db.list(this.tables.captureAttempts, {\n where: eq(columnsOf(this.tables.captureAttempts, \"buildId\"), buildId),\n orderBy: asc(columnsOf(this.tables.captureAttempts, \"attemptNo\")),\n })) as unknown as CaptureAttempt[];\n }\n\n async getByNo(buildId: string, attemptNo: number): Promise<CaptureAttempt | null> {\n const rows = (await this.db.list(this.tables.captureAttempts, {\n where: and(\n eq(columnsOf(this.tables.captureAttempts, \"buildId\"), buildId),\n eq(columnsOf(this.tables.captureAttempts, \"attemptNo\"), attemptNo),\n ),\n })) as unknown as CaptureAttempt[];\n return rows[0] ?? null;\n }\n\n async startAttempt(projectId: string, buildId: string, reqId?: string): Promise<CaptureAttempt> {\n const existing = await this.listByBuild(buildId);\n let attemptNo = 1;\n for (const row of existing) {\n if (row.attemptNo >= attemptNo) {\n attemptNo = row.attemptNo + 1;\n }\n }\n const now = new Date().toISOString();\n return (await this.db.insert(this.tables.captureAttempts, {\n id: ulid(),\n projectId,\n buildId,\n attemptNo,\n status: \"queued\",\n error: null,\n reqId: reqId ?? null,\n storyCount: 0,\n failedCount: 0,\n queuedAt: now,\n startedAt: null,\n finishedAt: null,\n createdAt: now,\n updatedAt: now,\n })) as unknown as CaptureAttempt;\n }\n\n async markRunning(id: string): Promise<CaptureAttempt> {\n const now = new Date().toISOString();\n return (await this.db.update(this.tables.captureAttempts, id, {\n status: \"running\",\n startedAt: now,\n updatedAt: now,\n })) as unknown as CaptureAttempt;\n }\n\n async markFinished(\n id: string,\n status: Extract<JobStatus, \"completed\" | \"failed\" | \"cancelled\">,\n error?: string,\n counts?: { storyCount: number; failedCount: number },\n ): Promise<CaptureAttempt> {\n const now = new Date().toISOString();\n return (await this.db.update(this.tables.captureAttempts, id, {\n status,\n error: error ?? null,\n storyCount: counts?.storyCount ?? 0,\n failedCount: counts?.failedCount ?? 0,\n finishedAt: now,\n updatedAt: now,\n })) as unknown as CaptureAttempt;\n }\n}\n\nfunction columnsOf(table: Table, column: string): SQLWrapper {\n return getTableColumns(table)[column] as unknown as SQLWrapper;\n}\n\n/**\n * Recorder writing one attempt log line to the `capture_logs` table.\n * Dispatch builds it per attempt; the orchestrator and pipeline call it\n * alongside every pino log so the UI can replay the run later.\n */\nexport type AttemptLogRecorder = (\n level: CaptureLogLevel,\n message: string,\n fields?: unknown,\n) => Promise<void>;\n\n/**\n * Record one attempt log line without breaking the capture on write failure.\n * Log-write failures are reported through pino and never thrown.\n */\nexport async function emitAttemptLog(\n record: AttemptLogRecorder | undefined,\n logger: Logger | undefined,\n level: CaptureLogLevel,\n message: string,\n fields?: unknown,\n): Promise<void> {\n if (!record) {\n return;\n }\n try {\n await record(level, message, fields);\n } catch (error) {\n logger?.warn({ err: error }, \"attempt log write failed\");\n }\n}\n"],"mappings":";;;;;;;;AAeA,IAAa,sBAAb,MAAiC;CAGZ;CAFnB;CACA,YACE,IACA,QACA;EAFiB,KAAA,KAAA;EAGjB,KAAK,SAAS,UAAU,EAAE,iBAAiB,GAAG,OAAO,gBAAgB;CACvE;CAEA,MAAM,YAAY,SAA4C;EAC5D,OAAQ,MAAM,KAAK,GAAG,KAAK,KAAK,OAAO,iBAAiB;GACtD,OAAO,GAAG,UAAU,KAAK,OAAO,iBAAiB,SAAS,GAAG,OAAO;GACpE,SAAS,IAAI,UAAU,KAAK,OAAO,iBAAiB,WAAW,CAAC;EAClE,CAAC;CACH;CAEA,MAAM,QAAQ,SAAiB,WAAmD;EAOhF,QAAO,MANa,KAAK,GAAG,KAAK,KAAK,OAAO,iBAAiB,EAC5D,OAAO,IACL,GAAG,UAAU,KAAK,OAAO,iBAAiB,SAAS,GAAG,OAAO,GAC7D,GAAG,UAAU,KAAK,OAAO,iBAAiB,WAAW,GAAG,SAAS,CACnE,EACF,CAAC,EAAA,CACW,MAAM;CACpB;CAEA,MAAM,aAAa,WAAmB,SAAiB,OAAyC;EAC9F,MAAM,WAAW,MAAM,KAAK,YAAY,OAAO;EAC/C,IAAI,YAAY;EAChB,KAAK,MAAM,OAAO,UAChB,IAAI,IAAI,aAAa,WACnB,YAAY,IAAI,YAAY;EAGhC,MAAM,uBAAM,IAAI,KAAK,EAAA,CAAE,YAAY;EACnC,OAAQ,MAAM,KAAK,GAAG,OAAO,KAAK,OAAO,iBAAiB;GACxD,IAAI,KAAK;GACT;GACA;GACA;GACA,QAAQ;GACR,OAAO;GACP,OAAO,SAAS;GAChB,YAAY;GACZ,aAAa;GACb,UAAU;GACV,WAAW;GACX,YAAY;GACZ,WAAW;GACX,WAAW;EACb,CAAC;CACH;CAEA,MAAM,YAAY,IAAqC;EACrD,MAAM,uBAAM,IAAI,KAAK,EAAA,CAAE,YAAY;EACnC,OAAQ,MAAM,KAAK,GAAG,OAAO,KAAK,OAAO,iBAAiB,IAAI;GAC5D,QAAQ;GACR,WAAW;GACX,WAAW;EACb,CAAC;CACH;CAEA,MAAM,aACJ,IACA,QACA,OACA,QACyB;EACzB,MAAM,uBAAM,IAAI,KAAK,EAAA,CAAE,YAAY;EACnC,OAAQ,MAAM,KAAK,GAAG,OAAO,KAAK,OAAO,iBAAiB,IAAI;GAC5D;GACA,OAAO,SAAS;GAChB,YAAY,QAAQ,cAAc;GAClC,aAAa,QAAQ,eAAe;GACpC,YAAY;GACZ,WAAW;EACb,CAAC;CACH;AACF;AAEA,SAAS,UAAU,OAAc,QAA4B;CAC3D,OAAO,gBAAgB,KAAK,CAAC,CAAC;AAChC;;;;;AAiBA,eAAsB,eACpB,QACA,QACA,OACA,SACA,QACe;CACf,IAAI,CAAC,QACH;CAEF,IAAI;EACF,MAAM,OAAO,OAAO,SAAS,MAAM;CACrC,SAAS,OAAO;EACd,QAAQ,KAAK,EAAE,KAAK,MAAM,GAAG,0BAA0B;CACzD;AACF"}
|
|
@@ -0,0 +1,21 @@
|
|
|
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 { CaptureLog, CaptureLogLevel } from "../schema/capture-attempt.mjs";
|
|
5
|
+
import { Table } from "drizzle-orm";
|
|
6
|
+
//#region src/models/capture-log.d.ts
|
|
7
|
+
/** Tables required by {@link CaptureLogModel}. */
|
|
8
|
+
interface CaptureLogTables {
|
|
9
|
+
captureLogs: Table;
|
|
10
|
+
}
|
|
11
|
+
/** Data operations for capture log lines (every line of an attempt). */
|
|
12
|
+
declare class CaptureLogModel {
|
|
13
|
+
private readonly db;
|
|
14
|
+
private readonly tables;
|
|
15
|
+
constructor(db: DatabaseAdapter, tables?: CaptureLogTables);
|
|
16
|
+
listByAttempt(attemptId: string, limit?: number): Promise<CaptureLog[]>;
|
|
17
|
+
append(projectId: string, buildId: string, attemptId: string, level: CaptureLogLevel, message: string, fields?: unknown): Promise<CaptureLog>;
|
|
18
|
+
}
|
|
19
|
+
//#endregion
|
|
20
|
+
export { CaptureLogModel, CaptureLogTables };
|
|
21
|
+
//# sourceMappingURL=capture-log.d.mts.map
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import "node:path";
|
|
2
|
+
import "node:url";
|
|
3
|
+
import.meta.url;
|
|
4
|
+
import { ulid } from "../utils/ulid.mjs";
|
|
5
|
+
import { asc, eq, getTableColumns } from "drizzle-orm";
|
|
6
|
+
//#region src/models/capture-log.ts
|
|
7
|
+
/** Capture log lines: every stored line of a capture attempt. */
|
|
8
|
+
/** Data operations for capture log lines (every line of an attempt). */
|
|
9
|
+
var CaptureLogModel = class {
|
|
10
|
+
db;
|
|
11
|
+
tables;
|
|
12
|
+
constructor(db, tables) {
|
|
13
|
+
this.db = db;
|
|
14
|
+
this.tables = tables ?? { captureLogs: db.tables.captureLogs };
|
|
15
|
+
}
|
|
16
|
+
async listByAttempt(attemptId, limit) {
|
|
17
|
+
return await this.db.list(this.tables.captureLogs, {
|
|
18
|
+
where: eq(columnsOf(this.tables.captureLogs, "attemptId"), attemptId),
|
|
19
|
+
orderBy: asc(columnsOf(this.tables.captureLogs, "seq")),
|
|
20
|
+
...limit === void 0 ? {} : { limit }
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
async append(projectId, buildId, attemptId, level, message, fields) {
|
|
24
|
+
const seq = await this.db.count(this.tables.captureLogs, eq(columnsOf(this.tables.captureLogs, "attemptId"), attemptId)) + 1;
|
|
25
|
+
return await this.db.insert(this.tables.captureLogs, {
|
|
26
|
+
id: ulid(),
|
|
27
|
+
projectId,
|
|
28
|
+
buildId,
|
|
29
|
+
attemptId,
|
|
30
|
+
seq,
|
|
31
|
+
level,
|
|
32
|
+
message,
|
|
33
|
+
fields: fields === void 0 ? null : JSON.stringify(fields),
|
|
34
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
function columnsOf(table, column) {
|
|
39
|
+
return getTableColumns(table)[column];
|
|
40
|
+
}
|
|
41
|
+
//#endregion
|
|
42
|
+
export { CaptureLogModel };
|
|
43
|
+
|
|
44
|
+
//# sourceMappingURL=capture-log.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capture-log.mjs","names":[],"sources":["../../src/models/capture-log.ts"],"sourcesContent":["/** Capture log lines: every stored line of a capture attempt. */\nimport { asc, eq, getTableColumns } from \"drizzle-orm\";\nimport type { SQLWrapper, Table } from \"drizzle-orm\";\nimport type { DatabaseAdapter } from \"../adapters/database.ts\";\nimport type { CaptureLog, CaptureLogLevel } from \"../schema/capture-attempt.ts\";\nimport { ulid } from \"../utils/ulid.ts\";\n\n/** Tables required by {@link CaptureLogModel}. */\nexport interface CaptureLogTables {\n captureLogs: Table;\n}\n\n/** Data operations for capture log lines (every line of an attempt). */\nexport class CaptureLogModel {\n private readonly tables: CaptureLogTables;\n constructor(\n private readonly db: DatabaseAdapter,\n tables?: CaptureLogTables,\n ) {\n this.tables = tables ?? { captureLogs: db.tables.captureLogs };\n }\n\n async listByAttempt(attemptId: string, limit?: number): Promise<CaptureLog[]> {\n return (await this.db.list(this.tables.captureLogs, {\n where: eq(columnsOf(this.tables.captureLogs, \"attemptId\"), attemptId),\n orderBy: asc(columnsOf(this.tables.captureLogs, \"seq\")),\n ...(limit === undefined ? {} : { limit }),\n })) as unknown as CaptureLog[];\n }\n\n async append(\n projectId: string,\n buildId: string,\n attemptId: string,\n level: CaptureLogLevel,\n message: string,\n fields?: unknown,\n ): Promise<CaptureLog> {\n const seq =\n (await this.db.count(\n this.tables.captureLogs,\n eq(columnsOf(this.tables.captureLogs, \"attemptId\"), attemptId),\n )) + 1;\n return (await this.db.insert(this.tables.captureLogs, {\n id: ulid(),\n projectId,\n buildId,\n attemptId,\n seq,\n level,\n message,\n fields: fields === undefined ? null : JSON.stringify(fields),\n createdAt: new Date().toISOString(),\n })) as unknown as CaptureLog;\n }\n}\n\nfunction columnsOf(table: Table, column: string): SQLWrapper {\n return getTableColumns(table)[column] as unknown as SQLWrapper;\n}\n"],"mappings":";;;;;;;;AAaA,IAAa,kBAAb,MAA6B;CAGR;CAFnB;CACA,YACE,IACA,QACA;EAFiB,KAAA,KAAA;EAGjB,KAAK,SAAS,UAAU,EAAE,aAAa,GAAG,OAAO,YAAY;CAC/D;CAEA,MAAM,cAAc,WAAmB,OAAuC;EAC5E,OAAQ,MAAM,KAAK,GAAG,KAAK,KAAK,OAAO,aAAa;GAClD,OAAO,GAAG,UAAU,KAAK,OAAO,aAAa,WAAW,GAAG,SAAS;GACpE,SAAS,IAAI,UAAU,KAAK,OAAO,aAAa,KAAK,CAAC;GACtD,GAAI,UAAU,KAAA,IAAY,CAAC,IAAI,EAAE,MAAM;EACzC,CAAC;CACH;CAEA,MAAM,OACJ,WACA,SACA,WACA,OACA,SACA,QACqB;EACrB,MAAM,MACH,MAAM,KAAK,GAAG,MACb,KAAK,OAAO,aACZ,GAAG,UAAU,KAAK,OAAO,aAAa,WAAW,GAAG,SAAS,CAC/D,IAAK;EACP,OAAQ,MAAM,KAAK,GAAG,OAAO,KAAK,OAAO,aAAa;GACpD,IAAI,KAAK;GACT;GACA;GACA;GACA;GACA;GACA;GACA,QAAQ,WAAW,KAAA,IAAY,OAAO,KAAK,UAAU,MAAM;GAC3D,4BAAW,IAAI,KAAK,EAAA,CAAE,YAAY;EACpC,CAAC;CACH;AACF;AAEA,SAAS,UAAU,OAAc,QAA4B;CAC3D,OAAO,gBAAgB,KAAK,CAAC,CAAC;AAChC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
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 { Comment } from "../schema/comment.mjs";
|
|
5
|
+
import { Table } from "drizzle-orm";
|
|
6
|
+
//#region src/models/comment.d.ts
|
|
7
|
+
/** Tables required by {@link CommentModel}. */
|
|
8
|
+
interface CommentTables {
|
|
9
|
+
comments: Table;
|
|
10
|
+
projects: Table;
|
|
11
|
+
}
|
|
12
|
+
/** Data operations for review comments. */
|
|
13
|
+
declare class CommentModel {
|
|
14
|
+
private readonly db;
|
|
15
|
+
private readonly tables;
|
|
16
|
+
constructor(db: DatabaseAdapter, tables?: CommentTables);
|
|
17
|
+
listByBuild(buildId: string): Promise<Comment[]>;
|
|
18
|
+
create(projectId: string, buildId: string, userId: string | null, input: CommentCreateInput): Promise<Comment>;
|
|
19
|
+
resolve(id: string): Promise<Comment>;
|
|
20
|
+
}
|
|
21
|
+
/** Input for creating a review comment. */
|
|
22
|
+
interface CommentCreateInput {
|
|
23
|
+
body: string;
|
|
24
|
+
snapshotId?: string;
|
|
25
|
+
parentId?: string;
|
|
26
|
+
}
|
|
27
|
+
//#endregion
|
|
28
|
+
export { CommentCreateInput, CommentModel, CommentTables };
|
|
29
|
+
//# sourceMappingURL=comment.d.mts.map
|
package/dist/models/comment.mjs
CHANGED
|
@@ -1,25 +1,28 @@
|
|
|
1
1
|
import "node:path";
|
|
2
2
|
import "node:url";
|
|
3
3
|
import.meta.url;
|
|
4
|
-
import { projects } from "../schema/project.mjs";
|
|
5
4
|
import { ulid } from "../utils/ulid.mjs";
|
|
6
|
-
import {
|
|
7
|
-
import { eq } from "drizzle-orm";
|
|
5
|
+
import { eq, getTableColumns } from "drizzle-orm";
|
|
8
6
|
//#region src/models/comment.ts
|
|
9
7
|
/** Build and snapshot review comments. */
|
|
10
8
|
/** Data operations for review comments. */
|
|
11
9
|
var CommentModel = class {
|
|
12
10
|
db;
|
|
13
|
-
|
|
11
|
+
tables;
|
|
12
|
+
constructor(db, tables) {
|
|
14
13
|
this.db = db;
|
|
14
|
+
this.tables = tables ?? {
|
|
15
|
+
comments: db.tables.comments,
|
|
16
|
+
projects: db.tables.projects
|
|
17
|
+
};
|
|
15
18
|
}
|
|
16
19
|
async listByBuild(buildId) {
|
|
17
|
-
return await this.db.list(comments, { where: eq(comments
|
|
20
|
+
return await this.db.list(this.tables.comments, { where: eq(getTableColumns(this.tables.comments)["buildId"], buildId) });
|
|
18
21
|
}
|
|
19
22
|
async create(projectId, buildId, userId, input) {
|
|
20
23
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
21
|
-
if (!await this.db.get(projects, projectId)) throw new Error(`Project not found: ${projectId}`);
|
|
22
|
-
return await this.db.insert(comments, {
|
|
24
|
+
if (!await this.db.get(this.tables.projects, projectId)) throw new Error(`Project not found: ${projectId}`);
|
|
25
|
+
return await this.db.insert(this.tables.comments, {
|
|
23
26
|
id: ulid(),
|
|
24
27
|
projectId,
|
|
25
28
|
buildId,
|
|
@@ -33,7 +36,7 @@ var CommentModel = class {
|
|
|
33
36
|
});
|
|
34
37
|
}
|
|
35
38
|
async resolve(id) {
|
|
36
|
-
return await this.db.update(comments, id, {
|
|
39
|
+
return await this.db.update(this.tables.comments, id, {
|
|
37
40
|
resolved: true,
|
|
38
41
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
39
42
|
});
|
|
@@ -1 +1 @@
|
|
|
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 {
|
|
1
|
+
{"version":3,"file":"comment.mjs","names":[],"sources":["../../src/models/comment.ts"],"sourcesContent":["/** Build and snapshot review comments. */\nimport { eq, getTableColumns } from \"drizzle-orm\";\nimport type { SQLWrapper, Table } from \"drizzle-orm\";\nimport type { DatabaseAdapter } from \"../adapters/database.ts\";\nimport type { Comment } from \"../schema/comment.ts\";\nimport { ulid } from \"../utils/ulid.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 constructor(\n private readonly db: DatabaseAdapter,\n tables?: CommentTables,\n ) {\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(getTableColumns(this.tables.comments)[\"buildId\"] as unknown as SQLWrapper, 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 return (await this.db.insert(this.tables.comments, {\n id: ulid(),\n projectId,\n buildId,\n snapshotId: input.snapshotId,\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 snapshotId?: string;\n parentId?: string;\n}\n"],"mappings":";;;;;;;;AAcA,IAAa,eAAb,MAA0B;CAGL;CAFnB;CACA,YACE,IACA,QACA;EAFiB,KAAA,KAAA;EAGjB,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,gBAAgB,KAAK,OAAO,QAAQ,CAAC,CAAC,YAAqC,OAAO,EAC9F,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,OAAQ,MAAM,KAAK,GAAG,OAAO,KAAK,OAAO,UAAU;GACjD,IAAI,KAAK;GACT;GACA;GACA,YAAY,MAAM;GAClB;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,21 @@
|
|
|
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 { ContentRef } from "../schema/content-ref.mjs";
|
|
5
|
+
import { Table } from "drizzle-orm";
|
|
6
|
+
//#region src/models/content-ref.d.ts
|
|
7
|
+
/** Content refs for deduplicated Storybook assets. */
|
|
8
|
+
declare class ContentRefModel {
|
|
9
|
+
private readonly db;
|
|
10
|
+
private readonly tables;
|
|
11
|
+
constructor(db: DatabaseAdapter, tables?: {
|
|
12
|
+
contentRefs: Table;
|
|
13
|
+
});
|
|
14
|
+
get(hash: string): Promise<ContentRef | null>;
|
|
15
|
+
upsert(hash: string): Promise<ContentRef>;
|
|
16
|
+
decrement(hash: string): Promise<ContentRef | null>;
|
|
17
|
+
listStale(cutoff: string): Promise<ContentRef[]>;
|
|
18
|
+
}
|
|
19
|
+
//#endregion
|
|
20
|
+
export { ContentRefModel };
|
|
21
|
+
//# sourceMappingURL=content-ref.d.mts.map
|