steroids-cli 0.4.47
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 +640 -0
- package/dist/cli/colors.d.ts +110 -0
- package/dist/cli/colors.d.ts.map +1 -0
- package/dist/cli/colors.js +228 -0
- package/dist/cli/colors.js.map +1 -0
- package/dist/cli/env.d.ts +159 -0
- package/dist/cli/env.d.ts.map +1 -0
- package/dist/cli/env.js +227 -0
- package/dist/cli/env.js.map +1 -0
- package/dist/cli/errors.d.ts +166 -0
- package/dist/cli/errors.d.ts.map +1 -0
- package/dist/cli/errors.js +244 -0
- package/dist/cli/errors.js.map +1 -0
- package/dist/cli/flags.d.ts +75 -0
- package/dist/cli/flags.d.ts.map +1 -0
- package/dist/cli/flags.js +232 -0
- package/dist/cli/flags.js.map +1 -0
- package/dist/cli/help.d.ts +97 -0
- package/dist/cli/help.d.ts.map +1 -0
- package/dist/cli/help.js +275 -0
- package/dist/cli/help.js.map +1 -0
- package/dist/cli/index.d.ts +13 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +29 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/interactive.d.ts +58 -0
- package/dist/cli/interactive.d.ts.map +1 -0
- package/dist/cli/interactive.js +127 -0
- package/dist/cli/interactive.js.map +1 -0
- package/dist/cli/output.d.ts +116 -0
- package/dist/cli/output.d.ts.map +1 -0
- package/dist/cli/output.js +178 -0
- package/dist/cli/output.js.map +1 -0
- package/dist/commands/about.d.ts +7 -0
- package/dist/commands/about.d.ts.map +1 -0
- package/dist/commands/about.js +259 -0
- package/dist/commands/about.js.map +1 -0
- package/dist/commands/ai.d.ts +6 -0
- package/dist/commands/ai.d.ts.map +1 -0
- package/dist/commands/ai.js +382 -0
- package/dist/commands/ai.js.map +1 -0
- package/dist/commands/backup.d.ts +3 -0
- package/dist/commands/backup.d.ts.map +1 -0
- package/dist/commands/backup.js +528 -0
- package/dist/commands/backup.js.map +1 -0
- package/dist/commands/completion.d.ts +3 -0
- package/dist/commands/completion.d.ts.map +1 -0
- package/dist/commands/completion.js +405 -0
- package/dist/commands/completion.js.map +1 -0
- package/dist/commands/config.d.ts +3 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +665 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/disputes.d.ts +3 -0
- package/dist/commands/disputes.d.ts.map +1 -0
- package/dist/commands/disputes.js +499 -0
- package/dist/commands/disputes.js.map +1 -0
- package/dist/commands/gc.d.ts +3 -0
- package/dist/commands/gc.d.ts.map +1 -0
- package/dist/commands/gc.js +300 -0
- package/dist/commands/gc.js.map +1 -0
- package/dist/commands/git.d.ts +3 -0
- package/dist/commands/git.d.ts.map +1 -0
- package/dist/commands/git.js +458 -0
- package/dist/commands/git.js.map +1 -0
- package/dist/commands/health.d.ts +3 -0
- package/dist/commands/health.d.ts.map +1 -0
- package/dist/commands/health.js +604 -0
- package/dist/commands/health.js.map +1 -0
- package/dist/commands/hooks.d.ts +6 -0
- package/dist/commands/hooks.d.ts.map +1 -0
- package/dist/commands/hooks.js +529 -0
- package/dist/commands/hooks.js.map +1 -0
- package/dist/commands/init.d.ts +6 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +200 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/llm.d.ts +7 -0
- package/dist/commands/llm.d.ts.map +1 -0
- package/dist/commands/llm.js +285 -0
- package/dist/commands/llm.js.map +1 -0
- package/dist/commands/locks.d.ts +3 -0
- package/dist/commands/locks.d.ts.map +1 -0
- package/dist/commands/locks.js +431 -0
- package/dist/commands/locks.js.map +1 -0
- package/dist/commands/logs.d.ts +3 -0
- package/dist/commands/logs.d.ts.map +1 -0
- package/dist/commands/logs.js +487 -0
- package/dist/commands/logs.js.map +1 -0
- package/dist/commands/loop-phases.d.ts +11 -0
- package/dist/commands/loop-phases.d.ts.map +1 -0
- package/dist/commands/loop-phases.js +204 -0
- package/dist/commands/loop-phases.js.map +1 -0
- package/dist/commands/loop.d.ts +3 -0
- package/dist/commands/loop.d.ts.map +1 -0
- package/dist/commands/loop.js +396 -0
- package/dist/commands/loop.js.map +1 -0
- package/dist/commands/projects.d.ts +6 -0
- package/dist/commands/projects.d.ts.map +1 -0
- package/dist/commands/projects.js +362 -0
- package/dist/commands/projects.js.map +1 -0
- package/dist/commands/purge.d.ts +3 -0
- package/dist/commands/purge.d.ts.map +1 -0
- package/dist/commands/purge.js +516 -0
- package/dist/commands/purge.js.map +1 -0
- package/dist/commands/runners.d.ts +3 -0
- package/dist/commands/runners.d.ts.map +1 -0
- package/dist/commands/runners.js +1076 -0
- package/dist/commands/runners.js.map +1 -0
- package/dist/commands/scan.d.ts +3 -0
- package/dist/commands/scan.d.ts.map +1 -0
- package/dist/commands/scan.js +291 -0
- package/dist/commands/scan.js.map +1 -0
- package/dist/commands/sections-commands.d.ts +9 -0
- package/dist/commands/sections-commands.d.ts.map +1 -0
- package/dist/commands/sections-commands.js +282 -0
- package/dist/commands/sections-commands.js.map +1 -0
- package/dist/commands/sections-graph.d.ts +25 -0
- package/dist/commands/sections-graph.d.ts.map +1 -0
- package/dist/commands/sections-graph.js +180 -0
- package/dist/commands/sections-graph.js.map +1 -0
- package/dist/commands/sections.d.ts +3 -0
- package/dist/commands/sections.d.ts.map +1 -0
- package/dist/commands/sections.js +376 -0
- package/dist/commands/sections.js.map +1 -0
- package/dist/commands/stats.d.ts +6 -0
- package/dist/commands/stats.d.ts.map +1 -0
- package/dist/commands/stats.js +324 -0
- package/dist/commands/stats.js.map +1 -0
- package/dist/commands/tasks.d.ts +3 -0
- package/dist/commands/tasks.d.ts.map +1 -0
- package/dist/commands/tasks.js +1115 -0
- package/dist/commands/tasks.js.map +1 -0
- package/dist/commands/web.d.ts +7 -0
- package/dist/commands/web.d.ts.map +1 -0
- package/dist/commands/web.js +204 -0
- package/dist/commands/web.js.map +1 -0
- package/dist/config/ai-setup.d.ts +27 -0
- package/dist/config/ai-setup.d.ts.map +1 -0
- package/dist/config/ai-setup.js +432 -0
- package/dist/config/ai-setup.js.map +1 -0
- package/dist/config/browser.d.ts +9 -0
- package/dist/config/browser.d.ts.map +1 -0
- package/dist/config/browser.js +200 -0
- package/dist/config/browser.js.map +1 -0
- package/dist/config/json-schema.d.ts +28 -0
- package/dist/config/json-schema.d.ts.map +1 -0
- package/dist/config/json-schema.js +84 -0
- package/dist/config/json-schema.js.map +1 -0
- package/dist/config/loader.d.ts +152 -0
- package/dist/config/loader.d.ts.map +1 -0
- package/dist/config/loader.js +270 -0
- package/dist/config/loader.js.map +1 -0
- package/dist/config/schema.d.ts +34 -0
- package/dist/config/schema.d.ts.map +1 -0
- package/dist/config/schema.js +437 -0
- package/dist/config/schema.js.map +1 -0
- package/dist/config/validator.d.ts +32 -0
- package/dist/config/validator.d.ts.map +1 -0
- package/dist/config/validator.js +187 -0
- package/dist/config/validator.js.map +1 -0
- package/dist/database/connection.d.ts +35 -0
- package/dist/database/connection.d.ts.map +1 -0
- package/dist/database/connection.js +208 -0
- package/dist/database/connection.js.map +1 -0
- package/dist/database/queries.d.ts +218 -0
- package/dist/database/queries.d.ts.map +1 -0
- package/dist/database/queries.js +613 -0
- package/dist/database/queries.js.map +1 -0
- package/dist/database/schema.d.ts +8 -0
- package/dist/database/schema.d.ts.map +1 -0
- package/dist/database/schema.js +160 -0
- package/dist/database/schema.js.map +1 -0
- package/dist/disputes/behavior.d.ts +106 -0
- package/dist/disputes/behavior.d.ts.map +1 -0
- package/dist/disputes/behavior.js +150 -0
- package/dist/disputes/behavior.js.map +1 -0
- package/dist/disputes/create.d.ts +59 -0
- package/dist/disputes/create.d.ts.map +1 -0
- package/dist/disputes/create.js +222 -0
- package/dist/disputes/create.js.map +1 -0
- package/dist/disputes/index.d.ts +21 -0
- package/dist/disputes/index.d.ts.map +1 -0
- package/dist/disputes/index.js +76 -0
- package/dist/disputes/index.js.map +1 -0
- package/dist/disputes/markdown.d.ts +41 -0
- package/dist/disputes/markdown.d.ts.map +1 -0
- package/dist/disputes/markdown.js +261 -0
- package/dist/disputes/markdown.js.map +1 -0
- package/dist/disputes/queries.d.ts +83 -0
- package/dist/disputes/queries.d.ts.map +1 -0
- package/dist/disputes/queries.js +180 -0
- package/dist/disputes/queries.js.map +1 -0
- package/dist/disputes/resolve.d.ts +57 -0
- package/dist/disputes/resolve.d.ts.map +1 -0
- package/dist/disputes/resolve.js +171 -0
- package/dist/disputes/resolve.js.map +1 -0
- package/dist/disputes/stale.d.ts +98 -0
- package/dist/disputes/stale.d.ts.map +1 -0
- package/dist/disputes/stale.js +205 -0
- package/dist/disputes/stale.js.map +1 -0
- package/dist/disputes/types.d.ts +92 -0
- package/dist/disputes/types.d.ts.map +1 -0
- package/dist/disputes/types.js +100 -0
- package/dist/disputes/types.js.map +1 -0
- package/dist/git/push.d.ts +26 -0
- package/dist/git/push.d.ts.map +1 -0
- package/dist/git/push.js +97 -0
- package/dist/git/push.js.map +1 -0
- package/dist/git/status.d.ts +61 -0
- package/dist/git/status.d.ts.map +1 -0
- package/dist/git/status.js +251 -0
- package/dist/git/status.js.map +1 -0
- package/dist/hooks/events.d.ts +72 -0
- package/dist/hooks/events.d.ts.map +1 -0
- package/dist/hooks/events.js +120 -0
- package/dist/hooks/events.js.map +1 -0
- package/dist/hooks/index.d.ts +19 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/index.js +48 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/hooks/integration.d.ts +69 -0
- package/dist/hooks/integration.d.ts.map +1 -0
- package/dist/hooks/integration.js +179 -0
- package/dist/hooks/integration.js.map +1 -0
- package/dist/hooks/merge.d.ts +115 -0
- package/dist/hooks/merge.d.ts.map +1 -0
- package/dist/hooks/merge.js +161 -0
- package/dist/hooks/merge.js.map +1 -0
- package/dist/hooks/orchestrator.d.ts +115 -0
- package/dist/hooks/orchestrator.d.ts.map +1 -0
- package/dist/hooks/orchestrator.js +226 -0
- package/dist/hooks/orchestrator.js.map +1 -0
- package/dist/hooks/payload.d.ts +294 -0
- package/dist/hooks/payload.d.ts.map +1 -0
- package/dist/hooks/payload.js +267 -0
- package/dist/hooks/payload.js.map +1 -0
- package/dist/hooks/script-runner.d.ts +63 -0
- package/dist/hooks/script-runner.d.ts.map +1 -0
- package/dist/hooks/script-runner.js +221 -0
- package/dist/hooks/script-runner.js.map +1 -0
- package/dist/hooks/templates.d.ts +104 -0
- package/dist/hooks/templates.d.ts.map +1 -0
- package/dist/hooks/templates.js +327 -0
- package/dist/hooks/templates.js.map +1 -0
- package/dist/hooks/webhook-runner.d.ts +69 -0
- package/dist/hooks/webhook-runner.d.ts.map +1 -0
- package/dist/hooks/webhook-runner.js +208 -0
- package/dist/hooks/webhook-runner.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +281 -0
- package/dist/index.js.map +1 -0
- package/dist/locking/cleanup.d.ts +70 -0
- package/dist/locking/cleanup.d.ts.map +1 -0
- package/dist/locking/cleanup.js +157 -0
- package/dist/locking/cleanup.js.map +1 -0
- package/dist/locking/queries.d.ts +116 -0
- package/dist/locking/queries.d.ts.map +1 -0
- package/dist/locking/queries.js +255 -0
- package/dist/locking/queries.js.map +1 -0
- package/dist/locking/section-lock.d.ts +74 -0
- package/dist/locking/section-lock.d.ts.map +1 -0
- package/dist/locking/section-lock.js +207 -0
- package/dist/locking/section-lock.js.map +1 -0
- package/dist/locking/task-lock.d.ts +92 -0
- package/dist/locking/task-lock.d.ts.map +1 -0
- package/dist/locking/task-lock.js +246 -0
- package/dist/locking/task-lock.js.map +1 -0
- package/dist/migrations/index.d.ts +7 -0
- package/dist/migrations/index.d.ts.map +1 -0
- package/dist/migrations/index.js +37 -0
- package/dist/migrations/index.js.map +1 -0
- package/dist/migrations/manifest.d.ts +92 -0
- package/dist/migrations/manifest.d.ts.map +1 -0
- package/dist/migrations/manifest.js +270 -0
- package/dist/migrations/manifest.js.map +1 -0
- package/dist/migrations/runner.d.ts +84 -0
- package/dist/migrations/runner.d.ts.map +1 -0
- package/dist/migrations/runner.js +351 -0
- package/dist/migrations/runner.js.map +1 -0
- package/dist/orchestrator/coder.d.ts +32 -0
- package/dist/orchestrator/coder.d.ts.map +1 -0
- package/dist/orchestrator/coder.js +174 -0
- package/dist/orchestrator/coder.js.map +1 -0
- package/dist/orchestrator/coordinator.d.ts +28 -0
- package/dist/orchestrator/coordinator.d.ts.map +1 -0
- package/dist/orchestrator/coordinator.js +256 -0
- package/dist/orchestrator/coordinator.js.map +1 -0
- package/dist/orchestrator/reviewer.d.ts +35 -0
- package/dist/orchestrator/reviewer.d.ts.map +1 -0
- package/dist/orchestrator/reviewer.js +241 -0
- package/dist/orchestrator/reviewer.js.map +1 -0
- package/dist/orchestrator/task-selector.d.ts +102 -0
- package/dist/orchestrator/task-selector.d.ts.map +1 -0
- package/dist/orchestrator/task-selector.js +341 -0
- package/dist/orchestrator/task-selector.js.map +1 -0
- package/dist/prompts/coder.d.ts +36 -0
- package/dist/prompts/coder.d.ts.map +1 -0
- package/dist/prompts/coder.js +315 -0
- package/dist/prompts/coder.js.map +1 -0
- package/dist/prompts/prompt-helpers.d.ts +51 -0
- package/dist/prompts/prompt-helpers.d.ts.map +1 -0
- package/dist/prompts/prompt-helpers.js +312 -0
- package/dist/prompts/prompt-helpers.js.map +1 -0
- package/dist/prompts/reviewer.d.ts +40 -0
- package/dist/prompts/reviewer.d.ts.map +1 -0
- package/dist/prompts/reviewer.js +438 -0
- package/dist/prompts/reviewer.js.map +1 -0
- package/dist/providers/api-models.d.ts +65 -0
- package/dist/providers/api-models.d.ts.map +1 -0
- package/dist/providers/api-models.js +323 -0
- package/dist/providers/api-models.js.map +1 -0
- package/dist/providers/claude.d.ts +53 -0
- package/dist/providers/claude.d.ts.map +1 -0
- package/dist/providers/claude.js +229 -0
- package/dist/providers/claude.js.map +1 -0
- package/dist/providers/codex.d.ts +53 -0
- package/dist/providers/codex.d.ts.map +1 -0
- package/dist/providers/codex.js +214 -0
- package/dist/providers/codex.js.map +1 -0
- package/dist/providers/gemini.d.ts +58 -0
- package/dist/providers/gemini.d.ts.map +1 -0
- package/dist/providers/gemini.js +242 -0
- package/dist/providers/gemini.js.map +1 -0
- package/dist/providers/index.d.ts +13 -0
- package/dist/providers/index.d.ts.map +1 -0
- package/dist/providers/index.js +49 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/providers/interface.d.ts +173 -0
- package/dist/providers/interface.d.ts.map +1 -0
- package/dist/providers/interface.js +96 -0
- package/dist/providers/interface.js.map +1 -0
- package/dist/providers/invocation-logger.d.ts +114 -0
- package/dist/providers/invocation-logger.d.ts.map +1 -0
- package/dist/providers/invocation-logger.js +298 -0
- package/dist/providers/invocation-logger.js.map +1 -0
- package/dist/providers/openai.d.ts +53 -0
- package/dist/providers/openai.d.ts.map +1 -0
- package/dist/providers/openai.js +232 -0
- package/dist/providers/openai.js.map +1 -0
- package/dist/providers/registry.d.ts +100 -0
- package/dist/providers/registry.d.ts.map +1 -0
- package/dist/providers/registry.js +178 -0
- package/dist/providers/registry.js.map +1 -0
- package/dist/runners/activity-log.d.ts +65 -0
- package/dist/runners/activity-log.d.ts.map +1 -0
- package/dist/runners/activity-log.js +148 -0
- package/dist/runners/activity-log.js.map +1 -0
- package/dist/runners/cron.d.ts +26 -0
- package/dist/runners/cron.d.ts.map +1 -0
- package/dist/runners/cron.js +176 -0
- package/dist/runners/cron.js.map +1 -0
- package/dist/runners/daemon.d.ts +71 -0
- package/dist/runners/daemon.d.ts.map +1 -0
- package/dist/runners/daemon.js +245 -0
- package/dist/runners/daemon.js.map +1 -0
- package/dist/runners/global-db.d.ts +31 -0
- package/dist/runners/global-db.d.ts.map +1 -0
- package/dist/runners/global-db.js +230 -0
- package/dist/runners/global-db.js.map +1 -0
- package/dist/runners/hang-detector.d.ts +38 -0
- package/dist/runners/hang-detector.d.ts.map +1 -0
- package/dist/runners/hang-detector.js +136 -0
- package/dist/runners/hang-detector.js.map +1 -0
- package/dist/runners/heartbeat.d.ts +39 -0
- package/dist/runners/heartbeat.d.ts.map +1 -0
- package/dist/runners/heartbeat.js +79 -0
- package/dist/runners/heartbeat.js.map +1 -0
- package/dist/runners/lock.d.ts +47 -0
- package/dist/runners/lock.d.ts.map +1 -0
- package/dist/runners/lock.js +150 -0
- package/dist/runners/lock.js.map +1 -0
- package/dist/runners/orchestrator-loop.d.ts +20 -0
- package/dist/runners/orchestrator-loop.d.ts.map +1 -0
- package/dist/runners/orchestrator-loop.js +285 -0
- package/dist/runners/orchestrator-loop.js.map +1 -0
- package/dist/runners/projects.d.ts +96 -0
- package/dist/runners/projects.d.ts.map +1 -0
- package/dist/runners/projects.js +255 -0
- package/dist/runners/projects.js.map +1 -0
- package/dist/runners/wakeup.d.ts +34 -0
- package/dist/runners/wakeup.d.ts.map +1 -0
- package/dist/runners/wakeup.js +291 -0
- package/dist/runners/wakeup.js.map +1 -0
- package/migrations/001_initial_schema.sql +106 -0
- package/migrations/002_add_commit_sha.sql +12 -0
- package/migrations/003_add_section_priority.sql +13 -0
- package/migrations/004_add_section_dependencies.sql +18 -0
- package/migrations/005_add_audit_actor_model.sql +10 -0
- package/migrations/006_add_task_invocations.sql +33 -0
- package/migrations/007_add_file_anchor.sql +14 -0
- package/migrations/manifest.json +62 -0
- package/package.json +49 -0
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Database queries for task and section lock management
|
|
4
|
+
* All lock operations are atomic using SQLite transactions
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.getTaskLock = getTaskLock;
|
|
8
|
+
exports.isTaskLockExpired = isTaskLockExpired;
|
|
9
|
+
exports.getTaskLockInfo = getTaskLockInfo;
|
|
10
|
+
exports.tryInsertTaskLock = tryInsertTaskLock;
|
|
11
|
+
exports.claimExpiredTaskLock = claimExpiredTaskLock;
|
|
12
|
+
exports.releaseTaskLock = releaseTaskLock;
|
|
13
|
+
exports.forceReleaseTaskLock = forceReleaseTaskLock;
|
|
14
|
+
exports.updateTaskLockHeartbeat = updateTaskLockHeartbeat;
|
|
15
|
+
exports.extendTaskLock = extendTaskLock;
|
|
16
|
+
exports.listTaskLocks = listTaskLocks;
|
|
17
|
+
exports.findExpiredTaskLocks = findExpiredTaskLocks;
|
|
18
|
+
exports.cleanupExpiredTaskLocks = cleanupExpiredTaskLocks;
|
|
19
|
+
exports.getSectionLock = getSectionLock;
|
|
20
|
+
exports.isSectionLockExpired = isSectionLockExpired;
|
|
21
|
+
exports.tryInsertSectionLock = tryInsertSectionLock;
|
|
22
|
+
exports.claimExpiredSectionLock = claimExpiredSectionLock;
|
|
23
|
+
exports.releaseSectionLock = releaseSectionLock;
|
|
24
|
+
exports.forceReleaseSectionLock = forceReleaseSectionLock;
|
|
25
|
+
exports.listSectionLocks = listSectionLocks;
|
|
26
|
+
exports.findExpiredSectionLocks = findExpiredSectionLocks;
|
|
27
|
+
exports.cleanupExpiredSectionLocks = cleanupExpiredSectionLocks;
|
|
28
|
+
// ============ Task Lock Queries ============
|
|
29
|
+
/**
|
|
30
|
+
* Get task lock by task ID
|
|
31
|
+
*/
|
|
32
|
+
function getTaskLock(db, taskId) {
|
|
33
|
+
return db
|
|
34
|
+
.prepare('SELECT * FROM task_locks WHERE task_id = ?')
|
|
35
|
+
.get(taskId);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Check if a task lock is expired
|
|
39
|
+
*/
|
|
40
|
+
function isTaskLockExpired(lock) {
|
|
41
|
+
const expiresAt = new Date(lock.expires_at).getTime();
|
|
42
|
+
return Date.now() > expiresAt;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Get task lock info with ownership and expiry status
|
|
46
|
+
*/
|
|
47
|
+
function getTaskLockInfo(db, taskId, runnerId) {
|
|
48
|
+
const lock = getTaskLock(db, taskId);
|
|
49
|
+
if (!lock) {
|
|
50
|
+
return {
|
|
51
|
+
isLocked: false,
|
|
52
|
+
lock: null,
|
|
53
|
+
isExpired: false,
|
|
54
|
+
isOwnedByUs: false,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
const isExpired = isTaskLockExpired(lock);
|
|
58
|
+
const isOwnedByUs = lock.runner_id === runnerId;
|
|
59
|
+
return {
|
|
60
|
+
isLocked: !isExpired,
|
|
61
|
+
lock,
|
|
62
|
+
isExpired,
|
|
63
|
+
isOwnedByUs,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Try to insert a new task lock (atomic)
|
|
68
|
+
* Returns true if lock was acquired, false if already exists
|
|
69
|
+
*/
|
|
70
|
+
function tryInsertTaskLock(db, taskId, runnerId, timeoutMinutes) {
|
|
71
|
+
const expiresAt = new Date(Date.now() + timeoutMinutes * 60 * 1000).toISOString();
|
|
72
|
+
try {
|
|
73
|
+
db.prepare(`INSERT INTO task_locks (task_id, runner_id, expires_at)
|
|
74
|
+
VALUES (?, ?, ?)`).run(taskId, runnerId, expiresAt);
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
catch (err) {
|
|
78
|
+
// IntegrityError means lock already exists
|
|
79
|
+
if (err instanceof Error && err.message.includes('UNIQUE constraint')) {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
throw err;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Claim an expired lock atomically
|
|
87
|
+
* Only succeeds if the lock is actually expired at execution time
|
|
88
|
+
*/
|
|
89
|
+
function claimExpiredTaskLock(db, taskId, runnerId, timeoutMinutes) {
|
|
90
|
+
const expiresAt = new Date(Date.now() + timeoutMinutes * 60 * 1000).toISOString();
|
|
91
|
+
const result = db.prepare(`UPDATE task_locks
|
|
92
|
+
SET runner_id = ?,
|
|
93
|
+
acquired_at = datetime('now'),
|
|
94
|
+
expires_at = ?,
|
|
95
|
+
heartbeat_at = datetime('now')
|
|
96
|
+
WHERE task_id = ?
|
|
97
|
+
AND expires_at < datetime('now')`).run(runnerId, expiresAt, taskId);
|
|
98
|
+
return result.changes > 0;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Release a task lock (only if owned by the specified runner)
|
|
102
|
+
* Returns true if lock was released, false if not owned
|
|
103
|
+
*/
|
|
104
|
+
function releaseTaskLock(db, taskId, runnerId) {
|
|
105
|
+
const result = db.prepare(`DELETE FROM task_locks
|
|
106
|
+
WHERE task_id = ?
|
|
107
|
+
AND runner_id = ?`).run(taskId, runnerId);
|
|
108
|
+
return result.changes > 0;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Force release a task lock (admin operation)
|
|
112
|
+
* Returns true if lock was released
|
|
113
|
+
*/
|
|
114
|
+
function forceReleaseTaskLock(db, taskId) {
|
|
115
|
+
const result = db.prepare(`DELETE FROM task_locks WHERE task_id = ?`).run(taskId);
|
|
116
|
+
return result.changes > 0;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Update heartbeat for a task lock
|
|
120
|
+
* Returns true if heartbeat was updated, false if lock not found or not owned
|
|
121
|
+
*/
|
|
122
|
+
function updateTaskLockHeartbeat(db, taskId, runnerId) {
|
|
123
|
+
const result = db.prepare(`UPDATE task_locks
|
|
124
|
+
SET heartbeat_at = datetime('now')
|
|
125
|
+
WHERE task_id = ?
|
|
126
|
+
AND runner_id = ?`).run(taskId, runnerId);
|
|
127
|
+
return result.changes > 0;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Extend a task lock's expiry time
|
|
131
|
+
* Returns true if extended, false if lock not found or not owned
|
|
132
|
+
*/
|
|
133
|
+
function extendTaskLock(db, taskId, runnerId, additionalMinutes) {
|
|
134
|
+
const newExpiresAt = new Date(Date.now() + additionalMinutes * 60 * 1000).toISOString();
|
|
135
|
+
const result = db.prepare(`UPDATE task_locks
|
|
136
|
+
SET expires_at = ?,
|
|
137
|
+
heartbeat_at = datetime('now')
|
|
138
|
+
WHERE task_id = ?
|
|
139
|
+
AND runner_id = ?`).run(newExpiresAt, taskId, runnerId);
|
|
140
|
+
return result.changes > 0;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* List all task locks
|
|
144
|
+
*/
|
|
145
|
+
function listTaskLocks(db) {
|
|
146
|
+
return db
|
|
147
|
+
.prepare('SELECT * FROM task_locks ORDER BY acquired_at DESC')
|
|
148
|
+
.all();
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Find all expired task locks
|
|
152
|
+
*/
|
|
153
|
+
function findExpiredTaskLocks(db) {
|
|
154
|
+
return db
|
|
155
|
+
.prepare(`SELECT * FROM task_locks
|
|
156
|
+
WHERE expires_at < datetime('now')
|
|
157
|
+
ORDER BY expires_at ASC`)
|
|
158
|
+
.all();
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Delete all expired task locks
|
|
162
|
+
* Returns the number of locks deleted
|
|
163
|
+
*/
|
|
164
|
+
function cleanupExpiredTaskLocks(db) {
|
|
165
|
+
const result = db.prepare(`DELETE FROM task_locks WHERE expires_at < datetime('now')`).run();
|
|
166
|
+
return result.changes;
|
|
167
|
+
}
|
|
168
|
+
// ============ Section Lock Queries ============
|
|
169
|
+
/**
|
|
170
|
+
* Get section lock by section ID
|
|
171
|
+
*/
|
|
172
|
+
function getSectionLock(db, sectionId) {
|
|
173
|
+
return db
|
|
174
|
+
.prepare('SELECT * FROM section_locks WHERE section_id = ?')
|
|
175
|
+
.get(sectionId);
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Check if a section lock is expired
|
|
179
|
+
*/
|
|
180
|
+
function isSectionLockExpired(lock) {
|
|
181
|
+
const expiresAt = new Date(lock.expires_at).getTime();
|
|
182
|
+
return Date.now() > expiresAt;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Try to insert a new section lock (atomic)
|
|
186
|
+
*/
|
|
187
|
+
function tryInsertSectionLock(db, sectionId, runnerId, timeoutMinutes) {
|
|
188
|
+
const expiresAt = new Date(Date.now() + timeoutMinutes * 60 * 1000).toISOString();
|
|
189
|
+
try {
|
|
190
|
+
db.prepare(`INSERT INTO section_locks (section_id, runner_id, expires_at)
|
|
191
|
+
VALUES (?, ?, ?)`).run(sectionId, runnerId, expiresAt);
|
|
192
|
+
return true;
|
|
193
|
+
}
|
|
194
|
+
catch (err) {
|
|
195
|
+
if (err instanceof Error && err.message.includes('UNIQUE constraint')) {
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
throw err;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Claim an expired section lock atomically
|
|
203
|
+
*/
|
|
204
|
+
function claimExpiredSectionLock(db, sectionId, runnerId, timeoutMinutes) {
|
|
205
|
+
const expiresAt = new Date(Date.now() + timeoutMinutes * 60 * 1000).toISOString();
|
|
206
|
+
const result = db.prepare(`UPDATE section_locks
|
|
207
|
+
SET runner_id = ?,
|
|
208
|
+
acquired_at = datetime('now'),
|
|
209
|
+
expires_at = ?
|
|
210
|
+
WHERE section_id = ?
|
|
211
|
+
AND expires_at < datetime('now')`).run(runnerId, expiresAt, sectionId);
|
|
212
|
+
return result.changes > 0;
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Release a section lock
|
|
216
|
+
*/
|
|
217
|
+
function releaseSectionLock(db, sectionId, runnerId) {
|
|
218
|
+
const result = db.prepare(`DELETE FROM section_locks
|
|
219
|
+
WHERE section_id = ?
|
|
220
|
+
AND runner_id = ?`).run(sectionId, runnerId);
|
|
221
|
+
return result.changes > 0;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Force release a section lock
|
|
225
|
+
*/
|
|
226
|
+
function forceReleaseSectionLock(db, sectionId) {
|
|
227
|
+
const result = db.prepare(`DELETE FROM section_locks WHERE section_id = ?`).run(sectionId);
|
|
228
|
+
return result.changes > 0;
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* List all section locks
|
|
232
|
+
*/
|
|
233
|
+
function listSectionLocks(db) {
|
|
234
|
+
return db
|
|
235
|
+
.prepare('SELECT * FROM section_locks ORDER BY acquired_at DESC')
|
|
236
|
+
.all();
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Find all expired section locks
|
|
240
|
+
*/
|
|
241
|
+
function findExpiredSectionLocks(db) {
|
|
242
|
+
return db
|
|
243
|
+
.prepare(`SELECT * FROM section_locks
|
|
244
|
+
WHERE expires_at < datetime('now')
|
|
245
|
+
ORDER BY expires_at ASC`)
|
|
246
|
+
.all();
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Delete all expired section locks
|
|
250
|
+
*/
|
|
251
|
+
function cleanupExpiredSectionLocks(db) {
|
|
252
|
+
const result = db.prepare(`DELETE FROM section_locks WHERE expires_at < datetime('now')`).run();
|
|
253
|
+
return result.changes;
|
|
254
|
+
}
|
|
255
|
+
//# sourceMappingURL=queries.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queries.js","sourceRoot":"","sources":["../../src/locking/queries.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Section locking for parallel work prevention
|
|
3
|
+
* Prevents multiple runners from working on tasks in the same section
|
|
4
|
+
*/
|
|
5
|
+
import type Database from 'better-sqlite3';
|
|
6
|
+
import { type SectionLock } from './queries.js';
|
|
7
|
+
export interface SectionLockAcquisitionResult {
|
|
8
|
+
acquired: boolean;
|
|
9
|
+
lock?: SectionLock;
|
|
10
|
+
reason?: 'already_owned' | 'acquired_new' | 'claimed_expired';
|
|
11
|
+
error?: SectionLockError;
|
|
12
|
+
}
|
|
13
|
+
export interface SectionLockError {
|
|
14
|
+
code: 'SECTION_LOCKED' | 'LOCK_NOT_FOUND' | 'PERMISSION_DENIED';
|
|
15
|
+
message: string;
|
|
16
|
+
details?: {
|
|
17
|
+
sectionId: string;
|
|
18
|
+
runnerId?: string;
|
|
19
|
+
expiresAt?: string;
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
export interface SectionLockReleaseResult {
|
|
23
|
+
released: boolean;
|
|
24
|
+
error?: SectionLockError;
|
|
25
|
+
}
|
|
26
|
+
export interface SectionLockManager {
|
|
27
|
+
acquire: () => SectionLockAcquisitionResult;
|
|
28
|
+
release: () => SectionLockReleaseResult;
|
|
29
|
+
isHeld: () => boolean;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Acquire a section lock with atomic operations
|
|
33
|
+
*
|
|
34
|
+
* Flow:
|
|
35
|
+
* 1. Try INSERT (fails if lock exists)
|
|
36
|
+
* 2. If exists, check if we own it
|
|
37
|
+
* 3. If owned by another, check if expired
|
|
38
|
+
* 4. If expired, claim atomically
|
|
39
|
+
* 5. If not expired, return failure
|
|
40
|
+
*/
|
|
41
|
+
export declare function acquireSectionLock(db: Database.Database, sectionId: string, runnerId: string, timeoutMinutes?: number): SectionLockAcquisitionResult;
|
|
42
|
+
/**
|
|
43
|
+
* Release a section lock (only by owner)
|
|
44
|
+
*/
|
|
45
|
+
export declare function releaseSectionLock(db: Database.Database, sectionId: string, runnerId: string): SectionLockReleaseResult;
|
|
46
|
+
/**
|
|
47
|
+
* Force release a section lock (admin operation)
|
|
48
|
+
*/
|
|
49
|
+
export declare function forceRelease(db: Database.Database, sectionId: string): SectionLockReleaseResult;
|
|
50
|
+
/**
|
|
51
|
+
* Create a section lock manager for a specific section
|
|
52
|
+
*/
|
|
53
|
+
export declare function createSectionLockManager(db: Database.Database, sectionId: string, runnerId: string, timeoutMinutes?: number): SectionLockManager;
|
|
54
|
+
/**
|
|
55
|
+
* Check if a section is locked
|
|
56
|
+
*/
|
|
57
|
+
export declare function isSectionLocked(db: Database.Database, sectionId: string): boolean;
|
|
58
|
+
/**
|
|
59
|
+
* Check if a section is locked by a specific runner
|
|
60
|
+
*/
|
|
61
|
+
export declare function isSectionLockedBy(db: Database.Database, sectionId: string, runnerId: string): boolean;
|
|
62
|
+
/**
|
|
63
|
+
* Get time until section lock expires (in milliseconds)
|
|
64
|
+
*/
|
|
65
|
+
export declare function getTimeUntilExpiry(db: Database.Database, sectionId: string): number | null;
|
|
66
|
+
/**
|
|
67
|
+
* Check if any tasks in the section are in progress by another runner
|
|
68
|
+
* This can be used before acquiring a section lock
|
|
69
|
+
*/
|
|
70
|
+
export declare function getSectionLockHolder(db: Database.Database, sectionId: string): SectionLock | null;
|
|
71
|
+
export declare const SECTION_LOCK_CONSTANTS: {
|
|
72
|
+
readonly DEFAULT_TIMEOUT_MINUTES: 120;
|
|
73
|
+
};
|
|
74
|
+
//# sourceMappingURL=section-lock.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"section-lock.d.ts","sourceRoot":"","sources":["../../src/locking/section-lock.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAOL,KAAK,WAAW,EACjB,MAAM,cAAc,CAAC;AAOtB,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,EAAE,OAAO,CAAC;IAClB,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB,MAAM,CAAC,EAAE,eAAe,GAAG,cAAc,GAAG,iBAAiB,CAAC;IAC9D,KAAK,CAAC,EAAE,gBAAgB,CAAC;CAC1B;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,gBAAgB,GAAG,gBAAgB,GAAG,mBAAmB,CAAC;IAChE,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE;QACR,SAAS,EAAE,MAAM,CAAC;QAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,CAAC;CACH;AAED,MAAM,WAAW,wBAAwB;IACvC,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,gBAAgB,CAAC;CAC1B;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,MAAM,4BAA4B,CAAC;IAC5C,OAAO,EAAE,MAAM,wBAAwB,CAAC;IACxC,MAAM,EAAE,MAAM,OAAO,CAAC;CACvB;AAID;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAChC,EAAE,EAAE,QAAQ,CAAC,QAAQ,EACrB,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,EAChB,cAAc,GAAE,MAAwC,GACvD,4BAA4B,CAqD9B;AAyBD;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,EAAE,EAAE,QAAQ,CAAC,QAAQ,EACrB,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,GACf,wBAAwB,CA+B1B;AAED;;GAEG;AACH,wBAAgB,YAAY,CAC1B,EAAE,EAAE,QAAQ,CAAC,QAAQ,EACrB,SAAS,EAAE,MAAM,GAChB,wBAAwB,CAe1B;AAID;;GAEG;AACH,wBAAgB,wBAAwB,CACtC,EAAE,EAAE,QAAQ,CAAC,QAAQ,EACrB,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,EAChB,cAAc,GAAE,MAAwC,GACvD,kBAAkB,CASpB;AAID;;GAEG;AACH,wBAAgB,eAAe,CAC7B,EAAE,EAAE,QAAQ,CAAC,QAAQ,EACrB,SAAS,EAAE,MAAM,GAChB,OAAO,CAIT;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,EAAE,EAAE,QAAQ,CAAC,QAAQ,EACrB,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,GACf,OAAO,CAIT;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,EAAE,EAAE,QAAQ,CAAC,QAAQ,EACrB,SAAS,EAAE,MAAM,GAChB,MAAM,GAAG,IAAI,CAOf;AAID;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,EAAE,EAAE,QAAQ,CAAC,QAAQ,EACrB,SAAS,EAAE,MAAM,GAChB,WAAW,GAAG,IAAI,CAMpB;AAID,eAAO,MAAM,sBAAsB;;CAEzB,CAAC"}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Section locking for parallel work prevention
|
|
4
|
+
* Prevents multiple runners from working on tasks in the same section
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.SECTION_LOCK_CONSTANTS = void 0;
|
|
8
|
+
exports.acquireSectionLock = acquireSectionLock;
|
|
9
|
+
exports.releaseSectionLock = releaseSectionLock;
|
|
10
|
+
exports.forceRelease = forceRelease;
|
|
11
|
+
exports.createSectionLockManager = createSectionLockManager;
|
|
12
|
+
exports.isSectionLocked = isSectionLocked;
|
|
13
|
+
exports.isSectionLockedBy = isSectionLockedBy;
|
|
14
|
+
exports.getTimeUntilExpiry = getTimeUntilExpiry;
|
|
15
|
+
exports.getSectionLockHolder = getSectionLockHolder;
|
|
16
|
+
const queries_js_1 = require("./queries.js");
|
|
17
|
+
// Default section lock timeout in minutes
|
|
18
|
+
const DEFAULT_SECTION_TIMEOUT_MINUTES = 120;
|
|
19
|
+
// ============ Lock Acquisition ============
|
|
20
|
+
/**
|
|
21
|
+
* Acquire a section lock with atomic operations
|
|
22
|
+
*
|
|
23
|
+
* Flow:
|
|
24
|
+
* 1. Try INSERT (fails if lock exists)
|
|
25
|
+
* 2. If exists, check if we own it
|
|
26
|
+
* 3. If owned by another, check if expired
|
|
27
|
+
* 4. If expired, claim atomically
|
|
28
|
+
* 5. If not expired, return failure
|
|
29
|
+
*/
|
|
30
|
+
function acquireSectionLock(db, sectionId, runnerId, timeoutMinutes = DEFAULT_SECTION_TIMEOUT_MINUTES) {
|
|
31
|
+
// Step 1: Try to insert new lock
|
|
32
|
+
if ((0, queries_js_1.tryInsertSectionLock)(db, sectionId, runnerId, timeoutMinutes)) {
|
|
33
|
+
const lock = (0, queries_js_1.getSectionLock)(db, sectionId);
|
|
34
|
+
return {
|
|
35
|
+
acquired: true,
|
|
36
|
+
lock: lock ?? undefined,
|
|
37
|
+
reason: 'acquired_new',
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
// Step 2: Lock exists, get current state
|
|
41
|
+
const existingLock = (0, queries_js_1.getSectionLock)(db, sectionId);
|
|
42
|
+
if (!existingLock) {
|
|
43
|
+
// Race condition: lock was deleted between insert attempt and now
|
|
44
|
+
if ((0, queries_js_1.tryInsertSectionLock)(db, sectionId, runnerId, timeoutMinutes)) {
|
|
45
|
+
const lock = (0, queries_js_1.getSectionLock)(db, sectionId);
|
|
46
|
+
return {
|
|
47
|
+
acquired: true,
|
|
48
|
+
lock: lock ?? undefined,
|
|
49
|
+
reason: 'acquired_new',
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
const currentLock = (0, queries_js_1.getSectionLock)(db, sectionId);
|
|
53
|
+
return createLockedError(sectionId, currentLock);
|
|
54
|
+
}
|
|
55
|
+
// Step 3: Check if we already own the lock
|
|
56
|
+
if (existingLock.runner_id === runnerId) {
|
|
57
|
+
return {
|
|
58
|
+
acquired: true,
|
|
59
|
+
lock: existingLock,
|
|
60
|
+
reason: 'already_owned',
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
// Step 4: Check if lock is expired
|
|
64
|
+
if ((0, queries_js_1.isSectionLockExpired)(existingLock)) {
|
|
65
|
+
if ((0, queries_js_1.claimExpiredSectionLock)(db, sectionId, runnerId, timeoutMinutes)) {
|
|
66
|
+
const lock = (0, queries_js_1.getSectionLock)(db, sectionId);
|
|
67
|
+
return {
|
|
68
|
+
acquired: true,
|
|
69
|
+
lock: lock ?? undefined,
|
|
70
|
+
reason: 'claimed_expired',
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
// Another runner claimed it first
|
|
74
|
+
const currentLock = (0, queries_js_1.getSectionLock)(db, sectionId);
|
|
75
|
+
return createLockedError(sectionId, currentLock);
|
|
76
|
+
}
|
|
77
|
+
// Step 5: Lock is held by another runner and not expired
|
|
78
|
+
return createLockedError(sectionId, existingLock);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Create a SECTION_LOCKED error response
|
|
82
|
+
*/
|
|
83
|
+
function createLockedError(sectionId, lock) {
|
|
84
|
+
return {
|
|
85
|
+
acquired: false,
|
|
86
|
+
error: {
|
|
87
|
+
code: 'SECTION_LOCKED',
|
|
88
|
+
message: 'Section is locked by another runner',
|
|
89
|
+
details: {
|
|
90
|
+
sectionId,
|
|
91
|
+
runnerId: lock?.runner_id,
|
|
92
|
+
expiresAt: lock?.expires_at,
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
// ============ Lock Release ============
|
|
98
|
+
/**
|
|
99
|
+
* Release a section lock (only by owner)
|
|
100
|
+
*/
|
|
101
|
+
function releaseSectionLock(db, sectionId, runnerId) {
|
|
102
|
+
const existingLock = (0, queries_js_1.getSectionLock)(db, sectionId);
|
|
103
|
+
if (!existingLock) {
|
|
104
|
+
return {
|
|
105
|
+
released: false,
|
|
106
|
+
error: {
|
|
107
|
+
code: 'LOCK_NOT_FOUND',
|
|
108
|
+
message: 'Lock does not exist',
|
|
109
|
+
details: { sectionId },
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
if (existingLock.runner_id !== runnerId) {
|
|
114
|
+
return {
|
|
115
|
+
released: false,
|
|
116
|
+
error: {
|
|
117
|
+
code: 'PERMISSION_DENIED',
|
|
118
|
+
message: 'Cannot release lock owned by another runner',
|
|
119
|
+
details: {
|
|
120
|
+
sectionId,
|
|
121
|
+
runnerId: existingLock.runner_id,
|
|
122
|
+
expiresAt: existingLock.expires_at,
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
(0, queries_js_1.releaseSectionLock)(db, sectionId, runnerId);
|
|
128
|
+
return { released: true };
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Force release a section lock (admin operation)
|
|
132
|
+
*/
|
|
133
|
+
function forceRelease(db, sectionId) {
|
|
134
|
+
const existed = (0, queries_js_1.forceReleaseSectionLock)(db, sectionId);
|
|
135
|
+
if (!existed) {
|
|
136
|
+
return {
|
|
137
|
+
released: false,
|
|
138
|
+
error: {
|
|
139
|
+
code: 'LOCK_NOT_FOUND',
|
|
140
|
+
message: 'Lock does not exist',
|
|
141
|
+
details: { sectionId },
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
return { released: true };
|
|
146
|
+
}
|
|
147
|
+
// ============ Lock Manager ============
|
|
148
|
+
/**
|
|
149
|
+
* Create a section lock manager for a specific section
|
|
150
|
+
*/
|
|
151
|
+
function createSectionLockManager(db, sectionId, runnerId, timeoutMinutes = DEFAULT_SECTION_TIMEOUT_MINUTES) {
|
|
152
|
+
return {
|
|
153
|
+
acquire: () => acquireSectionLock(db, sectionId, runnerId, timeoutMinutes),
|
|
154
|
+
release: () => releaseSectionLock(db, sectionId, runnerId),
|
|
155
|
+
isHeld: () => {
|
|
156
|
+
const lock = (0, queries_js_1.getSectionLock)(db, sectionId);
|
|
157
|
+
return lock !== null && lock.runner_id === runnerId && !(0, queries_js_1.isSectionLockExpired)(lock);
|
|
158
|
+
},
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
// ============ Lock Status Check ============
|
|
162
|
+
/**
|
|
163
|
+
* Check if a section is locked
|
|
164
|
+
*/
|
|
165
|
+
function isSectionLocked(db, sectionId) {
|
|
166
|
+
const lock = (0, queries_js_1.getSectionLock)(db, sectionId);
|
|
167
|
+
if (!lock)
|
|
168
|
+
return false;
|
|
169
|
+
return !(0, queries_js_1.isSectionLockExpired)(lock);
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Check if a section is locked by a specific runner
|
|
173
|
+
*/
|
|
174
|
+
function isSectionLockedBy(db, sectionId, runnerId) {
|
|
175
|
+
const lock = (0, queries_js_1.getSectionLock)(db, sectionId);
|
|
176
|
+
if (!lock)
|
|
177
|
+
return false;
|
|
178
|
+
return lock.runner_id === runnerId && !(0, queries_js_1.isSectionLockExpired)(lock);
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Get time until section lock expires (in milliseconds)
|
|
182
|
+
*/
|
|
183
|
+
function getTimeUntilExpiry(db, sectionId) {
|
|
184
|
+
const lock = (0, queries_js_1.getSectionLock)(db, sectionId);
|
|
185
|
+
if (!lock)
|
|
186
|
+
return null;
|
|
187
|
+
const expiresAt = new Date(lock.expires_at).getTime();
|
|
188
|
+
const remaining = expiresAt - Date.now();
|
|
189
|
+
return Math.max(0, remaining);
|
|
190
|
+
}
|
|
191
|
+
// ============ Helper Functions ============
|
|
192
|
+
/**
|
|
193
|
+
* Check if any tasks in the section are in progress by another runner
|
|
194
|
+
* This can be used before acquiring a section lock
|
|
195
|
+
*/
|
|
196
|
+
function getSectionLockHolder(db, sectionId) {
|
|
197
|
+
const lock = (0, queries_js_1.getSectionLock)(db, sectionId);
|
|
198
|
+
if (!lock || (0, queries_js_1.isSectionLockExpired)(lock)) {
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
201
|
+
return lock;
|
|
202
|
+
}
|
|
203
|
+
// ============ Constants Export ============
|
|
204
|
+
exports.SECTION_LOCK_CONSTANTS = {
|
|
205
|
+
DEFAULT_TIMEOUT_MINUTES: DEFAULT_SECTION_TIMEOUT_MINUTES,
|
|
206
|
+
};
|
|
207
|
+
//# 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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"}
|