adispatchd 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +195 -2
- package/dist/api/errors.d.ts +27 -0
- package/dist/api/errors.js +80 -0
- package/dist/api/errors.js.map +1 -0
- package/dist/api/index.d.ts +10 -0
- package/dist/api/index.js +11 -0
- package/dist/api/index.js.map +1 -0
- package/dist/api/logs.d.ts +30 -0
- package/dist/api/logs.js +88 -0
- package/dist/api/logs.js.map +1 -0
- package/dist/api/serialize.d.ts +65 -0
- package/dist/api/serialize.js +135 -0
- package/dist/api/serialize.js.map +1 -0
- package/dist/api/server.d.ts +89 -0
- package/dist/api/server.js +751 -0
- package/dist/api/server.js.map +1 -0
- package/dist/api/sse.d.ts +90 -0
- package/dist/api/sse.js +225 -0
- package/dist/api/sse.js.map +1 -0
- package/dist/app.d.ts +173 -0
- package/dist/app.js +488 -0
- package/dist/app.js.map +1 -0
- package/dist/cli/config-validate.d.ts +6 -0
- package/dist/cli/config-validate.js +39 -0
- package/dist/cli/config-validate.js.map +1 -0
- package/dist/cli/control.d.ts +31 -0
- package/dist/cli/control.js +186 -0
- package/dist/cli/control.js.map +1 -0
- package/dist/cli/http-client.d.ts +57 -0
- package/dist/cli/http-client.js +174 -0
- package/dist/cli/http-client.js.map +1 -0
- package/dist/cli/index.d.ts +14 -0
- package/dist/cli/index.js +14 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/init-templates.d.ts +20 -0
- package/dist/cli/init-templates.js +275 -0
- package/dist/cli/init-templates.js.map +1 -0
- package/dist/cli/init.d.ts +16 -0
- package/dist/cli/init.js +107 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/cli/main.d.ts +2 -0
- package/dist/cli/main.js +25 -0
- package/dist/cli/main.js.map +1 -0
- package/dist/cli/program.d.ts +102 -0
- package/dist/cli/program.js +133 -0
- package/dist/cli/program.js.map +1 -0
- package/dist/cli/service.d.ts +27 -0
- package/dist/cli/service.js +252 -0
- package/dist/cli/service.js.map +1 -0
- package/dist/cli/start.d.ts +22 -0
- package/dist/cli/start.js +133 -0
- package/dist/cli/start.js.map +1 -0
- package/dist/cli/workflow-dry-run.d.ts +13 -0
- package/dist/cli/workflow-dry-run.js +144 -0
- package/dist/cli/workflow-dry-run.js.map +1 -0
- package/dist/config/field-catalog.d.ts +33 -0
- package/dist/config/field-catalog.js +172 -0
- package/dist/config/field-catalog.js.map +1 -0
- package/dist/config/filter-schema.d.ts +15 -0
- package/dist/config/filter-schema.js +110 -0
- package/dist/config/filter-schema.js.map +1 -0
- package/dist/config/index.d.ts +15 -0
- package/dist/config/index.js +16 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/issues.d.ts +27 -0
- package/dist/config/issues.js +37 -0
- package/dist/config/issues.js.map +1 -0
- package/dist/config/loader.d.ts +32 -0
- package/dist/config/loader.js +250 -0
- package/dist/config/loader.js.map +1 -0
- package/dist/config/manager.d.ts +153 -0
- package/dist/config/manager.js +276 -0
- package/dist/config/manager.js.map +1 -0
- package/dist/config/model.d.ts +334 -0
- package/dist/config/model.js +2 -0
- package/dist/config/model.js.map +1 -0
- package/dist/config/normalize.d.ts +20 -0
- package/dist/config/normalize.js +200 -0
- package/dist/config/normalize.js.map +1 -0
- package/dist/config/parameters.d.ts +37 -0
- package/dist/config/parameters.js +71 -0
- package/dist/config/parameters.js.map +1 -0
- package/dist/config/public.d.ts +6 -0
- package/dist/config/public.js +18 -0
- package/dist/config/public.js.map +1 -0
- package/dist/config/schemas.d.ts +538 -0
- package/dist/config/schemas.js +321 -0
- package/dist/config/schemas.js.map +1 -0
- package/dist/config/snapshot.d.ts +38 -0
- package/dist/config/snapshot.js +57 -0
- package/dist/config/snapshot.js.map +1 -0
- package/dist/config/validate.d.ts +33 -0
- package/dist/config/validate.js +346 -0
- package/dist/config/validate.js.map +1 -0
- package/dist/core/clock.d.ts +59 -0
- package/dist/core/clock.js +133 -0
- package/dist/core/clock.js.map +1 -0
- package/dist/core/duration.d.ts +19 -0
- package/dist/core/duration.js +93 -0
- package/dist/core/duration.js.map +1 -0
- package/dist/core/errors.d.ts +62 -0
- package/dist/core/errors.js +72 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/ids.d.ts +57 -0
- package/dist/core/ids.js +88 -0
- package/dist/core/ids.js.map +1 -0
- package/dist/core/index.d.ts +13 -0
- package/dist/core/index.js +14 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/logger.d.ts +100 -0
- package/dist/core/logger.js +207 -0
- package/dist/core/logger.js.map +1 -0
- package/dist/core/paths.d.ts +84 -0
- package/dist/core/paths.js +138 -0
- package/dist/core/paths.js.map +1 -0
- package/dist/core/template.d.ts +136 -0
- package/dist/core/template.js +261 -0
- package/dist/core/template.js.map +1 -0
- package/dist/core/types.d.ts +544 -0
- package/dist/core/types.js +172 -0
- package/dist/core/types.js.map +1 -0
- package/dist/core/worker-protocol.d.ts +102 -0
- package/dist/core/worker-protocol.js +75 -0
- package/dist/core/worker-protocol.js.map +1 -0
- package/dist/executors/classify.d.ts +25 -0
- package/dist/executors/classify.js +54 -0
- package/dist/executors/classify.js.map +1 -0
- package/dist/executors/dispatcher.d.ts +35 -0
- package/dist/executors/dispatcher.js +68 -0
- package/dist/executors/dispatcher.js.map +1 -0
- package/dist/executors/env.d.ts +43 -0
- package/dist/executors/env.js +66 -0
- package/dist/executors/env.js.map +1 -0
- package/dist/executors/event-parser.d.ts +41 -0
- package/dist/executors/event-parser.js +106 -0
- package/dist/executors/event-parser.js.map +1 -0
- package/dist/executors/index.d.ts +12 -0
- package/dist/executors/index.js +13 -0
- package/dist/executors/index.js.map +1 -0
- package/dist/executors/log-capture.d.ts +57 -0
- package/dist/executors/log-capture.js +155 -0
- package/dist/executors/log-capture.js.map +1 -0
- package/dist/executors/process-identity.d.ts +124 -0
- package/dist/executors/process-identity.js +283 -0
- package/dist/executors/process-identity.js.map +1 -0
- package/dist/executors/shell-executor.d.ts +35 -0
- package/dist/executors/shell-executor.js +478 -0
- package/dist/executors/shell-executor.js.map +1 -0
- package/dist/executors/stream-redactor.d.ts +40 -0
- package/dist/executors/stream-redactor.js +89 -0
- package/dist/executors/stream-redactor.js.map +1 -0
- package/dist/executors/workspace.d.ts +10 -0
- package/dist/executors/workspace.js +47 -0
- package/dist/executors/workspace.js.map +1 -0
- package/dist/filters/evaluate.d.ts +77 -0
- package/dist/filters/evaluate.js +187 -0
- package/dist/filters/evaluate.js.map +1 -0
- package/dist/filters/fields.d.ts +35 -0
- package/dist/filters/fields.js +75 -0
- package/dist/filters/fields.js.map +1 -0
- package/dist/filters/index.d.ts +11 -0
- package/dist/filters/index.js +12 -0
- package/dist/filters/index.js.map +1 -0
- package/dist/filters/iso-date.d.ts +10 -0
- package/dist/filters/iso-date.js +42 -0
- package/dist/filters/iso-date.js.map +1 -0
- package/dist/filters/predicate.d.ts +24 -0
- package/dist/filters/predicate.js +178 -0
- package/dist/filters/predicate.js.map +1 -0
- package/dist/filters/priority.d.ts +51 -0
- package/dist/filters/priority.js +91 -0
- package/dist/filters/priority.js.map +1 -0
- package/dist/filters/pushdown.d.ts +12 -0
- package/dist/filters/pushdown.js +52 -0
- package/dist/filters/pushdown.js.map +1 -0
- package/dist/filters/shell-check.d.ts +110 -0
- package/dist/filters/shell-check.js +304 -0
- package/dist/filters/shell-check.js.map +1 -0
- package/dist/identities/github-app.d.ts +55 -0
- package/dist/identities/github-app.js +160 -0
- package/dist/identities/github-app.js.map +1 -0
- package/dist/identities/index.d.ts +6 -0
- package/dist/identities/index.js +7 -0
- package/dist/identities/index.js.map +1 -0
- package/dist/identities/jwt.d.ts +24 -0
- package/dist/identities/jwt.js +56 -0
- package/dist/identities/jwt.js.map +1 -0
- package/dist/lifecycle/context.d.ts +106 -0
- package/dist/lifecycle/context.js +170 -0
- package/dist/lifecycle/context.js.map +1 -0
- package/dist/lifecycle/engine.d.ts +185 -0
- package/dist/lifecycle/engine.js +551 -0
- package/dist/lifecycle/engine.js.map +1 -0
- package/dist/lifecycle/errors.d.ts +48 -0
- package/dist/lifecycle/errors.js +71 -0
- package/dist/lifecycle/errors.js.map +1 -0
- package/dist/lifecycle/index.d.ts +9 -0
- package/dist/lifecycle/index.js +9 -0
- package/dist/lifecycle/index.js.map +1 -0
- package/dist/lifecycle/performers.d.ts +66 -0
- package/dist/lifecycle/performers.js +179 -0
- package/dist/lifecycle/performers.js.map +1 -0
- package/dist/notifications/index.d.ts +6 -0
- package/dist/notifications/index.js +7 -0
- package/dist/notifications/index.js.map +1 -0
- package/dist/notifications/service.d.ts +115 -0
- package/dist/notifications/service.js +364 -0
- package/dist/notifications/service.js.map +1 -0
- package/dist/persistence/actions.d.ts +101 -0
- package/dist/persistence/actions.js +196 -0
- package/dist/persistence/actions.js.map +1 -0
- package/dist/persistence/blocks.d.ts +85 -0
- package/dist/persistence/blocks.js +140 -0
- package/dist/persistence/blocks.js.map +1 -0
- package/dist/persistence/cron-firings.d.ts +55 -0
- package/dist/persistence/cron-firings.js +70 -0
- package/dist/persistence/cron-firings.js.map +1 -0
- package/dist/persistence/database.d.ts +60 -0
- package/dist/persistence/database.js +124 -0
- package/dist/persistence/database.js.map +1 -0
- package/dist/persistence/errors.d.ts +39 -0
- package/dist/persistence/errors.js +67 -0
- package/dist/persistence/errors.js.map +1 -0
- package/dist/persistence/events.d.ts +72 -0
- package/dist/persistence/events.js +83 -0
- package/dist/persistence/events.js.map +1 -0
- package/dist/persistence/index.d.ts +18 -0
- package/dist/persistence/index.js +19 -0
- package/dist/persistence/index.js.map +1 -0
- package/dist/persistence/instance-lock.d.ts +26 -0
- package/dist/persistence/instance-lock.js +117 -0
- package/dist/persistence/instance-lock.js.map +1 -0
- package/dist/persistence/lock-keys.d.ts +16 -0
- package/dist/persistence/lock-keys.js +41 -0
- package/dist/persistence/lock-keys.js.map +1 -0
- package/dist/persistence/notifications.d.ts +113 -0
- package/dist/persistence/notifications.js +213 -0
- package/dist/persistence/notifications.js.map +1 -0
- package/dist/persistence/persistence.d.ts +46 -0
- package/dist/persistence/persistence.js +42 -0
- package/dist/persistence/persistence.js.map +1 -0
- package/dist/persistence/retention.d.ts +46 -0
- package/dist/persistence/retention.js +68 -0
- package/dist/persistence/retention.js.map +1 -0
- package/dist/persistence/runs.d.ts +215 -0
- package/dist/persistence/runs.js +417 -0
- package/dist/persistence/runs.js.map +1 -0
- package/dist/persistence/schema.d.ts +16 -0
- package/dist/persistence/schema.js +183 -0
- package/dist/persistence/schema.js.map +1 -0
- package/dist/persistence/sqlite.d.ts +49 -0
- package/dist/persistence/sqlite.js +131 -0
- package/dist/persistence/sqlite.js.map +1 -0
- package/dist/runs/controller.d.ts +307 -0
- package/dist/runs/controller.js +1078 -0
- package/dist/runs/controller.js.map +1 -0
- package/dist/runs/errors.d.ts +20 -0
- package/dist/runs/errors.js +26 -0
- package/dist/runs/errors.js.map +1 -0
- package/dist/runs/event-bus.d.ts +106 -0
- package/dist/runs/event-bus.js +105 -0
- package/dist/runs/event-bus.js.map +1 -0
- package/dist/runs/execution.d.ts +39 -0
- package/dist/runs/execution.js +146 -0
- package/dist/runs/execution.js.map +1 -0
- package/dist/runs/index.d.ts +12 -0
- package/dist/runs/index.js +13 -0
- package/dist/runs/index.js.map +1 -0
- package/dist/runs/retention.d.ts +64 -0
- package/dist/runs/retention.js +232 -0
- package/dist/runs/retention.js.map +1 -0
- package/dist/runs/workspace.d.ts +20 -0
- package/dist/runs/workspace.js +51 -0
- package/dist/runs/workspace.js.map +1 -0
- package/dist/scheduler/checks.d.ts +41 -0
- package/dist/scheduler/checks.js +111 -0
- package/dist/scheduler/checks.js.map +1 -0
- package/dist/scheduler/cron.d.ts +32 -0
- package/dist/scheduler/cron.js +83 -0
- package/dist/scheduler/cron.js.map +1 -0
- package/dist/scheduler/dry-run.d.ts +42 -0
- package/dist/scheduler/dry-run.js +167 -0
- package/dist/scheduler/dry-run.js.map +1 -0
- package/dist/scheduler/errors.d.ts +22 -0
- package/dist/scheduler/errors.js +22 -0
- package/dist/scheduler/errors.js.map +1 -0
- package/dist/scheduler/format.d.ts +3 -0
- package/dist/scheduler/format.js +73 -0
- package/dist/scheduler/format.js.map +1 -0
- package/dist/scheduler/index.d.ts +13 -0
- package/dist/scheduler/index.js +14 -0
- package/dist/scheduler/index.js.map +1 -0
- package/dist/scheduler/planner.d.ts +88 -0
- package/dist/scheduler/planner.js +255 -0
- package/dist/scheduler/planner.js.map +1 -0
- package/dist/scheduler/scheduler.d.ts +160 -0
- package/dist/scheduler/scheduler.js +680 -0
- package/dist/scheduler/scheduler.js.map +1 -0
- package/dist/scheduler/state.d.ts +26 -0
- package/dist/scheduler/state.js +25 -0
- package/dist/scheduler/state.js.map +1 -0
- package/dist/scheduler/types.d.ts +197 -0
- package/dist/scheduler/types.js +3 -0
- package/dist/scheduler/types.js.map +1 -0
- package/dist/service/index.d.ts +6 -0
- package/dist/service/index.js +7 -0
- package/dist/service/index.js.map +1 -0
- package/dist/service/install.d.ts +93 -0
- package/dist/service/install.js +250 -0
- package/dist/service/install.js.map +1 -0
- package/dist/service/unit.d.ts +50 -0
- package/dist/service/unit.js +171 -0
- package/dist/service/unit.js.map +1 -0
- package/dist/sources/github/adapter.d.ts +99 -0
- package/dist/sources/github/adapter.js +336 -0
- package/dist/sources/github/adapter.js.map +1 -0
- package/dist/sources/github/fields.d.ts +8 -0
- package/dist/sources/github/fields.js +72 -0
- package/dist/sources/github/fields.js.map +1 -0
- package/dist/sources/github/http.d.ts +100 -0
- package/dist/sources/github/http.js +288 -0
- package/dist/sources/github/http.js.map +1 -0
- package/dist/sources/github/index.d.ts +6 -0
- package/dist/sources/github/index.js +7 -0
- package/dist/sources/github/index.js.map +1 -0
- package/dist/sources/github/pushdown.d.ts +14 -0
- package/dist/sources/github/pushdown.js +47 -0
- package/dist/sources/github/pushdown.js.map +1 -0
- package/dist/sources/github/refs.d.ts +17 -0
- package/dist/sources/github/refs.js +61 -0
- package/dist/sources/github/refs.js.map +1 -0
- package/dist/sources/index.d.ts +6 -0
- package/dist/sources/index.js +7 -0
- package/dist/sources/index.js.map +1 -0
- package/dist/ui/index.d.ts +12 -0
- package/dist/ui/index.js +28 -0
- package/dist/ui/index.js.map +1 -0
- package/dist/ui/public/app.css +239 -0
- package/dist/ui/public/favicon.svg +1 -0
- package/dist/ui/public/index.html +27 -0
- package/dist/ui/public/js/client/api.js +68 -0
- package/dist/ui/public/js/client/app.js +146 -0
- package/dist/ui/public/js/client/dom.js +34 -0
- package/dist/ui/public/js/client/page.js +9 -0
- package/dist/ui/public/js/client/pages/actions.js +35 -0
- package/dist/ui/public/js/client/pages/blocked.js +58 -0
- package/dist/ui/public/js/client/pages/config.js +126 -0
- package/dist/ui/public/js/client/pages/dashboard.js +175 -0
- package/dist/ui/public/js/client/pages/run.js +367 -0
- package/dist/ui/public/js/client/pages/runs.js +72 -0
- package/dist/ui/public/js/client/pages/views.js +80 -0
- package/dist/ui/public/js/client/pages/workflows.js +291 -0
- package/dist/ui/public/js/client/types.js +1 -0
- package/dist/ui/public/js/client/ui.js +152 -0
- package/dist/ui/public/js/shared/dom.js +77 -0
- package/dist/ui/public/js/shared/events.js +88 -0
- package/dist/ui/public/js/shared/format.js +213 -0
- package/dist/ui/public/js/shared/logs.js +136 -0
- package/dist/ui/public/js/shared/params.js +90 -0
- package/dist/ui/public/js/shared/route.js +36 -0
- package/dist/ui/public/js/shared/sort.js +70 -0
- package/dist/ui/public/js/shared/sse.js +193 -0
- package/dist/ui/public/js/shared/throttle.js +65 -0
- package/dist/views/index.d.ts +6 -0
- package/dist/views/index.js +7 -0
- package/dist/views/index.js.map +1 -0
- package/dist/views/service.d.ts +131 -0
- package/dist/views/service.js +264 -0
- package/dist/views/service.js.map +1 -0
- package/docs/api.md +239 -0
- package/docs/configuration-reference.md +1785 -0
- package/docs/cron-semantics.md +96 -0
- package/docs/manual-tests/github-fixture/config.yaml +30 -0
- package/docs/manual-tests/github-fixture/helpers.sh +93 -0
- package/docs/manual-tests/github-fixture/identities.yaml +18 -0
- package/docs/manual-tests/github-fixture/scripts/test-worker.sh +100 -0
- package/docs/manual-tests/github-fixture/sources.yaml +13 -0
- package/docs/manual-tests/github-fixture/views/dependencies.yaml +17 -0
- package/docs/manual-tests/github-fixture/views/needs-maintainer.yaml +17 -0
- package/docs/manual-tests/github-fixture/workflows/coder-test.yaml +76 -0
- package/docs/manual-tests/github-fixture/workflows/commenter-test.yaml +63 -0
- package/docs/manual-tests/github-fixture/workflows/reviewer-test.yaml +60 -0
- package/docs/manual-tests/github.md +1308 -0
- package/docs/manual-tests/process-supervision.md +64 -0
- package/docs/manual-tests/systemd.md +68 -0
- package/docs/operations.md +271 -0
- package/docs/worker-protocol.md +145 -0
- package/examples/config/config.yaml +35 -0
- package/examples/config/identities.yaml +18 -0
- package/examples/config/sources.yaml +13 -0
- package/examples/config/views/needs-maintainer.yaml +15 -0
- package/examples/config/workflows/coder-project-a.yaml +86 -0
- package/examples/config/workflows/deploy.yaml +15 -0
- package/examples/config/workflows/nightly-maintenance.yaml +13 -0
- package/package.json +75 -4
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Opening the orchestrator database and applying versioned migrations with a
|
|
3
|
+
* pre-migration backup (Spec §4, §12, §21).
|
|
4
|
+
*/
|
|
5
|
+
import { existsSync, mkdirSync, statSync } from 'node:fs';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import { DatabaseSync } from 'node:sqlite';
|
|
8
|
+
import { dataPaths, formatCompactTimestamp, noopLogger, systemClock } from '../core/index.js';
|
|
9
|
+
import { MigrationError } from './errors.js';
|
|
10
|
+
import { MIGRATIONS } from './schema.js';
|
|
11
|
+
import { all, get, run, transaction } from './sqlite.js';
|
|
12
|
+
/** Default SQLite busy timeout (ms). */
|
|
13
|
+
export const DEFAULT_BUSY_TIMEOUT_MS = 5_000;
|
|
14
|
+
/**
|
|
15
|
+
* Open (creating if needed) `<dataDir>/adispatchd.sqlite`, configure it
|
|
16
|
+
* (foreign keys, WAL, busy timeout) and apply pending migrations. If the
|
|
17
|
+
* database already existed and migrations are pending, it is first backed up
|
|
18
|
+
* to `<dataDir>/backups/adispatchd-<version>-<ts>.sqlite`.
|
|
19
|
+
*
|
|
20
|
+
* The caller should hold the data-dir instance lock ({@link acquireInstanceLock}) first.
|
|
21
|
+
*/
|
|
22
|
+
export function openDatabase(dataDir, options = {}) {
|
|
23
|
+
const paths = dataPaths(dataDir);
|
|
24
|
+
mkdirSync(paths.dataDir, { recursive: true });
|
|
25
|
+
const existed = existsSync(paths.database) && statSync(paths.database).size > 0;
|
|
26
|
+
const db = new DatabaseSync(paths.database);
|
|
27
|
+
try {
|
|
28
|
+
configureConnection(db, options.busyTimeoutMs ?? DEFAULT_BUSY_TIMEOUT_MS);
|
|
29
|
+
const migration = migrate(db, {
|
|
30
|
+
migrations: options.migrations ?? MIGRATIONS,
|
|
31
|
+
backupDir: existed ? paths.backupsDir : null,
|
|
32
|
+
clock: options.clock ?? systemClock,
|
|
33
|
+
logger: options.logger ?? noopLogger,
|
|
34
|
+
});
|
|
35
|
+
return { db, path: paths.database, migration, close: () => db.close() };
|
|
36
|
+
}
|
|
37
|
+
catch (err) {
|
|
38
|
+
db.close();
|
|
39
|
+
throw err;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/** Apply connection pragmas. */
|
|
43
|
+
export function configureConnection(db, busyTimeoutMs = DEFAULT_BUSY_TIMEOUT_MS) {
|
|
44
|
+
db.exec(`PRAGMA busy_timeout = ${Math.max(0, Math.trunc(busyTimeoutMs))}`);
|
|
45
|
+
db.exec('PRAGMA foreign_keys = ON');
|
|
46
|
+
db.exec('PRAGMA journal_mode = WAL');
|
|
47
|
+
db.exec('PRAGMA synchronous = NORMAL');
|
|
48
|
+
}
|
|
49
|
+
/** Current schema version (0 if `schema_migrations` does not exist yet). */
|
|
50
|
+
export function schemaVersion(db) {
|
|
51
|
+
const table = get(db, `SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'schema_migrations'`);
|
|
52
|
+
if (!table)
|
|
53
|
+
return 0;
|
|
54
|
+
const row = get(db, 'SELECT MAX(version) AS v FROM schema_migrations');
|
|
55
|
+
return Number(row?.v ?? 0);
|
|
56
|
+
}
|
|
57
|
+
/** Apply all pending migrations, each in its own transaction. Idempotent. */
|
|
58
|
+
export function migrate(db, options) {
|
|
59
|
+
validateMigrations(options.migrations);
|
|
60
|
+
db.exec(`CREATE TABLE IF NOT EXISTS schema_migrations (
|
|
61
|
+
version INTEGER PRIMARY KEY,
|
|
62
|
+
name TEXT NOT NULL,
|
|
63
|
+
applied_at TEXT NOT NULL
|
|
64
|
+
)`);
|
|
65
|
+
const applied = new Set(all(db, 'SELECT version FROM schema_migrations').map((r) => Number(r.version)));
|
|
66
|
+
const fromVersion = applied.size === 0 ? 0 : Math.max(...applied);
|
|
67
|
+
const known = options.migrations.length === 0 ? 0 : Math.max(...options.migrations.map((m) => m.version));
|
|
68
|
+
if (fromVersion > known) {
|
|
69
|
+
throw new MigrationError(`Database schema version ${fromVersion} is newer than this build supports (${known}); refusing to start`, { fromVersion, known });
|
|
70
|
+
}
|
|
71
|
+
const pending = options.migrations.filter((m) => !applied.has(m.version));
|
|
72
|
+
let backupPath = null;
|
|
73
|
+
if (pending.length > 0 && options.backupDir !== null) {
|
|
74
|
+
backupPath = backupDatabase(db, options.backupDir, fromVersion, options.clock);
|
|
75
|
+
options.logger.info('database backup written before migration', { backupPath, fromVersion });
|
|
76
|
+
}
|
|
77
|
+
for (const migration of pending) {
|
|
78
|
+
try {
|
|
79
|
+
transaction(db, () => {
|
|
80
|
+
migration.up(db);
|
|
81
|
+
run(db, 'INSERT INTO schema_migrations (version, name, applied_at) VALUES (?, ?, ?)', [
|
|
82
|
+
migration.version,
|
|
83
|
+
migration.name,
|
|
84
|
+
options.clock.now().toISOString(),
|
|
85
|
+
]);
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
catch (err) {
|
|
89
|
+
throw new MigrationError(`Migration ${migration.version} (${migration.name}) failed`, { version: migration.version, backupPath }, err);
|
|
90
|
+
}
|
|
91
|
+
options.logger.info('database migration applied', { version: migration.version, name: migration.name });
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
fromVersion,
|
|
95
|
+
toVersion: schemaVersion(db),
|
|
96
|
+
applied: pending.map((m) => m.version),
|
|
97
|
+
backupPath,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Write a consistent copy of the database (`VACUUM INTO`, safe in WAL mode)
|
|
102
|
+
* to `<backupDir>/adispatchd-<version>-<ts>.sqlite`. Returns its path.
|
|
103
|
+
*/
|
|
104
|
+
export function backupDatabase(db, backupDir, version, clock) {
|
|
105
|
+
mkdirSync(backupDir, { recursive: true });
|
|
106
|
+
const base = `adispatchd-${version}-${formatCompactTimestamp(clock.now(), true)}`;
|
|
107
|
+
let target = path.join(backupDir, `${base}.sqlite`);
|
|
108
|
+
for (let i = 1; existsSync(target); i++)
|
|
109
|
+
target = path.join(backupDir, `${base}-${i}.sqlite`);
|
|
110
|
+
db.prepare('VACUUM INTO ?').run(target);
|
|
111
|
+
return target;
|
|
112
|
+
}
|
|
113
|
+
function validateMigrations(migrations) {
|
|
114
|
+
let previous = 0;
|
|
115
|
+
for (const m of migrations) {
|
|
116
|
+
if (!Number.isInteger(m.version) || m.version <= previous) {
|
|
117
|
+
throw new MigrationError('Migrations must have strictly increasing positive integer versions', {
|
|
118
|
+
version: m.version,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
previous = m.version;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=database.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"database.js","sourceRoot":"","sources":["../../src/persistence/database.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persistence-specific errors. All extend {@link OrchestratorError} so callers
|
|
3
|
+
* (API: 409/404 mapping) can react on the stable `code`.
|
|
4
|
+
*/
|
|
5
|
+
import { OrchestratorError } from '../core/index.js';
|
|
6
|
+
/** An active lock for `(workflowId, lockKey)` already exists (HTTP 409). */
|
|
7
|
+
export declare class LockConflictError extends OrchestratorError {
|
|
8
|
+
readonly workflowId: string;
|
|
9
|
+
readonly lockKey: string;
|
|
10
|
+
/** Run currently holding the lock. */
|
|
11
|
+
readonly holderRunId: string;
|
|
12
|
+
constructor(workflowId: string, lockKey: string, holderRunId: string);
|
|
13
|
+
}
|
|
14
|
+
/** Reservation refused because an active retry block exists (only with `failIfBlocked`). */
|
|
15
|
+
export declare class BlockedError extends OrchestratorError {
|
|
16
|
+
readonly blockId: number;
|
|
17
|
+
constructor(workflowId: string, itemKey: string | null, blockId: number);
|
|
18
|
+
}
|
|
19
|
+
/** An active block already exists for the workflow/scope. */
|
|
20
|
+
export declare class BlockExistsError extends OrchestratorError {
|
|
21
|
+
readonly blockId: number;
|
|
22
|
+
constructor(blockId: number);
|
|
23
|
+
}
|
|
24
|
+
/** A run status transition that the state machine (core `canTransition`) does not allow. */
|
|
25
|
+
export declare class InvalidTransitionError extends OrchestratorError {
|
|
26
|
+
constructor(message: string, details: Record<string, unknown>);
|
|
27
|
+
}
|
|
28
|
+
/** A referenced row does not exist. */
|
|
29
|
+
export declare class NotFoundError extends OrchestratorError {
|
|
30
|
+
constructor(entity: string, id: string | number);
|
|
31
|
+
}
|
|
32
|
+
/** Another orchestrator instance holds the data-dir instance lock (Spec §21). */
|
|
33
|
+
export declare class InstanceLockedError extends OrchestratorError {
|
|
34
|
+
constructor(lockPath: string, holder: Record<string, unknown> | null, cause?: unknown);
|
|
35
|
+
}
|
|
36
|
+
/** Schema migration failed or the database schema is newer than this build. */
|
|
37
|
+
export declare class MigrationError extends OrchestratorError {
|
|
38
|
+
constructor(message: string, details?: Record<string, unknown>, cause?: unknown);
|
|
39
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persistence-specific errors. All extend {@link OrchestratorError} so callers
|
|
3
|
+
* (API: 409/404 mapping) can react on the stable `code`.
|
|
4
|
+
*/
|
|
5
|
+
import { OrchestratorError } from '../core/index.js';
|
|
6
|
+
/** An active lock for `(workflowId, lockKey)` already exists (HTTP 409). */
|
|
7
|
+
export class LockConflictError extends OrchestratorError {
|
|
8
|
+
workflowId;
|
|
9
|
+
lockKey;
|
|
10
|
+
/** Run currently holding the lock. */
|
|
11
|
+
holderRunId;
|
|
12
|
+
constructor(workflowId, lockKey, holderRunId) {
|
|
13
|
+
super('lock_conflict', `Workflow '${workflowId}' already has an active run for '${lockKey}' (${holderRunId})`, {
|
|
14
|
+
details: { workflowId, lockKey, holderRunId },
|
|
15
|
+
});
|
|
16
|
+
this.workflowId = workflowId;
|
|
17
|
+
this.lockKey = lockKey;
|
|
18
|
+
this.holderRunId = holderRunId;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/** Reservation refused because an active retry block exists (only with `failIfBlocked`). */
|
|
22
|
+
export class BlockedError extends OrchestratorError {
|
|
23
|
+
blockId;
|
|
24
|
+
constructor(workflowId, itemKey, blockId) {
|
|
25
|
+
super('blocked', `Workflow '${workflowId}' is blocked for '${itemKey ?? '<workflow>'}' (block ${blockId})`, {
|
|
26
|
+
details: { workflowId, itemKey, blockId },
|
|
27
|
+
});
|
|
28
|
+
this.blockId = blockId;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/** An active block already exists for the workflow/scope. */
|
|
32
|
+
export class BlockExistsError extends OrchestratorError {
|
|
33
|
+
blockId;
|
|
34
|
+
constructor(blockId) {
|
|
35
|
+
super('block_exists', `An active block already exists (block ${blockId})`, { details: { blockId } });
|
|
36
|
+
this.blockId = blockId;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/** A run status transition that the state machine (core `canTransition`) does not allow. */
|
|
40
|
+
export class InvalidTransitionError extends OrchestratorError {
|
|
41
|
+
constructor(message, details) {
|
|
42
|
+
super('invalid_transition', message, { details });
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/** A referenced row does not exist. */
|
|
46
|
+
export class NotFoundError extends OrchestratorError {
|
|
47
|
+
constructor(entity, id) {
|
|
48
|
+
super('not_found', `${entity} '${String(id)}' not found`, { details: { entity, id } });
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/** Another orchestrator instance holds the data-dir instance lock (Spec §21). */
|
|
52
|
+
export class InstanceLockedError extends OrchestratorError {
|
|
53
|
+
constructor(lockPath, holder, cause) {
|
|
54
|
+
const who = holder && typeof holder.pid === 'number' ? ` (pid ${holder.pid})` : '';
|
|
55
|
+
super('instance_locked', `Another orchestrator instance is running on this data directory${who}: ${lockPath}`, {
|
|
56
|
+
details: { lockPath, holder },
|
|
57
|
+
cause,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/** Schema migration failed or the database schema is newer than this build. */
|
|
62
|
+
export class MigrationError extends OrchestratorError {
|
|
63
|
+
constructor(message, details, cause) {
|
|
64
|
+
super('migration_error', message, { ...(details ? { details } : {}), cause });
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/persistence/errors.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAErD,4EAA4E;AAC5E,MAAM,OAAO,iBAAkB,SAAQ,iBAAiB;IAC7C,UAAU,CAAS;IACnB,OAAO,CAAS;IACzB,sCAAsC;IAC7B,WAAW,CAAS;IAE7B,YAAY,UAAkB,EAAE,OAAe,EAAE,WAAmB;QAClE,KAAK,CAAC,eAAe,EAAE,aAAa,UAAU,oCAAoC,OAAO,MAAM,WAAW,GAAG,EAAE;YAC7G,OAAO,EAAE,EAAE,UAAU,EAAE,OAAO,EAAE,WAAW,EAAE;SAC9C,CAAC,CAAC;QACH,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;QAC7B,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;IACjC,CAAC;CACF;AAED,4FAA4F;AAC5F,MAAM,OAAO,YAAa,SAAQ,iBAAiB;IACxC,OAAO,CAAS;IAEzB,YAAY,UAAkB,EAAE,OAAsB,EAAE,OAAe;QACrE,KAAK,CAAC,SAAS,EAAE,aAAa,UAAU,qBAAqB,OAAO,IAAI,YAAY,YAAY,OAAO,GAAG,EAAE;YAC1G,OAAO,EAAE,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,EAAE;SAC1C,CAAC,CAAC;QACH,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACzB,CAAC;CACF;AAED,6DAA6D;AAC7D,MAAM,OAAO,gBAAiB,SAAQ,iBAAiB;IAC5C,OAAO,CAAS;IAEzB,YAAY,OAAe;QACzB,KAAK,CAAC,cAAc,EAAE,yCAAyC,OAAO,GAAG,EAAE,EAAE,OAAO,EAAE,EAAE,OAAO,EAAE,EAAE,CAAC,CAAC;QACrG,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACzB,CAAC;CACF;AAED,4FAA4F;AAC5F,MAAM,OAAO,sBAAuB,SAAQ,iBAAiB;IAC3D,YAAY,OAAe,EAAE,OAAgC;QAC3D,KAAK,CAAC,oBAAoB,EAAE,OAAO,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;IACpD,CAAC;CACF;AAED,uCAAuC;AACvC,MAAM,OAAO,aAAc,SAAQ,iBAAiB;IAClD,YAAY,MAAc,EAAE,EAAmB;QAC7C,KAAK,CAAC,WAAW,EAAE,GAAG,MAAM,KAAK,MAAM,CAAC,EAAE,CAAC,aAAa,EAAE,EAAE,OAAO,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC;IACzF,CAAC;CACF;AAED,iFAAiF;AACjF,MAAM,OAAO,mBAAoB,SAAQ,iBAAiB;IACxD,YAAY,QAAgB,EAAE,MAAsC,EAAE,KAAe;QACnF,MAAM,GAAG,GAAG,MAAM,IAAI,OAAO,MAAM,CAAC,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,SAAS,MAAM,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;QACnF,KAAK,CAAC,iBAAiB,EAAE,kEAAkE,GAAG,KAAK,QAAQ,EAAE,EAAE;YAC7G,OAAO,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE;YAC7B,KAAK;SACN,CAAC,CAAC;IACL,CAAC;CACF;AAED,+EAA+E;AAC/E,MAAM,OAAO,cAAe,SAAQ,iBAAiB;IACnD,YAAY,OAAe,EAAE,OAAiC,EAAE,KAAe;QAC7E,KAAK,CAAC,iBAAiB,EAAE,OAAO,EAAE,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;IAChF,CAAC;CACF"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structured run events (Spec §9.3, §12, §15 SSE). Every event has a
|
|
3
|
+
* globally monotonic `id` (SQLite AUTOINCREMENT: never reused after deletion
|
|
4
|
+
* of committed rows – usable as SSE `Last-Event-ID`) and a per-run `sequence`.
|
|
5
|
+
*
|
|
6
|
+
* Caveat: the ID of an insert that is rolled back may be handed out again.
|
|
7
|
+
* Therefore an event (and its ID) must only be published to live consumers
|
|
8
|
+
* (event bus / SSE) after the surrounding transaction committed – use
|
|
9
|
+
* `Repositories.afterCommit` / `afterCommit(db, cb)`. Persisted IDs are then
|
|
10
|
+
* strictly increasing in commit order (single writer connection).
|
|
11
|
+
*/
|
|
12
|
+
import type { DatabaseSync } from 'node:sqlite';
|
|
13
|
+
import type { Clock, ExecutorDiagnostic, WorkerEvent } from '../core/index.js';
|
|
14
|
+
/**
|
|
15
|
+
* Who produced the event: `worker` = `@@ADISPATCH_EVENT` line (untrusted, informative),
|
|
16
|
+
* `orchestrator` = status transitions and executor diagnostics.
|
|
17
|
+
*/
|
|
18
|
+
export type EventOrigin = 'worker' | 'orchestrator';
|
|
19
|
+
/** Event type of orchestrator-side run status transitions. Payload: {@link RunStatusEventPayload}. */
|
|
20
|
+
export declare const RUN_STATUS_EVENT = "run.status";
|
|
21
|
+
/** Prefix of executor diagnostic event types, e.g. `diagnostic.log_truncated` (Spec §12 note on log/DB inconsistencies). */
|
|
22
|
+
export declare const DIAGNOSTIC_EVENT_PREFIX = "diagnostic.";
|
|
23
|
+
/** Payload of a {@link RUN_STATUS_EVENT}. */
|
|
24
|
+
export interface RunStatusEventPayload {
|
|
25
|
+
from: string | null;
|
|
26
|
+
to: string;
|
|
27
|
+
[key: string]: unknown;
|
|
28
|
+
}
|
|
29
|
+
/** A persisted run event. */
|
|
30
|
+
export interface RunEventRecord {
|
|
31
|
+
/** Global, strictly increasing event ID. */
|
|
32
|
+
id: number;
|
|
33
|
+
runId: string;
|
|
34
|
+
/** 1-based, strictly increasing per run. */
|
|
35
|
+
sequence: number;
|
|
36
|
+
timestamp: string;
|
|
37
|
+
origin: EventOrigin;
|
|
38
|
+
type: string;
|
|
39
|
+
payload: Record<string, unknown>;
|
|
40
|
+
}
|
|
41
|
+
/** Options for {@link EventRepository.append}. */
|
|
42
|
+
export interface AppendEventOptions {
|
|
43
|
+
origin?: EventOrigin;
|
|
44
|
+
at?: Date;
|
|
45
|
+
}
|
|
46
|
+
/** Repository for `run_events`. */
|
|
47
|
+
export declare class EventRepository {
|
|
48
|
+
private readonly db;
|
|
49
|
+
private readonly clock;
|
|
50
|
+
constructor(db: DatabaseSync, clock: Clock);
|
|
51
|
+
/**
|
|
52
|
+
* Append an event. Worker `status` events with a string `message` also
|
|
53
|
+
* update `runs.status_message` (same transaction).
|
|
54
|
+
*/
|
|
55
|
+
append(runId: string, type: string, payload: Record<string, unknown>, options?: AppendEventOptions): RunEventRecord;
|
|
56
|
+
/** Persist a structured worker event (origin `worker`). */
|
|
57
|
+
appendWorkerEvent(runId: string, event: WorkerEvent): RunEventRecord;
|
|
58
|
+
/** Persist an executor diagnostic as `diagnostic.<type>` (origin `orchestrator`). */
|
|
59
|
+
appendDiagnostic(runId: string, diagnostic: ExecutorDiagnostic): RunEventRecord;
|
|
60
|
+
/** Events with `id > afterId` in ID order (SSE replay). Optionally restricted to one run. */
|
|
61
|
+
listSince(afterId: number, options?: {
|
|
62
|
+
runId?: string;
|
|
63
|
+
limit?: number;
|
|
64
|
+
}): RunEventRecord[];
|
|
65
|
+
/** Events of one run in sequence order. */
|
|
66
|
+
listForRun(runId: string, options?: {
|
|
67
|
+
afterSequence?: number;
|
|
68
|
+
limit?: number;
|
|
69
|
+
}): RunEventRecord[];
|
|
70
|
+
/** Highest event ID ever assigned (0 if none); does not decrease when events are deleted. */
|
|
71
|
+
latestId(): number;
|
|
72
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { NotFoundError } from './errors.js';
|
|
2
|
+
import { all, get, iso, run, transaction } from './sqlite.js';
|
|
3
|
+
/** Event type of orchestrator-side run status transitions. Payload: {@link RunStatusEventPayload}. */
|
|
4
|
+
export const RUN_STATUS_EVENT = 'run.status';
|
|
5
|
+
/** Prefix of executor diagnostic event types, e.g. `diagnostic.log_truncated` (Spec §12 note on log/DB inconsistencies). */
|
|
6
|
+
export const DIAGNOSTIC_EVENT_PREFIX = 'diagnostic.';
|
|
7
|
+
/** Repository for `run_events`. */
|
|
8
|
+
export class EventRepository {
|
|
9
|
+
db;
|
|
10
|
+
clock;
|
|
11
|
+
constructor(db, clock) {
|
|
12
|
+
this.db = db;
|
|
13
|
+
this.clock = clock;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Append an event. Worker `status` events with a string `message` also
|
|
17
|
+
* update `runs.status_message` (same transaction).
|
|
18
|
+
*/
|
|
19
|
+
append(runId, type, payload, options = {}) {
|
|
20
|
+
const origin = options.origin ?? 'orchestrator';
|
|
21
|
+
const at = iso(options.at ?? this.clock.now());
|
|
22
|
+
return transaction(this.db, () => {
|
|
23
|
+
if (!get(this.db, 'SELECT 1 FROM runs WHERE id = ?', [runId]))
|
|
24
|
+
throw new NotFoundError('run', runId);
|
|
25
|
+
const seqRow = get(this.db, 'SELECT COALESCE(MAX(sequence), 0) + 1 AS s FROM run_events WHERE run_id = ?', [runId]);
|
|
26
|
+
const sequence = Number(seqRow?.s ?? 1);
|
|
27
|
+
const res = run(this.db, 'INSERT INTO run_events (run_id, sequence, timestamp, origin, type, payload_json) VALUES (?, ?, ?, ?, ?, ?)', [runId, sequence, at, origin, type, JSON.stringify(payload)]);
|
|
28
|
+
if (origin === 'worker' && type === 'status' && typeof payload.message === 'string') {
|
|
29
|
+
run(this.db, 'UPDATE runs SET status_message = ?, updated_at = ? WHERE id = ?', [payload.message, at, runId]);
|
|
30
|
+
}
|
|
31
|
+
return { id: res.lastInsertRowid, runId, sequence, timestamp: at, origin, type, payload };
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
/** Persist a structured worker event (origin `worker`). */
|
|
35
|
+
appendWorkerEvent(runId, event) {
|
|
36
|
+
return this.append(runId, event.type, event.data, { origin: 'worker', at: event.at });
|
|
37
|
+
}
|
|
38
|
+
/** Persist an executor diagnostic as `diagnostic.<type>` (origin `orchestrator`). */
|
|
39
|
+
appendDiagnostic(runId, diagnostic) {
|
|
40
|
+
const payload = { message: diagnostic.message };
|
|
41
|
+
if (diagnostic.stream)
|
|
42
|
+
payload.stream = diagnostic.stream;
|
|
43
|
+
if (diagnostic.details)
|
|
44
|
+
payload.details = diagnostic.details;
|
|
45
|
+
return this.append(runId, `${DIAGNOSTIC_EVENT_PREFIX}${diagnostic.type}`, payload, { at: diagnostic.at });
|
|
46
|
+
}
|
|
47
|
+
/** Events with `id > afterId` in ID order (SSE replay). Optionally restricted to one run. */
|
|
48
|
+
listSince(afterId, options = {}) {
|
|
49
|
+
const params = [afterId];
|
|
50
|
+
let sql = 'SELECT * FROM run_events WHERE id > ?';
|
|
51
|
+
if (options.runId !== undefined) {
|
|
52
|
+
sql += ' AND run_id = ?';
|
|
53
|
+
params.push(options.runId);
|
|
54
|
+
}
|
|
55
|
+
sql += ' ORDER BY id ASC LIMIT ?';
|
|
56
|
+
params.push(options.limit ?? 1000);
|
|
57
|
+
return all(this.db, sql, params).map(toEvent);
|
|
58
|
+
}
|
|
59
|
+
/** Events of one run in sequence order. */
|
|
60
|
+
listForRun(runId, options = {}) {
|
|
61
|
+
return all(this.db, 'SELECT * FROM run_events WHERE run_id = ? AND sequence > ? ORDER BY sequence ASC LIMIT ?', [
|
|
62
|
+
runId,
|
|
63
|
+
options.afterSequence ?? 0,
|
|
64
|
+
options.limit ?? 10_000,
|
|
65
|
+
]).map(toEvent);
|
|
66
|
+
}
|
|
67
|
+
/** Highest event ID ever assigned (0 if none); does not decrease when events are deleted. */
|
|
68
|
+
latestId() {
|
|
69
|
+
return Number(get(this.db, `SELECT seq FROM sqlite_sequence WHERE name = 'run_events'`)?.seq ?? 0);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function toEvent(row) {
|
|
73
|
+
return {
|
|
74
|
+
id: Number(row.id),
|
|
75
|
+
runId: String(row.run_id),
|
|
76
|
+
sequence: Number(row.sequence),
|
|
77
|
+
timestamp: String(row.timestamp),
|
|
78
|
+
origin: row.origin,
|
|
79
|
+
type: String(row.type),
|
|
80
|
+
payload: JSON.parse(String(row.payload_json)),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=events.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.js","sourceRoot":"","sources":["../../src/persistence/events.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Component `persistence` – public API (WI-03).
|
|
3
|
+
* Other components import from this module only.
|
|
4
|
+
*/
|
|
5
|
+
export { openPersistence, createRepositories, type Persistence, type Repositories } from './persistence.js';
|
|
6
|
+
export { openDatabase, migrate, schemaVersion, backupDatabase, configureConnection, DEFAULT_BUSY_TIMEOUT_MS, type OpenDatabaseOptions, type OpenedDatabase, type MigrationResult, type MigrateOptions, } from './database.js';
|
|
7
|
+
export { MIGRATIONS, type Migration } from './schema.js';
|
|
8
|
+
export { acquireInstanceLock, INSTANCE_LOCK_FILES, type InstanceLock, type InstanceLockHolder } from './instance-lock.js';
|
|
9
|
+
export { lockKeyFor, blockScopeKey, type LockKeyInput } from './lock-keys.js';
|
|
10
|
+
export { RunRepository, finalizationTarget, type RunRecord, type ReserveRunInput, type RunUpdate, type BlockPolicy, type BeginFinalizationOptions, type FinalizationResult, type RunListQuery, type RunPage, type LockRecord, } from './runs.js';
|
|
11
|
+
export { EventRepository, RUN_STATUS_EVENT, DIAGNOSTIC_EVENT_PREFIX, type EventOrigin, type RunEventRecord, type RunStatusEventPayload, type AppendEventOptions, } from './events.js';
|
|
12
|
+
export { ActionRepository, ACTION_STATUS_TRANSITIONS, type ActionJournalEntry, type RunActionRecord, type ActionAttemptRecord, type FailedActionRecord, } from './actions.js';
|
|
13
|
+
export { BlockRepository, type BlockRecord, type CreateBlockInput, type FailureCounterState, } from './blocks.js';
|
|
14
|
+
export { NotificationRepository, type NotificationRecord, type NotificationDeliveryRecord, type DeliveryAttemptOutcome, type DeliveryState, } from './notifications.js';
|
|
15
|
+
export { CronFiringRepository, type CronFiringRecord, type CronDecision, type RecordCronFiringInput, } from './cron-firings.js';
|
|
16
|
+
export { RetentionRepository, type RunRetentionCategory, type RunRetentionCandidate } from './retention.js';
|
|
17
|
+
export { LockConflictError, BlockedError, BlockExistsError, InvalidTransitionError, NotFoundError, InstanceLockedError, MigrationError, } from './errors.js';
|
|
18
|
+
export { transaction, afterCommit } from './sqlite.js';
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Component `persistence` – public API (WI-03).
|
|
3
|
+
* Other components import from this module only.
|
|
4
|
+
*/
|
|
5
|
+
export { openPersistence, createRepositories } from './persistence.js';
|
|
6
|
+
export { openDatabase, migrate, schemaVersion, backupDatabase, configureConnection, DEFAULT_BUSY_TIMEOUT_MS, } from './database.js';
|
|
7
|
+
export { MIGRATIONS } from './schema.js';
|
|
8
|
+
export { acquireInstanceLock, INSTANCE_LOCK_FILES } from './instance-lock.js';
|
|
9
|
+
export { lockKeyFor, blockScopeKey } from './lock-keys.js';
|
|
10
|
+
export { RunRepository, finalizationTarget, } from './runs.js';
|
|
11
|
+
export { EventRepository, RUN_STATUS_EVENT, DIAGNOSTIC_EVENT_PREFIX, } from './events.js';
|
|
12
|
+
export { ActionRepository, ACTION_STATUS_TRANSITIONS, } from './actions.js';
|
|
13
|
+
export { BlockRepository, } from './blocks.js';
|
|
14
|
+
export { NotificationRepository, } from './notifications.js';
|
|
15
|
+
export { CronFiringRepository, } from './cron-firings.js';
|
|
16
|
+
export { RetentionRepository } from './retention.js';
|
|
17
|
+
export { LockConflictError, BlockedError, BlockExistsError, InvalidTransitionError, NotFoundError, InstanceLockedError, MigrationError, } from './errors.js';
|
|
18
|
+
export { transaction, afterCommit } from './sqlite.js';
|
|
19
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/persistence/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAuC,MAAM,kBAAkB,CAAC;AAC5G,OAAO,EACL,YAAY,EACZ,OAAO,EACP,aAAa,EACb,cAAc,EACd,mBAAmB,EACnB,uBAAuB,GAKxB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,UAAU,EAAkB,MAAM,aAAa,CAAC;AACzD,OAAO,EAAE,mBAAmB,EAAE,mBAAmB,EAA8C,MAAM,oBAAoB,CAAC;AAC1H,OAAO,EAAE,UAAU,EAAE,aAAa,EAAqB,MAAM,gBAAgB,CAAC;AAC9E,OAAO,EACL,aAAa,EACb,kBAAkB,GAUnB,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,eAAe,EACf,gBAAgB,EAChB,uBAAuB,GAKxB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,gBAAgB,EAChB,yBAAyB,GAK1B,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,eAAe,GAIhB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,sBAAsB,GAKvB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,oBAAoB,GAIrB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,mBAAmB,EAAyD,MAAM,gBAAgB,CAAC;AAC5G,OAAO,EACL,iBAAiB,EACjB,YAAY,EACZ,gBAAgB,EAChB,sBAAsB,EACtB,aAAa,EACb,mBAAmB,EACnB,cAAc,GACf,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type Clock } from '../core/index.js';
|
|
2
|
+
/** File names inside the data dir that copy/backup/cleanup routines must never open. */
|
|
3
|
+
export declare const INSTANCE_LOCK_FILES: readonly ['adispatchd.lock', 'adispatchd.lock.json'];
|
|
4
|
+
/** A held instance lock. */
|
|
5
|
+
export interface InstanceLock {
|
|
6
|
+
/** Path of the lock file. */
|
|
7
|
+
readonly path: string;
|
|
8
|
+
/** True until {@link release} was called. */
|
|
9
|
+
readonly held: boolean;
|
|
10
|
+
/** Release the lock (idempotent). Also released automatically on process exit. */
|
|
11
|
+
release(): void;
|
|
12
|
+
}
|
|
13
|
+
/** Diagnostic holder information. */
|
|
14
|
+
export interface InstanceLockHolder {
|
|
15
|
+
pid: number;
|
|
16
|
+
hostname: string;
|
|
17
|
+
acquiredAt: string;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Acquire the exclusive instance lock for `dataDir` (creates the directory if
|
|
21
|
+
* needed). Throws {@link InstanceLockedError} immediately if another live
|
|
22
|
+
* instance – in this or another process – holds it.
|
|
23
|
+
*/
|
|
24
|
+
export declare function acquireInstanceLock(dataDir: string, options?: {
|
|
25
|
+
clock?: Clock;
|
|
26
|
+
}): InstanceLock;
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Data-dir instance lock (Spec §21): only one orchestrator per data directory.
|
|
3
|
+
*
|
|
4
|
+
* Approach: an OS-level advisory lock held by SQLite. `adispatchd.lock` is a
|
|
5
|
+
* tiny SQLite database opened with `locking_mode=EXCLUSIVE`; after the first
|
|
6
|
+
* write transaction SQLite keeps the EXCLUSIVE file lock (POSIX `fcntl` on
|
|
7
|
+
* Unix, `LockFileEx` on Windows) until the connection closes. The operating
|
|
8
|
+
* system drops such locks automatically when the process exits – also on
|
|
9
|
+
* SIGKILL/crash – so there is no "stale lock" that needs heuristic detection:
|
|
10
|
+
* a lock left by a dead process is simply free again, and a live holder can
|
|
11
|
+
* never be overridden (no PID/start-time guessing that could admit two
|
|
12
|
+
* instances). SQLite also tracks locks per inode inside one process, so a
|
|
13
|
+
* second acquisition from the same process is refused as well.
|
|
14
|
+
*
|
|
15
|
+
* Holder information (pid, host, start time) is written into the lock
|
|
16
|
+
* database and mirrored to `adispatchd.lock.json` for diagnostics only; it
|
|
17
|
+
* never decides who owns the lock.
|
|
18
|
+
*
|
|
19
|
+
* The connection is kept strongly reachable in a module-level set until
|
|
20
|
+
* `release()`: otherwise dropping the returned handle would let the garbage
|
|
21
|
+
* collector close the connection and silently free the OS lock.
|
|
22
|
+
*
|
|
23
|
+
* Limitations / rules:
|
|
24
|
+
* - Advisory `fcntl` locks are unreliable on some network file systems (NFS
|
|
25
|
+
* without lock daemon) – the data dir must be on a local file system.
|
|
26
|
+
* - POSIX `fcntl` locks belong to the process and are dropped when the process
|
|
27
|
+
* closes ANY file descriptor of that file. Therefore the holding process must
|
|
28
|
+
* never open `adispatchd.lock` otherwise (no `readFileSync`, `fs.cp`,
|
|
29
|
+
* copying the data dir, ...). Backup/copy/cleanup routines must exclude
|
|
30
|
+
* `adispatchd.lock` (and `adispatchd.lock.json`); `backupDatabase` only
|
|
31
|
+
* reads `adispatchd.sqlite` via SQLite. Opening it through `node:sqlite`
|
|
32
|
+
* in the same process is safe (SQLite shares lock state per inode).
|
|
33
|
+
*/
|
|
34
|
+
import { readFileSync, rmSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
35
|
+
import os from 'node:os';
|
|
36
|
+
import { DatabaseSync } from 'node:sqlite';
|
|
37
|
+
import { dataPaths, systemClock } from '../core/index.js';
|
|
38
|
+
import { InstanceLockedError } from './errors.js';
|
|
39
|
+
/** Connections holding a lock; strong references prevent GC from closing them. */
|
|
40
|
+
const HELD = new Set();
|
|
41
|
+
/** File names inside the data dir that copy/backup/cleanup routines must never open. */
|
|
42
|
+
export const INSTANCE_LOCK_FILES = ['adispatchd.lock', 'adispatchd.lock.json'];
|
|
43
|
+
/**
|
|
44
|
+
* Acquire the exclusive instance lock for `dataDir` (creates the directory if
|
|
45
|
+
* needed). Throws {@link InstanceLockedError} immediately if another live
|
|
46
|
+
* instance – in this or another process – holds it.
|
|
47
|
+
*/
|
|
48
|
+
export function acquireInstanceLock(dataDir, options = {}) {
|
|
49
|
+
const paths = dataPaths(dataDir);
|
|
50
|
+
mkdirSync(paths.dataDir, { recursive: true });
|
|
51
|
+
const lockPath = paths.instanceLock;
|
|
52
|
+
const infoPath = `${lockPath}.json`;
|
|
53
|
+
const db = new DatabaseSync(lockPath);
|
|
54
|
+
const holder = {
|
|
55
|
+
pid: process.pid,
|
|
56
|
+
hostname: os.hostname(),
|
|
57
|
+
acquiredAt: (options.clock ?? systemClock).now().toISOString(),
|
|
58
|
+
};
|
|
59
|
+
try {
|
|
60
|
+
db.exec('PRAGMA busy_timeout = 0');
|
|
61
|
+
db.exec('PRAGMA locking_mode = EXCLUSIVE');
|
|
62
|
+
db.exec('BEGIN EXCLUSIVE');
|
|
63
|
+
db.exec('CREATE TABLE IF NOT EXISTS holder (pid INTEGER NOT NULL, hostname TEXT NOT NULL, acquired_at TEXT NOT NULL)');
|
|
64
|
+
db.exec('DELETE FROM holder');
|
|
65
|
+
db.prepare('INSERT INTO holder (pid, hostname, acquired_at) VALUES (?, ?, ?)').run(holder.pid, holder.hostname, holder.acquiredAt);
|
|
66
|
+
db.exec('COMMIT');
|
|
67
|
+
}
|
|
68
|
+
catch (err) {
|
|
69
|
+
try {
|
|
70
|
+
db.close();
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
// ignore
|
|
74
|
+
}
|
|
75
|
+
if (err instanceof Error && /locked|busy/i.test(err.message)) {
|
|
76
|
+
throw new InstanceLockedError(lockPath, readHolderInfo(infoPath), err);
|
|
77
|
+
}
|
|
78
|
+
throw err;
|
|
79
|
+
}
|
|
80
|
+
try {
|
|
81
|
+
writeFileSync(infoPath, `${JSON.stringify(holder)}\n`, { mode: 0o600 });
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
// diagnostics only
|
|
85
|
+
}
|
|
86
|
+
HELD.add(db);
|
|
87
|
+
let held = true;
|
|
88
|
+
return {
|
|
89
|
+
path: lockPath,
|
|
90
|
+
get held() {
|
|
91
|
+
return held;
|
|
92
|
+
},
|
|
93
|
+
release() {
|
|
94
|
+
if (!held)
|
|
95
|
+
return;
|
|
96
|
+
held = false;
|
|
97
|
+
try {
|
|
98
|
+
rmSync(infoPath, { force: true });
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
// diagnostics only
|
|
102
|
+
}
|
|
103
|
+
HELD.delete(db);
|
|
104
|
+
db.close();
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
function readHolderInfo(infoPath) {
|
|
109
|
+
try {
|
|
110
|
+
const parsed = JSON.parse(readFileSync(infoPath, 'utf8'));
|
|
111
|
+
return parsed && typeof parsed === 'object' ? parsed : null;
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=instance-lock.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"instance-lock.js","sourceRoot":"","sources":["../../src/persistence/instance-lock.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACzE,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,SAAS,EAAE,WAAW,EAAc,MAAM,kBAAkB,CAAC;AACtE,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAElD,kFAAkF;AAClF,MAAM,IAAI,GAAG,IAAI,GAAG,EAAgB,CAAC;AAErC,wFAAwF;AACxF,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,iBAAiB,EAAE,sBAAsB,CAAU,CAAC;AAmBxF;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CAAC,OAAe,EAAE,OAAO,GAAsB,EAAE;IAClF,MAAM,KAAK,GAAG,SAAS,CAAC,OAAO,CAAC,CAAC;IACjC,SAAS,CAAC,KAAK,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC9C,MAAM,QAAQ,GAAG,KAAK,CAAC,YAAY,CAAC;IACpC,MAAM,QAAQ,GAAG,GAAG,QAAQ,OAAO,CAAC;IACpC,MAAM,EAAE,GAAG,IAAI,YAAY,CAAC,QAAQ,CAAC,CAAC;IACtC,MAAM,MAAM,GAAuB;QACjC,GAAG,EAAE,OAAO,CAAC,GAAG;QAChB,QAAQ,EAAE,EAAE,CAAC,QAAQ,EAAE;QACvB,UAAU,EAAE,CAAC,OAAO,CAAC,KAAK,IAAI,WAAW,CAAC,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE;KAC/D,CAAC;IACF,IAAI,CAAC;QACH,EAAE,CAAC,IAAI,CAAC,yBAAyB,CAAC,CAAC;QACnC,EAAE,CAAC,IAAI,CAAC,iCAAiC,CAAC,CAAC;QAC3C,EAAE,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;QAC3B,EAAE,CAAC,IAAI,CAAC,6GAA6G,CAAC,CAAC;QACvH,EAAE,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC;QAC9B,EAAE,CAAC,OAAO,CAAC,kEAAkE,CAAC,CAAC,GAAG,CAChF,MAAM,CAAC,GAAG,EACV,MAAM,CAAC,QAAQ,EACf,MAAM,CAAC,UAAU,CAClB,CAAC;QACF,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACpB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,CAAC;YACH,EAAE,CAAC,KAAK,EAAE,CAAC;QACb,CAAC;QAAC,MAAM,CAAC;YACP,SAAS;QACX,CAAC;QACD,IAAI,GAAG,YAAY,KAAK,IAAI,cAAc,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;YAC7D,MAAM,IAAI,mBAAmB,CAAC,QAAQ,EAAE,cAAc,CAAC,QAAQ,CAAC,EAAE,GAAG,CAAC,CAAC;QACzE,CAAC;QACD,MAAM,GAAG,CAAC;IACZ,CAAC;IACD,IAAI,CAAC;QACH,aAAa,CAAC,QAAQ,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAC1E,CAAC;IAAC,MAAM,CAAC;QACP,mBAAmB;IACrB,CAAC;IACD,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IACb,IAAI,IAAI,GAAG,IAAI,CAAC;IAChB,OAAO;QACL,IAAI,EAAE,QAAQ;QACd,IAAI,IAAI;YACN,OAAO,IAAI,CAAC;QACd,CAAC;QACD,OAAO;YACL,IAAI,CAAC,IAAI;gBAAE,OAAO;YAClB,IAAI,GAAG,KAAK,CAAC;YACb,IAAI,CAAC;gBACH,MAAM,CAAC,QAAQ,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;YACpC,CAAC;YAAC,MAAM,CAAC;gBACP,mBAAmB;YACrB,CAAC;YACD,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YAChB,EAAE,CAAC,KAAK,EAAE,CAAC;QACb,CAAC;KACF,CAAC;AACJ,CAAC;AAED,SAAS,cAAc,CAAC,QAAgB;IACtC,IAAI,CAAC;QACH,MAAM,MAAM,GAAY,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC;QACnE,OAAO,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAE,MAAkC,CAAC,CAAC,CAAC,IAAI,CAAC;IAC3F,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { TriggerType } from '../core/index.js';
|
|
2
|
+
/** Inputs determining a run's lock key. */
|
|
3
|
+
export interface LockKeyInput {
|
|
4
|
+
triggerType: TriggerType;
|
|
5
|
+
runId: string;
|
|
6
|
+
sourceItemKey?: string | null;
|
|
7
|
+
/** Cron slot; required for `schedule` runs without item. */
|
|
8
|
+
scheduledFor?: Date | string | null;
|
|
9
|
+
}
|
|
10
|
+
/** Compute the lock key for a run (see module docs). */
|
|
11
|
+
export declare function lockKeyFor(input: LockKeyInput): string;
|
|
12
|
+
/**
|
|
13
|
+
* Internal non-null scope key for failure counters/blocks: the item key, or
|
|
14
|
+
* `''` for workflow scope (sourceless runs).
|
|
15
|
+
*/
|
|
16
|
+
export declare function blockScopeKey(itemKey: string | null | undefined): string;
|