@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,49 @@
|
|
|
1
|
+
import "node:path";
|
|
2
|
+
import "node:url";
|
|
3
|
+
import.meta.url;
|
|
4
|
+
//#region src/models/content-ref.ts
|
|
5
|
+
/** Content refs for deduplicated Storybook assets. */
|
|
6
|
+
var ContentRefModel = class {
|
|
7
|
+
db;
|
|
8
|
+
tables;
|
|
9
|
+
constructor(db, tables) {
|
|
10
|
+
this.db = db;
|
|
11
|
+
this.tables = tables ?? { contentRefs: db.tables.contentRefs };
|
|
12
|
+
}
|
|
13
|
+
async get(hash) {
|
|
14
|
+
return await this.db.get(this.tables.contentRefs, hash);
|
|
15
|
+
}
|
|
16
|
+
async upsert(hash) {
|
|
17
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
18
|
+
const existing = await this.get(hash);
|
|
19
|
+
if (existing) return await this.db.update(this.tables.contentRefs, hash, {
|
|
20
|
+
refCount: existing.refCount + 1,
|
|
21
|
+
lastSeenAt: now
|
|
22
|
+
});
|
|
23
|
+
return await this.db.insert(this.tables.contentRefs, {
|
|
24
|
+
hash,
|
|
25
|
+
refCount: 1,
|
|
26
|
+
lastSeenAt: now,
|
|
27
|
+
createdAt: now
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
async decrement(hash) {
|
|
31
|
+
const existing = await this.get(hash);
|
|
32
|
+
if (!existing) return null;
|
|
33
|
+
if (existing.refCount <= 1) {
|
|
34
|
+
await this.db.remove(this.tables.contentRefs, hash);
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
return await this.db.update(this.tables.contentRefs, hash, {
|
|
38
|
+
refCount: existing.refCount - 1,
|
|
39
|
+
lastSeenAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
async listStale(cutoff) {
|
|
43
|
+
return (await this.db.list(this.tables.contentRefs)).filter((r) => r.refCount === 0 && r.lastSeenAt < cutoff);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
//#endregion
|
|
47
|
+
export { ContentRefModel };
|
|
48
|
+
|
|
49
|
+
//# sourceMappingURL=content-ref.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"content-ref.mjs","names":[],"sources":["../../src/models/content-ref.ts"],"sourcesContent":["import type { Table } from \"drizzle-orm\";\nimport type { DatabaseAdapter } from \"../adapters/database.ts\";\nimport type { ContentRef } from \"../schema/content-ref.ts\";\n\n/** Content refs for deduplicated Storybook assets. */\nexport class ContentRefModel {\n private readonly tables: { contentRefs: Table };\n constructor(\n private readonly db: DatabaseAdapter,\n tables?: { contentRefs: Table },\n ) {\n this.tables = tables ?? { contentRefs: db.tables.contentRefs };\n }\n\n async get(hash: string): Promise<ContentRef | null> {\n return (await this.db.get(this.tables.contentRefs, hash)) as unknown as ContentRef | null;\n }\n\n async upsert(hash: string): Promise<ContentRef> {\n const now = new Date().toISOString();\n const existing = await this.get(hash);\n if (existing) {\n return (await this.db.update(this.tables.contentRefs, hash, {\n refCount: existing.refCount + 1,\n lastSeenAt: now,\n })) as unknown as ContentRef;\n }\n return (await this.db.insert(this.tables.contentRefs, {\n hash,\n refCount: 1,\n lastSeenAt: now,\n createdAt: now,\n })) as unknown as ContentRef;\n }\n\n async decrement(hash: string): Promise<ContentRef | null> {\n const existing = await this.get(hash);\n if (!existing) return null;\n if (existing.refCount <= 1) {\n await this.db.remove(this.tables.contentRefs, hash);\n return null;\n }\n return (await this.db.update(this.tables.contentRefs, hash, {\n refCount: existing.refCount - 1,\n lastSeenAt: new Date().toISOString(),\n })) as unknown as ContentRef;\n }\n\n async listStale(cutoff: string): Promise<ContentRef[]> {\n const all = (await this.db.list(this.tables.contentRefs)) as unknown as ContentRef[];\n return all.filter((r) => r.refCount === 0 && r.lastSeenAt < cutoff);\n }\n}\n"],"mappings":";;;;;AAKA,IAAa,kBAAb,MAA6B;CAGR;CAFnB;CACA,YACE,IACA,QACA;EAFiB,KAAA,KAAA;EAGjB,KAAK,SAAS,UAAU,EAAE,aAAa,GAAG,OAAO,YAAY;CAC/D;CAEA,MAAM,IAAI,MAA0C;EAClD,OAAQ,MAAM,KAAK,GAAG,IAAI,KAAK,OAAO,aAAa,IAAI;CACzD;CAEA,MAAM,OAAO,MAAmC;EAC9C,MAAM,uBAAM,IAAI,KAAK,EAAA,CAAE,YAAY;EACnC,MAAM,WAAW,MAAM,KAAK,IAAI,IAAI;EACpC,IAAI,UACF,OAAQ,MAAM,KAAK,GAAG,OAAO,KAAK,OAAO,aAAa,MAAM;GAC1D,UAAU,SAAS,WAAW;GAC9B,YAAY;EACd,CAAC;EAEH,OAAQ,MAAM,KAAK,GAAG,OAAO,KAAK,OAAO,aAAa;GACpD;GACA,UAAU;GACV,YAAY;GACZ,WAAW;EACb,CAAC;CACH;CAEA,MAAM,UAAU,MAA0C;EACxD,MAAM,WAAW,MAAM,KAAK,IAAI,IAAI;EACpC,IAAI,CAAC,UAAU,OAAO;EACtB,IAAI,SAAS,YAAY,GAAG;GAC1B,MAAM,KAAK,GAAG,OAAO,KAAK,OAAO,aAAa,IAAI;GAClD,OAAO;EACT;EACA,OAAQ,MAAM,KAAK,GAAG,OAAO,KAAK,OAAO,aAAa,MAAM;GAC1D,UAAU,SAAS,WAAW;GAC9B,6BAAY,IAAI,KAAK,EAAA,CAAE,YAAY;EACrC,CAAC;CACH;CAEA,MAAM,UAAU,QAAuC;EAErD,QAAO,MADY,KAAK,GAAG,KAAK,KAAK,OAAO,WAAW,EAAA,CAC5C,QAAQ,MAAM,EAAE,aAAa,KAAK,EAAE,aAAa,MAAM;CACpE;AACF"}
|
|
@@ -0,0 +1,36 @@
|
|
|
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 { SecretKeys } from "../utils/encrypt.mjs";
|
|
5
|
+
//#region src/models/credentials.d.ts
|
|
6
|
+
/** A row no configured key can decrypt. */
|
|
7
|
+
interface UnreadableCredential {
|
|
8
|
+
source: string;
|
|
9
|
+
id: string;
|
|
10
|
+
}
|
|
11
|
+
/** Which key each stored credential needs. */
|
|
12
|
+
interface CredentialProbe {
|
|
13
|
+
total: number;
|
|
14
|
+
/** Rows readable with the current secret. */
|
|
15
|
+
current: number;
|
|
16
|
+
/** Rows only readable with `previousSecret`; run re-encryption. */
|
|
17
|
+
previous: number;
|
|
18
|
+
unreadable: UnreadableCredential[];
|
|
19
|
+
}
|
|
20
|
+
/** Outcome of {@link reencryptCredentials}. */
|
|
21
|
+
interface ReencryptResult {
|
|
22
|
+
reencrypted: number;
|
|
23
|
+
failed: number;
|
|
24
|
+
unreadable: UnreadableCredential[];
|
|
25
|
+
}
|
|
26
|
+
/** Count stored credentials by the key they need; read-only. */
|
|
27
|
+
declare function probeCredentials(db: DatabaseAdapter, keys: SecretKeys): Promise<CredentialProbe>;
|
|
28
|
+
/**
|
|
29
|
+
* Re-encrypt every credential still under `keys.previous` with `keys.current`.
|
|
30
|
+
* Idempotent; unreadable rows are left untouched and reported, and one failed
|
|
31
|
+
* write does not stop the rest.
|
|
32
|
+
*/
|
|
33
|
+
declare function reencryptCredentials(db: DatabaseAdapter, keys: SecretKeys): Promise<ReencryptResult>;
|
|
34
|
+
//#endregion
|
|
35
|
+
export { CredentialProbe, ReencryptResult, UnreadableCredential, probeCredentials, reencryptCredentials };
|
|
36
|
+
//# sourceMappingURL=credentials.d.mts.map
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import "node:path";
|
|
2
|
+
import "node:url";
|
|
3
|
+
import.meta.url;
|
|
4
|
+
import { decryptWithKey, encrypt } from "../utils/encrypt.mjs";
|
|
5
|
+
//#region src/models/credentials.ts
|
|
6
|
+
/** Tables that hold encrypted credentials, with the column to read. */
|
|
7
|
+
const SOURCES = [
|
|
8
|
+
{
|
|
9
|
+
name: "webhooks",
|
|
10
|
+
table: "webhooks",
|
|
11
|
+
column: "secretEncrypted"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
name: "git status tokens",
|
|
15
|
+
table: "projectStatusConfigs",
|
|
16
|
+
column: "tokenEncrypted"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
name: "notification channels",
|
|
20
|
+
table: "notificationChannels",
|
|
21
|
+
column: "secretEncrypted"
|
|
22
|
+
}
|
|
23
|
+
];
|
|
24
|
+
/** Every non-empty encrypted value across the credential tables. */
|
|
25
|
+
async function storedCredentials(db) {
|
|
26
|
+
return (await Promise.all(SOURCES.map(async (source) => {
|
|
27
|
+
return (await db.list(db.tables[source.table])).flatMap((row) => {
|
|
28
|
+
const ciphertext = row[source.column];
|
|
29
|
+
return typeof ciphertext === "string" && ciphertext !== "" ? [{
|
|
30
|
+
source,
|
|
31
|
+
id: row.id,
|
|
32
|
+
ciphertext
|
|
33
|
+
}] : [];
|
|
34
|
+
});
|
|
35
|
+
}))).flat();
|
|
36
|
+
}
|
|
37
|
+
function classify(keys, ciphertext) {
|
|
38
|
+
try {
|
|
39
|
+
return decryptWithKey(keys, ciphertext).keyUsed;
|
|
40
|
+
} catch {
|
|
41
|
+
return "unreadable";
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/** Count stored credentials by the key they need; read-only. */
|
|
45
|
+
async function probeCredentials(db, keys) {
|
|
46
|
+
const stored = await storedCredentials(db);
|
|
47
|
+
const probe = {
|
|
48
|
+
total: stored.length,
|
|
49
|
+
current: 0,
|
|
50
|
+
previous: 0,
|
|
51
|
+
unreadable: []
|
|
52
|
+
};
|
|
53
|
+
for (const item of stored) {
|
|
54
|
+
const state = classify(keys, item.ciphertext);
|
|
55
|
+
if (state === "unreadable") probe.unreadable.push({
|
|
56
|
+
source: item.source.name,
|
|
57
|
+
id: item.id
|
|
58
|
+
});
|
|
59
|
+
else probe[state] += 1;
|
|
60
|
+
}
|
|
61
|
+
return probe;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Re-encrypt every credential still under `keys.previous` with `keys.current`.
|
|
65
|
+
* Idempotent; unreadable rows are left untouched and reported, and one failed
|
|
66
|
+
* write does not stop the rest.
|
|
67
|
+
*/
|
|
68
|
+
async function reencryptCredentials(db, keys) {
|
|
69
|
+
const result = {
|
|
70
|
+
reencrypted: 0,
|
|
71
|
+
failed: 0,
|
|
72
|
+
unreadable: []
|
|
73
|
+
};
|
|
74
|
+
const outcomes = await Promise.all((await storedCredentials(db)).map(async (item) => await reencryptOne(db, keys, item)));
|
|
75
|
+
for (const outcome of outcomes) if (outcome.state === "unreadable") result.unreadable.push(outcome.row);
|
|
76
|
+
else if (outcome.state !== "skipped") result[outcome.state] += 1;
|
|
77
|
+
return result;
|
|
78
|
+
}
|
|
79
|
+
async function reencryptOne(db, keys, item) {
|
|
80
|
+
let plain;
|
|
81
|
+
try {
|
|
82
|
+
plain = decryptWithKey(keys, item.ciphertext);
|
|
83
|
+
} catch {
|
|
84
|
+
return {
|
|
85
|
+
state: "unreadable",
|
|
86
|
+
row: {
|
|
87
|
+
source: item.source.name,
|
|
88
|
+
id: item.id
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
if (plain.keyUsed === "current") return { state: "skipped" };
|
|
93
|
+
try {
|
|
94
|
+
await db.update(db.tables[item.source.table], item.id, { [item.source.column]: encrypt(keys.current, plain.plaintext) });
|
|
95
|
+
return { state: "reencrypted" };
|
|
96
|
+
} catch {
|
|
97
|
+
return { state: "failed" };
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
//#endregion
|
|
101
|
+
export { probeCredentials, reencryptCredentials };
|
|
102
|
+
|
|
103
|
+
//# sourceMappingURL=credentials.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credentials.mjs","names":[],"sources":["../../src/models/credentials.ts"],"sourcesContent":["/**\n * Stored-credential inventory and re-encryption for secret rotation.\n *\n * Webhook secrets, git-provider tokens, and notification-channel secrets are\n * encrypted under `ShelfConfig.secret`. After a rotation the old secret stays\n * available as `previousSecret` (decrypt-only); {@link probeCredentials} reports\n * which key each row needs and {@link reencryptCredentials} moves rows to the\n * current key. Neither returns or logs credential values.\n */\nimport type { DatabaseAdapter } from \"../adapters/database.ts\";\nimport { decryptWithKey, encrypt, type SecretKeys } from \"../utils/encrypt.ts\";\n\n/** Tables that hold encrypted credentials, with the column to read. */\nconst SOURCES = [\n { name: \"webhooks\", table: \"webhooks\", column: \"secretEncrypted\" },\n { name: \"git status tokens\", table: \"projectStatusConfigs\", column: \"tokenEncrypted\" },\n { name: \"notification channels\", table: \"notificationChannels\", column: \"secretEncrypted\" },\n] as const;\n\n/** A row no configured key can decrypt. */\nexport interface UnreadableCredential {\n source: string;\n id: string;\n}\n\n/** Which key each stored credential needs. */\nexport interface CredentialProbe {\n total: number;\n /** Rows readable with the current secret. */\n current: number;\n /** Rows only readable with `previousSecret`; run re-encryption. */\n previous: number;\n unreadable: UnreadableCredential[];\n}\n\n/** Outcome of {@link reencryptCredentials}. */\nexport interface ReencryptResult {\n reencrypted: number;\n failed: number;\n unreadable: UnreadableCredential[];\n}\n\ninterface StoredCredential {\n source: (typeof SOURCES)[number];\n id: string;\n ciphertext: string;\n}\n\n/** Every non-empty encrypted value across the credential tables. */\nasync function storedCredentials(db: DatabaseAdapter): Promise<StoredCredential[]> {\n const lists = await Promise.all(\n SOURCES.map(async (source) => {\n const rows = (await db.list(db.tables[source.table])) as unknown as (Record<\n string,\n unknown\n > & {\n id: string;\n })[];\n return rows.flatMap((row) => {\n const ciphertext = row[source.column];\n return typeof ciphertext === \"string\" && ciphertext !== \"\"\n ? [{ source, id: row.id, ciphertext }]\n : [];\n });\n }),\n );\n return lists.flat();\n}\n\nfunction classify(keys: SecretKeys, ciphertext: string): \"current\" | \"previous\" | \"unreadable\" {\n try {\n return decryptWithKey(keys, ciphertext).keyUsed;\n } catch {\n return \"unreadable\";\n }\n}\n\n/** Count stored credentials by the key they need; read-only. */\nexport async function probeCredentials(\n db: DatabaseAdapter,\n keys: SecretKeys,\n): Promise<CredentialProbe> {\n const stored = await storedCredentials(db);\n const probe: CredentialProbe = { total: stored.length, current: 0, previous: 0, unreadable: [] };\n for (const item of stored) {\n const state = classify(keys, item.ciphertext);\n if (state === \"unreadable\") {\n probe.unreadable.push({ source: item.source.name, id: item.id });\n } else {\n probe[state] += 1;\n }\n }\n return probe;\n}\n\n/**\n * Re-encrypt every credential still under `keys.previous` with `keys.current`.\n * Idempotent; unreadable rows are left untouched and reported, and one failed\n * write does not stop the rest.\n */\nexport async function reencryptCredentials(\n db: DatabaseAdapter,\n keys: SecretKeys,\n): Promise<ReencryptResult> {\n const result: ReencryptResult = { reencrypted: 0, failed: 0, unreadable: [] };\n const outcomes = await Promise.all(\n (await storedCredentials(db)).map(async (item) => await reencryptOne(db, keys, item)),\n );\n for (const outcome of outcomes) {\n if (outcome.state === \"unreadable\") {\n result.unreadable.push(outcome.row);\n } else if (outcome.state !== \"skipped\") {\n result[outcome.state] += 1;\n }\n }\n return result;\n}\n\ntype Outcome =\n | { state: \"reencrypted\" | \"failed\" | \"skipped\" }\n | { state: \"unreadable\"; row: UnreadableCredential };\n\nasync function reencryptOne(\n db: DatabaseAdapter,\n keys: SecretKeys,\n item: StoredCredential,\n): Promise<Outcome> {\n let plain: { plaintext: string; keyUsed: string };\n try {\n plain = decryptWithKey(keys, item.ciphertext);\n } catch {\n return { state: \"unreadable\", row: { source: item.source.name, id: item.id } };\n }\n if (plain.keyUsed === \"current\") {\n return { state: \"skipped\" };\n }\n try {\n await db.update(db.tables[item.source.table], item.id, {\n [item.source.column]: encrypt(keys.current, plain.plaintext),\n });\n return { state: \"reencrypted\" };\n } catch {\n return { state: \"failed\" };\n }\n}\n"],"mappings":";;;;;;AAaA,MAAM,UAAU;CACd;EAAE,MAAM;EAAY,OAAO;EAAY,QAAQ;CAAkB;CACjE;EAAE,MAAM;EAAqB,OAAO;EAAwB,QAAQ;CAAiB;CACrF;EAAE,MAAM;EAAyB,OAAO;EAAwB,QAAQ;CAAkB;AAC5F;;AAgCA,eAAe,kBAAkB,IAAkD;CAiBjF,QAAO,MAhBa,QAAQ,IAC1B,QAAQ,IAAI,OAAO,WAAW;EAO5B,QAAO,MANa,GAAG,KAAK,GAAG,OAAO,OAAO,MAAM,EAAA,CAMvC,SAAS,QAAQ;GAC3B,MAAM,aAAa,IAAI,OAAO;GAC9B,OAAO,OAAO,eAAe,YAAY,eAAe,KACpD,CAAC;IAAE;IAAQ,IAAI,IAAI;IAAI;GAAW,CAAC,IACnC,CAAC;EACP,CAAC;CACH,CAAC,CACH,EAAA,CACa,KAAK;AACpB;AAEA,SAAS,SAAS,MAAkB,YAA2D;CAC7F,IAAI;EACF,OAAO,eAAe,MAAM,UAAU,CAAC,CAAC;CAC1C,QAAQ;EACN,OAAO;CACT;AACF;;AAGA,eAAsB,iBACpB,IACA,MAC0B;CAC1B,MAAM,SAAS,MAAM,kBAAkB,EAAE;CACzC,MAAM,QAAyB;EAAE,OAAO,OAAO;EAAQ,SAAS;EAAG,UAAU;EAAG,YAAY,CAAC;CAAE;CAC/F,KAAK,MAAM,QAAQ,QAAQ;EACzB,MAAM,QAAQ,SAAS,MAAM,KAAK,UAAU;EAC5C,IAAI,UAAU,cACZ,MAAM,WAAW,KAAK;GAAE,QAAQ,KAAK,OAAO;GAAM,IAAI,KAAK;EAAG,CAAC;OAE/D,MAAM,UAAU;CAEpB;CACA,OAAO;AACT;;;;;;AAOA,eAAsB,qBACpB,IACA,MAC0B;CAC1B,MAAM,SAA0B;EAAE,aAAa;EAAG,QAAQ;EAAG,YAAY,CAAC;CAAE;CAC5E,MAAM,WAAW,MAAM,QAAQ,KAC5B,MAAM,kBAAkB,EAAE,EAAA,CAAG,IAAI,OAAO,SAAS,MAAM,aAAa,IAAI,MAAM,IAAI,CAAC,CACtF;CACA,KAAK,MAAM,WAAW,UACpB,IAAI,QAAQ,UAAU,cACpB,OAAO,WAAW,KAAK,QAAQ,GAAG;MAC7B,IAAI,QAAQ,UAAU,WAC3B,OAAO,QAAQ,UAAU;CAG7B,OAAO;AACT;AAMA,eAAe,aACb,IACA,MACA,MACkB;CAClB,IAAI;CACJ,IAAI;EACF,QAAQ,eAAe,MAAM,KAAK,UAAU;CAC9C,QAAQ;EACN,OAAO;GAAE,OAAO;GAAc,KAAK;IAAE,QAAQ,KAAK,OAAO;IAAM,IAAI,KAAK;GAAG;EAAE;CAC/E;CACA,IAAI,MAAM,YAAY,WACpB,OAAO,EAAE,OAAO,UAAU;CAE5B,IAAI;EACF,MAAM,GAAG,OAAO,GAAG,OAAO,KAAK,OAAO,QAAQ,KAAK,IAAI,GACpD,KAAK,OAAO,SAAS,QAAQ,KAAK,SAAS,MAAM,SAAS,EAC7D,CAAC;EACD,OAAO,EAAE,OAAO,cAAc;CAChC,QAAQ;EACN,OAAO,EAAE,OAAO,SAAS;CAC3B;AACF"}
|
|
@@ -0,0 +1,58 @@
|
|
|
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 { BuildLabel, LabelType } from "../schema/label.mjs";
|
|
5
|
+
import { Table } from "drizzle-orm";
|
|
6
|
+
//#region src/models/label.d.ts
|
|
7
|
+
/** Tables required by {@link LabelModel}. */
|
|
8
|
+
interface LabelTables {
|
|
9
|
+
builds: Table;
|
|
10
|
+
buildLabels: Table;
|
|
11
|
+
labelTypes: Table;
|
|
12
|
+
}
|
|
13
|
+
/** Data operations for label types and build labels. */
|
|
14
|
+
declare class LabelModel {
|
|
15
|
+
private readonly db;
|
|
16
|
+
private readonly tables;
|
|
17
|
+
/**
|
|
18
|
+
* @param db - Database adapter.
|
|
19
|
+
* @param tables - Table handles.
|
|
20
|
+
*/
|
|
21
|
+
constructor(db: DatabaseAdapter, tables?: LabelTables);
|
|
22
|
+
/**
|
|
23
|
+
* Seed the default label types for a project if missing.
|
|
24
|
+
*
|
|
25
|
+
* @param projectId - Project ID.
|
|
26
|
+
*/
|
|
27
|
+
seedFor(projectId: string): Promise<void>;
|
|
28
|
+
/** List all label types for a project. */
|
|
29
|
+
listTypes(projectId: string): Promise<LabelType[]>;
|
|
30
|
+
/** Fetch a label type by key, or null if not found. */
|
|
31
|
+
getType(projectId: string, key: string): Promise<LabelType | null>;
|
|
32
|
+
/** Create a custom label type for a project. */
|
|
33
|
+
createType(projectId: string, input: {
|
|
34
|
+
key: string;
|
|
35
|
+
name: string;
|
|
36
|
+
linkTemplate?: string;
|
|
37
|
+
color?: string;
|
|
38
|
+
}): Promise<LabelType>;
|
|
39
|
+
/** Update a custom label type's name, template or color. */
|
|
40
|
+
updateType(projectId: string, key: string, input: {
|
|
41
|
+
name?: string;
|
|
42
|
+
linkTemplate?: string | null;
|
|
43
|
+
color?: string | null;
|
|
44
|
+
}): Promise<LabelType | null>;
|
|
45
|
+
/** Remove a custom label type, rejecting reserved or persistent types. */
|
|
46
|
+
removeType(projectId: string, key: string): Promise<void>;
|
|
47
|
+
/** Attach a label value to a build. */
|
|
48
|
+
attach(projectId: string, buildId: string, typeKey: string, value: string): Promise<BuildLabel>;
|
|
49
|
+
/** List all labels attached to a build. */
|
|
50
|
+
listForBuild(buildId: string): Promise<BuildLabel[]>;
|
|
51
|
+
/** Return the id of the latest build carrying a given label value, if any. */
|
|
52
|
+
latestBuildId(projectId: string, typeKey: string, value: string): Promise<string | null>;
|
|
53
|
+
/** Return whether a build is marked as persistent. */
|
|
54
|
+
hasPersistent(projectId: string, buildId: string): Promise<boolean>;
|
|
55
|
+
}
|
|
56
|
+
//#endregion
|
|
57
|
+
export { LabelModel, LabelTables };
|
|
58
|
+
//# sourceMappingURL=label.d.mts.map
|
package/dist/models/label.mjs
CHANGED
|
@@ -1,31 +1,26 @@
|
|
|
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, labelTypes } from "../schema/label.mjs";
|
|
6
|
-
import { ulid } from "../utils/ulid.mjs";
|
|
7
4
|
import { PERSISTENT_LABEL_KEY, RESERVED_LABEL_KEYS, SEEDED_LABEL_KEYS } from "../types.mjs";
|
|
8
|
-
import {
|
|
5
|
+
import { ulid } from "../utils/ulid.mjs";
|
|
6
|
+
import { and, desc, eq, getTableColumns } from "drizzle-orm";
|
|
9
7
|
//#region src/models/label.ts
|
|
10
8
|
/** Build labels and project label types. */
|
|
11
|
-
const SEEDED_NAMES = {
|
|
12
|
-
branch: "Branch",
|
|
13
|
-
persistent: "Persistent",
|
|
14
|
-
pr: "Pull request",
|
|
15
|
-
mr: "Merge request",
|
|
16
|
-
jira: "Jira issue",
|
|
17
|
-
linear: "Linear issue",
|
|
18
|
-
figma: "Figma file",
|
|
19
|
-
custom: "Custom"
|
|
20
|
-
};
|
|
21
9
|
/** Data operations for label types and build labels. */
|
|
22
10
|
var LabelModel = class {
|
|
23
11
|
db;
|
|
12
|
+
tables;
|
|
24
13
|
/**
|
|
25
14
|
* @param db - Database adapter.
|
|
15
|
+
* @param tables - Table handles.
|
|
26
16
|
*/
|
|
27
|
-
constructor(db) {
|
|
17
|
+
constructor(db, tables) {
|
|
28
18
|
this.db = db;
|
|
19
|
+
this.tables = tables ?? {
|
|
20
|
+
builds: db.tables.builds,
|
|
21
|
+
buildLabels: db.tables.buildLabels,
|
|
22
|
+
labelTypes: db.tables.labelTypes
|
|
23
|
+
};
|
|
29
24
|
}
|
|
30
25
|
/**
|
|
31
26
|
* Seed the default label types for a project if missing.
|
|
@@ -38,7 +33,7 @@ var LabelModel = class {
|
|
|
38
33
|
const existingKeys = new Set(existing.map((t) => t.key));
|
|
39
34
|
const missing = SEEDED_LABEL_KEYS.filter((key) => !existingKeys.has(key));
|
|
40
35
|
await Promise.all(missing.map(async (key) => {
|
|
41
|
-
await this.db.insert(labelTypes, {
|
|
36
|
+
await this.db.insert(this.tables.labelTypes, {
|
|
42
37
|
id: ulid(),
|
|
43
38
|
projectId,
|
|
44
39
|
key,
|
|
@@ -49,18 +44,18 @@ var LabelModel = class {
|
|
|
49
44
|
}
|
|
50
45
|
/** List all label types for a project. */
|
|
51
46
|
async listTypes(projectId) {
|
|
52
|
-
return await this.db.list(labelTypes, { where: eq(labelTypes
|
|
47
|
+
return await this.db.list(this.tables.labelTypes, { where: eq(getTableColumns(this.tables.labelTypes)["projectId"], projectId) });
|
|
53
48
|
}
|
|
54
49
|
/** Fetch a label type by key, or null if not found. */
|
|
55
50
|
async getType(projectId, key) {
|
|
56
|
-
return (await this.db.list(labelTypes, {
|
|
57
|
-
where: and(eq(labelTypes
|
|
51
|
+
return (await this.db.list(this.tables.labelTypes, {
|
|
52
|
+
where: and(eq(getTableColumns(this.tables.labelTypes)["projectId"], projectId), eq(getTableColumns(this.tables.labelTypes)["key"], key)),
|
|
58
53
|
limit: 1
|
|
59
54
|
}))[0] ?? null;
|
|
60
55
|
}
|
|
61
56
|
/** Create a custom label type for a project. */
|
|
62
57
|
async createType(projectId, input) {
|
|
63
|
-
return await this.db.insert(labelTypes, {
|
|
58
|
+
return await this.db.insert(this.tables.labelTypes, {
|
|
64
59
|
id: ulid(),
|
|
65
60
|
projectId,
|
|
66
61
|
key: input.key,
|
|
@@ -75,7 +70,7 @@ var LabelModel = class {
|
|
|
75
70
|
if (key === "persistent" || RESERVED_LABEL_KEYS.includes(key)) throw new Error(`Label type '${key}' cannot be updated.`);
|
|
76
71
|
const existing = await this.getType(projectId, key);
|
|
77
72
|
if (!existing) return null;
|
|
78
|
-
return await this.db.update(labelTypes, existing.id, {
|
|
73
|
+
return await this.db.update(this.tables.labelTypes, existing.id, {
|
|
79
74
|
name: input.name ?? existing.name,
|
|
80
75
|
linkTemplate: input.linkTemplate === void 0 ? existing.linkTemplate : input.linkTemplate,
|
|
81
76
|
color: input.color === void 0 ? existing.color : input.color
|
|
@@ -85,11 +80,11 @@ var LabelModel = class {
|
|
|
85
80
|
async removeType(projectId, key) {
|
|
86
81
|
if (key === "persistent" || RESERVED_LABEL_KEYS.includes(key)) throw new Error(`Label type '${key}' cannot be removed.`);
|
|
87
82
|
const existing = await this.getType(projectId, key);
|
|
88
|
-
if (existing) await this.db.remove(labelTypes, existing.id);
|
|
83
|
+
if (existing) await this.db.remove(this.tables.labelTypes, existing.id);
|
|
89
84
|
}
|
|
90
85
|
/** Attach a label value to a build. */
|
|
91
86
|
async attach(projectId, buildId, typeKey, value) {
|
|
92
|
-
return await this.db.insert(buildLabels, {
|
|
87
|
+
return await this.db.insert(this.tables.buildLabels, {
|
|
93
88
|
id: ulid(),
|
|
94
89
|
projectId,
|
|
95
90
|
buildId,
|
|
@@ -100,20 +95,30 @@ var LabelModel = class {
|
|
|
100
95
|
}
|
|
101
96
|
/** List all labels attached to a build. */
|
|
102
97
|
async listForBuild(buildId) {
|
|
103
|
-
return await this.db.list(buildLabels, { where: eq(buildLabels
|
|
98
|
+
return await this.db.list(this.tables.buildLabels, { where: eq(getTableColumns(this.tables.buildLabels)["buildId"], buildId) });
|
|
104
99
|
}
|
|
105
100
|
/** Return the id of the latest build carrying a given label value, if any. */
|
|
106
101
|
async latestBuildId(projectId, typeKey, value) {
|
|
107
|
-
const labels = await this.db.list(buildLabels, { where: and(eq(buildLabels
|
|
102
|
+
const labels = await this.db.list(this.tables.buildLabels, { where: and(eq(getTableColumns(this.tables.buildLabels)["projectId"], projectId), eq(getTableColumns(this.tables.buildLabels)["typeKey"], typeKey), eq(getTableColumns(this.tables.buildLabels)["value"], value)) });
|
|
108
103
|
if (labels.length === 0) return null;
|
|
109
104
|
const idSet = new Set(labels.map((l) => l.buildId));
|
|
110
|
-
return (await this.db.list(builds, { orderBy: desc(builds
|
|
105
|
+
return (await this.db.list(this.tables.builds, { orderBy: desc(getTableColumns(this.tables.builds)["createdAt"]) })).find((b) => idSet.has(b.id))?.id ?? null;
|
|
111
106
|
}
|
|
112
107
|
/** Return whether a build is marked as persistent. */
|
|
113
108
|
async hasPersistent(projectId, buildId) {
|
|
114
|
-
return (await this.db.list(buildLabels, { where: and(eq(buildLabels
|
|
109
|
+
return (await this.db.list(this.tables.buildLabels, { where: and(eq(getTableColumns(this.tables.buildLabels)["projectId"], projectId), eq(getTableColumns(this.tables.buildLabels)["buildId"], buildId), eq(getTableColumns(this.tables.buildLabels)["typeKey"], PERSISTENT_LABEL_KEY)) })).length > 0;
|
|
115
110
|
}
|
|
116
111
|
};
|
|
112
|
+
const SEEDED_NAMES = {
|
|
113
|
+
branch: "Branch",
|
|
114
|
+
persistent: "Persistent",
|
|
115
|
+
pr: "Pull request",
|
|
116
|
+
mr: "Merge request",
|
|
117
|
+
jira: "Jira issue",
|
|
118
|
+
linear: "Linear issue",
|
|
119
|
+
figma: "Figma file",
|
|
120
|
+
custom: "Custom"
|
|
121
|
+
};
|
|
117
122
|
//#endregion
|
|
118
123
|
export { LabelModel };
|
|
119
124
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"label.mjs","names":[],"sources":["../../src/models/label.ts"],"sourcesContent":["/** Build labels and project label types. */\nimport { and, desc, eq } from \"drizzle-orm\";\nimport type { DatabaseAdapter } from \"../adapters/database.ts\";\nimport { builds } from \"../schema/build.ts\";\nimport { buildLabels, labelTypes } from \"../schema/label.ts\";\nimport type { BuildLabel, LabelType } from \"../schema/label.ts\";\nimport { PERSISTENT_LABEL_KEY, RESERVED_LABEL_KEYS, SEEDED_LABEL_KEYS } from \"../types.ts\";\nimport { ulid } from \"../utils/ulid.ts\";\n\nconst SEEDED_NAMES: Record<string, string> = {\n branch: \"Branch\",\n persistent: \"Persistent\",\n pr: \"Pull request\",\n mr: \"Merge request\",\n jira: \"Jira issue\",\n linear: \"Linear issue\",\n figma: \"Figma file\",\n custom: \"Custom\",\n};\n\n/** Data operations for label types and build labels. */\nexport class LabelModel {\n /**\n * @param db - Database adapter.\n */\n constructor(private readonly db: DatabaseAdapter) {}\n\n /**\n * Seed the default label types for a project if missing.\n *\n * @param projectId - Project ID.\n */\n async seedFor(projectId: string): Promise<void> {\n const now = new Date().toISOString();\n const existing = await this.listTypes(projectId);\n const existingKeys = new Set(existing.map((t) => t.key));\n const missing = SEEDED_LABEL_KEYS.filter((key) => !existingKeys.has(key));\n await Promise.all(\n missing.map(async (key) => {\n await this.db.insert(labelTypes, {\n id: ulid(),\n projectId,\n key,\n name: SEEDED_NAMES[key] ?? key,\n createdAt: now,\n });\n }),\n );\n }\n\n /** List all label types for a project. */\n async listTypes(projectId: string): Promise<LabelType[]> {\n return await this.db.list(labelTypes, { where: eq(labelTypes.projectId, projectId) });\n }\n\n /** Fetch a label type by key, or null if not found. */\n async getType(projectId: string, key: string): Promise<LabelType | null> {\n const rows = await this.db.list(labelTypes, {\n where: and(eq(labelTypes.projectId, projectId), eq(labelTypes.key, key)),\n limit: 1,\n });\n return rows[0] ?? null;\n }\n\n /** Create a custom label type for a project. */\n async createType(\n projectId: string,\n input: { key: string; name: string; linkTemplate?: string; color?: string },\n ): Promise<LabelType> {\n return await this.db.insert(labelTypes, {\n id: ulid(),\n projectId,\n key: input.key,\n name: input.name,\n linkTemplate: input.linkTemplate,\n color: input.color,\n createdAt: new Date().toISOString(),\n });\n }\n\n /** Update a custom label type's name, template or color. */\n async updateType(\n projectId: string,\n key: string,\n input: { name?: string; linkTemplate?: string | null; color?: string | null },\n ): Promise<LabelType | null> {\n if (key === PERSISTENT_LABEL_KEY || RESERVED_LABEL_KEYS.includes(key as never)) {\n throw new Error(`Label type '${key}' cannot be updated.`);\n }\n const existing = await this.getType(projectId, key);\n if (!existing) {\n return null;\n }\n return await this.db.update(labelTypes, existing.id, {\n name: input.name ?? existing.name,\n linkTemplate: input.linkTemplate === undefined ? existing.linkTemplate : input.linkTemplate,\n color: input.color === undefined ? existing.color : input.color,\n });\n }\n\n /** Remove a custom label type, rejecting reserved or persistent types. */\n async removeType(projectId: string, key: string): Promise<void> {\n if (key === PERSISTENT_LABEL_KEY || RESERVED_LABEL_KEYS.includes(key as never)) {\n throw new Error(`Label type '${key}' cannot be removed.`);\n }\n const existing = await this.getType(projectId, key);\n if (existing) {\n await this.db.remove(labelTypes, existing.id);\n }\n }\n\n /** Attach a label value to a build. */\n async attach(\n projectId: string,\n buildId: string,\n typeKey: string,\n value: string,\n ): Promise<BuildLabel> {\n return await this.db.insert(buildLabels, {\n id: ulid(),\n projectId,\n buildId,\n typeKey,\n value,\n createdAt: new Date().toISOString(),\n });\n }\n\n /** List all labels attached to a build. */\n async listForBuild(buildId: string): Promise<BuildLabel[]> {\n return await this.db.list(buildLabels, { where: eq(buildLabels.buildId, buildId) });\n }\n\n /** Return the id of the latest build carrying a given label value, if any. */\n async latestBuildId(projectId: string, typeKey: string, value: string): Promise<string | null> {\n const labels = await this.db.list(buildLabels, {\n where: and(\n eq(buildLabels.projectId, projectId),\n eq(buildLabels.typeKey, typeKey),\n eq(buildLabels.value, value),\n ),\n });\n if (labels.length === 0) {\n return null;\n }\n const idSet = new Set(labels.map((l) => l.buildId));\n const rows = await this.db.list(builds, { orderBy: desc(builds.createdAt) });\n return rows.find((b) => idSet.has(b.id))?.id ?? null;\n }\n\n /** Return whether a build is marked as persistent. */\n async hasPersistent(projectId: string, buildId: string): Promise<boolean> {\n const labels = await this.db.list(buildLabels, {\n where: and(\n eq(buildLabels.projectId, projectId),\n eq(buildLabels.buildId, buildId),\n eq(buildLabels.typeKey, PERSISTENT_LABEL_KEY),\n ),\n });\n return labels.length > 0;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"label.mjs","names":[],"sources":["../../src/models/label.ts"],"sourcesContent":["/** Build labels and project label types. */\nimport { and, desc, eq, getTableColumns } from \"drizzle-orm\";\nimport type { SQLWrapper, Table } from \"drizzle-orm\";\nimport type { DatabaseAdapter } from \"../adapters/database.ts\";\nimport type { BuildLabel, LabelType } from \"../schema/label.ts\";\nimport { PERSISTENT_LABEL_KEY, RESERVED_LABEL_KEYS, SEEDED_LABEL_KEYS } from \"../types.ts\";\nimport { ulid } from \"../utils/ulid.ts\";\n\n/** Tables required by {@link LabelModel}. */\nexport interface LabelTables {\n builds: Table;\n buildLabels: Table;\n labelTypes: Table;\n}\n\n/** Data operations for label types and build labels. */\nexport class LabelModel {\n private readonly tables: LabelTables;\n /**\n * @param db - Database adapter.\n * @param tables - Table handles.\n */\n constructor(\n private readonly db: DatabaseAdapter,\n tables?: LabelTables,\n ) {\n this.tables = tables ?? {\n builds: db.tables.builds,\n buildLabels: db.tables.buildLabels,\n labelTypes: db.tables.labelTypes,\n };\n }\n\n /**\n * Seed the default label types for a project if missing.\n *\n * @param projectId - Project ID.\n */\n async seedFor(projectId: string): Promise<void> {\n const now = new Date().toISOString();\n const existing = await this.listTypes(projectId);\n const existingKeys = new Set(existing.map((t) => t.key));\n const missing = SEEDED_LABEL_KEYS.filter((key) => !existingKeys.has(key));\n await Promise.all(\n missing.map(async (key) => {\n await this.db.insert(this.tables.labelTypes, {\n id: ulid(),\n projectId,\n key,\n name: SEEDED_NAMES[key] ?? key,\n createdAt: now,\n });\n }),\n );\n }\n\n /** List all label types for a project. */\n async listTypes(projectId: string): Promise<LabelType[]> {\n return (await this.db.list(this.tables.labelTypes, {\n where: eq(\n getTableColumns(this.tables.labelTypes)[\"projectId\"] as unknown as SQLWrapper,\n projectId,\n ),\n })) as unknown as LabelType[];\n }\n\n /** Fetch a label type by key, or null if not found. */\n async getType(projectId: string, key: string): Promise<LabelType | null> {\n const rows = (await this.db.list(this.tables.labelTypes, {\n where: and(\n eq(\n getTableColumns(this.tables.labelTypes)[\"projectId\"] as unknown as SQLWrapper,\n projectId,\n ),\n eq(getTableColumns(this.tables.labelTypes)[\"key\"] as unknown as SQLWrapper, key),\n ),\n limit: 1,\n })) as unknown as LabelType[];\n return rows[0] ?? null;\n }\n\n /** Create a custom label type for a project. */\n async createType(\n projectId: string,\n input: { key: string; name: string; linkTemplate?: string; color?: string },\n ): Promise<LabelType> {\n return (await this.db.insert(this.tables.labelTypes, {\n id: ulid(),\n projectId,\n key: input.key,\n name: input.name,\n linkTemplate: input.linkTemplate,\n color: input.color,\n createdAt: new Date().toISOString(),\n })) as unknown as LabelType;\n }\n\n /** Update a custom label type's name, template or color. */\n async updateType(\n projectId: string,\n key: string,\n input: { name?: string; linkTemplate?: string | null; color?: string | null },\n ): Promise<LabelType | null> {\n if (key === PERSISTENT_LABEL_KEY || RESERVED_LABEL_KEYS.includes(key as never)) {\n throw new Error(`Label type '${key}' cannot be updated.`);\n }\n const existing = await this.getType(projectId, key);\n if (!existing) {\n return null;\n }\n return (await this.db.update(this.tables.labelTypes, existing.id, {\n name: input.name ?? existing.name,\n linkTemplate: input.linkTemplate === undefined ? existing.linkTemplate : input.linkTemplate,\n color: input.color === undefined ? existing.color : input.color,\n })) as unknown as LabelType;\n }\n\n /** Remove a custom label type, rejecting reserved or persistent types. */\n async removeType(projectId: string, key: string): Promise<void> {\n if (key === PERSISTENT_LABEL_KEY || RESERVED_LABEL_KEYS.includes(key as never)) {\n throw new Error(`Label type '${key}' cannot be removed.`);\n }\n const existing = await this.getType(projectId, key);\n if (existing) {\n await this.db.remove(this.tables.labelTypes, existing.id);\n }\n }\n\n /** Attach a label value to a build. */\n async attach(\n projectId: string,\n buildId: string,\n typeKey: string,\n value: string,\n ): Promise<BuildLabel> {\n return (await this.db.insert(this.tables.buildLabels, {\n id: ulid(),\n projectId,\n buildId,\n typeKey,\n value,\n createdAt: new Date().toISOString(),\n })) as unknown as BuildLabel;\n }\n\n /** List all labels attached to a build. */\n async listForBuild(buildId: string): Promise<BuildLabel[]> {\n return (await this.db.list(this.tables.buildLabels, {\n where: eq(\n getTableColumns(this.tables.buildLabels)[\"buildId\"] as unknown as SQLWrapper,\n buildId,\n ),\n })) as unknown as BuildLabel[];\n }\n\n /** Return the id of the latest build carrying a given label value, if any. */\n async latestBuildId(projectId: string, typeKey: string, value: string): Promise<string | null> {\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(getTableColumns(this.tables.buildLabels)[\"typeKey\"] as unknown as SQLWrapper, typeKey),\n eq(getTableColumns(this.tables.buildLabels)[\"value\"] as unknown as SQLWrapper, value),\n ),\n })) as unknown as BuildLabel[];\n if (labels.length === 0) {\n return null;\n }\n const idSet = new Set(labels.map((l) => l.buildId));\n const rows = (await this.db.list(this.tables.builds, {\n orderBy: desc(getTableColumns(this.tables.builds)[\"createdAt\"] as unknown as SQLWrapper),\n })) as unknown as { id: string }[];\n return rows.find((b) => idSet.has(b.id))?.id ?? null;\n }\n\n /** Return whether a build is marked as persistent. */\n async hasPersistent(projectId: string, buildId: string): Promise<boolean> {\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(getTableColumns(this.tables.buildLabels)[\"buildId\"] as unknown as SQLWrapper, buildId),\n eq(\n getTableColumns(this.tables.buildLabels)[\"typeKey\"] as unknown as SQLWrapper,\n PERSISTENT_LABEL_KEY,\n ),\n ),\n })) as unknown as BuildLabel[];\n return labels.length > 0;\n }\n}\n\nconst SEEDED_NAMES: Record<string, string> = {\n branch: \"Branch\",\n persistent: \"Persistent\",\n pr: \"Pull request\",\n mr: \"Merge request\",\n jira: \"Jira issue\",\n linear: \"Linear issue\",\n figma: \"Figma file\",\n custom: \"Custom\",\n};\n"],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
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 { ProjectRole, SiteRole } from "../types.mjs";
|
|
5
|
+
import { ProjectMember } from "../schema/member.mjs";
|
|
6
|
+
import { Table } from "drizzle-orm";
|
|
7
|
+
//#region src/models/member.d.ts
|
|
8
|
+
/** Tables required by {@link MemberModel}. */
|
|
9
|
+
interface MemberTables {
|
|
10
|
+
projectMembers: Table;
|
|
11
|
+
}
|
|
12
|
+
/** Data operations for project membership and roles. */
|
|
13
|
+
declare class MemberModel {
|
|
14
|
+
private readonly db;
|
|
15
|
+
private readonly tables;
|
|
16
|
+
/**
|
|
17
|
+
* @param db - Database adapter.
|
|
18
|
+
* @param tables - Table handles.
|
|
19
|
+
*/
|
|
20
|
+
constructor(db: DatabaseAdapter, tables?: MemberTables);
|
|
21
|
+
/** List all members of a project. */
|
|
22
|
+
list(projectId: string): Promise<ProjectMember[]>;
|
|
23
|
+
/** Fetch a project member by project and user id, or null if not found. */
|
|
24
|
+
get(projectId: string, userId: string): Promise<ProjectMember | null>;
|
|
25
|
+
/** Set a user's role in a project, creating the membership if needed. */
|
|
26
|
+
set(projectId: string, userId: string, role: ProjectRole, source?: string): Promise<ProjectMember>;
|
|
27
|
+
/** Remove a user from a project if they are a member. */
|
|
28
|
+
remove(projectId: string, userId: string): Promise<void>;
|
|
29
|
+
/** Resolve a user's effective project role, honoring site-wide admins. */
|
|
30
|
+
effectiveRole(siteRole: SiteRole, projectId: string, userId: string): Promise<ProjectRole | null>;
|
|
31
|
+
}
|
|
32
|
+
//#endregion
|
|
33
|
+
export { MemberModel, MemberTables };
|
|
34
|
+
//# sourceMappingURL=member.d.mts.map
|
package/dist/models/member.mjs
CHANGED
|
@@ -2,51 +2,60 @@ import "node:path";
|
|
|
2
2
|
import "node:url";
|
|
3
3
|
import.meta.url;
|
|
4
4
|
import { ulid } from "../utils/ulid.mjs";
|
|
5
|
-
import {
|
|
6
|
-
import { and, eq } from "drizzle-orm";
|
|
5
|
+
import { and, eq, getTableColumns } from "drizzle-orm";
|
|
7
6
|
//#region src/models/member.ts
|
|
8
7
|
/** Project membership and role resolution. */
|
|
9
8
|
/** Data operations for project membership and roles. */
|
|
10
9
|
var MemberModel = class {
|
|
11
10
|
db;
|
|
11
|
+
tables;
|
|
12
12
|
/**
|
|
13
13
|
* @param db - Database adapter.
|
|
14
|
+
* @param tables - Table handles.
|
|
14
15
|
*/
|
|
15
|
-
constructor(db) {
|
|
16
|
+
constructor(db, tables) {
|
|
16
17
|
this.db = db;
|
|
18
|
+
this.tables = tables ?? { projectMembers: db.tables.projectMembers };
|
|
17
19
|
}
|
|
18
20
|
/** List all members of a project. */
|
|
19
21
|
async list(projectId) {
|
|
20
|
-
return await this.db.list(projectMembers, { where: eq(projectMembers
|
|
22
|
+
return await this.db.list(this.tables.projectMembers, { where: eq(getTableColumns(this.tables.projectMembers)["projectId"], projectId) });
|
|
21
23
|
}
|
|
22
24
|
/** Fetch a project member by project and user id, or null if not found. */
|
|
23
25
|
async get(projectId, userId) {
|
|
24
|
-
return (await this.db.list(projectMembers, {
|
|
25
|
-
where: and(eq(projectMembers
|
|
26
|
+
return (await this.db.list(this.tables.projectMembers, {
|
|
27
|
+
where: and(eq(getTableColumns(this.tables.projectMembers)["projectId"], projectId), eq(getTableColumns(this.tables.projectMembers)["userId"], userId)),
|
|
26
28
|
limit: 1
|
|
27
29
|
}))[0] ?? null;
|
|
28
30
|
}
|
|
29
31
|
/** Set a user's role in a project, creating the membership if needed. */
|
|
30
|
-
async set(projectId, userId, role) {
|
|
32
|
+
async set(projectId, userId, role, source = "manual") {
|
|
31
33
|
const existing = await this.get(projectId, userId);
|
|
32
|
-
if (existing) return this.db.update(projectMembers, existing.id, {
|
|
33
|
-
|
|
34
|
+
if (existing) return await this.db.update(this.tables.projectMembers, existing.id, {
|
|
35
|
+
role,
|
|
36
|
+
source
|
|
37
|
+
});
|
|
38
|
+
return await this.db.insert(this.tables.projectMembers, {
|
|
34
39
|
id: ulid(),
|
|
35
40
|
projectId,
|
|
36
41
|
userId,
|
|
37
42
|
role,
|
|
43
|
+
source,
|
|
38
44
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
39
45
|
});
|
|
40
46
|
}
|
|
41
47
|
/** Remove a user from a project if they are a member. */
|
|
42
48
|
async remove(projectId, userId) {
|
|
43
49
|
const existing = await this.get(projectId, userId);
|
|
44
|
-
if (existing) await this.db.remove(projectMembers, existing.id);
|
|
50
|
+
if (existing) await this.db.remove(this.tables.projectMembers, existing.id);
|
|
45
51
|
}
|
|
46
52
|
/** Resolve a user's effective project role, honoring site-wide admins. */
|
|
47
53
|
async effectiveRole(siteRole, projectId, userId) {
|
|
48
54
|
if (siteRole === "admin") return "admin";
|
|
49
|
-
|
|
55
|
+
const member = await this.get(projectId, userId);
|
|
56
|
+
if (member) return member.role;
|
|
57
|
+
if (siteRole === "viewer") return "viewer";
|
|
58
|
+
return null;
|
|
50
59
|
}
|
|
51
60
|
};
|
|
52
61
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"member.mjs","names":[],"sources":["../../src/models/member.ts"],"sourcesContent":["/** Project membership and role resolution. */\nimport { and, eq } from \"drizzle-orm\";\nimport type {
|
|
1
|
+
{"version":3,"file":"member.mjs","names":[],"sources":["../../src/models/member.ts"],"sourcesContent":["/** Project membership and role resolution. */\nimport { and, eq, getTableColumns } from \"drizzle-orm\";\nimport type { SQLWrapper, Table } from \"drizzle-orm\";\nimport type { DatabaseAdapter } from \"../adapters/database.ts\";\nimport type { ProjectMember } from \"../schema/member.ts\";\nimport type { ProjectRole, SiteRole } from \"../types.ts\";\nimport { ulid } from \"../utils/ulid.ts\";\n\n/** Tables required by {@link MemberModel}. */\nexport interface MemberTables {\n projectMembers: Table;\n}\n\n/** Data operations for project membership and roles. */\nexport class MemberModel {\n private readonly tables: MemberTables;\n /**\n * @param db - Database adapter.\n * @param tables - Table handles.\n */\n constructor(\n private readonly db: DatabaseAdapter,\n tables?: MemberTables,\n ) {\n this.tables = tables ?? { projectMembers: db.tables.projectMembers };\n }\n\n /** List all members of a project. */\n async list(projectId: string): Promise<ProjectMember[]> {\n return (await this.db.list(this.tables.projectMembers, {\n where: eq(\n getTableColumns(this.tables.projectMembers)[\"projectId\"] as unknown as SQLWrapper,\n projectId,\n ),\n })) as unknown as ProjectMember[];\n }\n\n /** Fetch a project member by project and user id, or null if not found. */\n async get(projectId: string, userId: string): Promise<ProjectMember | null> {\n const rows = (await this.db.list(this.tables.projectMembers, {\n where: and(\n eq(\n getTableColumns(this.tables.projectMembers)[\"projectId\"] as unknown as SQLWrapper,\n projectId,\n ),\n eq(getTableColumns(this.tables.projectMembers)[\"userId\"] as unknown as SQLWrapper, userId),\n ),\n limit: 1,\n })) as unknown as ProjectMember[];\n return rows[0] ?? null;\n }\n\n /** Set a user's role in a project, creating the membership if needed. */\n async set(\n projectId: string,\n userId: string,\n role: ProjectRole,\n source = \"manual\",\n ): Promise<ProjectMember> {\n const existing = await this.get(projectId, userId);\n if (existing) {\n return (await this.db.update(this.tables.projectMembers, existing.id, {\n role,\n source,\n })) as unknown as ProjectMember;\n }\n return (await this.db.insert(this.tables.projectMembers, {\n id: ulid(),\n projectId,\n userId,\n role,\n source,\n createdAt: new Date().toISOString(),\n })) as unknown as ProjectMember;\n }\n\n /** Remove a user from a project if they are a member. */\n async remove(projectId: string, userId: string): Promise<void> {\n const existing = await this.get(projectId, userId);\n if (existing) {\n await this.db.remove(this.tables.projectMembers, existing.id);\n }\n }\n\n /** Resolve a user's effective project role, honoring site-wide admins. */\n async effectiveRole(\n siteRole: SiteRole,\n projectId: string,\n userId: string,\n ): Promise<ProjectRole | null> {\n if (siteRole === \"admin\") {\n return \"admin\";\n }\n const member = await this.get(projectId, userId);\n if (member) {\n return member.role;\n }\n // Site viewers read every project; explicit membership always wins.\n if (siteRole === \"viewer\") {\n return \"viewer\";\n }\n return null;\n }\n}\n"],"mappings":";;;;;;;;AAcA,IAAa,cAAb,MAAyB;CAOJ;CANnB;;;;;CAKA,YACE,IACA,QACA;EAFiB,KAAA,KAAA;EAGjB,KAAK,SAAS,UAAU,EAAE,gBAAgB,GAAG,OAAO,eAAe;CACrE;;CAGA,MAAM,KAAK,WAA6C;EACtD,OAAQ,MAAM,KAAK,GAAG,KAAK,KAAK,OAAO,gBAAgB,EACrD,OAAO,GACL,gBAAgB,KAAK,OAAO,cAAc,CAAC,CAAC,cAC5C,SACF,EACF,CAAC;CACH;;CAGA,MAAM,IAAI,WAAmB,QAA+C;EAW1E,QAAO,MAVa,KAAK,GAAG,KAAK,KAAK,OAAO,gBAAgB;GAC3D,OAAO,IACL,GACE,gBAAgB,KAAK,OAAO,cAAc,CAAC,CAAC,cAC5C,SACF,GACA,GAAG,gBAAgB,KAAK,OAAO,cAAc,CAAC,CAAC,WAAoC,MAAM,CAC3F;GACA,OAAO;EACT,CAAC,EAAA,CACW,MAAM;CACpB;;CAGA,MAAM,IACJ,WACA,QACA,MACA,SAAS,UACe;EACxB,MAAM,WAAW,MAAM,KAAK,IAAI,WAAW,MAAM;EACjD,IAAI,UACF,OAAQ,MAAM,KAAK,GAAG,OAAO,KAAK,OAAO,gBAAgB,SAAS,IAAI;GACpE;GACA;EACF,CAAC;EAEH,OAAQ,MAAM,KAAK,GAAG,OAAO,KAAK,OAAO,gBAAgB;GACvD,IAAI,KAAK;GACT;GACA;GACA;GACA;GACA,4BAAW,IAAI,KAAK,EAAA,CAAE,YAAY;EACpC,CAAC;CACH;;CAGA,MAAM,OAAO,WAAmB,QAA+B;EAC7D,MAAM,WAAW,MAAM,KAAK,IAAI,WAAW,MAAM;EACjD,IAAI,UACF,MAAM,KAAK,GAAG,OAAO,KAAK,OAAO,gBAAgB,SAAS,EAAE;CAEhE;;CAGA,MAAM,cACJ,UACA,WACA,QAC6B;EAC7B,IAAI,aAAa,SACf,OAAO;EAET,MAAM,SAAS,MAAM,KAAK,IAAI,WAAW,MAAM;EAC/C,IAAI,QACF,OAAO,OAAO;EAGhB,IAAI,aAAa,UACf,OAAO;EAET,OAAO;CACT;AACF"}
|
|
@@ -0,0 +1,52 @@
|
|
|
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 { SecretInput } from "../utils/encrypt.mjs";
|
|
5
|
+
import { NotificationChannelRow } from "../schema/notification.mjs";
|
|
6
|
+
import { Table } from "drizzle-orm";
|
|
7
|
+
//#region src/models/notification-channel.d.ts
|
|
8
|
+
/** Tables required by {@link NotificationChannelModel}. */
|
|
9
|
+
interface NotificationChannelTables {
|
|
10
|
+
notificationChannels: Table;
|
|
11
|
+
}
|
|
12
|
+
/** Input for creating a notification channel. */
|
|
13
|
+
interface NotificationChannelCreateInput {
|
|
14
|
+
projectId: string | null;
|
|
15
|
+
provider: string;
|
|
16
|
+
config: Record<string, unknown>;
|
|
17
|
+
secret?: string;
|
|
18
|
+
events?: string[];
|
|
19
|
+
enabled?: boolean;
|
|
20
|
+
}
|
|
21
|
+
/** Data operations for notification channels. */
|
|
22
|
+
declare class NotificationChannelModel {
|
|
23
|
+
private readonly db;
|
|
24
|
+
private readonly tables;
|
|
25
|
+
private readonly secret?;
|
|
26
|
+
/**
|
|
27
|
+
* @param db - Database adapter.
|
|
28
|
+
* @param tables - Table handles (defaults to the adapter's tables).
|
|
29
|
+
* @param secret - Server secret for channel-secret encryption.
|
|
30
|
+
*/
|
|
31
|
+
constructor(db: DatabaseAdapter, tables?: NotificationChannelTables, secret?: SecretInput);
|
|
32
|
+
/** List channels for a project. */
|
|
33
|
+
list(projectId: string): Promise<NotificationChannelRow[]>;
|
|
34
|
+
/** List site-wide admin channels (`project_id IS NULL`). */
|
|
35
|
+
listSystem(): Promise<NotificationChannelRow[]>;
|
|
36
|
+
/** Create a channel (secret encrypted at rest when provided). */
|
|
37
|
+
create(input: NotificationChannelCreateInput): Promise<NotificationChannelRow>;
|
|
38
|
+
/** Fetch a channel by id, or null. */
|
|
39
|
+
get(id: string): Promise<NotificationChannelRow | null>;
|
|
40
|
+
/** Remove a channel by id. */
|
|
41
|
+
remove(id: string): Promise<void>;
|
|
42
|
+
/** Decrypt the secret for a stored row (in memory only, at send time). */
|
|
43
|
+
decryptSecret(row: NotificationChannelRow): string;
|
|
44
|
+
/** Decode the JSON-serialized event list of a channel. */
|
|
45
|
+
static eventsOf(row: NotificationChannelRow): string[];
|
|
46
|
+
/** Decode the JSON-serialized config of a channel. */
|
|
47
|
+
static configOf(row: NotificationChannelRow): Record<string, unknown>;
|
|
48
|
+
private column;
|
|
49
|
+
}
|
|
50
|
+
//#endregion
|
|
51
|
+
export { NotificationChannelCreateInput, NotificationChannelModel, NotificationChannelTables };
|
|
52
|
+
//# sourceMappingURL=notification-channel.d.mts.map
|