@lwelliott/cortex-cli 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +159 -0
- package/dist/cli/audit.d.ts +3 -0
- package/dist/cli/audit.d.ts.map +1 -0
- package/dist/cli/audit.js +78 -0
- package/dist/cli/audit.js.map +1 -0
- package/dist/cli/doc.d.ts +3 -0
- package/dist/cli/doc.d.ts.map +1 -0
- package/dist/cli/doc.js +291 -0
- package/dist/cli/doc.js.map +1 -0
- package/dist/cli/entity.d.ts +3 -0
- package/dist/cli/entity.d.ts.map +1 -0
- package/dist/cli/entity.js +135 -0
- package/dist/cli/entity.js.map +1 -0
- package/dist/cli/format-selector.d.ts +30 -0
- package/dist/cli/format-selector.d.ts.map +1 -0
- package/dist/cli/format-selector.js +101 -0
- package/dist/cli/format-selector.js.map +1 -0
- package/dist/cli/formatters/audit-formatter.d.ts +33 -0
- package/dist/cli/formatters/audit-formatter.d.ts.map +1 -0
- package/dist/cli/formatters/audit-formatter.js +204 -0
- package/dist/cli/formatters/audit-formatter.js.map +1 -0
- package/dist/cli/formatters/json.d.ts +10 -0
- package/dist/cli/formatters/json.d.ts.map +1 -0
- package/dist/cli/formatters/json.js +31 -0
- package/dist/cli/formatters/json.js.map +1 -0
- package/dist/cli/formatters/markdown.d.ts +12 -0
- package/dist/cli/formatters/markdown.d.ts.map +1 -0
- package/dist/cli/formatters/markdown.js +341 -0
- package/dist/cli/formatters/markdown.js.map +1 -0
- package/dist/cli/formatters/quiet.d.ts +15 -0
- package/dist/cli/formatters/quiet.d.ts.map +1 -0
- package/dist/cli/formatters/quiet.js +75 -0
- package/dist/cli/formatters/quiet.js.map +1 -0
- package/dist/cli/formatters/table.d.ts +11 -0
- package/dist/cli/formatters/table.d.ts.map +1 -0
- package/dist/cli/formatters/table.js +726 -0
- package/dist/cli/formatters/table.js.map +1 -0
- package/dist/cli/graph.d.ts +3 -0
- package/dist/cli/graph.d.ts.map +1 -0
- package/dist/cli/graph.js +209 -0
- package/dist/cli/graph.js.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +144 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/lesson.d.ts +3 -0
- package/dist/cli/lesson.d.ts.map +1 -0
- package/dist/cli/lesson.js +85 -0
- package/dist/cli/lesson.js.map +1 -0
- package/dist/cli/project-resolver.d.ts +17 -0
- package/dist/cli/project-resolver.d.ts.map +1 -0
- package/dist/cli/project-resolver.js +25 -0
- package/dist/cli/project-resolver.js.map +1 -0
- package/dist/cli/project.d.ts +12 -0
- package/dist/cli/project.d.ts.map +1 -0
- package/dist/cli/project.js +169 -0
- package/dist/cli/project.js.map +1 -0
- package/dist/cli/relation.d.ts +3 -0
- package/dist/cli/relation.d.ts.map +1 -0
- package/dist/cli/relation.js +128 -0
- package/dist/cli/relation.js.map +1 -0
- package/dist/cli/review.d.ts +3 -0
- package/dist/cli/review.d.ts.map +1 -0
- package/dist/cli/review.js +131 -0
- package/dist/cli/review.js.map +1 -0
- package/dist/cli/router.d.ts +38 -0
- package/dist/cli/router.d.ts.map +1 -0
- package/dist/cli/router.js +49 -0
- package/dist/cli/router.js.map +1 -0
- package/dist/cli/schema.d.ts +3 -0
- package/dist/cli/schema.d.ts.map +1 -0
- package/dist/cli/schema.js +94 -0
- package/dist/cli/schema.js.map +1 -0
- package/dist/cli/sprint.d.ts +12 -0
- package/dist/cli/sprint.d.ts.map +1 -0
- package/dist/cli/sprint.js +200 -0
- package/dist/cli/sprint.js.map +1 -0
- package/dist/cli/task.d.ts +12 -0
- package/dist/cli/task.d.ts.map +1 -0
- package/dist/cli/task.js +252 -0
- package/dist/cli/task.js.map +1 -0
- package/dist/cli/test-report.d.ts +3 -0
- package/dist/cli/test-report.d.ts.map +1 -0
- package/dist/cli/test-report.js +134 -0
- package/dist/cli/test-report.js.map +1 -0
- package/dist/cli/validate.d.ts +8 -0
- package/dist/cli/validate.d.ts.map +1 -0
- package/dist/cli/validate.js +86 -0
- package/dist/cli/validate.js.map +1 -0
- package/dist/config/config.d.ts +18 -0
- package/dist/config/config.d.ts.map +1 -0
- package/dist/config/config.js +70 -0
- package/dist/config/config.js.map +1 -0
- package/dist/config/registry.d.ts +52 -0
- package/dist/config/registry.d.ts.map +1 -0
- package/dist/config/registry.js +193 -0
- package/dist/config/registry.js.map +1 -0
- package/dist/config/resolver.d.ts +26 -0
- package/dist/config/resolver.d.ts.map +1 -0
- package/dist/config/resolver.js +87 -0
- package/dist/config/resolver.js.map +1 -0
- package/dist/db/builtin-schema-constants.d.ts +109 -0
- package/dist/db/builtin-schema-constants.d.ts.map +1 -0
- package/dist/db/builtin-schema-constants.js +32 -0
- package/dist/db/builtin-schema-constants.js.map +1 -0
- package/dist/db/connection.d.ts +34 -0
- package/dist/db/connection.d.ts.map +1 -0
- package/dist/db/connection.js +140 -0
- package/dist/db/connection.js.map +1 -0
- package/dist/db/generated/schema-types.d.ts +134 -0
- package/dist/db/generated/schema-types.d.ts.map +1 -0
- package/dist/db/generated/schema-types.js +31 -0
- package/dist/db/generated/schema-types.js.map +1 -0
- package/dist/db/locks.d.ts +106 -0
- package/dist/db/locks.d.ts.map +1 -0
- package/dist/db/locks.js +211 -0
- package/dist/db/locks.js.map +1 -0
- package/dist/db/migration.d.ts +65 -0
- package/dist/db/migration.d.ts.map +1 -0
- package/dist/db/migration.js +233 -0
- package/dist/db/migration.js.map +1 -0
- package/dist/db/retry.d.ts +26 -0
- package/dist/db/retry.d.ts.map +1 -0
- package/dist/db/retry.js +71 -0
- package/dist/db/retry.js.map +1 -0
- package/dist/db/schema.d.ts +21 -0
- package/dist/db/schema.d.ts.map +1 -0
- package/dist/db/schema.js +830 -0
- package/dist/db/schema.js.map +1 -0
- package/dist/models/common.d.ts +27 -0
- package/dist/models/common.d.ts.map +1 -0
- package/dist/models/common.js +4 -0
- package/dist/models/common.js.map +1 -0
- package/dist/models/document.d.ts +89 -0
- package/dist/models/document.d.ts.map +1 -0
- package/dist/models/document.js +5 -0
- package/dist/models/document.js.map +1 -0
- package/dist/models/entity-model.d.ts +30 -0
- package/dist/models/entity-model.d.ts.map +1 -0
- package/dist/models/entity-model.js +99 -0
- package/dist/models/entity-model.js.map +1 -0
- package/dist/models/entity.d.ts +88 -0
- package/dist/models/entity.d.ts.map +1 -0
- package/dist/models/entity.js +5 -0
- package/dist/models/entity.js.map +1 -0
- package/dist/models/graph-schema.d.ts +43 -0
- package/dist/models/graph-schema.d.ts.map +1 -0
- package/dist/models/graph-schema.js +76 -0
- package/dist/models/graph-schema.js.map +1 -0
- package/dist/models/project.d.ts +49 -0
- package/dist/models/project.d.ts.map +1 -0
- package/dist/models/project.js +18 -0
- package/dist/models/project.js.map +1 -0
- package/dist/models/relation.d.ts +35 -0
- package/dist/models/relation.d.ts.map +1 -0
- package/dist/models/relation.js +212 -0
- package/dist/models/relation.js.map +1 -0
- package/dist/models/review.d.ts +86 -0
- package/dist/models/review.d.ts.map +1 -0
- package/dist/models/review.js +5 -0
- package/dist/models/review.js.map +1 -0
- package/dist/models/sprint.d.ts +38 -0
- package/dist/models/sprint.d.ts.map +1 -0
- package/dist/models/sprint.js +4 -0
- package/dist/models/sprint.js.map +1 -0
- package/dist/models/task.d.ts +75 -0
- package/dist/models/task.d.ts.map +1 -0
- package/dist/models/task.js +4 -0
- package/dist/models/task.js.map +1 -0
- package/dist/models/validation.d.ts +25 -0
- package/dist/models/validation.d.ts.map +1 -0
- package/dist/models/validation.js +4 -0
- package/dist/models/validation.js.map +1 -0
- package/dist/services/audit-check-service.d.ts +16 -0
- package/dist/services/audit-check-service.d.ts.map +1 -0
- package/dist/services/audit-check-service.js +137 -0
- package/dist/services/audit-check-service.js.map +1 -0
- package/dist/services/audit-checks/dangling-relations-check.d.ts +7 -0
- package/dist/services/audit-checks/dangling-relations-check.d.ts.map +1 -0
- package/dist/services/audit-checks/dangling-relations-check.js +52 -0
- package/dist/services/audit-checks/dangling-relations-check.js.map +1 -0
- package/dist/services/audit-checks/duplicate-ids-check.d.ts +4 -0
- package/dist/services/audit-checks/duplicate-ids-check.d.ts.map +1 -0
- package/dist/services/audit-checks/duplicate-ids-check.js +49 -0
- package/dist/services/audit-checks/duplicate-ids-check.js.map +1 -0
- package/dist/services/audit-checks/fix-engine.d.ts +24 -0
- package/dist/services/audit-checks/fix-engine.d.ts.map +1 -0
- package/dist/services/audit-checks/fix-engine.js +114 -0
- package/dist/services/audit-checks/fix-engine.js.map +1 -0
- package/dist/services/audit-checks/fr-nfr-to-fs-nfs-check.d.ts +9 -0
- package/dist/services/audit-checks/fr-nfr-to-fs-nfs-check.d.ts.map +1 -0
- package/dist/services/audit-checks/fr-nfr-to-fs-nfs-check.js +56 -0
- package/dist/services/audit-checks/fr-nfr-to-fs-nfs-check.js.map +1 -0
- package/dist/services/audit-checks/fs-nfs-to-sad-ad-qa-check.d.ts +10 -0
- package/dist/services/audit-checks/fs-nfs-to-sad-ad-qa-check.d.ts.map +1 -0
- package/dist/services/audit-checks/fs-nfs-to-sad-ad-qa-check.js +57 -0
- package/dist/services/audit-checks/fs-nfs-to-sad-ad-qa-check.js.map +1 -0
- package/dist/services/audit-checks/fs-nfs-to-ts-check.d.ts +9 -0
- package/dist/services/audit-checks/fs-nfs-to-ts-check.d.ts.map +1 -0
- package/dist/services/audit-checks/fs-nfs-to-ts-check.js +56 -0
- package/dist/services/audit-checks/fs-nfs-to-ts-check.js.map +1 -0
- package/dist/services/audit-checks/inverse-consistency-check.d.ts +7 -0
- package/dist/services/audit-checks/inverse-consistency-check.d.ts.map +1 -0
- package/dist/services/audit-checks/inverse-consistency-check.js +54 -0
- package/dist/services/audit-checks/inverse-consistency-check.js.map +1 -0
- package/dist/services/audit-checks/mapping-check.d.ts +4 -0
- package/dist/services/audit-checks/mapping-check.d.ts.map +1 -0
- package/dist/services/audit-checks/mapping-check.js +74 -0
- package/dist/services/audit-checks/mapping-check.js.map +1 -0
- package/dist/services/audit-checks/orphan-documents-check.d.ts +9 -0
- package/dist/services/audit-checks/orphan-documents-check.d.ts.map +1 -0
- package/dist/services/audit-checks/orphan-documents-check.js +54 -0
- package/dist/services/audit-checks/orphan-documents-check.js.map +1 -0
- package/dist/services/audit-checks/orphan-entities-check.d.ts +4 -0
- package/dist/services/audit-checks/orphan-entities-check.d.ts.map +1 -0
- package/dist/services/audit-checks/orphan-entities-check.js +39 -0
- package/dist/services/audit-checks/orphan-entities-check.js.map +1 -0
- package/dist/services/audit-checks/sad-ad-qa-tc-to-src-check.d.ts +9 -0
- package/dist/services/audit-checks/sad-ad-qa-tc-to-src-check.d.ts.map +1 -0
- package/dist/services/audit-checks/sad-ad-qa-tc-to-src-check.js +56 -0
- package/dist/services/audit-checks/sad-ad-qa-tc-to-src-check.js.map +1 -0
- package/dist/services/audit-checks/sprint-consistency-check.d.ts +4 -0
- package/dist/services/audit-checks/sprint-consistency-check.d.ts.map +1 -0
- package/dist/services/audit-checks/sprint-consistency-check.js +80 -0
- package/dist/services/audit-checks/sprint-consistency-check.js.map +1 -0
- package/dist/services/audit-checks/src-filesystem-only-check.d.ts +10 -0
- package/dist/services/audit-checks/src-filesystem-only-check.d.ts.map +1 -0
- package/dist/services/audit-checks/src-filesystem-only-check.js +106 -0
- package/dist/services/audit-checks/src-filesystem-only-check.js.map +1 -0
- package/dist/services/audit-checks/ts-to-tc-check.d.ts +10 -0
- package/dist/services/audit-checks/ts-to-tc-check.d.ts.map +1 -0
- package/dist/services/audit-checks/ts-to-tc-check.js +58 -0
- package/dist/services/audit-checks/ts-to-tc-check.js.map +1 -0
- package/dist/services/audit-checks/type-mismatch-check.d.ts +4 -0
- package/dist/services/audit-checks/type-mismatch-check.d.ts.map +1 -0
- package/dist/services/audit-checks/type-mismatch-check.js +55 -0
- package/dist/services/audit-checks/type-mismatch-check.js.map +1 -0
- package/dist/services/audit-checks/types.d.ts +66 -0
- package/dist/services/audit-checks/types.d.ts.map +1 -0
- package/dist/services/audit-checks/types.js +30 -0
- package/dist/services/audit-checks/types.js.map +1 -0
- package/dist/services/audit-checks/us-to-fr-nfr-check.d.ts +9 -0
- package/dist/services/audit-checks/us-to-fr-nfr-check.d.ts.map +1 -0
- package/dist/services/audit-checks/us-to-fr-nfr-check.js +56 -0
- package/dist/services/audit-checks/us-to-fr-nfr-check.js.map +1 -0
- package/dist/services/doc-entity-mapping.d.ts +19 -0
- package/dist/services/doc-entity-mapping.d.ts.map +1 -0
- package/dist/services/doc-entity-mapping.js +35 -0
- package/dist/services/doc-entity-mapping.js.map +1 -0
- package/dist/services/document-service.d.ts +58 -0
- package/dist/services/document-service.d.ts.map +1 -0
- package/dist/services/document-service.js +409 -0
- package/dist/services/document-service.js.map +1 -0
- package/dist/services/graph-export-service.d.ts +12 -0
- package/dist/services/graph-export-service.d.ts.map +1 -0
- package/dist/services/graph-export-service.js +112 -0
- package/dist/services/graph-export-service.js.map +1 -0
- package/dist/services/graph-service.d.ts +21 -0
- package/dist/services/graph-service.d.ts.map +1 -0
- package/dist/services/graph-service.js +49 -0
- package/dist/services/graph-service.js.map +1 -0
- package/dist/services/graph-validation-service.d.ts +13 -0
- package/dist/services/graph-validation-service.d.ts.map +1 -0
- package/dist/services/graph-validation-service.js +171 -0
- package/dist/services/graph-validation-service.js.map +1 -0
- package/dist/services/project.d.ts +80 -0
- package/dist/services/project.d.ts.map +1 -0
- package/dist/services/project.js +256 -0
- package/dist/services/project.js.map +1 -0
- package/dist/services/review-service.d.ts +81 -0
- package/dist/services/review-service.d.ts.map +1 -0
- package/dist/services/review-service.js +242 -0
- package/dist/services/review-service.js.map +1 -0
- package/dist/services/schema-migration-service.d.ts +44 -0
- package/dist/services/schema-migration-service.d.ts.map +1 -0
- package/dist/services/schema-migration-service.js +894 -0
- package/dist/services/schema-migration-service.js.map +1 -0
- package/dist/services/sprint.d.ts +71 -0
- package/dist/services/sprint.d.ts.map +1 -0
- package/dist/services/sprint.js +238 -0
- package/dist/services/sprint.js.map +1 -0
- package/dist/services/stage-validator.d.ts +36 -0
- package/dist/services/stage-validator.d.ts.map +1 -0
- package/dist/services/stage-validator.js +78 -0
- package/dist/services/stage-validator.js.map +1 -0
- package/dist/services/task.d.ts +75 -0
- package/dist/services/task.d.ts.map +1 -0
- package/dist/services/task.js +487 -0
- package/dist/services/task.js.map +1 -0
- package/dist/services/validation.d.ts +24 -0
- package/dist/services/validation.d.ts.map +1 -0
- package/dist/services/validation.js +53 -0
- package/dist/services/validation.js.map +1 -0
- package/dist/state-machines/sprint-state.d.ts +25 -0
- package/dist/state-machines/sprint-state.d.ts.map +1 -0
- package/dist/state-machines/sprint-state.js +64 -0
- package/dist/state-machines/sprint-state.js.map +1 -0
- package/dist/state-machines/task-state.d.ts +31 -0
- package/dist/state-machines/task-state.d.ts.map +1 -0
- package/dist/state-machines/task-state.js +83 -0
- package/dist/state-machines/task-state.js.map +1 -0
- package/dist/utils/errors.d.ts +673 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +1447 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/format.d.ts +52 -0
- package/dist/utils/format.d.ts.map +1 -0
- package/dist/utils/format.js +65 -0
- package/dist/utils/format.js.map +1 -0
- package/dist/validators/board-integrity.d.ts +25 -0
- package/dist/validators/board-integrity.d.ts.map +1 -0
- package/dist/validators/board-integrity.js +102 -0
- package/dist/validators/board-integrity.js.map +1 -0
- package/dist/validators/board-shape.d.ts +13 -0
- package/dist/validators/board-shape.d.ts.map +1 -0
- package/dist/validators/board-shape.js +74 -0
- package/dist/validators/board-shape.js.map +1 -0
- package/dist/validators/owner-validator.d.ts +30 -0
- package/dist/validators/owner-validator.d.ts.map +1 -0
- package/dist/validators/owner-validator.js +70 -0
- package/dist/validators/owner-validator.js.map +1 -0
- package/dist/validators/spawn-legality.d.ts +19 -0
- package/dist/validators/spawn-legality.d.ts.map +1 -0
- package/dist/validators/spawn-legality.js +77 -0
- package/dist/validators/spawn-legality.js.map +1 -0
- package/dist/validators/state-field-checker.d.ts +8 -0
- package/dist/validators/state-field-checker.d.ts.map +1 -0
- package/dist/validators/state-field-checker.js +47 -0
- package/dist/validators/state-field-checker.js.map +1 -0
- package/dist/validators/state-field-recorder.d.ts +39 -0
- package/dist/validators/state-field-recorder.d.ts.map +1 -0
- package/dist/validators/state-field-recorder.js +62 -0
- package/dist/validators/state-field-recorder.js.map +1 -0
- package/package.json +53 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import type Database from 'better-sqlite3';
|
|
2
|
+
/** Options for lock acquisition */
|
|
3
|
+
export interface LockOptions {
|
|
4
|
+
/** Lock name (e.g., "kanban:sprint-2026050301") */
|
|
5
|
+
name: string;
|
|
6
|
+
/** Unique identifier for the lock holder (e.g., PID + timestamp) */
|
|
7
|
+
owner: string;
|
|
8
|
+
/** Max wait time in ms (default: 5000) */
|
|
9
|
+
timeoutMs?: number;
|
|
10
|
+
/** Lock considered stale after this many ms (default: 60000) */
|
|
11
|
+
staleMs?: number;
|
|
12
|
+
/** Retry interval in ms (default: 50) */
|
|
13
|
+
retryIntervalMs?: number;
|
|
14
|
+
}
|
|
15
|
+
/** Handle returned after successful lock acquisition */
|
|
16
|
+
export interface LockHandle {
|
|
17
|
+
/** The lock name */
|
|
18
|
+
name: string;
|
|
19
|
+
/** The owner identifier */
|
|
20
|
+
owner: string;
|
|
21
|
+
/** Release the lock */
|
|
22
|
+
release: () => void;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Generate a unique owner string using PID and timestamp.
|
|
26
|
+
*/
|
|
27
|
+
export declare function generateLockOwner(): string;
|
|
28
|
+
/**
|
|
29
|
+
* Clear stale locks for a given lock name.
|
|
30
|
+
* A lock is stale if COALESCE(last_heartbeat, acquired_at) is older than now - staleMs.
|
|
31
|
+
* Per FS-SS-003-0004 VR-003.
|
|
32
|
+
*
|
|
33
|
+
* @returns Number of stale locks cleared
|
|
34
|
+
*/
|
|
35
|
+
export declare function clearStaleLocks(db: Database.Database, name: string, staleMs?: number): number;
|
|
36
|
+
/**
|
|
37
|
+
* Attempt to acquire a lock (single attempt, no retry).
|
|
38
|
+
* Uses TEXT (ISO 8601) for acquired_at per FS-SS-003-0003 Data Model.
|
|
39
|
+
*
|
|
40
|
+
* @returns true if acquired, false if lock is held by another owner
|
|
41
|
+
*/
|
|
42
|
+
export declare function tryAcquireLock(db: Database.Database, name: string, owner: string): boolean;
|
|
43
|
+
/**
|
|
44
|
+
* Refresh/heartbeat a lock — update last_heartbeat timestamp.
|
|
45
|
+
* Per FS-SS-003-0004 VR-005: operations exceeding the stale threshold
|
|
46
|
+
* must explicitly extend their lock.
|
|
47
|
+
*
|
|
48
|
+
* @returns true if the lock was refreshed, false if not found or wrong owner
|
|
49
|
+
*/
|
|
50
|
+
export declare function refreshLock(db: Database.Database, name: string, owner: string): boolean;
|
|
51
|
+
/**
|
|
52
|
+
* Get information about a lock (if held).
|
|
53
|
+
*/
|
|
54
|
+
export declare function getLockInfo(db: Database.Database, name: string): {
|
|
55
|
+
owner: string;
|
|
56
|
+
acquiredAt: string;
|
|
57
|
+
lastHeartbeat: string | null;
|
|
58
|
+
} | null;
|
|
59
|
+
/**
|
|
60
|
+
* Acquire an advisory lock with timeout and stale detection.
|
|
61
|
+
* Implements the retry loop with configurable timeout.
|
|
62
|
+
*
|
|
63
|
+
* Per FS-SS-003-0003 VR-008: re-evaluates lock staleness at intervals no
|
|
64
|
+
* greater than the advisory lock acquisition timeout. The default retry
|
|
65
|
+
* interval (50ms) is well below the default timeout (5000ms), so each
|
|
66
|
+
* retry iteration satisfies this bound.
|
|
67
|
+
*
|
|
68
|
+
* @throws LockTimeoutError if the lock cannot be acquired within timeoutMs (ERR-LOCK-002)
|
|
69
|
+
*/
|
|
70
|
+
export declare function acquireLock(db: Database.Database, opts: LockOptions): LockHandle;
|
|
71
|
+
/**
|
|
72
|
+
* Acquire multiple advisory locks in alphabetical order.
|
|
73
|
+
* Per FS-SS-003-0003 VR-006/VR-007: locks must be acquired in alphabetical
|
|
74
|
+
* order by lock name to prevent deadlocks.
|
|
75
|
+
*
|
|
76
|
+
* On partial failure (some locks acquired but a later one times out),
|
|
77
|
+
* all acquired locks are released before the error is thrown.
|
|
78
|
+
*
|
|
79
|
+
* @throws LockTimeoutError if any lock cannot be acquired
|
|
80
|
+
*/
|
|
81
|
+
export declare function acquireLocks(db: Database.Database, lockNames: string[], owner: string, opts?: Omit<LockOptions, 'name' | 'owner'>): LockHandle[];
|
|
82
|
+
/**
|
|
83
|
+
* Release an advisory lock.
|
|
84
|
+
* Only the owner can release their own lock.
|
|
85
|
+
*
|
|
86
|
+
* @returns true if the lock was released, false if not found or wrong owner
|
|
87
|
+
*/
|
|
88
|
+
export declare function releaseLock(db: Database.Database, name: string, owner: string): boolean;
|
|
89
|
+
/**
|
|
90
|
+
* Force-release a lock regardless of owner.
|
|
91
|
+
* Use with caution — only for administrative purposes.
|
|
92
|
+
*/
|
|
93
|
+
export declare function forceReleaseLock(db: Database.Database, name: string): boolean;
|
|
94
|
+
/**
|
|
95
|
+
* Execute a function within a lock context, guaranteeing release in finally.
|
|
96
|
+
* Per FS-SS-003-0003 VR-003: locks must be released upon operation completion
|
|
97
|
+
* (success, failure, or exception).
|
|
98
|
+
*
|
|
99
|
+
* @param db - Database connection
|
|
100
|
+
* @param opts - Lock acquisition options
|
|
101
|
+
* @param fn - Function to execute while holding the lock
|
|
102
|
+
* @returns The return value of fn
|
|
103
|
+
* @throws LockTimeoutError if the lock cannot be acquired
|
|
104
|
+
*/
|
|
105
|
+
export declare function withLock<T>(db: Database.Database, opts: LockOptions, fn: () => T): T;
|
|
106
|
+
//# sourceMappingURL=locks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"locks.d.ts","sourceRoot":"","sources":["../../src/db/locks.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAG3C,mCAAmC;AACnC,MAAM,WAAW,WAAW;IAC1B,mDAAmD;IACnD,IAAI,EAAE,MAAM,CAAC;IACb,oEAAoE;IACpE,KAAK,EAAE,MAAM,CAAC;IACd,0CAA0C;IAC1C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gEAAgE;IAChE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,yCAAyC;IACzC,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,wDAAwD;AACxD,MAAM,WAAW,UAAU;IACzB,oBAAoB;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,2BAA2B;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,uBAAuB;IACvB,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAMD;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,CAE1C;AASD;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,MAAyB,GAAG,MAAM,CAQ/G;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAY1F;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAKvF;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,EAAE,IAAI,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GAAG,IAAI,CAY3I;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,WAAW,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,EAAE,IAAI,EAAE,WAAW,GAAG,UAAU,CA4ChF;AAED;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,EAAE,SAAS,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,UAAU,EAAE,CAkBhJ;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAKvF;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAK7E;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,QAAQ,CAAC,CAAC,EAAE,EAAE,EAAE,QAAQ,CAAC,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,CAOpF"}
|
package/dist/db/locks.js
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Advisory lock implementation
|
|
3
|
+
// Implements AD-0006: Advisory Locks for Multi-Step Operations
|
|
4
|
+
// + FS-SS-003-0003 (lock management), FS-SS-003-0004 (stale detection),
|
|
5
|
+
// FS-SS-003-0007 (error reporting)
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.generateLockOwner = generateLockOwner;
|
|
8
|
+
exports.clearStaleLocks = clearStaleLocks;
|
|
9
|
+
exports.tryAcquireLock = tryAcquireLock;
|
|
10
|
+
exports.refreshLock = refreshLock;
|
|
11
|
+
exports.getLockInfo = getLockInfo;
|
|
12
|
+
exports.acquireLock = acquireLock;
|
|
13
|
+
exports.acquireLocks = acquireLocks;
|
|
14
|
+
exports.releaseLock = releaseLock;
|
|
15
|
+
exports.forceReleaseLock = forceReleaseLock;
|
|
16
|
+
exports.withLock = withLock;
|
|
17
|
+
const errors_1 = require("../utils/errors");
|
|
18
|
+
const DEFAULT_TIMEOUT_MS = 5000;
|
|
19
|
+
const DEFAULT_STALE_MS = 60000; // FS-SS-003-0004 VR-001: default 60 seconds
|
|
20
|
+
const DEFAULT_RETRY_INTERVAL_MS = 50;
|
|
21
|
+
/**
|
|
22
|
+
* Generate a unique owner string using PID and timestamp.
|
|
23
|
+
*/
|
|
24
|
+
function generateLockOwner() {
|
|
25
|
+
return `${process.pid}:${Date.now()}`;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Get the current ISO 8601 timestamp.
|
|
29
|
+
*/
|
|
30
|
+
function isoNow() {
|
|
31
|
+
return new Date().toISOString();
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Clear stale locks for a given lock name.
|
|
35
|
+
* A lock is stale if COALESCE(last_heartbeat, acquired_at) is older than now - staleMs.
|
|
36
|
+
* Per FS-SS-003-0004 VR-003.
|
|
37
|
+
*
|
|
38
|
+
* @returns Number of stale locks cleared
|
|
39
|
+
*/
|
|
40
|
+
function clearStaleLocks(db, name, staleMs = DEFAULT_STALE_MS) {
|
|
41
|
+
const cutoff = new Date(Date.now() - staleMs).toISOString();
|
|
42
|
+
const result = db
|
|
43
|
+
.prepare('DELETE FROM locks WHERE name = ? AND COALESCE(last_heartbeat, acquired_at) < ?')
|
|
44
|
+
.run(name, cutoff);
|
|
45
|
+
return result.changes;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Attempt to acquire a lock (single attempt, no retry).
|
|
49
|
+
* Uses TEXT (ISO 8601) for acquired_at per FS-SS-003-0003 Data Model.
|
|
50
|
+
*
|
|
51
|
+
* @returns true if acquired, false if lock is held by another owner
|
|
52
|
+
*/
|
|
53
|
+
function tryAcquireLock(db, name, owner) {
|
|
54
|
+
try {
|
|
55
|
+
db.prepare('INSERT INTO locks (name, owner, acquired_at) VALUES (?, ?, ?)')
|
|
56
|
+
.run(name, owner, isoNow());
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
// UNIQUE constraint violation means lock is already held
|
|
61
|
+
if (err instanceof Error && err.message.includes('UNIQUE constraint failed')) {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
throw err;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Refresh/heartbeat a lock — update last_heartbeat timestamp.
|
|
69
|
+
* Per FS-SS-003-0004 VR-005: operations exceeding the stale threshold
|
|
70
|
+
* must explicitly extend their lock.
|
|
71
|
+
*
|
|
72
|
+
* @returns true if the lock was refreshed, false if not found or wrong owner
|
|
73
|
+
*/
|
|
74
|
+
function refreshLock(db, name, owner) {
|
|
75
|
+
const result = db
|
|
76
|
+
.prepare('UPDATE locks SET last_heartbeat = ? WHERE name = ? AND owner = ?')
|
|
77
|
+
.run(isoNow(), name, owner);
|
|
78
|
+
return result.changes > 0;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Get information about a lock (if held).
|
|
82
|
+
*/
|
|
83
|
+
function getLockInfo(db, name) {
|
|
84
|
+
const row = db
|
|
85
|
+
.prepare('SELECT owner, acquired_at, last_heartbeat FROM locks WHERE name = ?')
|
|
86
|
+
.get(name);
|
|
87
|
+
if (!row)
|
|
88
|
+
return null;
|
|
89
|
+
return {
|
|
90
|
+
owner: row.owner,
|
|
91
|
+
acquiredAt: row.acquired_at,
|
|
92
|
+
lastHeartbeat: row.last_heartbeat,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Acquire an advisory lock with timeout and stale detection.
|
|
97
|
+
* Implements the retry loop with configurable timeout.
|
|
98
|
+
*
|
|
99
|
+
* Per FS-SS-003-0003 VR-008: re-evaluates lock staleness at intervals no
|
|
100
|
+
* greater than the advisory lock acquisition timeout. The default retry
|
|
101
|
+
* interval (50ms) is well below the default timeout (5000ms), so each
|
|
102
|
+
* retry iteration satisfies this bound.
|
|
103
|
+
*
|
|
104
|
+
* @throws LockTimeoutError if the lock cannot be acquired within timeoutMs (ERR-LOCK-002)
|
|
105
|
+
*/
|
|
106
|
+
function acquireLock(db, opts) {
|
|
107
|
+
const { name, owner, timeoutMs = DEFAULT_TIMEOUT_MS, staleMs = DEFAULT_STALE_MS, retryIntervalMs = DEFAULT_RETRY_INTERVAL_MS, } = opts;
|
|
108
|
+
const startTime = Date.now();
|
|
109
|
+
const deadline = startTime + timeoutMs;
|
|
110
|
+
while (true) {
|
|
111
|
+
// Clear stale locks before each attempt (FS-SS-003-0004 VR-002)
|
|
112
|
+
clearStaleLocks(db, name, staleMs);
|
|
113
|
+
// Try to acquire
|
|
114
|
+
if (tryAcquireLock(db, name, owner)) {
|
|
115
|
+
return {
|
|
116
|
+
name,
|
|
117
|
+
owner,
|
|
118
|
+
release: () => releaseLock(db, name, owner),
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
// Check timeout
|
|
122
|
+
const elapsed = Date.now() - startTime;
|
|
123
|
+
if (Date.now() >= deadline) {
|
|
124
|
+
// Query current lock owner for error reporting (FS-SS-003-0007 VR-002)
|
|
125
|
+
const info = getLockInfo(db, name);
|
|
126
|
+
const currentOwner = info?.owner ?? 'unknown';
|
|
127
|
+
throw new errors_1.LockTimeoutError(name, currentOwner, elapsed);
|
|
128
|
+
}
|
|
129
|
+
// Wait before retry
|
|
130
|
+
const sleepMs = Math.min(retryIntervalMs, deadline - Date.now());
|
|
131
|
+
if (sleepMs > 0) {
|
|
132
|
+
// Synchronous busy-wait (Node.js main-thread compatible)
|
|
133
|
+
const end = Date.now() + sleepMs;
|
|
134
|
+
while (Date.now() < end) {
|
|
135
|
+
// busy-wait
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Acquire multiple advisory locks in alphabetical order.
|
|
142
|
+
* Per FS-SS-003-0003 VR-006/VR-007: locks must be acquired in alphabetical
|
|
143
|
+
* order by lock name to prevent deadlocks.
|
|
144
|
+
*
|
|
145
|
+
* On partial failure (some locks acquired but a later one times out),
|
|
146
|
+
* all acquired locks are released before the error is thrown.
|
|
147
|
+
*
|
|
148
|
+
* @throws LockTimeoutError if any lock cannot be acquired
|
|
149
|
+
*/
|
|
150
|
+
function acquireLocks(db, lockNames, owner, opts) {
|
|
151
|
+
// Sort alphabetically to prevent deadlocks (VR-006)
|
|
152
|
+
const sorted = [...lockNames].sort();
|
|
153
|
+
const acquired = [];
|
|
154
|
+
try {
|
|
155
|
+
for (const name of sorted) {
|
|
156
|
+
const handle = acquireLock(db, { name, owner, ...opts });
|
|
157
|
+
acquired.push(handle);
|
|
158
|
+
}
|
|
159
|
+
return acquired;
|
|
160
|
+
}
|
|
161
|
+
catch (err) {
|
|
162
|
+
// Rollback: release all acquired locks on partial failure
|
|
163
|
+
for (const handle of acquired) {
|
|
164
|
+
handle.release();
|
|
165
|
+
}
|
|
166
|
+
throw err;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Release an advisory lock.
|
|
171
|
+
* Only the owner can release their own lock.
|
|
172
|
+
*
|
|
173
|
+
* @returns true if the lock was released, false if not found or wrong owner
|
|
174
|
+
*/
|
|
175
|
+
function releaseLock(db, name, owner) {
|
|
176
|
+
const result = db
|
|
177
|
+
.prepare('DELETE FROM locks WHERE name = ? AND owner = ?')
|
|
178
|
+
.run(name, owner);
|
|
179
|
+
return result.changes > 0;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Force-release a lock regardless of owner.
|
|
183
|
+
* Use with caution — only for administrative purposes.
|
|
184
|
+
*/
|
|
185
|
+
function forceReleaseLock(db, name) {
|
|
186
|
+
const result = db
|
|
187
|
+
.prepare('DELETE FROM locks WHERE name = ?')
|
|
188
|
+
.run(name);
|
|
189
|
+
return result.changes > 0;
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Execute a function within a lock context, guaranteeing release in finally.
|
|
193
|
+
* Per FS-SS-003-0003 VR-003: locks must be released upon operation completion
|
|
194
|
+
* (success, failure, or exception).
|
|
195
|
+
*
|
|
196
|
+
* @param db - Database connection
|
|
197
|
+
* @param opts - Lock acquisition options
|
|
198
|
+
* @param fn - Function to execute while holding the lock
|
|
199
|
+
* @returns The return value of fn
|
|
200
|
+
* @throws LockTimeoutError if the lock cannot be acquired
|
|
201
|
+
*/
|
|
202
|
+
function withLock(db, opts, fn) {
|
|
203
|
+
const handle = acquireLock(db, opts);
|
|
204
|
+
try {
|
|
205
|
+
return fn();
|
|
206
|
+
}
|
|
207
|
+
finally {
|
|
208
|
+
handle.release();
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
//# sourceMappingURL=locks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"locks.js","sourceRoot":"","sources":["../../src/db/locks.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type Database from 'better-sqlite3';
|
|
2
|
+
/**
|
|
3
|
+
* Compute SHA-256 checksum of migration SQL content.
|
|
4
|
+
*/
|
|
5
|
+
export declare function computeChecksum(sql: string): string;
|
|
6
|
+
/**
|
|
7
|
+
* Get the current migration version from the database.
|
|
8
|
+
* Returns 0 if no migrations have been applied.
|
|
9
|
+
*/
|
|
10
|
+
export declare function getCurrentVersion(db: Database.Database): number;
|
|
11
|
+
/**
|
|
12
|
+
* Verify schema integrity: all expected tables and indexes exist.
|
|
13
|
+
* Returns an array of missing table/index names, or empty if all present.
|
|
14
|
+
*/
|
|
15
|
+
export declare function verifySchemaIntegrity(db: Database.Database): string[];
|
|
16
|
+
/**
|
|
17
|
+
* Verify migration checksums.
|
|
18
|
+
* By default (verifyAll=false), only checks the latest migration.
|
|
19
|
+
* With verifyAll=true, checks all applied migrations.
|
|
20
|
+
*
|
|
21
|
+
* @throws MigrationChecksumMismatchError on mismatch (ERR-MIG-003)
|
|
22
|
+
*/
|
|
23
|
+
export declare function verifyChecksums(db: Database.Database, verifyAll?: boolean): void;
|
|
24
|
+
/**
|
|
25
|
+
* Run all pending migrations on the database.
|
|
26
|
+
* Each migration is applied in a transaction.
|
|
27
|
+
* After all migrations, a schema integrity check is performed.
|
|
28
|
+
* Idempotent: if all migrations are already applied, no action is taken.
|
|
29
|
+
*
|
|
30
|
+
* @param db - Database connection
|
|
31
|
+
* @param targetVersion - Optional target version (defaults to latest)
|
|
32
|
+
* @throws MigrationError if a migration fails (ERR-MIG-001)
|
|
33
|
+
* @throws MigrationIntegrityError if integrity check fails (ERR-MIG-002)
|
|
34
|
+
*/
|
|
35
|
+
export declare function runMigrations(db: Database.Database, targetVersion?: number): void;
|
|
36
|
+
/**
|
|
37
|
+
* Initialize the builtin graph schema (relation types + graph_schema metadata).
|
|
38
|
+
*
|
|
39
|
+
* Implements AD-0048: Two-pass FK-safe insertion strategy.
|
|
40
|
+
*
|
|
41
|
+
* Pass 1: Insert all relation types WITHOUT inverse_of (avoids FK violations
|
|
42
|
+
* when the referenced inverse type row does not yet exist).
|
|
43
|
+
* Pass 2: UPDATE each type's inverse_of to its final value.
|
|
44
|
+
*
|
|
45
|
+
* Both passes run within a single transaction with FK enforcement active.
|
|
46
|
+
*
|
|
47
|
+
* @requires PRAGMA foreign_keys = ON — caller must ensure FK enforcement is enabled
|
|
48
|
+
* before invoking this function.
|
|
49
|
+
*/
|
|
50
|
+
export declare function initializeBuiltinSchema(db: Database.Database): void;
|
|
51
|
+
/**
|
|
52
|
+
* Initialize a fresh database with the full schema.
|
|
53
|
+
* Also verifies the latest migration checksum on open (VR-006).
|
|
54
|
+
* Builtin schema is initialized after migrations (FS-SS-008-0007-A).
|
|
55
|
+
*/
|
|
56
|
+
export declare function initializeSchema(db: Database.Database, verifyAll?: boolean, skipBuiltinSchema?: boolean): void;
|
|
57
|
+
/**
|
|
58
|
+
* Check if the database schema is up to date.
|
|
59
|
+
*/
|
|
60
|
+
export declare function isSchemaUpToDate(db: Database.Database): boolean;
|
|
61
|
+
/**
|
|
62
|
+
* Get list of pending migration versions.
|
|
63
|
+
*/
|
|
64
|
+
export declare function getPendingMigrations(db: Database.Database): number[];
|
|
65
|
+
//# sourceMappingURL=migration.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"migration.d.ts","sourceRoot":"","sources":["../../src/db/migration.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAO3C;;GAEG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,GAAG,MAAM,CAY/D;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,GAAG,MAAM,EAAE,CAyBrE;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,EAAE,SAAS,GAAE,OAAe,GAAG,IAAI,CAqBvF;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,aAAa,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CA+DjF;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,uBAAuB,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,GAAG,IAAI,CAiDnE;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,EAAE,SAAS,GAAE,OAAe,EAAE,iBAAiB,GAAE,OAAe,GAAG,IAAI,CAY5H;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,GAAG,OAAO,CAE/D;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,GAAG,MAAM,EAAE,CAGpE"}
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Schema migration runner
|
|
3
|
+
// Implements AD-0002: Sequential Versioned Migrations
|
|
4
|
+
// + FS-SS-001-0002 VR-004 (integrity check), VR-006 (checksum verification)
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.computeChecksum = computeChecksum;
|
|
7
|
+
exports.getCurrentVersion = getCurrentVersion;
|
|
8
|
+
exports.verifySchemaIntegrity = verifySchemaIntegrity;
|
|
9
|
+
exports.verifyChecksums = verifyChecksums;
|
|
10
|
+
exports.runMigrations = runMigrations;
|
|
11
|
+
exports.initializeBuiltinSchema = initializeBuiltinSchema;
|
|
12
|
+
exports.initializeSchema = initializeSchema;
|
|
13
|
+
exports.isSchemaUpToDate = isSchemaUpToDate;
|
|
14
|
+
exports.getPendingMigrations = getPendingMigrations;
|
|
15
|
+
const crypto_1 = require("crypto");
|
|
16
|
+
const schema_1 = require("./schema");
|
|
17
|
+
const schema_types_1 = require("./generated/schema-types");
|
|
18
|
+
const errors_1 = require("../utils/errors");
|
|
19
|
+
const retry_1 = require("./retry");
|
|
20
|
+
/**
|
|
21
|
+
* Compute SHA-256 checksum of migration SQL content.
|
|
22
|
+
*/
|
|
23
|
+
function computeChecksum(sql) {
|
|
24
|
+
return (0, crypto_1.createHash)('sha256').update(sql).digest('hex');
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Get the current migration version from the database.
|
|
28
|
+
* Returns 0 if no migrations have been applied.
|
|
29
|
+
*/
|
|
30
|
+
function getCurrentVersion(db) {
|
|
31
|
+
// Check if _migrations table exists
|
|
32
|
+
const tableExists = db
|
|
33
|
+
.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='_migrations'")
|
|
34
|
+
.get();
|
|
35
|
+
if (!tableExists) {
|
|
36
|
+
return 0;
|
|
37
|
+
}
|
|
38
|
+
const row = db.prepare('SELECT MAX(version) as v FROM _migrations').get();
|
|
39
|
+
return row?.v ?? 0;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Verify schema integrity: all expected tables and indexes exist.
|
|
43
|
+
* Returns an array of missing table/index names, or empty if all present.
|
|
44
|
+
*/
|
|
45
|
+
function verifySchemaIntegrity(db) {
|
|
46
|
+
const existingTables = new Set(db.prepare("SELECT name FROM sqlite_master WHERE type='table'").all()
|
|
47
|
+
.map(r => r.name));
|
|
48
|
+
const existingIndexes = new Set(db.prepare("SELECT name FROM sqlite_master WHERE type='index'").all()
|
|
49
|
+
.map(r => r.name));
|
|
50
|
+
const missing = [];
|
|
51
|
+
for (const table of schema_1.EXPECTED_TABLES) {
|
|
52
|
+
if (!existingTables.has(table)) {
|
|
53
|
+
missing.push(`table:${table}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
for (const index of schema_1.EXPECTED_INDEXES) {
|
|
57
|
+
if (!existingIndexes.has(index)) {
|
|
58
|
+
missing.push(`index:${index}`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return missing;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Verify migration checksums.
|
|
65
|
+
* By default (verifyAll=false), only checks the latest migration.
|
|
66
|
+
* With verifyAll=true, checks all applied migrations.
|
|
67
|
+
*
|
|
68
|
+
* @throws MigrationChecksumMismatchError on mismatch (ERR-MIG-003)
|
|
69
|
+
*/
|
|
70
|
+
function verifyChecksums(db, verifyAll = false) {
|
|
71
|
+
const currentVersion = getCurrentVersion(db);
|
|
72
|
+
if (currentVersion === 0)
|
|
73
|
+
return;
|
|
74
|
+
const rows = db
|
|
75
|
+
.prepare('SELECT version, checksum FROM _migrations ORDER BY version')
|
|
76
|
+
.all();
|
|
77
|
+
const versionsToCheck = verifyAll
|
|
78
|
+
? rows
|
|
79
|
+
: rows.filter(r => r.version === currentVersion);
|
|
80
|
+
for (const row of versionsToCheck) {
|
|
81
|
+
const migration = schema_1.MIGRATIONS.find(m => m.version === row.version);
|
|
82
|
+
if (!migration)
|
|
83
|
+
continue; // Unknown version — can't verify
|
|
84
|
+
const expected = computeChecksum(migration.sql);
|
|
85
|
+
if (row.checksum !== expected) {
|
|
86
|
+
throw new errors_1.MigrationChecksumMismatchError(row.version, expected, row.checksum);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Run all pending migrations on the database.
|
|
92
|
+
* Each migration is applied in a transaction.
|
|
93
|
+
* After all migrations, a schema integrity check is performed.
|
|
94
|
+
* Idempotent: if all migrations are already applied, no action is taken.
|
|
95
|
+
*
|
|
96
|
+
* @param db - Database connection
|
|
97
|
+
* @param targetVersion - Optional target version (defaults to latest)
|
|
98
|
+
* @throws MigrationError if a migration fails (ERR-MIG-001)
|
|
99
|
+
* @throws MigrationIntegrityError if integrity check fails (ERR-MIG-002)
|
|
100
|
+
*/
|
|
101
|
+
function runMigrations(db, targetVersion) {
|
|
102
|
+
const target = targetVersion ?? schema_1.LATEST_VERSION;
|
|
103
|
+
const current = getCurrentVersion(db);
|
|
104
|
+
if (current > target) {
|
|
105
|
+
throw new errors_1.MigrationVersionError(current, [`Database version (${current}) is ahead of target version (${target}). This application version may be outdated.`]);
|
|
106
|
+
}
|
|
107
|
+
if (current === target) {
|
|
108
|
+
return; // Already at target version
|
|
109
|
+
}
|
|
110
|
+
const pending = schema_1.MIGRATIONS.filter(m => m.version > current && m.version <= target)
|
|
111
|
+
.sort((a, b) => a.version - b.version);
|
|
112
|
+
let lastAppliedVersion = current;
|
|
113
|
+
// Disable FK enforcement during migrations (needed for DROP COLUMN on FK-referenced columns)
|
|
114
|
+
const fkWasOn = db.pragma('foreign_keys', { simple: true });
|
|
115
|
+
if (fkWasOn)
|
|
116
|
+
db.pragma('foreign_keys = OFF');
|
|
117
|
+
for (const migration of pending) {
|
|
118
|
+
const checksum = computeChecksum(migration.sql);
|
|
119
|
+
const transaction = db.transaction(() => {
|
|
120
|
+
try {
|
|
121
|
+
db.exec(migration.sql);
|
|
122
|
+
db.prepare('INSERT INTO _migrations (version, checksum) VALUES (?, ?)')
|
|
123
|
+
.run(migration.version, checksum);
|
|
124
|
+
}
|
|
125
|
+
catch (err) {
|
|
126
|
+
// Allow idempotent migrations: ignore "duplicate column" errors for ALTER TABLE ADD COLUMN
|
|
127
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
128
|
+
if (msg.includes('duplicate column name') || msg.includes('no such column')) {
|
|
129
|
+
// Column already exists (ADD) or already removed (DROP) — record migration as applied
|
|
130
|
+
db.prepare('INSERT INTO _migrations (version, checksum) VALUES (?, ?)')
|
|
131
|
+
.run(migration.version, checksum);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
throw new errors_1.MigrationError(`Migration v${migration.version} (${migration.description}) failed: ${err instanceof Error ? err.message : String(err)}`, { version: migration.version, description: migration.description, originalError: err });
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
transaction();
|
|
138
|
+
lastAppliedVersion = migration.version;
|
|
139
|
+
}
|
|
140
|
+
// Re-enable FK enforcement if it was on
|
|
141
|
+
if (fkWasOn)
|
|
142
|
+
db.pragma('foreign_keys = ON');
|
|
143
|
+
// VR-004: Schema integrity check after all migrations (only when fully migrated)
|
|
144
|
+
if (lastAppliedVersion >= schema_1.LATEST_VERSION || target >= schema_1.LATEST_VERSION) {
|
|
145
|
+
const missing = verifySchemaIntegrity(db);
|
|
146
|
+
if (missing.length > 0) {
|
|
147
|
+
throw new errors_1.MigrationIntegrityError(lastAppliedVersion, missing);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Initialize the builtin graph schema (relation types + graph_schema metadata).
|
|
153
|
+
*
|
|
154
|
+
* Implements AD-0048: Two-pass FK-safe insertion strategy.
|
|
155
|
+
*
|
|
156
|
+
* Pass 1: Insert all relation types WITHOUT inverse_of (avoids FK violations
|
|
157
|
+
* when the referenced inverse type row does not yet exist).
|
|
158
|
+
* Pass 2: UPDATE each type's inverse_of to its final value.
|
|
159
|
+
*
|
|
160
|
+
* Both passes run within a single transaction with FK enforcement active.
|
|
161
|
+
*
|
|
162
|
+
* @requires PRAGMA foreign_keys = ON — caller must ensure FK enforcement is enabled
|
|
163
|
+
* before invoking this function.
|
|
164
|
+
*/
|
|
165
|
+
function initializeBuiltinSchema(db) {
|
|
166
|
+
// Defensive check: FK enforcement must be ON for the two-pass strategy to be meaningful
|
|
167
|
+
const fkState = db.pragma('foreign_keys', { simple: true });
|
|
168
|
+
if (fkState === 0) {
|
|
169
|
+
throw new Error('initializeBuiltinSchema requires foreign_keys = ON');
|
|
170
|
+
}
|
|
171
|
+
const now = new Date().toISOString();
|
|
172
|
+
(0, retry_1.withTransaction)(db, () => {
|
|
173
|
+
// builtin:3.0 (Sprint 31): entity_types table dropped.
|
|
174
|
+
// Entity types are derived dynamically from documents.type.
|
|
175
|
+
// No entity type initialization is performed here.
|
|
176
|
+
// Pass 1: Insert all relation types WITHOUT inverse_of
|
|
177
|
+
// This avoids FK violations when the inverse type row does not yet exist.
|
|
178
|
+
for (const rt of schema_types_1.RELATION_TYPES) {
|
|
179
|
+
db.prepare(`
|
|
180
|
+
INSERT OR IGNORE INTO relation_types (name, from_types, to_types, cardinality, inverse_of, description, created_at, updated_at)
|
|
181
|
+
VALUES (?, ?, ?, ?, NULL, ?, ?, ?)
|
|
182
|
+
`).run(rt.name, JSON.stringify([...rt.from_types]), JSON.stringify([...rt.to_types]), rt.cardinality, rt.description, now, now);
|
|
183
|
+
}
|
|
184
|
+
// Pass 2: UPDATE inverse_of for each type that has one
|
|
185
|
+
for (const rt of schema_types_1.RELATION_TYPES) {
|
|
186
|
+
if (rt.inverse_of) {
|
|
187
|
+
db.prepare(`
|
|
188
|
+
UPDATE relation_types SET inverse_of = ?, updated_at = ?
|
|
189
|
+
WHERE name = ? AND inverse_of IS NULL
|
|
190
|
+
`).run(rt.inverse_of, now, rt.name);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
// Record builtin schema source — only if no row exists (preserve YAML bootstrap metadata)
|
|
194
|
+
const existingMeta = db.prepare('SELECT COUNT(*) as cnt FROM graph_schema').get();
|
|
195
|
+
if (existingMeta.cnt === 0) {
|
|
196
|
+
db.prepare(`
|
|
197
|
+
INSERT INTO graph_schema (source_file, version, bootstrapped_at, checksum)
|
|
198
|
+
VALUES ('builtin:3.0', 'builtin:3.0', ?, NULL)
|
|
199
|
+
`).run(now);
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Initialize a fresh database with the full schema.
|
|
205
|
+
* Also verifies the latest migration checksum on open (VR-006).
|
|
206
|
+
* Builtin schema is initialized after migrations (FS-SS-008-0007-A).
|
|
207
|
+
*/
|
|
208
|
+
function initializeSchema(db, verifyAll = false, skipBuiltinSchema = false) {
|
|
209
|
+
// Verify existing checksums before running migrations
|
|
210
|
+
const currentVersion = getCurrentVersion(db);
|
|
211
|
+
if (currentVersion > 0) {
|
|
212
|
+
verifyChecksums(db, verifyAll);
|
|
213
|
+
}
|
|
214
|
+
runMigrations(db);
|
|
215
|
+
// FS-SS-008-0007-A: Initialize builtin graph schema
|
|
216
|
+
if (!skipBuiltinSchema) {
|
|
217
|
+
initializeBuiltinSchema(db);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Check if the database schema is up to date.
|
|
222
|
+
*/
|
|
223
|
+
function isSchemaUpToDate(db) {
|
|
224
|
+
return getCurrentVersion(db) >= schema_1.LATEST_VERSION;
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Get list of pending migration versions.
|
|
228
|
+
*/
|
|
229
|
+
function getPendingMigrations(db) {
|
|
230
|
+
const current = getCurrentVersion(db);
|
|
231
|
+
return schema_1.MIGRATIONS.filter(m => m.version > current).map(m => m.version);
|
|
232
|
+
}
|
|
233
|
+
//# sourceMappingURL=migration.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"migration.js","sourceRoot":"","sources":["../../src/db/migration.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type Database from 'better-sqlite3';
|
|
2
|
+
export interface RetryOptions {
|
|
3
|
+
/** Maximum number of retries (default: 5) */
|
|
4
|
+
maxRetries?: number;
|
|
5
|
+
/** Base delay in ms for exponential backoff (default: 50) */
|
|
6
|
+
baseMs?: number;
|
|
7
|
+
/** Maximum delay in ms for exponential backoff (default: 1000) */
|
|
8
|
+
maxMs?: number;
|
|
9
|
+
/** Maximum total wait time in ms across all retries (default: 30000) */
|
|
10
|
+
maxTotalWaitMs?: number;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Execute a function with retry on SQLITE_BUSY using exponential backoff.
|
|
14
|
+
* Each retry doubles the delay, capped at maxMs.
|
|
15
|
+
* Total wait time is capped by maxTotalWaitMs per FS-SS-003-0005 VR-007.
|
|
16
|
+
*
|
|
17
|
+
* @param fn - The function to execute (typically a DB write operation)
|
|
18
|
+
* @param options - Retry configuration
|
|
19
|
+
* @throws DatabaseBusyError (ERR-LOCK-001) if all retries are exhausted
|
|
20
|
+
*/
|
|
21
|
+
export declare function withRetry<T>(fn: () => T, options?: RetryOptions): T;
|
|
22
|
+
/**
|
|
23
|
+
* Execute a database operation within a transaction with retry on busy.
|
|
24
|
+
*/
|
|
25
|
+
export declare function withTransaction<T>(db: Database.Database, fn: () => T, options?: RetryOptions): T;
|
|
26
|
+
//# sourceMappingURL=retry.d.ts.map
|