@storyshelf/core 0.3.2 → 0.5.4
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,23 @@
|
|
|
1
|
+
import __tsdown_shims_path from 'node:path';
|
|
2
|
+
import __tsdown_shims_url from 'node:url';
|
|
3
|
+
import { AdapterSetupContext } from "../adapter/metadata.mjs";
|
|
4
|
+
import { AdapterSetupResult, AdapterSetupSources, HookEntry } from "../adapter/setup.mjs";
|
|
5
|
+
import { Logger } from "pino";
|
|
6
|
+
//#region src/adapters/setup-runner.d.ts
|
|
7
|
+
/** Collect every available `setup` hook in deterministic order. */
|
|
8
|
+
declare function collectSetups(sources: AdapterSetupSources): HookEntry[];
|
|
9
|
+
/** Collect every available `teardown` hook in deterministic order. */
|
|
10
|
+
declare function collectTeardowns(sources: AdapterSetupSources): HookEntry[];
|
|
11
|
+
/**
|
|
12
|
+
* Bind the host logger on every adapter that accepts one, scoped by kind.
|
|
13
|
+
* Adapters without `setLogger` keep today's behavior (silent when no
|
|
14
|
+
* explicit `options.logger` was passed at construction).
|
|
15
|
+
*/
|
|
16
|
+
declare function bindAdapterLoggers(sources: AdapterSetupSources, logger: Logger): void;
|
|
17
|
+
/** Run setup hooks concurrently; failures are collected, never thrown. */
|
|
18
|
+
declare function runAdapterSetups(entries: HookEntry[], ctx: AdapterSetupContext, logger: Logger): Promise<AdapterSetupResult>;
|
|
19
|
+
/** Run teardown hooks concurrently; failures are collected, never thrown. */
|
|
20
|
+
declare function runAdapterTeardowns(entries: HookEntry[], ctx: AdapterSetupContext, logger: Logger): Promise<AdapterSetupResult>;
|
|
21
|
+
//#endregion
|
|
22
|
+
export { bindAdapterLoggers, collectSetups, collectTeardowns, runAdapterSetups, runAdapterTeardowns };
|
|
23
|
+
//# sourceMappingURL=setup-runner.d.mts.map
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import "node:path";
|
|
2
|
+
import "node:url";
|
|
3
|
+
import.meta.url;
|
|
4
|
+
import { sanitizeErrorText } from "../utils/redact.mjs";
|
|
5
|
+
//#region src/adapters/setup-runner.ts
|
|
6
|
+
function hookEntry(adapter, hook) {
|
|
7
|
+
const lifecycle = adapter.lifecycle;
|
|
8
|
+
if (!lifecycle) return null;
|
|
9
|
+
const fn = lifecycle[hook];
|
|
10
|
+
const { category, kind, name } = adapter.metadata;
|
|
11
|
+
return {
|
|
12
|
+
category,
|
|
13
|
+
kind,
|
|
14
|
+
name,
|
|
15
|
+
run: async (ctx) => {
|
|
16
|
+
await fn(ctx);
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
function pushHook(entries, adapter, hook) {
|
|
21
|
+
if (!adapter) return;
|
|
22
|
+
const entry = hookEntry(adapter, hook);
|
|
23
|
+
if (entry) entries.push(entry);
|
|
24
|
+
}
|
|
25
|
+
function pushAll(entries, sources, hook) {
|
|
26
|
+
pushHook(entries, sources.database, hook);
|
|
27
|
+
pushHook(entries, sources.storage, hook);
|
|
28
|
+
pushHook(entries, sources.captureRunner, hook);
|
|
29
|
+
pushHook(entries, sources.captureQueue, hook);
|
|
30
|
+
for (const provider of sources.gitHosts ?? []) pushHook(entries, provider, hook);
|
|
31
|
+
for (const notifier of sources.notifiers ?? []) pushHook(entries, notifier, hook);
|
|
32
|
+
pushHook(entries, sources.emailSender, hook);
|
|
33
|
+
}
|
|
34
|
+
/** Collect every available `setup` hook in deterministic order. */
|
|
35
|
+
function collectSetups(sources) {
|
|
36
|
+
const entries = [];
|
|
37
|
+
pushAll(entries, sources, "setup");
|
|
38
|
+
return entries;
|
|
39
|
+
}
|
|
40
|
+
/** Collect every available `teardown` hook in deterministic order. */
|
|
41
|
+
function collectTeardowns(sources) {
|
|
42
|
+
const entries = [];
|
|
43
|
+
pushAll(entries, sources, "teardown");
|
|
44
|
+
return entries;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Bind the host logger on every adapter that accepts one, scoped by kind.
|
|
48
|
+
* Adapters without `setLogger` keep today's behavior (silent when no
|
|
49
|
+
* explicit `options.logger` was passed at construction).
|
|
50
|
+
*/
|
|
51
|
+
function bindAdapterLoggers(sources, logger) {
|
|
52
|
+
const adapters = [
|
|
53
|
+
sources.database,
|
|
54
|
+
sources.storage,
|
|
55
|
+
sources.captureRunner,
|
|
56
|
+
sources.captureQueue,
|
|
57
|
+
...sources.gitHosts ?? [],
|
|
58
|
+
...sources.notifiers ?? [],
|
|
59
|
+
sources.emailSender
|
|
60
|
+
];
|
|
61
|
+
for (const adapter of adapters) adapter?.setLogger?.(logger.child({ component: adapter.metadata.kind }));
|
|
62
|
+
}
|
|
63
|
+
function messageOf(reason) {
|
|
64
|
+
const text = reason instanceof Error ? reason.message : String(reason);
|
|
65
|
+
return sanitizeErrorText(text, 500);
|
|
66
|
+
}
|
|
67
|
+
/** Budgets that trigger slow-hook warnings (observe only, never abort). */
|
|
68
|
+
const SLOW_SETUP_MS = 1e4;
|
|
69
|
+
const SLOW_TEARDOWN_MS = 5e3;
|
|
70
|
+
/** Run one hook with start/done logging and a slow-hook warning. */
|
|
71
|
+
async function runHook(entry, ctx, logger, slowMs) {
|
|
72
|
+
const { category, kind, name } = entry;
|
|
73
|
+
const started = Date.now();
|
|
74
|
+
logger.info({
|
|
75
|
+
category,
|
|
76
|
+
kind,
|
|
77
|
+
name
|
|
78
|
+
}, "adapter hook start");
|
|
79
|
+
try {
|
|
80
|
+
await entry.run(ctx);
|
|
81
|
+
} finally {
|
|
82
|
+
const durationMs = Date.now() - started;
|
|
83
|
+
logger.info({
|
|
84
|
+
category,
|
|
85
|
+
kind,
|
|
86
|
+
name,
|
|
87
|
+
durationMs
|
|
88
|
+
}, "adapter hook done");
|
|
89
|
+
if (durationMs > slowMs) logger.warn({
|
|
90
|
+
category,
|
|
91
|
+
kind,
|
|
92
|
+
name,
|
|
93
|
+
durationMs,
|
|
94
|
+
slowMs
|
|
95
|
+
}, "slow adapter hook");
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
function toFailure(entry, reason) {
|
|
99
|
+
return {
|
|
100
|
+
category: entry.category,
|
|
101
|
+
kind: entry.kind,
|
|
102
|
+
name: entry.name,
|
|
103
|
+
error: messageOf(reason)
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
function toResult(entries, outcomes, logger, verb) {
|
|
107
|
+
const failures = [];
|
|
108
|
+
for (const [index, entry] of entries.entries()) {
|
|
109
|
+
const outcome = outcomes[index];
|
|
110
|
+
if (outcome?.status === "rejected") failures.push(toFailure(entry, outcome.reason));
|
|
111
|
+
}
|
|
112
|
+
if (failures.length > 0) logger.error({ failures }, `adapter ${verb} failed`);
|
|
113
|
+
else logger.info({ count: entries.length }, `adapters ${verb}d`);
|
|
114
|
+
return {
|
|
115
|
+
ok: failures.length === 0,
|
|
116
|
+
failures
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
/** One run of collected hooks with start/done logging and hung visibility. */
|
|
120
|
+
async function runHooks(entries, ctx, logger, verb, slowMs) {
|
|
121
|
+
let settled = false;
|
|
122
|
+
const timer = setTimeout(() => {
|
|
123
|
+
if (!settled) logger.warn({
|
|
124
|
+
count: entries.length,
|
|
125
|
+
slowMs
|
|
126
|
+
}, `adapters ${verb} still running`);
|
|
127
|
+
}, slowMs);
|
|
128
|
+
timer.unref?.();
|
|
129
|
+
try {
|
|
130
|
+
return toResult(entries, await Promise.allSettled(entries.map(async (entry) => {
|
|
131
|
+
await runHook(entry, ctx, logger, slowMs);
|
|
132
|
+
})), logger, verb);
|
|
133
|
+
} finally {
|
|
134
|
+
settled = true;
|
|
135
|
+
clearTimeout(timer);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
/** Run setup hooks concurrently; failures are collected, never thrown. */
|
|
139
|
+
async function runAdapterSetups(entries, ctx, logger) {
|
|
140
|
+
return await runHooks(entries, ctx, logger, "setup", SLOW_SETUP_MS);
|
|
141
|
+
}
|
|
142
|
+
/** Run teardown hooks concurrently; failures are collected, never thrown. */
|
|
143
|
+
async function runAdapterTeardowns(entries, ctx, logger) {
|
|
144
|
+
return await runHooks(entries, ctx, logger, "teardown", SLOW_TEARDOWN_MS);
|
|
145
|
+
}
|
|
146
|
+
//#endregion
|
|
147
|
+
export { bindAdapterLoggers, collectSetups, collectTeardowns, runAdapterSetups, runAdapterTeardowns };
|
|
148
|
+
|
|
149
|
+
//# sourceMappingURL=setup-runner.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setup-runner.mjs","names":[],"sources":["../../src/adapters/setup-runner.ts"],"sourcesContent":["import type { Logger } from \"pino\";\nimport { sanitizeErrorText } from \"../utils/redact.ts\";\nimport type { AdapterLifecycle, AdapterMetadata, AdapterSetupContext } from \"./metadata.ts\";\nimport type {\n AdapterSetupFailure,\n AdapterSetupResult,\n AdapterSetupSources,\n HookEntry,\n} from \"./setup.ts\";\n\ninterface LifecycleCarrier {\n readonly metadata: AdapterMetadata;\n readonly lifecycle?: AdapterLifecycle;\n}\n\nfunction hookEntry(adapter: LifecycleCarrier, hook: \"setup\" | \"teardown\"): HookEntry | null {\n const lifecycle = adapter.lifecycle;\n if (!lifecycle) {\n return null;\n }\n const fn = lifecycle[hook];\n const { category, kind, name } = adapter.metadata;\n return {\n category,\n kind,\n name,\n run: async (ctx) => {\n await fn(ctx);\n },\n };\n}\n\nfunction pushHook(\n entries: HookEntry[],\n adapter: LifecycleCarrier | undefined,\n hook: \"setup\" | \"teardown\",\n): void {\n if (!adapter) {\n return;\n }\n const entry = hookEntry(adapter, hook);\n if (entry) {\n entries.push(entry);\n }\n}\n\nfunction pushAll(\n entries: HookEntry[],\n sources: AdapterSetupSources,\n hook: \"setup\" | \"teardown\",\n): void {\n pushHook(entries, sources.database, hook);\n pushHook(entries, sources.storage, hook);\n pushHook(entries, sources.captureRunner, hook);\n pushHook(entries, sources.captureQueue, hook);\n for (const provider of sources.gitHosts ?? []) {\n pushHook(entries, provider, hook);\n }\n for (const notifier of sources.notifiers ?? []) {\n pushHook(entries, notifier, hook);\n }\n pushHook(entries, sources.emailSender, hook);\n}\n\n/** Collect every available `setup` hook in deterministic order. */\nexport function collectSetups(sources: AdapterSetupSources): HookEntry[] {\n const entries: HookEntry[] = [];\n pushAll(entries, sources, \"setup\");\n return entries;\n}\n\n/** Collect every available `teardown` hook in deterministic order. */\nexport function collectTeardowns(sources: AdapterSetupSources): HookEntry[] {\n const entries: HookEntry[] = [];\n pushAll(entries, sources, \"teardown\");\n return entries;\n}\n\ninterface LoggerCarrier {\n readonly metadata: AdapterMetadata;\n readonly setLogger?: (logger: Logger) => void;\n}\n\n/**\n * Bind the host logger on every adapter that accepts one, scoped by kind.\n * Adapters without `setLogger` keep today's behavior (silent when no\n * explicit `options.logger` was passed at construction).\n */\nexport function bindAdapterLoggers(sources: AdapterSetupSources, logger: Logger): void {\n const adapters: ReadonlyArray<LoggerCarrier | undefined> = [\n sources.database,\n sources.storage,\n sources.captureRunner,\n sources.captureQueue,\n ...(sources.gitHosts ?? []),\n ...(sources.notifiers ?? []),\n sources.emailSender,\n ];\n for (const adapter of adapters) {\n adapter?.setLogger?.(logger.child({ component: adapter.metadata.kind }));\n }\n}\n\nfunction messageOf(reason: unknown): string {\n const text = reason instanceof Error ? reason.message : String(reason);\n return sanitizeErrorText(text, 500);\n}\n\n/** Budgets that trigger slow-hook warnings (observe only, never abort). */\nconst SLOW_SETUP_MS = 10_000;\nconst SLOW_TEARDOWN_MS = 5_000;\n\n/** Run one hook with start/done logging and a slow-hook warning. */\nasync function runHook(\n entry: HookEntry,\n ctx: AdapterSetupContext,\n logger: Logger,\n slowMs: number,\n): Promise<void> {\n const { category, kind, name } = entry;\n const started = Date.now();\n logger.info({ category, kind, name }, \"adapter hook start\");\n try {\n await entry.run(ctx);\n } finally {\n const durationMs = Date.now() - started;\n logger.info({ category, kind, name, durationMs }, \"adapter hook done\");\n if (durationMs > slowMs) {\n logger.warn({ category, kind, name, durationMs, slowMs }, \"slow adapter hook\");\n }\n }\n}\n\nfunction toFailure(entry: HookEntry, reason: unknown): AdapterSetupFailure {\n return { category: entry.category, kind: entry.kind, name: entry.name, error: messageOf(reason) };\n}\n\nfunction toResult(\n entries: HookEntry[],\n outcomes: PromiseSettledResult<void>[],\n logger: Logger,\n verb: string,\n): AdapterSetupResult {\n const failures: AdapterSetupFailure[] = [];\n for (const [index, entry] of entries.entries()) {\n const outcome: PromiseSettledResult<void> | undefined = outcomes[index];\n if (outcome?.status === \"rejected\") {\n failures.push(toFailure(entry, outcome.reason));\n }\n }\n if (failures.length > 0) {\n logger.error({ failures }, `adapter ${verb} failed`);\n } else {\n logger.info({ count: entries.length }, `adapters ${verb}d`);\n }\n return { ok: failures.length === 0, failures };\n}\n\n/** One run of collected hooks with start/done logging and hung visibility. */\nasync function runHooks(\n entries: HookEntry[],\n ctx: AdapterSetupContext,\n logger: Logger,\n verb: string,\n slowMs: number,\n): Promise<AdapterSetupResult> {\n let settled = false;\n const timer = setTimeout(() => {\n if (!settled) {\n logger.warn({ count: entries.length, slowMs }, `adapters ${verb} still running`);\n }\n }, slowMs);\n timer.unref?.();\n try {\n const outcomes = await Promise.allSettled(\n entries.map(async (entry) => {\n await runHook(entry, ctx, logger, slowMs);\n }),\n );\n return toResult(entries, outcomes, logger, verb);\n } finally {\n settled = true;\n clearTimeout(timer);\n }\n}\n\n/** Run setup hooks concurrently; failures are collected, never thrown. */\nexport async function runAdapterSetups(\n entries: HookEntry[],\n ctx: AdapterSetupContext,\n logger: Logger,\n): Promise<AdapterSetupResult> {\n return await runHooks(entries, ctx, logger, \"setup\", SLOW_SETUP_MS);\n}\n\n/** Run teardown hooks concurrently; failures are collected, never thrown. */\nexport async function runAdapterTeardowns(\n entries: HookEntry[],\n ctx: AdapterSetupContext,\n logger: Logger,\n): Promise<AdapterSetupResult> {\n return await runHooks(entries, ctx, logger, \"teardown\", SLOW_TEARDOWN_MS);\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import __tsdown_shims_path from 'node:path';
|
|
2
|
+
import __tsdown_shims_url from 'node:url';
|
|
3
|
+
import { AdapterSetupFailure, AdapterSetupSources } from "../adapter/setup.mjs";
|
|
4
|
+
//#region src/adapters/validate.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* Validate one adapter against its slot. Returns human-readable reasons
|
|
7
|
+
* (empty when valid). Never throws — callers decide fail-fast vs collect.
|
|
8
|
+
*/
|
|
9
|
+
declare function validateAdapter(adapter: unknown, expectedCategory: string, slot: string): string[];
|
|
10
|
+
/**
|
|
11
|
+
* Validate the auth engine shape (auth is optional — undefined is valid).
|
|
12
|
+
* Returns human-readable reasons, empty when sound.
|
|
13
|
+
*/
|
|
14
|
+
declare function validateAuth(auth?: unknown): string[];
|
|
15
|
+
/**
|
|
16
|
+
* Validate every adapter in the boot sources. Returns per-adapter failures
|
|
17
|
+
* (empty when the whole assembly is sound).
|
|
18
|
+
*/
|
|
19
|
+
declare function validateAdapterSources(sources: AdapterSetupSources): AdapterSetupFailure[];
|
|
20
|
+
/**
|
|
21
|
+
* Validate a full boot assembly: adapters plus the optional auth engine.
|
|
22
|
+
* App assembly calls this — `ShelfOptions` carries `auth` alongside the
|
|
23
|
+
* adapter slots, and an invalid engine fails boot like any adapter.
|
|
24
|
+
*/
|
|
25
|
+
declare function validateBootAssembly(sources: AdapterSetupSources & {
|
|
26
|
+
auth?: unknown;
|
|
27
|
+
}): AdapterSetupFailure[];
|
|
28
|
+
//#endregion
|
|
29
|
+
export { validateAdapter, validateAdapterSources, validateAuth, validateBootAssembly };
|
|
30
|
+
//# sourceMappingURL=validate.d.mts.map
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
import "node:path";
|
|
2
|
+
import "node:url";
|
|
3
|
+
import.meta.url;
|
|
4
|
+
import { REQUIRED_TABLE_KEYS } from "../adapter/database.mjs";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
//#region src/adapters/validate.ts
|
|
7
|
+
/**
|
|
8
|
+
* Boot-time adapter validation: fail fast on third-party adapters that
|
|
9
|
+
* cannot satisfy their slot contract.
|
|
10
|
+
*
|
|
11
|
+
* Types alone cannot stop a bad adapter — this module checks shape at
|
|
12
|
+
* runtime before any I/O runs. `createShelfApp` throws synchronously;
|
|
13
|
+
* the lifecycle runner returns failures without executing hooks.
|
|
14
|
+
*/
|
|
15
|
+
const metadataSchema = z.object({
|
|
16
|
+
name: z.string().min(1),
|
|
17
|
+
version: z.string().min(1),
|
|
18
|
+
kind: z.string().min(1),
|
|
19
|
+
category: z.string().min(1)
|
|
20
|
+
});
|
|
21
|
+
const DATABASE_METHODS = [
|
|
22
|
+
"insert",
|
|
23
|
+
"update",
|
|
24
|
+
"get",
|
|
25
|
+
"remove",
|
|
26
|
+
"list",
|
|
27
|
+
"count",
|
|
28
|
+
"all"
|
|
29
|
+
];
|
|
30
|
+
const STORAGE_METHODS = [
|
|
31
|
+
"read",
|
|
32
|
+
"write",
|
|
33
|
+
"delete",
|
|
34
|
+
"exists",
|
|
35
|
+
"list",
|
|
36
|
+
"writeStream",
|
|
37
|
+
"readStream"
|
|
38
|
+
];
|
|
39
|
+
const RUNNER_METHODS = ["render", "cancel"];
|
|
40
|
+
const QUEUE_METHODS = [
|
|
41
|
+
"enqueue",
|
|
42
|
+
"status",
|
|
43
|
+
"active",
|
|
44
|
+
"recent"
|
|
45
|
+
];
|
|
46
|
+
/** Unknown value narrowed to a property bag for shape checks. */
|
|
47
|
+
function asRecord(value) {
|
|
48
|
+
return typeof value === "object" && value !== null ? value : null;
|
|
49
|
+
}
|
|
50
|
+
/** String field of a record, or null when absent/non-string. */
|
|
51
|
+
function stringField(record, key) {
|
|
52
|
+
const value = record?.[key];
|
|
53
|
+
return typeof value === "string" ? value : null;
|
|
54
|
+
}
|
|
55
|
+
/** Reasons the adapter identity fails for the expected slot. */
|
|
56
|
+
function metadataReasons(adapter, expected) {
|
|
57
|
+
const parsed = metadataSchema.safeParse(adapter["metadata"]);
|
|
58
|
+
if (!parsed.success) return [`invalid metadata: ${parsed.error.issues.map((i) => i.path.join(".")).join(",")}`];
|
|
59
|
+
const category = parsed.data.category;
|
|
60
|
+
return category === expected ? [] : [`category "${category}" does not match slot "${expected}"`];
|
|
61
|
+
}
|
|
62
|
+
/** Reasons the optional lifecycle violates the all-or-nothing rule. */
|
|
63
|
+
function lifecycleReasons(adapter) {
|
|
64
|
+
const lifecycle = adapter["lifecycle"];
|
|
65
|
+
if (lifecycle === void 0) return [];
|
|
66
|
+
const hooks = asRecord(lifecycle);
|
|
67
|
+
if (!hooks) return ["lifecycle must be an object with setup/teardown/health"];
|
|
68
|
+
const missing = [
|
|
69
|
+
"setup",
|
|
70
|
+
"teardown",
|
|
71
|
+
"health"
|
|
72
|
+
].filter((hook) => typeof hooks[hook] !== "function");
|
|
73
|
+
return missing.length > 0 ? [`partial lifecycle (missing: ${missing.join(",")})`] : [];
|
|
74
|
+
}
|
|
75
|
+
/** Reasons the adapter lacks required slot methods. */
|
|
76
|
+
function methodReasons(adapter, methods) {
|
|
77
|
+
const missing = methods.filter((method) => typeof adapter[method] !== "function");
|
|
78
|
+
return missing.length > 0 ? [`missing methods: ${missing.join(",")}`] : [];
|
|
79
|
+
}
|
|
80
|
+
/** Reasons a database adapter lacks its table map. */
|
|
81
|
+
function tableReasons(adapter) {
|
|
82
|
+
const tables = asRecord(adapter["tables"]);
|
|
83
|
+
if (!tables) return ["missing tables map"];
|
|
84
|
+
const missing = REQUIRED_TABLE_KEYS.filter((table) => tables[table] === void 0);
|
|
85
|
+
return missing.length > 0 ? [`missing tables: ${missing.join(",")}`] : [];
|
|
86
|
+
}
|
|
87
|
+
/** Reasons a queue adapter has a partial poll extension. */
|
|
88
|
+
function queuePollReasons(record) {
|
|
89
|
+
const present = [
|
|
90
|
+
"poll",
|
|
91
|
+
"ack",
|
|
92
|
+
"nack"
|
|
93
|
+
].filter((method) => record[method] !== void 0);
|
|
94
|
+
if (present.length === 0 || present.length === 3) return [
|
|
95
|
+
"poll",
|
|
96
|
+
"ack",
|
|
97
|
+
"nack"
|
|
98
|
+
].filter((method) => record[method] !== void 0 && typeof record[method] !== "function").map((method) => `queue poll extension "${method}" must be a function`);
|
|
99
|
+
return [`partial poll extension (present: ${present.join(",")}; need poll+ack+nack)`];
|
|
100
|
+
}
|
|
101
|
+
/** Whether a metadata schema looks like a zod schema (has parse). */
|
|
102
|
+
function hasSchemaParse(provider) {
|
|
103
|
+
return typeof (asRecord(asRecord(provider)?.["metadata"])?.["schema"])?.parse === "function";
|
|
104
|
+
}
|
|
105
|
+
/** Reasons a notifier provider lacks its descriptor shape. */
|
|
106
|
+
function notifierReasons(provider) {
|
|
107
|
+
const record = asRecord(provider);
|
|
108
|
+
if (!record) return ["notifier provider must be an object"];
|
|
109
|
+
const reasons = metadataReasons(record, "notifier");
|
|
110
|
+
if (typeof record["create"] !== "function") reasons.push("missing methods: create");
|
|
111
|
+
if (!hasSchemaParse(provider)) reasons.push("metadata.schema must be a zod schema");
|
|
112
|
+
return reasons;
|
|
113
|
+
}
|
|
114
|
+
/** Reasons an email sender lacks its transport shape. */
|
|
115
|
+
function emailSenderReasons(sender) {
|
|
116
|
+
const record = asRecord(sender);
|
|
117
|
+
if (!record) return ["email sender must be an object"];
|
|
118
|
+
const reasons = metadataReasons(record, "notifier");
|
|
119
|
+
if (typeof record["send"] !== "function") reasons.push("missing methods: send");
|
|
120
|
+
return reasons;
|
|
121
|
+
}
|
|
122
|
+
/** Reasons a git provider lacks its descriptor shape. */
|
|
123
|
+
function gitReasons(provider) {
|
|
124
|
+
const record = asRecord(provider);
|
|
125
|
+
if (!record) return ["git provider must be an object"];
|
|
126
|
+
const reasons = metadataReasons(record, "git-host");
|
|
127
|
+
if (typeof record["create"] !== "function") reasons.push("missing methods: create");
|
|
128
|
+
if (!hasSchemaParse(provider)) reasons.push("metadata.schema must be a zod schema");
|
|
129
|
+
return reasons;
|
|
130
|
+
}
|
|
131
|
+
/** Required methods for a slot category. */
|
|
132
|
+
function slotMethods(category) {
|
|
133
|
+
if (category === "database") return DATABASE_METHODS;
|
|
134
|
+
if (category === "storage") return STORAGE_METHODS;
|
|
135
|
+
if (category === "capture-runner") return RUNNER_METHODS;
|
|
136
|
+
return QUEUE_METHODS;
|
|
137
|
+
}
|
|
138
|
+
/** Extra per-slot reasons (table map for databases, poll rule for queues). */
|
|
139
|
+
function slotExtraReasons(record, category) {
|
|
140
|
+
if (category === "database") return tableReasons(record);
|
|
141
|
+
return category === "capture-queue" ? queuePollReasons(record) : [];
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Validate one adapter against its slot. Returns human-readable reasons
|
|
145
|
+
* (empty when valid). Never throws — callers decide fail-fast vs collect.
|
|
146
|
+
*/
|
|
147
|
+
function validateAdapter(adapter, expectedCategory, slot) {
|
|
148
|
+
const record = asRecord(adapter);
|
|
149
|
+
if (!record) return [`${slot} adapter must be an object`];
|
|
150
|
+
const reasons = metadataReasons(record, expectedCategory).map((r) => `${slot}: ${r}`);
|
|
151
|
+
const extra = [
|
|
152
|
+
...lifecycleReasons(record),
|
|
153
|
+
...methodReasons(record, slotMethods(expectedCategory)),
|
|
154
|
+
...slotExtraReasons(record, expectedCategory)
|
|
155
|
+
];
|
|
156
|
+
for (const reason of extra) reasons.push(`${slot}: ${reason}`);
|
|
157
|
+
return reasons;
|
|
158
|
+
}
|
|
159
|
+
/** Auth surface methods every engine must expose. */
|
|
160
|
+
const AUTH_METHODS = [
|
|
161
|
+
"handler",
|
|
162
|
+
"loginMethods",
|
|
163
|
+
"setup",
|
|
164
|
+
"issueInvite",
|
|
165
|
+
"verifyInvite",
|
|
166
|
+
"acceptInvite",
|
|
167
|
+
"passkeysEnabled",
|
|
168
|
+
"listSessions",
|
|
169
|
+
"listPasskeys",
|
|
170
|
+
"hasPassword",
|
|
171
|
+
"setDisabled",
|
|
172
|
+
"check",
|
|
173
|
+
"createSession",
|
|
174
|
+
"destroySession"
|
|
175
|
+
];
|
|
176
|
+
/** Reasons a method-id list is unusable (blank or duplicate ids). */
|
|
177
|
+
function methodIdReasons(ids) {
|
|
178
|
+
const reasons = [];
|
|
179
|
+
if (ids.some((id) => id.length === 0)) reasons.push("login method with blank id");
|
|
180
|
+
if (new Set(ids).size !== ids.length) reasons.push("duplicate login method ids");
|
|
181
|
+
return reasons;
|
|
182
|
+
}
|
|
183
|
+
/** Reasons login method descriptors are unusable (duplicate/blank ids). */
|
|
184
|
+
function loginMethodReasons(auth) {
|
|
185
|
+
let ids;
|
|
186
|
+
try {
|
|
187
|
+
ids = auth.loginMethods().map((method) => method.id);
|
|
188
|
+
} catch (error) {
|
|
189
|
+
return [`loginMethods() threw: ${error instanceof Error ? error.message : String(error)}`];
|
|
190
|
+
}
|
|
191
|
+
return methodIdReasons(ids);
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Validate the auth engine shape (auth is optional — undefined is valid).
|
|
195
|
+
* Returns human-readable reasons, empty when sound.
|
|
196
|
+
*/
|
|
197
|
+
function validateAuth(auth) {
|
|
198
|
+
if (auth === void 0) return [];
|
|
199
|
+
const record = asRecord(auth);
|
|
200
|
+
if (!record) return ["auth must be an object"];
|
|
201
|
+
const reasons = methodReasons(record, AUTH_METHODS).map((reason) => `auth: ${reason}`);
|
|
202
|
+
if (reasons.length === 0) for (const reason of loginMethodReasons(auth)) reasons.push(`auth: ${reason}`);
|
|
203
|
+
return reasons;
|
|
204
|
+
}
|
|
205
|
+
/** Failure entry for an invalid auth engine. */
|
|
206
|
+
function checkAuth(auth) {
|
|
207
|
+
const reasons = validateAuth(auth);
|
|
208
|
+
if (reasons.length === 0) return [];
|
|
209
|
+
return [{
|
|
210
|
+
category: "auth",
|
|
211
|
+
kind: "auth",
|
|
212
|
+
name: "auth",
|
|
213
|
+
error: reasons.join("; ")
|
|
214
|
+
}];
|
|
215
|
+
}
|
|
216
|
+
/** Failures for git-host or notifier provider lists. */
|
|
217
|
+
function checkProviders(checks) {
|
|
218
|
+
const failures = [];
|
|
219
|
+
for (const check of checks) {
|
|
220
|
+
const reasons = check.kinds === "git" ? gitReasons(check.provider) : notifierReasons(check.provider);
|
|
221
|
+
if (reasons.length === 0) continue;
|
|
222
|
+
const slot = check.kinds === "git" ? `gitHosts[${check.index}]` : `notifiers[${check.index}]`;
|
|
223
|
+
const category = check.kinds === "git" ? "git-host" : "notifier";
|
|
224
|
+
const metadata = asRecord(asRecord(check.provider)?.["metadata"]);
|
|
225
|
+
failures.push({
|
|
226
|
+
category,
|
|
227
|
+
kind: stringField(metadata, "kind") ?? "unknown",
|
|
228
|
+
name: stringField(metadata, "name") ?? slot,
|
|
229
|
+
error: reasons.map((reason) => `${slot}: ${reason}`).join("; ")
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
return failures;
|
|
233
|
+
}
|
|
234
|
+
/** Failure for the email sender slot. */
|
|
235
|
+
function checkEmailSender(sender) {
|
|
236
|
+
if (sender === void 0) return [];
|
|
237
|
+
const reasons = emailSenderReasons(sender);
|
|
238
|
+
if (reasons.length === 0) return [];
|
|
239
|
+
const metadata = asRecord(asRecord(sender)?.["metadata"]);
|
|
240
|
+
return [{
|
|
241
|
+
category: "notifier",
|
|
242
|
+
kind: stringField(metadata, "kind") ?? "unknown",
|
|
243
|
+
name: stringField(metadata, "name") ?? "emailSender",
|
|
244
|
+
error: reasons.map((reason) => `emailSender: ${reason}`).join("; ")
|
|
245
|
+
}];
|
|
246
|
+
}
|
|
247
|
+
/** Failures for the core database/storage/runner/queue slots. */
|
|
248
|
+
function checkSlots(checks) {
|
|
249
|
+
const failures = [];
|
|
250
|
+
for (const check of checks) {
|
|
251
|
+
const reasons = validateAdapter(check.adapter, check.category, check.slot);
|
|
252
|
+
if (reasons.length === 0) continue;
|
|
253
|
+
const metadata = asRecord(asRecord(check.adapter)?.["metadata"]);
|
|
254
|
+
failures.push({
|
|
255
|
+
category: check.category,
|
|
256
|
+
kind: stringField(metadata, "kind") ?? "unknown",
|
|
257
|
+
name: stringField(metadata, "name") ?? check.slot,
|
|
258
|
+
error: reasons.join("; ")
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
return failures;
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Validate every adapter in the boot sources. Returns per-adapter failures
|
|
265
|
+
* (empty when the whole assembly is sound).
|
|
266
|
+
*/
|
|
267
|
+
function validateAdapterSources(sources) {
|
|
268
|
+
const checks = [{
|
|
269
|
+
adapter: sources.database,
|
|
270
|
+
category: "database",
|
|
271
|
+
slot: "database"
|
|
272
|
+
}, {
|
|
273
|
+
adapter: sources.storage,
|
|
274
|
+
category: "storage",
|
|
275
|
+
slot: "storage"
|
|
276
|
+
}];
|
|
277
|
+
if (sources.captureRunner !== void 0) checks.push({
|
|
278
|
+
adapter: sources.captureRunner,
|
|
279
|
+
category: "capture-runner",
|
|
280
|
+
slot: "captureRunner"
|
|
281
|
+
});
|
|
282
|
+
if (sources.captureQueue !== void 0) checks.push({
|
|
283
|
+
adapter: sources.captureQueue,
|
|
284
|
+
category: "capture-queue",
|
|
285
|
+
slot: "captureQueue"
|
|
286
|
+
});
|
|
287
|
+
const providers = [...(sources.gitHosts ?? []).map((provider, index) => ({
|
|
288
|
+
provider,
|
|
289
|
+
index,
|
|
290
|
+
kinds: "git"
|
|
291
|
+
})), ...(sources.notifiers ?? []).map((provider, index) => ({
|
|
292
|
+
provider,
|
|
293
|
+
index,
|
|
294
|
+
kinds: "notifier"
|
|
295
|
+
}))];
|
|
296
|
+
return [
|
|
297
|
+
...checkSlots(checks),
|
|
298
|
+
...checkProviders(providers),
|
|
299
|
+
...checkEmailSender(sources.emailSender)
|
|
300
|
+
];
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Validate a full boot assembly: adapters plus the optional auth engine.
|
|
304
|
+
* App assembly calls this — `ShelfOptions` carries `auth` alongside the
|
|
305
|
+
* adapter slots, and an invalid engine fails boot like any adapter.
|
|
306
|
+
*/
|
|
307
|
+
function validateBootAssembly(sources) {
|
|
308
|
+
return [...validateAdapterSources(sources), ...checkAuth(sources.auth)];
|
|
309
|
+
}
|
|
310
|
+
//#endregion
|
|
311
|
+
export { validateAdapter, validateAdapterSources, validateAuth, validateBootAssembly };
|
|
312
|
+
|
|
313
|
+
//# sourceMappingURL=validate.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate.mjs","names":[],"sources":["../../src/adapters/validate.ts"],"sourcesContent":["/**\n * Boot-time adapter validation: fail fast on third-party adapters that\n * cannot satisfy their slot contract.\n *\n * Types alone cannot stop a bad adapter — this module checks shape at\n * runtime before any I/O runs. `createShelfApp` throws synchronously;\n * the lifecycle runner returns failures without executing hooks.\n */\nimport { z } from \"zod\";\nimport type { Auth } from \"../auth.ts\";\nimport { REQUIRED_TABLE_KEYS } from \"./database.ts\";\nimport type { AdapterSetupFailure, AdapterSetupSources } from \"./setup.ts\";\n\nconst metadataSchema = z.object({\n name: z.string().min(1),\n version: z.string().min(1),\n kind: z.string().min(1),\n category: z.string().min(1),\n});\n\nconst DATABASE_METHODS = [\"insert\", \"update\", \"get\", \"remove\", \"list\", \"count\", \"all\"];\n\nconst STORAGE_METHODS = [\"read\", \"write\", \"delete\", \"exists\", \"list\", \"writeStream\", \"readStream\"];\n\nconst RUNNER_METHODS = [\"render\", \"cancel\"];\n\nconst QUEUE_METHODS = [\"enqueue\", \"status\", \"active\", \"recent\"];\n\n/** Unknown value narrowed to a property bag for shape checks. */\nfunction asRecord(value: unknown): Record<string, unknown> | null {\n return typeof value === \"object\" && value !== null ? (value as Record<string, unknown>) : null;\n}\n\n/** String field of a record, or null when absent/non-string. */\nfunction stringField(record: Record<string, unknown> | null, key: string): string | null {\n const value = record?.[key];\n return typeof value === \"string\" ? value : null;\n}\n\n/** Reasons the adapter identity fails for the expected slot. */\nfunction metadataReasons(adapter: Record<string, unknown>, expected: string): string[] {\n const parsed = metadataSchema.safeParse(adapter[\"metadata\"]);\n if (!parsed.success) {\n return [`invalid metadata: ${parsed.error.issues.map((i) => i.path.join(\".\")).join(\",\")}`];\n }\n const category = (parsed.data as { category: string }).category;\n return category === expected ? [] : [`category \"${category}\" does not match slot \"${expected}\"`];\n}\n\n/** Reasons the optional lifecycle violates the all-or-nothing rule. */\nfunction lifecycleReasons(adapter: Record<string, unknown>): string[] {\n const lifecycle = adapter[\"lifecycle\"];\n if (lifecycle === undefined) {\n return [];\n }\n const hooks = asRecord(lifecycle);\n if (!hooks) {\n return [\"lifecycle must be an object with setup/teardown/health\"];\n }\n const missing = [\"setup\", \"teardown\", \"health\"].filter(\n (hook) => typeof hooks[hook] !== \"function\",\n );\n return missing.length > 0 ? [`partial lifecycle (missing: ${missing.join(\",\")})`] : [];\n}\n\n/** Reasons the adapter lacks required slot methods. */\nfunction methodReasons(adapter: Record<string, unknown>, methods: string[]): string[] {\n const missing = methods.filter((method) => typeof adapter[method] !== \"function\");\n return missing.length > 0 ? [`missing methods: ${missing.join(\",\")}`] : [];\n}\n\n/** Reasons a database adapter lacks its table map. */\nfunction tableReasons(adapter: Record<string, unknown>): string[] {\n const tables = asRecord(adapter[\"tables\"]);\n if (!tables) {\n return [\"missing tables map\"];\n }\n const missing = REQUIRED_TABLE_KEYS.filter((table) => tables[table] === undefined);\n return missing.length > 0 ? [`missing tables: ${missing.join(\",\")}`] : [];\n}\n\n/** Reasons a queue adapter has a partial poll extension. */\nfunction queuePollReasons(record: Record<string, unknown>): string[] {\n const present = [\"poll\", \"ack\", \"nack\"].filter((method) => record[method] !== undefined);\n if (present.length === 0 || present.length === 3) {\n const mistyped = [\"poll\", \"ack\", \"nack\"].filter(\n (method) => record[method] !== undefined && typeof record[method] !== \"function\",\n );\n return mistyped.map((method) => `queue poll extension \"${method}\" must be a function`);\n }\n return [`partial poll extension (present: ${present.join(\",\")}; need poll+ack+nack)`];\n}\n\n/** Whether a metadata schema looks like a zod schema (has parse). */\nfunction hasSchemaParse(provider: unknown): boolean {\n const schema = asRecord(asRecord(provider)?.[\"metadata\"])?.[\"schema\"];\n return typeof (schema as { parse?: unknown } | undefined)?.parse === \"function\";\n}\n\n/** Reasons a notifier provider lacks its descriptor shape. */\nfunction notifierReasons(provider: unknown): string[] {\n const record = asRecord(provider);\n if (!record) {\n return [\"notifier provider must be an object\"];\n }\n const reasons = metadataReasons(record, \"notifier\");\n if (typeof record[\"create\"] !== \"function\") {\n reasons.push(\"missing methods: create\");\n }\n if (!hasSchemaParse(provider)) {\n reasons.push(\"metadata.schema must be a zod schema\");\n }\n return reasons;\n}\n\n/** Reasons an email sender lacks its transport shape. */\nfunction emailSenderReasons(sender: unknown): string[] {\n const record = asRecord(sender);\n if (!record) {\n return [\"email sender must be an object\"];\n }\n const reasons = metadataReasons(record, \"notifier\");\n if (typeof record[\"send\"] !== \"function\") {\n reasons.push(\"missing methods: send\");\n }\n return reasons;\n}\n\n/** Reasons a git provider lacks its descriptor shape. */\nfunction gitReasons(provider: unknown): string[] {\n const record = asRecord(provider);\n if (!record) {\n return [\"git provider must be an object\"];\n }\n const reasons = metadataReasons(record, \"git-host\");\n if (typeof record[\"create\"] !== \"function\") {\n reasons.push(\"missing methods: create\");\n }\n if (!hasSchemaParse(provider)) {\n reasons.push(\"metadata.schema must be a zod schema\");\n }\n return reasons;\n}\n\n/** Required methods for a slot category. */\nfunction slotMethods(category: string): string[] {\n if (category === \"database\") {\n return DATABASE_METHODS;\n }\n if (category === \"storage\") {\n return STORAGE_METHODS;\n }\n if (category === \"capture-runner\") {\n return RUNNER_METHODS;\n }\n return QUEUE_METHODS;\n}\n\n/** Extra per-slot reasons (table map for databases, poll rule for queues). */\nfunction slotExtraReasons(record: Record<string, unknown>, category: string): string[] {\n if (category === \"database\") {\n return tableReasons(record);\n }\n return category === \"capture-queue\" ? queuePollReasons(record) : [];\n}\n\n/**\n * Validate one adapter against its slot. Returns human-readable reasons\n * (empty when valid). Never throws — callers decide fail-fast vs collect.\n */\nexport function validateAdapter(\n adapter: unknown,\n expectedCategory: string,\n slot: string,\n): string[] {\n const record = asRecord(adapter);\n if (!record) {\n return [`${slot} adapter must be an object`];\n }\n const reasons = metadataReasons(record, expectedCategory).map((r) => `${slot}: ${r}`);\n const extra = [\n ...lifecycleReasons(record),\n ...methodReasons(record, slotMethods(expectedCategory)),\n ...slotExtraReasons(record, expectedCategory),\n ];\n for (const reason of extra) {\n reasons.push(`${slot}: ${reason}`);\n }\n return reasons;\n}\n\n/** Auth surface methods every engine must expose. */\nconst AUTH_METHODS = [\n \"handler\",\n \"loginMethods\",\n \"setup\",\n \"issueInvite\",\n \"verifyInvite\",\n \"acceptInvite\",\n \"passkeysEnabled\",\n \"listSessions\",\n \"listPasskeys\",\n \"hasPassword\",\n \"setDisabled\",\n \"check\",\n \"createSession\",\n \"destroySession\",\n];\n\n/** Reasons a method-id list is unusable (blank or duplicate ids). */\nfunction methodIdReasons(ids: string[]): string[] {\n const reasons: string[] = [];\n if (ids.some((id) => id.length === 0)) {\n reasons.push(\"login method with blank id\");\n }\n if (new Set(ids).size !== ids.length) {\n reasons.push(\"duplicate login method ids\");\n }\n return reasons;\n}\n\n/** Reasons login method descriptors are unusable (duplicate/blank ids). */\nfunction loginMethodReasons(auth: Auth): string[] {\n let ids: string[];\n try {\n ids = auth.loginMethods().map((method) => method.id);\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n return [`loginMethods() threw: ${message}`];\n }\n return methodIdReasons(ids);\n}\n\n/**\n * Validate the auth engine shape (auth is optional — undefined is valid).\n * Returns human-readable reasons, empty when sound.\n */\nexport function validateAuth(auth?: unknown): string[] {\n if (auth === undefined) {\n return [];\n }\n const record = asRecord(auth);\n if (!record) {\n return [\"auth must be an object\"];\n }\n const reasons = methodReasons(record, AUTH_METHODS).map((reason) => `auth: ${reason}`);\n if (reasons.length === 0) {\n for (const reason of loginMethodReasons(auth as Auth)) {\n reasons.push(`auth: ${reason}`);\n }\n }\n return reasons;\n}\n\n/** Failure entry for an invalid auth engine. */\nfunction checkAuth(auth: unknown): AdapterSetupFailure[] {\n const reasons = validateAuth(auth);\n if (reasons.length === 0) {\n return [];\n }\n return [{ category: \"auth\", kind: \"auth\", name: \"auth\", error: reasons.join(\"; \") }];\n}\n\n/** One slot check for source validation. */\ninterface SlotCheck {\n adapter: unknown;\n category: string;\n slot: string;\n}\n\n/** Descriptor check for provider lists (git hosts, notifiers). */\ninterface ProviderCheck {\n provider: unknown;\n index: number;\n kinds: \"git\" | \"notifier\";\n}\n\n/** Failures for git-host or notifier provider lists. */\nfunction checkProviders(checks: ProviderCheck[]): AdapterSetupFailure[] {\n const failures: AdapterSetupFailure[] = [];\n for (const check of checks) {\n const reasons =\n check.kinds === \"git\" ? gitReasons(check.provider) : notifierReasons(check.provider);\n if (reasons.length === 0) {\n continue;\n }\n const slot = check.kinds === \"git\" ? `gitHosts[${check.index}]` : `notifiers[${check.index}]`;\n const category = check.kinds === \"git\" ? \"git-host\" : \"notifier\";\n const metadata = asRecord(asRecord(check.provider)?.[\"metadata\"]);\n failures.push({\n category,\n kind: stringField(metadata, \"kind\") ?? \"unknown\",\n name: stringField(metadata, \"name\") ?? slot,\n error: reasons.map((reason) => `${slot}: ${reason}`).join(\"; \"),\n });\n }\n return failures;\n}\n\n/** Failure for the email sender slot. */\nfunction checkEmailSender(sender: unknown): AdapterSetupFailure[] {\n if (sender === undefined) {\n return [];\n }\n const reasons = emailSenderReasons(sender);\n if (reasons.length === 0) {\n return [];\n }\n const metadata = asRecord(asRecord(sender)?.[\"metadata\"]);\n return [\n {\n category: \"notifier\",\n kind: stringField(metadata, \"kind\") ?? \"unknown\",\n name: stringField(metadata, \"name\") ?? \"emailSender\",\n error: reasons.map((reason) => `emailSender: ${reason}`).join(\"; \"),\n },\n ];\n}\n\n/** Failures for the core database/storage/runner/queue slots. */\nfunction checkSlots(checks: SlotCheck[]): AdapterSetupFailure[] {\n const failures: AdapterSetupFailure[] = [];\n for (const check of checks) {\n const reasons = validateAdapter(check.adapter, check.category, check.slot);\n if (reasons.length === 0) {\n continue;\n }\n const metadata = asRecord(asRecord(check.adapter)?.[\"metadata\"]);\n failures.push({\n category: check.category,\n kind: stringField(metadata, \"kind\") ?? \"unknown\",\n name: stringField(metadata, \"name\") ?? check.slot,\n error: reasons.join(\"; \"),\n });\n }\n return failures;\n}\n\n/**\n * Validate every adapter in the boot sources. Returns per-adapter failures\n * (empty when the whole assembly is sound).\n */\nexport function validateAdapterSources(sources: AdapterSetupSources): AdapterSetupFailure[] {\n const checks: SlotCheck[] = [\n { adapter: sources.database, category: \"database\", slot: \"database\" },\n { adapter: sources.storage, category: \"storage\", slot: \"storage\" },\n ];\n if (sources.captureRunner !== undefined) {\n checks.push({\n adapter: sources.captureRunner,\n category: \"capture-runner\",\n slot: \"captureRunner\",\n });\n }\n if (sources.captureQueue !== undefined) {\n checks.push({ adapter: sources.captureQueue, category: \"capture-queue\", slot: \"captureQueue\" });\n }\n const providers: ProviderCheck[] = [\n ...(sources.gitHosts ?? []).map((provider, index) => ({\n provider,\n index,\n kinds: \"git\" as const,\n })),\n ...(sources.notifiers ?? []).map((provider, index) => ({\n provider,\n index,\n kinds: \"notifier\" as const,\n })),\n ];\n return [\n ...checkSlots(checks),\n ...checkProviders(providers),\n ...checkEmailSender(sources.emailSender),\n ];\n}\n\n/**\n * Validate a full boot assembly: adapters plus the optional auth engine.\n * App assembly calls this — `ShelfOptions` carries `auth` alongside the\n * adapter slots, and an invalid engine fails boot like any adapter.\n */\nexport function validateBootAssembly(\n sources: AdapterSetupSources & { auth?: unknown },\n): AdapterSetupFailure[] {\n return [...validateAdapterSources(sources), ...checkAuth(sources.auth)];\n}\n"],"mappings":"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"}
|