steroids-api 0.2.7
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/dist/API/src/index.d.ts +10 -0
- package/dist/API/src/index.d.ts.map +1 -0
- package/dist/API/src/index.js +130 -0
- package/dist/API/src/index.js.map +1 -0
- package/dist/API/src/routes/activity.d.ts +7 -0
- package/dist/API/src/routes/activity.d.ts.map +1 -0
- package/dist/API/src/routes/activity.js +252 -0
- package/dist/API/src/routes/activity.js.map +1 -0
- package/dist/API/src/routes/config.d.ts +7 -0
- package/dist/API/src/routes/config.d.ts.map +1 -0
- package/dist/API/src/routes/config.js +521 -0
- package/dist/API/src/routes/config.js.map +1 -0
- package/dist/API/src/routes/health.d.ts +7 -0
- package/dist/API/src/routes/health.d.ts.map +1 -0
- package/dist/API/src/routes/health.js +172 -0
- package/dist/API/src/routes/health.js.map +1 -0
- package/dist/API/src/routes/incidents.d.ts +7 -0
- package/dist/API/src/routes/incidents.d.ts.map +1 -0
- package/dist/API/src/routes/incidents.js +117 -0
- package/dist/API/src/routes/incidents.js.map +1 -0
- package/dist/API/src/routes/projects.d.ts +7 -0
- package/dist/API/src/routes/projects.d.ts.map +1 -0
- package/dist/API/src/routes/projects.js +398 -0
- package/dist/API/src/routes/projects.js.map +1 -0
- package/dist/API/src/routes/runners.d.ts +7 -0
- package/dist/API/src/routes/runners.d.ts.map +1 -0
- package/dist/API/src/routes/runners.js +242 -0
- package/dist/API/src/routes/runners.js.map +1 -0
- package/dist/API/src/routes/tasks.d.ts +7 -0
- package/dist/API/src/routes/tasks.d.ts.map +1 -0
- package/dist/API/src/routes/tasks.js +1007 -0
- package/dist/API/src/routes/tasks.js.map +1 -0
- package/dist/API/src/utils/validation.d.ts +22 -0
- package/dist/API/src/utils/validation.d.ts.map +1 -0
- package/dist/API/src/utils/validation.js +50 -0
- package/dist/API/src/utils/validation.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +184 -0
- package/dist/index.js.map +1 -0
- package/dist/routes/activity.d.ts +7 -0
- package/dist/routes/activity.d.ts.map +1 -0
- package/dist/routes/activity.js +252 -0
- package/dist/routes/activity.js.map +1 -0
- package/dist/routes/config.d.ts +7 -0
- package/dist/routes/config.d.ts.map +1 -0
- package/dist/routes/config.js +647 -0
- package/dist/routes/config.js.map +1 -0
- package/dist/routes/credit-alerts.d.ts +2 -0
- package/dist/routes/credit-alerts.d.ts.map +1 -0
- package/dist/routes/credit-alerts.js +97 -0
- package/dist/routes/credit-alerts.js.map +1 -0
- package/dist/routes/health.d.ts +7 -0
- package/dist/routes/health.d.ts.map +1 -0
- package/dist/routes/health.js +200 -0
- package/dist/routes/health.js.map +1 -0
- package/dist/routes/incidents.d.ts +7 -0
- package/dist/routes/incidents.d.ts.map +1 -0
- package/dist/routes/incidents.js +117 -0
- package/dist/routes/incidents.js.map +1 -0
- package/dist/routes/projects.d.ts +7 -0
- package/dist/routes/projects.d.ts.map +1 -0
- package/dist/routes/projects.js +643 -0
- package/dist/routes/projects.js.map +1 -0
- package/dist/routes/runners.d.ts +7 -0
- package/dist/routes/runners.d.ts.map +1 -0
- package/dist/routes/runners.js +299 -0
- package/dist/routes/runners.js.map +1 -0
- package/dist/routes/skills.d.ts +3 -0
- package/dist/routes/skills.d.ts.map +1 -0
- package/dist/routes/skills.js +109 -0
- package/dist/routes/skills.js.map +1 -0
- package/dist/routes/storage.d.ts +7 -0
- package/dist/routes/storage.d.ts.map +1 -0
- package/dist/routes/storage.js +93 -0
- package/dist/routes/storage.js.map +1 -0
- package/dist/routes/tasks.d.ts +7 -0
- package/dist/routes/tasks.d.ts.map +1 -0
- package/dist/routes/tasks.js +1145 -0
- package/dist/routes/tasks.js.map +1 -0
- package/dist/src/cleanup/invocation-logs.d.ts +30 -0
- package/dist/src/cleanup/invocation-logs.d.ts.map +1 -0
- package/dist/src/cleanup/invocation-logs.js +66 -0
- package/dist/src/cleanup/invocation-logs.js.map +1 -0
- package/dist/src/commands/loop-phases.d.ts +11 -0
- package/dist/src/commands/loop-phases.d.ts.map +1 -0
- package/dist/src/commands/loop-phases.js +304 -0
- package/dist/src/commands/loop-phases.js.map +1 -0
- package/dist/src/config/loader.d.ts +160 -0
- package/dist/src/config/loader.d.ts.map +1 -0
- package/dist/src/config/loader.js +276 -0
- package/dist/src/config/loader.js.map +1 -0
- package/dist/src/database/connection.d.ts +35 -0
- package/dist/src/database/connection.d.ts.map +1 -0
- package/dist/src/database/connection.js +197 -0
- package/dist/src/database/connection.js.map +1 -0
- package/dist/src/database/queries.d.ts +220 -0
- package/dist/src/database/queries.d.ts.map +1 -0
- package/dist/src/database/queries.js +589 -0
- package/dist/src/database/queries.js.map +1 -0
- package/dist/src/database/schema.d.ts +8 -0
- package/dist/src/database/schema.d.ts.map +1 -0
- package/dist/src/database/schema.js +184 -0
- package/dist/src/database/schema.js.map +1 -0
- package/dist/src/git/push.d.ts +26 -0
- package/dist/src/git/push.d.ts.map +1 -0
- package/dist/src/git/push.js +91 -0
- package/dist/src/git/push.js.map +1 -0
- package/dist/src/git/status.d.ts +83 -0
- package/dist/src/git/status.d.ts.map +1 -0
- package/dist/src/git/status.js +315 -0
- package/dist/src/git/status.js.map +1 -0
- package/dist/src/health/stuck-task-detector.d.ts +131 -0
- package/dist/src/health/stuck-task-detector.d.ts.map +1 -0
- package/dist/src/health/stuck-task-detector.js +233 -0
- package/dist/src/health/stuck-task-detector.js.map +1 -0
- package/dist/src/health/stuck-task-recovery.d.ts +45 -0
- package/dist/src/health/stuck-task-recovery.d.ts.map +1 -0
- package/dist/src/health/stuck-task-recovery.js +309 -0
- package/dist/src/health/stuck-task-recovery.js.map +1 -0
- package/dist/src/index.d.ts +10 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +130 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/locking/queries.d.ts +116 -0
- package/dist/src/locking/queries.d.ts.map +1 -0
- package/dist/src/locking/queries.js +232 -0
- package/dist/src/locking/queries.js.map +1 -0
- package/dist/src/locking/section-lock.d.ts +74 -0
- package/dist/src/locking/section-lock.d.ts.map +1 -0
- package/dist/src/locking/section-lock.js +196 -0
- package/dist/src/locking/section-lock.js.map +1 -0
- package/dist/src/locking/task-lock.d.ts +92 -0
- package/dist/src/locking/task-lock.d.ts.map +1 -0
- package/dist/src/locking/task-lock.js +233 -0
- package/dist/src/locking/task-lock.js.map +1 -0
- package/dist/src/migrations/index.d.ts +7 -0
- package/dist/src/migrations/index.d.ts.map +1 -0
- package/dist/src/migrations/index.js +9 -0
- package/dist/src/migrations/index.js.map +1 -0
- package/dist/src/migrations/manifest.d.ts +92 -0
- package/dist/src/migrations/manifest.d.ts.map +1 -0
- package/dist/src/migrations/manifest.js +255 -0
- package/dist/src/migrations/manifest.js.map +1 -0
- package/dist/src/migrations/runner.d.ts +84 -0
- package/dist/src/migrations/runner.d.ts.map +1 -0
- package/dist/src/migrations/runner.js +338 -0
- package/dist/src/migrations/runner.js.map +1 -0
- package/dist/src/orchestrator/coder.d.ts +32 -0
- package/dist/src/orchestrator/coder.d.ts.map +1 -0
- package/dist/src/orchestrator/coder.js +170 -0
- package/dist/src/orchestrator/coder.js.map +1 -0
- package/dist/src/orchestrator/coordinator.d.ts +28 -0
- package/dist/src/orchestrator/coordinator.d.ts.map +1 -0
- package/dist/src/orchestrator/coordinator.js +252 -0
- package/dist/src/orchestrator/coordinator.js.map +1 -0
- package/dist/src/orchestrator/fallback-handler.d.ts +24 -0
- package/dist/src/orchestrator/fallback-handler.d.ts.map +1 -0
- package/dist/src/orchestrator/fallback-handler.js +280 -0
- package/dist/src/orchestrator/fallback-handler.js.map +1 -0
- package/dist/src/orchestrator/invoke.d.ts +14 -0
- package/dist/src/orchestrator/invoke.d.ts.map +1 -0
- package/dist/src/orchestrator/invoke.js +76 -0
- package/dist/src/orchestrator/invoke.js.map +1 -0
- package/dist/src/orchestrator/post-coder.d.ts +10 -0
- package/dist/src/orchestrator/post-coder.d.ts.map +1 -0
- package/dist/src/orchestrator/post-coder.js +198 -0
- package/dist/src/orchestrator/post-coder.js.map +1 -0
- package/dist/src/orchestrator/post-reviewer.d.ts +10 -0
- package/dist/src/orchestrator/post-reviewer.d.ts.map +1 -0
- package/dist/src/orchestrator/post-reviewer.js +199 -0
- package/dist/src/orchestrator/post-reviewer.js.map +1 -0
- package/dist/src/orchestrator/reviewer.d.ts +35 -0
- package/dist/src/orchestrator/reviewer.d.ts.map +1 -0
- package/dist/src/orchestrator/reviewer.js +237 -0
- package/dist/src/orchestrator/reviewer.js.map +1 -0
- package/dist/src/orchestrator/schemas.d.ts +10 -0
- package/dist/src/orchestrator/schemas.d.ts.map +1 -0
- package/dist/src/orchestrator/schemas.js +81 -0
- package/dist/src/orchestrator/schemas.js.map +1 -0
- package/dist/src/orchestrator/task-selector.d.ts +102 -0
- package/dist/src/orchestrator/task-selector.d.ts.map +1 -0
- package/dist/src/orchestrator/task-selector.js +326 -0
- package/dist/src/orchestrator/task-selector.js.map +1 -0
- package/dist/src/orchestrator/types.d.ts +74 -0
- package/dist/src/orchestrator/types.d.ts.map +1 -0
- package/dist/src/orchestrator/types.js +5 -0
- package/dist/src/orchestrator/types.js.map +1 -0
- package/dist/src/prompts/coder.d.ts +36 -0
- package/dist/src/prompts/coder.d.ts.map +1 -0
- package/dist/src/prompts/coder.js +303 -0
- package/dist/src/prompts/coder.js.map +1 -0
- package/dist/src/prompts/prompt-helpers.d.ts +51 -0
- package/dist/src/prompts/prompt-helpers.d.ts.map +1 -0
- package/dist/src/prompts/prompt-helpers.js +299 -0
- package/dist/src/prompts/prompt-helpers.js.map +1 -0
- package/dist/src/prompts/reviewer.d.ts +40 -0
- package/dist/src/prompts/reviewer.d.ts.map +1 -0
- package/dist/src/prompts/reviewer.js +416 -0
- package/dist/src/prompts/reviewer.js.map +1 -0
- package/dist/src/providers/claude.d.ts +53 -0
- package/dist/src/providers/claude.d.ts.map +1 -0
- package/dist/src/providers/claude.js +227 -0
- package/dist/src/providers/claude.js.map +1 -0
- package/dist/src/providers/codex.d.ts +53 -0
- package/dist/src/providers/codex.d.ts.map +1 -0
- package/dist/src/providers/codex.js +253 -0
- package/dist/src/providers/codex.js.map +1 -0
- package/dist/src/providers/gemini.d.ts +58 -0
- package/dist/src/providers/gemini.d.ts.map +1 -0
- package/dist/src/providers/gemini.js +240 -0
- package/dist/src/providers/gemini.js.map +1 -0
- package/dist/src/providers/interface.d.ts +185 -0
- package/dist/src/providers/interface.d.ts.map +1 -0
- package/dist/src/providers/interface.js +92 -0
- package/dist/src/providers/interface.js.map +1 -0
- package/dist/src/providers/invocation-logger.d.ts +97 -0
- package/dist/src/providers/invocation-logger.d.ts.map +1 -0
- package/dist/src/providers/invocation-logger.js +378 -0
- package/dist/src/providers/invocation-logger.js.map +1 -0
- package/dist/src/providers/openai.d.ts +53 -0
- package/dist/src/providers/openai.d.ts.map +1 -0
- package/dist/src/providers/openai.js +230 -0
- package/dist/src/providers/openai.js.map +1 -0
- package/dist/src/providers/registry.d.ts +100 -0
- package/dist/src/providers/registry.d.ts.map +1 -0
- package/dist/src/providers/registry.js +170 -0
- package/dist/src/providers/registry.js.map +1 -0
- package/dist/src/routes/activity.d.ts +7 -0
- package/dist/src/routes/activity.d.ts.map +1 -0
- package/dist/src/routes/activity.js +252 -0
- package/dist/src/routes/activity.js.map +1 -0
- package/dist/src/routes/config.d.ts +7 -0
- package/dist/src/routes/config.d.ts.map +1 -0
- package/dist/src/routes/config.js +521 -0
- package/dist/src/routes/config.js.map +1 -0
- package/dist/src/routes/health.d.ts +7 -0
- package/dist/src/routes/health.d.ts.map +1 -0
- package/dist/src/routes/health.js +172 -0
- package/dist/src/routes/health.js.map +1 -0
- package/dist/src/routes/incidents.d.ts +7 -0
- package/dist/src/routes/incidents.d.ts.map +1 -0
- package/dist/src/routes/incidents.js +117 -0
- package/dist/src/routes/incidents.js.map +1 -0
- package/dist/src/routes/projects.d.ts +7 -0
- package/dist/src/routes/projects.d.ts.map +1 -0
- package/dist/src/routes/projects.js +398 -0
- package/dist/src/routes/projects.js.map +1 -0
- package/dist/src/routes/runners.d.ts +7 -0
- package/dist/src/routes/runners.d.ts.map +1 -0
- package/dist/src/routes/runners.js +242 -0
- package/dist/src/routes/runners.js.map +1 -0
- package/dist/src/routes/tasks.d.ts +7 -0
- package/dist/src/routes/tasks.d.ts.map +1 -0
- package/dist/src/routes/tasks.js +1007 -0
- package/dist/src/routes/tasks.js.map +1 -0
- package/dist/src/runners/activity-log.d.ts +65 -0
- package/dist/src/runners/activity-log.d.ts.map +1 -0
- package/dist/src/runners/activity-log.js +140 -0
- package/dist/src/runners/activity-log.js.map +1 -0
- package/dist/src/runners/cron.d.ts +30 -0
- package/dist/src/runners/cron.d.ts.map +1 -0
- package/dist/src/runners/cron.js +333 -0
- package/dist/src/runners/cron.js.map +1 -0
- package/dist/src/runners/daemon.d.ts +71 -0
- package/dist/src/runners/daemon.d.ts.map +1 -0
- package/dist/src/runners/daemon.js +233 -0
- package/dist/src/runners/daemon.js.map +1 -0
- package/dist/src/runners/global-db.d.ts +31 -0
- package/dist/src/runners/global-db.d.ts.map +1 -0
- package/dist/src/runners/global-db.js +220 -0
- package/dist/src/runners/global-db.js.map +1 -0
- package/dist/src/runners/hang-detector.d.ts +38 -0
- package/dist/src/runners/hang-detector.d.ts.map +1 -0
- package/dist/src/runners/hang-detector.js +130 -0
- package/dist/src/runners/hang-detector.js.map +1 -0
- package/dist/src/runners/heartbeat.d.ts +39 -0
- package/dist/src/runners/heartbeat.d.ts.map +1 -0
- package/dist/src/runners/heartbeat.js +71 -0
- package/dist/src/runners/heartbeat.js.map +1 -0
- package/dist/src/runners/lock.d.ts +47 -0
- package/dist/src/runners/lock.d.ts.map +1 -0
- package/dist/src/runners/lock.js +140 -0
- package/dist/src/runners/lock.js.map +1 -0
- package/dist/src/runners/orchestrator-loop.d.ts +20 -0
- package/dist/src/runners/orchestrator-loop.d.ts.map +1 -0
- package/dist/src/runners/orchestrator-loop.js +208 -0
- package/dist/src/runners/orchestrator-loop.js.map +1 -0
- package/dist/src/runners/projects.d.ts +96 -0
- package/dist/src/runners/projects.d.ts.map +1 -0
- package/dist/src/runners/projects.js +243 -0
- package/dist/src/runners/projects.js.map +1 -0
- package/dist/src/runners/wakeup.d.ts +37 -0
- package/dist/src/runners/wakeup.d.ts.map +1 -0
- package/dist/src/runners/wakeup.js +355 -0
- package/dist/src/runners/wakeup.js.map +1 -0
- package/dist/src/utils/validation.d.ts +22 -0
- package/dist/src/utils/validation.d.ts.map +1 -0
- package/dist/src/utils/validation.js +50 -0
- package/dist/src/utils/validation.js.map +1 -0
- package/dist/utils/sqlite.d.ts +17 -0
- package/dist/utils/sqlite.d.ts.map +1 -0
- package/dist/utils/sqlite.js +27 -0
- package/dist/utils/sqlite.js.map +1 -0
- package/dist/utils/storage-cache.d.ts +33 -0
- package/dist/utils/storage-cache.d.ts.map +1 -0
- package/dist/utils/storage-cache.js +81 -0
- package/dist/utils/storage-cache.js.map +1 -0
- package/dist/utils/validation.d.ts +22 -0
- package/dist/utils/validation.d.ts.map +1 -0
- package/dist/utils/validation.js +51 -0
- package/dist/utils/validation.js.map +1 -0
- package/package.json +39 -0
- package/src/index.ts +199 -0
- package/src/routes/activity.ts +302 -0
- package/src/routes/config.ts +723 -0
- package/src/routes/credit-alerts.ts +73 -0
- package/src/routes/health.ts +219 -0
- package/src/routes/incidents.ts +131 -0
- package/src/routes/projects.ts +854 -0
- package/src/routes/runners.ts +357 -0
- package/src/routes/skills.ts +127 -0
- package/src/routes/storage.ts +108 -0
- package/src/routes/tasks.ts +1372 -0
- package/src/utils/sqlite.ts +36 -0
- package/src/utils/storage-cache.ts +107 -0
- package/src/utils/validation.ts +61 -0
- package/tsconfig.json +20 -0
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Section locking for parallel work prevention
|
|
3
|
+
* Prevents multiple runners from working on tasks in the same section
|
|
4
|
+
*/
|
|
5
|
+
import { getSectionLock, isSectionLockExpired, tryInsertSectionLock, claimExpiredSectionLock, releaseSectionLock as releaseSectionLockQuery, forceReleaseSectionLock, } from './queries.js';
|
|
6
|
+
// Default section lock timeout in minutes
|
|
7
|
+
const DEFAULT_SECTION_TIMEOUT_MINUTES = 120;
|
|
8
|
+
// ============ Lock Acquisition ============
|
|
9
|
+
/**
|
|
10
|
+
* Acquire a section lock with atomic operations
|
|
11
|
+
*
|
|
12
|
+
* Flow:
|
|
13
|
+
* 1. Try INSERT (fails if lock exists)
|
|
14
|
+
* 2. If exists, check if we own it
|
|
15
|
+
* 3. If owned by another, check if expired
|
|
16
|
+
* 4. If expired, claim atomically
|
|
17
|
+
* 5. If not expired, return failure
|
|
18
|
+
*/
|
|
19
|
+
export function acquireSectionLock(db, sectionId, runnerId, timeoutMinutes = DEFAULT_SECTION_TIMEOUT_MINUTES) {
|
|
20
|
+
// Step 1: Try to insert new lock
|
|
21
|
+
if (tryInsertSectionLock(db, sectionId, runnerId, timeoutMinutes)) {
|
|
22
|
+
const lock = getSectionLock(db, sectionId);
|
|
23
|
+
return {
|
|
24
|
+
acquired: true,
|
|
25
|
+
lock: lock ?? undefined,
|
|
26
|
+
reason: 'acquired_new',
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
// Step 2: Lock exists, get current state
|
|
30
|
+
const existingLock = getSectionLock(db, sectionId);
|
|
31
|
+
if (!existingLock) {
|
|
32
|
+
// Race condition: lock was deleted between insert attempt and now
|
|
33
|
+
if (tryInsertSectionLock(db, sectionId, runnerId, timeoutMinutes)) {
|
|
34
|
+
const lock = getSectionLock(db, sectionId);
|
|
35
|
+
return {
|
|
36
|
+
acquired: true,
|
|
37
|
+
lock: lock ?? undefined,
|
|
38
|
+
reason: 'acquired_new',
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
const currentLock = getSectionLock(db, sectionId);
|
|
42
|
+
return createLockedError(sectionId, currentLock);
|
|
43
|
+
}
|
|
44
|
+
// Step 3: Check if we already own the lock
|
|
45
|
+
if (existingLock.runner_id === runnerId) {
|
|
46
|
+
return {
|
|
47
|
+
acquired: true,
|
|
48
|
+
lock: existingLock,
|
|
49
|
+
reason: 'already_owned',
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
// Step 4: Check if lock is expired
|
|
53
|
+
if (isSectionLockExpired(existingLock)) {
|
|
54
|
+
if (claimExpiredSectionLock(db, sectionId, runnerId, timeoutMinutes)) {
|
|
55
|
+
const lock = getSectionLock(db, sectionId);
|
|
56
|
+
return {
|
|
57
|
+
acquired: true,
|
|
58
|
+
lock: lock ?? undefined,
|
|
59
|
+
reason: 'claimed_expired',
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
// Another runner claimed it first
|
|
63
|
+
const currentLock = getSectionLock(db, sectionId);
|
|
64
|
+
return createLockedError(sectionId, currentLock);
|
|
65
|
+
}
|
|
66
|
+
// Step 5: Lock is held by another runner and not expired
|
|
67
|
+
return createLockedError(sectionId, existingLock);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Create a SECTION_LOCKED error response
|
|
71
|
+
*/
|
|
72
|
+
function createLockedError(sectionId, lock) {
|
|
73
|
+
return {
|
|
74
|
+
acquired: false,
|
|
75
|
+
error: {
|
|
76
|
+
code: 'SECTION_LOCKED',
|
|
77
|
+
message: 'Section is locked by another runner',
|
|
78
|
+
details: {
|
|
79
|
+
sectionId,
|
|
80
|
+
runnerId: lock?.runner_id,
|
|
81
|
+
expiresAt: lock?.expires_at,
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
// ============ Lock Release ============
|
|
87
|
+
/**
|
|
88
|
+
* Release a section lock (only by owner)
|
|
89
|
+
*/
|
|
90
|
+
export function releaseSectionLock(db, sectionId, runnerId) {
|
|
91
|
+
const existingLock = getSectionLock(db, sectionId);
|
|
92
|
+
if (!existingLock) {
|
|
93
|
+
return {
|
|
94
|
+
released: false,
|
|
95
|
+
error: {
|
|
96
|
+
code: 'LOCK_NOT_FOUND',
|
|
97
|
+
message: 'Lock does not exist',
|
|
98
|
+
details: { sectionId },
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
if (existingLock.runner_id !== runnerId) {
|
|
103
|
+
return {
|
|
104
|
+
released: false,
|
|
105
|
+
error: {
|
|
106
|
+
code: 'PERMISSION_DENIED',
|
|
107
|
+
message: 'Cannot release lock owned by another runner',
|
|
108
|
+
details: {
|
|
109
|
+
sectionId,
|
|
110
|
+
runnerId: existingLock.runner_id,
|
|
111
|
+
expiresAt: existingLock.expires_at,
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
releaseSectionLockQuery(db, sectionId, runnerId);
|
|
117
|
+
return { released: true };
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Force release a section lock (admin operation)
|
|
121
|
+
*/
|
|
122
|
+
export function forceRelease(db, sectionId) {
|
|
123
|
+
const existed = forceReleaseSectionLock(db, sectionId);
|
|
124
|
+
if (!existed) {
|
|
125
|
+
return {
|
|
126
|
+
released: false,
|
|
127
|
+
error: {
|
|
128
|
+
code: 'LOCK_NOT_FOUND',
|
|
129
|
+
message: 'Lock does not exist',
|
|
130
|
+
details: { sectionId },
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
return { released: true };
|
|
135
|
+
}
|
|
136
|
+
// ============ Lock Manager ============
|
|
137
|
+
/**
|
|
138
|
+
* Create a section lock manager for a specific section
|
|
139
|
+
*/
|
|
140
|
+
export function createSectionLockManager(db, sectionId, runnerId, timeoutMinutes = DEFAULT_SECTION_TIMEOUT_MINUTES) {
|
|
141
|
+
return {
|
|
142
|
+
acquire: () => acquireSectionLock(db, sectionId, runnerId, timeoutMinutes),
|
|
143
|
+
release: () => releaseSectionLock(db, sectionId, runnerId),
|
|
144
|
+
isHeld: () => {
|
|
145
|
+
const lock = getSectionLock(db, sectionId);
|
|
146
|
+
return lock !== null && lock.runner_id === runnerId && !isSectionLockExpired(lock);
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
// ============ Lock Status Check ============
|
|
151
|
+
/**
|
|
152
|
+
* Check if a section is locked
|
|
153
|
+
*/
|
|
154
|
+
export function isSectionLocked(db, sectionId) {
|
|
155
|
+
const lock = getSectionLock(db, sectionId);
|
|
156
|
+
if (!lock)
|
|
157
|
+
return false;
|
|
158
|
+
return !isSectionLockExpired(lock);
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Check if a section is locked by a specific runner
|
|
162
|
+
*/
|
|
163
|
+
export function isSectionLockedBy(db, sectionId, runnerId) {
|
|
164
|
+
const lock = getSectionLock(db, sectionId);
|
|
165
|
+
if (!lock)
|
|
166
|
+
return false;
|
|
167
|
+
return lock.runner_id === runnerId && !isSectionLockExpired(lock);
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Get time until section lock expires (in milliseconds)
|
|
171
|
+
*/
|
|
172
|
+
export function getTimeUntilExpiry(db, sectionId) {
|
|
173
|
+
const lock = getSectionLock(db, sectionId);
|
|
174
|
+
if (!lock)
|
|
175
|
+
return null;
|
|
176
|
+
const expiresAt = new Date(lock.expires_at).getTime();
|
|
177
|
+
const remaining = expiresAt - Date.now();
|
|
178
|
+
return Math.max(0, remaining);
|
|
179
|
+
}
|
|
180
|
+
// ============ Helper Functions ============
|
|
181
|
+
/**
|
|
182
|
+
* Check if any tasks in the section are in progress by another runner
|
|
183
|
+
* This can be used before acquiring a section lock
|
|
184
|
+
*/
|
|
185
|
+
export function getSectionLockHolder(db, sectionId) {
|
|
186
|
+
const lock = getSectionLock(db, sectionId);
|
|
187
|
+
if (!lock || isSectionLockExpired(lock)) {
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
return lock;
|
|
191
|
+
}
|
|
192
|
+
// ============ Constants Export ============
|
|
193
|
+
export const SECTION_LOCK_CONSTANTS = {
|
|
194
|
+
DEFAULT_TIMEOUT_MINUTES: DEFAULT_SECTION_TIMEOUT_MINUTES,
|
|
195
|
+
};
|
|
196
|
+
//# sourceMappingURL=section-lock.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"section-lock.js","sourceRoot":"","sources":["../../../../src/locking/section-lock.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Task locking with atomic operations
|
|
3
|
+
* Prevents multiple runners from working on the same task
|
|
4
|
+
*/
|
|
5
|
+
import type Database from 'better-sqlite3';
|
|
6
|
+
import { type TaskLock } from './queries.js';
|
|
7
|
+
export interface LockAcquisitionResult {
|
|
8
|
+
acquired: boolean;
|
|
9
|
+
lock?: TaskLock;
|
|
10
|
+
reason?: 'already_owned' | 'acquired_new' | 'claimed_expired';
|
|
11
|
+
error?: LockError;
|
|
12
|
+
}
|
|
13
|
+
export interface LockError {
|
|
14
|
+
code: 'TASK_LOCKED' | 'LOCK_NOT_FOUND' | 'PERMISSION_DENIED';
|
|
15
|
+
message: string;
|
|
16
|
+
details?: {
|
|
17
|
+
taskId: string;
|
|
18
|
+
runnerId?: string;
|
|
19
|
+
expiresAt?: string;
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
export interface LockReleaseResult {
|
|
23
|
+
released: boolean;
|
|
24
|
+
error?: LockError;
|
|
25
|
+
}
|
|
26
|
+
export interface TaskLockManager {
|
|
27
|
+
acquire: () => LockAcquisitionResult;
|
|
28
|
+
release: () => LockReleaseResult;
|
|
29
|
+
heartbeat: () => boolean;
|
|
30
|
+
extend: (additionalMinutes?: number) => boolean;
|
|
31
|
+
isHeld: () => boolean;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Acquire a task lock with atomic operations
|
|
35
|
+
*
|
|
36
|
+
* Flow:
|
|
37
|
+
* 1. Try INSERT (fails if lock exists)
|
|
38
|
+
* 2. If exists, check if we own it
|
|
39
|
+
* 3. If owned by another, check if expired
|
|
40
|
+
* 4. If expired, claim atomically
|
|
41
|
+
* 5. If not expired, return failure
|
|
42
|
+
*/
|
|
43
|
+
export declare function acquireTaskLock(db: Database.Database, taskId: string, runnerId: string, timeoutMinutes?: number): LockAcquisitionResult;
|
|
44
|
+
/**
|
|
45
|
+
* Release a task lock (only by owner)
|
|
46
|
+
*/
|
|
47
|
+
export declare function releaseTaskLock(db: Database.Database, taskId: string, runnerId: string): LockReleaseResult;
|
|
48
|
+
/**
|
|
49
|
+
* Force release a task lock (admin operation, ignores owner)
|
|
50
|
+
*/
|
|
51
|
+
export declare function forceRelease(db: Database.Database, taskId: string): LockReleaseResult;
|
|
52
|
+
/**
|
|
53
|
+
* Update heartbeat for a task lock
|
|
54
|
+
* Should be called periodically while holding the lock
|
|
55
|
+
*/
|
|
56
|
+
export declare function heartbeat(db: Database.Database, taskId: string, runnerId: string): boolean;
|
|
57
|
+
/**
|
|
58
|
+
* Extend a task lock's expiry time
|
|
59
|
+
*/
|
|
60
|
+
export declare function extend(db: Database.Database, taskId: string, runnerId: string, additionalMinutes?: number): boolean;
|
|
61
|
+
/**
|
|
62
|
+
* Create a task lock manager for a specific task
|
|
63
|
+
* Provides a convenient interface for managing a single lock
|
|
64
|
+
*/
|
|
65
|
+
export declare function createTaskLockManager(db: Database.Database, taskId: string, runnerId: string, timeoutMinutes?: number): TaskLockManager;
|
|
66
|
+
export interface HeartbeatHandle {
|
|
67
|
+
start: () => void;
|
|
68
|
+
stop: () => void;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Create an automatic heartbeat manager that updates
|
|
72
|
+
* the lock heartbeat at regular intervals
|
|
73
|
+
*/
|
|
74
|
+
export declare function createHeartbeatLoop(db: Database.Database, taskId: string, runnerId: string, intervalMs?: number): HeartbeatHandle;
|
|
75
|
+
/**
|
|
76
|
+
* Check if a task is locked
|
|
77
|
+
*/
|
|
78
|
+
export declare function isTaskLocked(db: Database.Database, taskId: string): boolean;
|
|
79
|
+
/**
|
|
80
|
+
* Check if a task is locked by a specific runner
|
|
81
|
+
*/
|
|
82
|
+
export declare function isTaskLockedBy(db: Database.Database, taskId: string, runnerId: string): boolean;
|
|
83
|
+
/**
|
|
84
|
+
* Get time until lock expires (in milliseconds)
|
|
85
|
+
* Returns null if not locked, 0 if already expired
|
|
86
|
+
*/
|
|
87
|
+
export declare function getTimeUntilExpiry(db: Database.Database, taskId: string): number | null;
|
|
88
|
+
export declare const LOCK_CONSTANTS: {
|
|
89
|
+
readonly DEFAULT_TIMEOUT_MINUTES: 60;
|
|
90
|
+
readonly HEARTBEAT_INTERVAL_MS: number;
|
|
91
|
+
};
|
|
92
|
+
//# sourceMappingURL=task-lock.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"task-lock.d.ts","sourceRoot":"","sources":["../../../../src/locking/task-lock.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Task locking with atomic operations
|
|
3
|
+
* Prevents multiple runners from working on the same task
|
|
4
|
+
*/
|
|
5
|
+
import { getTaskLock, isTaskLockExpired, tryInsertTaskLock, claimExpiredTaskLock, releaseTaskLock as releaseTaskLockQuery, forceReleaseTaskLock, updateTaskLockHeartbeat, extendTaskLock, } from './queries.js';
|
|
6
|
+
// Default lock timeout in minutes
|
|
7
|
+
const DEFAULT_LOCK_TIMEOUT_MINUTES = 60;
|
|
8
|
+
const HEARTBEAT_INTERVAL_MS = 30 * 1000; // 30 seconds
|
|
9
|
+
// ============ Lock Acquisition ============
|
|
10
|
+
/**
|
|
11
|
+
* Acquire a task lock with atomic operations
|
|
12
|
+
*
|
|
13
|
+
* Flow:
|
|
14
|
+
* 1. Try INSERT (fails if lock exists)
|
|
15
|
+
* 2. If exists, check if we own it
|
|
16
|
+
* 3. If owned by another, check if expired
|
|
17
|
+
* 4. If expired, claim atomically
|
|
18
|
+
* 5. If not expired, return failure
|
|
19
|
+
*/
|
|
20
|
+
export function acquireTaskLock(db, taskId, runnerId, timeoutMinutes = DEFAULT_LOCK_TIMEOUT_MINUTES) {
|
|
21
|
+
// Step 1: Try to insert new lock
|
|
22
|
+
if (tryInsertTaskLock(db, taskId, runnerId, timeoutMinutes)) {
|
|
23
|
+
const lock = getTaskLock(db, taskId);
|
|
24
|
+
return {
|
|
25
|
+
acquired: true,
|
|
26
|
+
lock: lock ?? undefined,
|
|
27
|
+
reason: 'acquired_new',
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
// Step 2: Lock exists, get current state
|
|
31
|
+
const existingLock = getTaskLock(db, taskId);
|
|
32
|
+
if (!existingLock) {
|
|
33
|
+
// Race condition: lock was deleted between insert attempt and now
|
|
34
|
+
// Try to acquire again
|
|
35
|
+
if (tryInsertTaskLock(db, taskId, runnerId, timeoutMinutes)) {
|
|
36
|
+
const lock = getTaskLock(db, taskId);
|
|
37
|
+
return {
|
|
38
|
+
acquired: true,
|
|
39
|
+
lock: lock ?? undefined,
|
|
40
|
+
reason: 'acquired_new',
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
// Still failed, get the current lock
|
|
44
|
+
const currentLock = getTaskLock(db, taskId);
|
|
45
|
+
return createLockedError(taskId, currentLock);
|
|
46
|
+
}
|
|
47
|
+
// Step 3: Check if we already own the lock
|
|
48
|
+
if (existingLock.runner_id === runnerId) {
|
|
49
|
+
// Refresh the lock expiry
|
|
50
|
+
extendTaskLock(db, taskId, runnerId, timeoutMinutes);
|
|
51
|
+
const updatedLock = getTaskLock(db, taskId);
|
|
52
|
+
return {
|
|
53
|
+
acquired: true,
|
|
54
|
+
lock: updatedLock ?? undefined,
|
|
55
|
+
reason: 'already_owned',
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
// Step 4: Check if lock is expired
|
|
59
|
+
if (isTaskLockExpired(existingLock)) {
|
|
60
|
+
// Try to claim the expired lock atomically
|
|
61
|
+
if (claimExpiredTaskLock(db, taskId, runnerId, timeoutMinutes)) {
|
|
62
|
+
const lock = getTaskLock(db, taskId);
|
|
63
|
+
return {
|
|
64
|
+
acquired: true,
|
|
65
|
+
lock: lock ?? undefined,
|
|
66
|
+
reason: 'claimed_expired',
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
// Another runner claimed it first
|
|
70
|
+
const currentLock = getTaskLock(db, taskId);
|
|
71
|
+
return createLockedError(taskId, currentLock);
|
|
72
|
+
}
|
|
73
|
+
// Step 5: Lock is held by another runner and not expired
|
|
74
|
+
return createLockedError(taskId, existingLock);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Create a TASK_LOCKED error response
|
|
78
|
+
*/
|
|
79
|
+
function createLockedError(taskId, lock) {
|
|
80
|
+
return {
|
|
81
|
+
acquired: false,
|
|
82
|
+
error: {
|
|
83
|
+
code: 'TASK_LOCKED',
|
|
84
|
+
message: 'Task is locked by another runner',
|
|
85
|
+
details: {
|
|
86
|
+
taskId,
|
|
87
|
+
runnerId: lock?.runner_id,
|
|
88
|
+
expiresAt: lock?.expires_at,
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
// ============ Lock Release ============
|
|
94
|
+
/**
|
|
95
|
+
* Release a task lock (only by owner)
|
|
96
|
+
*/
|
|
97
|
+
export function releaseTaskLock(db, taskId, runnerId) {
|
|
98
|
+
const existingLock = getTaskLock(db, taskId);
|
|
99
|
+
if (!existingLock) {
|
|
100
|
+
return {
|
|
101
|
+
released: false,
|
|
102
|
+
error: {
|
|
103
|
+
code: 'LOCK_NOT_FOUND',
|
|
104
|
+
message: 'Lock does not exist',
|
|
105
|
+
details: { taskId },
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
if (existingLock.runner_id !== runnerId) {
|
|
110
|
+
return {
|
|
111
|
+
released: false,
|
|
112
|
+
error: {
|
|
113
|
+
code: 'PERMISSION_DENIED',
|
|
114
|
+
message: 'Cannot release lock owned by another runner',
|
|
115
|
+
details: {
|
|
116
|
+
taskId,
|
|
117
|
+
runnerId: existingLock.runner_id,
|
|
118
|
+
expiresAt: existingLock.expires_at,
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
releaseTaskLockQuery(db, taskId, runnerId);
|
|
124
|
+
return { released: true };
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Force release a task lock (admin operation, ignores owner)
|
|
128
|
+
*/
|
|
129
|
+
export function forceRelease(db, taskId) {
|
|
130
|
+
const existed = forceReleaseTaskLock(db, taskId);
|
|
131
|
+
if (!existed) {
|
|
132
|
+
return {
|
|
133
|
+
released: false,
|
|
134
|
+
error: {
|
|
135
|
+
code: 'LOCK_NOT_FOUND',
|
|
136
|
+
message: 'Lock does not exist',
|
|
137
|
+
details: { taskId },
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
return { released: true };
|
|
142
|
+
}
|
|
143
|
+
// ============ Lock Heartbeat ============
|
|
144
|
+
/**
|
|
145
|
+
* Update heartbeat for a task lock
|
|
146
|
+
* Should be called periodically while holding the lock
|
|
147
|
+
*/
|
|
148
|
+
export function heartbeat(db, taskId, runnerId) {
|
|
149
|
+
return updateTaskLockHeartbeat(db, taskId, runnerId);
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Extend a task lock's expiry time
|
|
153
|
+
*/
|
|
154
|
+
export function extend(db, taskId, runnerId, additionalMinutes = DEFAULT_LOCK_TIMEOUT_MINUTES) {
|
|
155
|
+
return extendTaskLock(db, taskId, runnerId, additionalMinutes);
|
|
156
|
+
}
|
|
157
|
+
// ============ Lock Manager ============
|
|
158
|
+
/**
|
|
159
|
+
* Create a task lock manager for a specific task
|
|
160
|
+
* Provides a convenient interface for managing a single lock
|
|
161
|
+
*/
|
|
162
|
+
export function createTaskLockManager(db, taskId, runnerId, timeoutMinutes = DEFAULT_LOCK_TIMEOUT_MINUTES) {
|
|
163
|
+
return {
|
|
164
|
+
acquire: () => acquireTaskLock(db, taskId, runnerId, timeoutMinutes),
|
|
165
|
+
release: () => releaseTaskLock(db, taskId, runnerId),
|
|
166
|
+
heartbeat: () => updateTaskLockHeartbeat(db, taskId, runnerId),
|
|
167
|
+
extend: (additionalMinutes = timeoutMinutes) => extendTaskLock(db, taskId, runnerId, additionalMinutes),
|
|
168
|
+
isHeld: () => {
|
|
169
|
+
const lock = getTaskLock(db, taskId);
|
|
170
|
+
return lock !== null && lock.runner_id === runnerId && !isTaskLockExpired(lock);
|
|
171
|
+
},
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Create an automatic heartbeat manager that updates
|
|
176
|
+
* the lock heartbeat at regular intervals
|
|
177
|
+
*/
|
|
178
|
+
export function createHeartbeatLoop(db, taskId, runnerId, intervalMs = HEARTBEAT_INTERVAL_MS) {
|
|
179
|
+
let intervalId = null;
|
|
180
|
+
const beat = () => {
|
|
181
|
+
updateTaskLockHeartbeat(db, taskId, runnerId);
|
|
182
|
+
};
|
|
183
|
+
const start = () => {
|
|
184
|
+
if (intervalId)
|
|
185
|
+
return; // Already running
|
|
186
|
+
beat(); // Immediate first beat
|
|
187
|
+
intervalId = setInterval(beat, intervalMs);
|
|
188
|
+
};
|
|
189
|
+
const stop = () => {
|
|
190
|
+
if (intervalId) {
|
|
191
|
+
clearInterval(intervalId);
|
|
192
|
+
intervalId = null;
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
return { start, stop };
|
|
196
|
+
}
|
|
197
|
+
// ============ Lock Status Check ============
|
|
198
|
+
/**
|
|
199
|
+
* Check if a task is locked
|
|
200
|
+
*/
|
|
201
|
+
export function isTaskLocked(db, taskId) {
|
|
202
|
+
const lock = getTaskLock(db, taskId);
|
|
203
|
+
if (!lock)
|
|
204
|
+
return false;
|
|
205
|
+
return !isTaskLockExpired(lock);
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Check if a task is locked by a specific runner
|
|
209
|
+
*/
|
|
210
|
+
export function isTaskLockedBy(db, taskId, runnerId) {
|
|
211
|
+
const lock = getTaskLock(db, taskId);
|
|
212
|
+
if (!lock)
|
|
213
|
+
return false;
|
|
214
|
+
return lock.runner_id === runnerId && !isTaskLockExpired(lock);
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Get time until lock expires (in milliseconds)
|
|
218
|
+
* Returns null if not locked, 0 if already expired
|
|
219
|
+
*/
|
|
220
|
+
export function getTimeUntilExpiry(db, taskId) {
|
|
221
|
+
const lock = getTaskLock(db, taskId);
|
|
222
|
+
if (!lock)
|
|
223
|
+
return null;
|
|
224
|
+
const expiresAt = new Date(lock.expires_at).getTime();
|
|
225
|
+
const remaining = expiresAt - Date.now();
|
|
226
|
+
return Math.max(0, remaining);
|
|
227
|
+
}
|
|
228
|
+
// ============ Constants Export ============
|
|
229
|
+
export const LOCK_CONSTANTS = {
|
|
230
|
+
DEFAULT_TIMEOUT_MINUTES: DEFAULT_LOCK_TIMEOUT_MINUTES,
|
|
231
|
+
HEARTBEAT_INTERVAL_MS,
|
|
232
|
+
};
|
|
233
|
+
//# sourceMappingURL=task-lock.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"task-lock.js","sourceRoot":"","sources":["../../../../src/locking/task-lock.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Migration system exports
|
|
3
|
+
* Re-exports all migration-related functionality
|
|
4
|
+
*/
|
|
5
|
+
export { MigrationEntry, MigrationManifest, AppliedMigration, getBundledManifestPath, getMigrationFilePath, getCachedManifestPath, readBundledManifest, readCachedManifest, cacheManifest, parseManifest, validateManifest, calculateChecksum, verifyChecksum, findPendingMigrations, findOrphanedMigrations, getMigrationById, getCliSupportedVersions, } from './manifest.js';
|
|
6
|
+
export { MigrationResult, MigrationStatus, parseMigrationFile, readMigrationFile, getAppliedMigrations, getDatabaseVersion, createBackup, applyMigration, rollbackMigration, runMigrations, rollbackToVersion, getMigrationStatus, autoMigrate, } from './runner.js';
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/migrations/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EACL,cAAc,EACd,iBAAiB,EACjB,gBAAgB,EAChB,sBAAsB,EACtB,oBAAoB,EACpB,qBAAqB,EACrB,mBAAmB,EACnB,kBAAkB,EAClB,aAAa,EACb,aAAa,EACb,gBAAgB,EAChB,iBAAiB,EACjB,cAAc,EACd,qBAAqB,EACrB,sBAAsB,EACtB,gBAAgB,EAChB,uBAAuB,GACxB,MAAM,eAAe,CAAC;AAGvB,OAAO,EACL,eAAe,EACf,eAAe,EACf,kBAAkB,EAClB,iBAAiB,EACjB,oBAAoB,EACpB,kBAAkB,EAClB,YAAY,EACZ,cAAc,EACd,iBAAiB,EACjB,aAAa,EACb,iBAAiB,EACjB,kBAAkB,EAClB,WAAW,GACZ,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Migration system exports
|
|
3
|
+
* Re-exports all migration-related functionality
|
|
4
|
+
*/
|
|
5
|
+
// Manifest management
|
|
6
|
+
export { getBundledManifestPath, getMigrationFilePath, getCachedManifestPath, readBundledManifest, readCachedManifest, cacheManifest, parseManifest, validateManifest, calculateChecksum, verifyChecksum, findPendingMigrations, findOrphanedMigrations, getMigrationById, getCliSupportedVersions, } from './manifest.js';
|
|
7
|
+
// Migration runner
|
|
8
|
+
export { parseMigrationFile, readMigrationFile, getAppliedMigrations, getDatabaseVersion, createBackup, applyMigration, rollbackMigration, runMigrations, rollbackToVersion, getMigrationStatus, autoMigrate, } from './runner.js';
|
|
9
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/migrations/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,sBAAsB;AACtB,OAAO,EAIL,sBAAsB,EACtB,oBAAoB,EACpB,qBAAqB,EACrB,mBAAmB,EACnB,kBAAkB,EAClB,aAAa,EACb,aAAa,EACb,gBAAgB,EAChB,iBAAiB,EACjB,cAAc,EACd,qBAAqB,EACrB,sBAAsB,EACtB,gBAAgB,EAChB,uBAAuB,GACxB,MAAM,eAAe,CAAC;AAEvB,mBAAmB;AACnB,OAAO,EAGL,kBAAkB,EAClB,iBAAiB,EACjB,oBAAoB,EACpB,kBAAkB,EAClB,YAAY,EACZ,cAAc,EACd,iBAAiB,EACjB,aAAa,EACb,iBAAiB,EACjB,kBAAkB,EAClB,WAAW,GACZ,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Migration manifest management
|
|
3
|
+
* Handles reading, parsing, and validating the migration manifest
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Single migration entry in the manifest
|
|
7
|
+
*/
|
|
8
|
+
export interface MigrationEntry {
|
|
9
|
+
id: number;
|
|
10
|
+
name: string;
|
|
11
|
+
file: string;
|
|
12
|
+
description: string;
|
|
13
|
+
checksum: string;
|
|
14
|
+
cliVersion: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Migration manifest structure
|
|
18
|
+
*/
|
|
19
|
+
export interface MigrationManifest {
|
|
20
|
+
version: string;
|
|
21
|
+
latestDbVersion: number;
|
|
22
|
+
migrations: MigrationEntry[];
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Applied migration record from database
|
|
26
|
+
*/
|
|
27
|
+
export interface AppliedMigration {
|
|
28
|
+
id: number;
|
|
29
|
+
name: string;
|
|
30
|
+
checksum: string;
|
|
31
|
+
applied_at: string;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Get the path to the bundled manifest (in the package)
|
|
35
|
+
*/
|
|
36
|
+
export declare function getBundledManifestPath(): string;
|
|
37
|
+
/**
|
|
38
|
+
* Get the path to a migration file (in the package)
|
|
39
|
+
*/
|
|
40
|
+
export declare function getMigrationFilePath(filename: string): string;
|
|
41
|
+
/**
|
|
42
|
+
* Get the path to the cached manifest
|
|
43
|
+
*/
|
|
44
|
+
export declare function getCachedManifestPath(): string;
|
|
45
|
+
/**
|
|
46
|
+
* Read the bundled manifest from the package
|
|
47
|
+
*/
|
|
48
|
+
export declare function readBundledManifest(): MigrationManifest;
|
|
49
|
+
/**
|
|
50
|
+
* Read the cached manifest if it exists and is valid
|
|
51
|
+
*/
|
|
52
|
+
export declare function readCachedManifest(): MigrationManifest | null;
|
|
53
|
+
/**
|
|
54
|
+
* Save manifest to cache
|
|
55
|
+
*/
|
|
56
|
+
export declare function cacheManifest(manifest: MigrationManifest): void;
|
|
57
|
+
/**
|
|
58
|
+
* Parse and validate manifest content
|
|
59
|
+
*/
|
|
60
|
+
export declare function parseManifest(content: string): MigrationManifest;
|
|
61
|
+
/**
|
|
62
|
+
* Validate manifest structure
|
|
63
|
+
*/
|
|
64
|
+
export declare function validateManifest(manifest: MigrationManifest): void;
|
|
65
|
+
/**
|
|
66
|
+
* Calculate SHA256 checksum for migration file content
|
|
67
|
+
*/
|
|
68
|
+
export declare function calculateChecksum(content: string): string;
|
|
69
|
+
/**
|
|
70
|
+
* Verify checksum matches
|
|
71
|
+
*/
|
|
72
|
+
export declare function verifyChecksum(content: string, expectedChecksum: string): boolean;
|
|
73
|
+
/**
|
|
74
|
+
* Find pending migrations that need to be applied
|
|
75
|
+
*/
|
|
76
|
+
export declare function findPendingMigrations(manifest: MigrationManifest, appliedMigrations: AppliedMigration[]): MigrationEntry[];
|
|
77
|
+
/**
|
|
78
|
+
* Find migrations to rollback (applied but not in manifest)
|
|
79
|
+
*/
|
|
80
|
+
export declare function findOrphanedMigrations(manifest: MigrationManifest, appliedMigrations: AppliedMigration[]): AppliedMigration[];
|
|
81
|
+
/**
|
|
82
|
+
* Get migration entry by ID
|
|
83
|
+
*/
|
|
84
|
+
export declare function getMigrationById(manifest: MigrationManifest, id: number): MigrationEntry | null;
|
|
85
|
+
/**
|
|
86
|
+
* Get the current supported database version range for this CLI
|
|
87
|
+
*/
|
|
88
|
+
export declare function getCliSupportedVersions(manifest: MigrationManifest): {
|
|
89
|
+
min: number;
|
|
90
|
+
max: number;
|
|
91
|
+
};
|
|
92
|
+
//# sourceMappingURL=manifest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../../../src/migrations/manifest.ts"],"names":[],"mappings":"AAAA;;;GAGG;AA8EH;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,cAAc,EAAE,CAAC;CAC9B;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;CACpB;AAeD;;GAEG;AACH,wBAAgB,sBAAsB,IAAI,MAAM,CAG/C;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAE7D;AAED;;GAEG;AACH,wBAAgB,qBAAqB,IAAI,MAAM,CAE9C;AAED;;GAEG;AACH,wBAAgB,mBAAmB,IAAI,iBAAiB,CASvD;AAED;;GAEG;AACH,wBAAgB,kBAAkB,IAAI,iBAAiB,GAAG,IAAI,CAkB7D;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,iBAAiB,GAAG,IAAI,CAc/D;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,iBAAiB,CAIhE;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,iBAAiB,GAAG,IAAI,CAmClE;AA6BD;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAIzD;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,GAAG,OAAO,CAQjF;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,iBAAiB,EAC3B,iBAAiB,EAAE,gBAAgB,EAAE,GACpC,cAAc,EAAE,CAGlB;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,iBAAiB,EAC3B,iBAAiB,EAAE,gBAAgB,EAAE,GACpC,gBAAgB,EAAE,CAGpB;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,iBAAiB,EAAE,EAAE,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI,CAE/F;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,iBAAiB,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CASjG"}
|