@axiom-lattice/local-stores 3.1.24 → 3.2.1
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/.turbo/turbo-build.log +10 -10
- package/CHANGELOG.md +58 -0
- package/dist/index.d.mts +12 -1
- package/dist/index.d.ts +12 -1
- package/dist/index.js +66 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +66 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
- package/src/__tests__/LocalScheduleStorage.test.ts +143 -0
- package/src/__tests__/LocalThreadMessageQueueStore.test.ts +40 -0
- package/src/stores/LocalScheduleStorage.ts +23 -0
- package/src/stores/LocalThreadMessageQueueStore.ts +56 -1
package/dist/index.mjs
CHANGED
|
@@ -4506,6 +4506,9 @@ CREATE TABLE IF NOT EXISTS lt_thread_message_queue (
|
|
|
4506
4506
|
custom_run_config TEXT,
|
|
4507
4507
|
trusted_run_context TEXT,
|
|
4508
4508
|
execution_mode TEXT CHECK (execution_mode IS NULL OR execution_mode = 'followup'),
|
|
4509
|
+
locked_by TEXT,
|
|
4510
|
+
locked_at TEXT,
|
|
4511
|
+
lease_expires_at TEXT,
|
|
4509
4512
|
created_at TEXT NOT NULL
|
|
4510
4513
|
);
|
|
4511
4514
|
CREATE INDEX IF NOT EXISTS idx_lt_tmq_thread ON lt_thread_message_queue(thread_id, status);
|
|
@@ -4524,6 +4527,16 @@ var LocalThreadMessageQueueStore = class {
|
|
|
4524
4527
|
if (!names.has("execution_mode")) {
|
|
4525
4528
|
db.run("ALTER TABLE lt_thread_message_queue ADD COLUMN execution_mode TEXT CHECK (execution_mode IS NULL OR execution_mode = 'followup')");
|
|
4526
4529
|
}
|
|
4530
|
+
if (!names.has("locked_by")) {
|
|
4531
|
+
db.run("ALTER TABLE lt_thread_message_queue ADD COLUMN locked_by TEXT");
|
|
4532
|
+
}
|
|
4533
|
+
if (!names.has("locked_at")) {
|
|
4534
|
+
db.run("ALTER TABLE lt_thread_message_queue ADD COLUMN locked_at TEXT");
|
|
4535
|
+
}
|
|
4536
|
+
if (!names.has("lease_expires_at")) {
|
|
4537
|
+
db.run("ALTER TABLE lt_thread_message_queue ADD COLUMN lease_expires_at TEXT");
|
|
4538
|
+
}
|
|
4539
|
+
db.run("CREATE INDEX IF NOT EXISTS idx_lt_tmq_lease ON lt_thread_message_queue(status, lease_expires_at)");
|
|
4527
4540
|
}
|
|
4528
4541
|
async addMessage(params) {
|
|
4529
4542
|
const trusted = validateQueueTrust(params);
|
|
@@ -4648,6 +4661,37 @@ var LocalThreadMessageQueueStore = class {
|
|
|
4648
4661
|
).all(threadId, ...filter.params);
|
|
4649
4662
|
return rows2.map(rowToMessage);
|
|
4650
4663
|
}
|
|
4664
|
+
async claimPendingMessages(threadId, scope, options) {
|
|
4665
|
+
const filter = scopeClause(scope);
|
|
4666
|
+
const limitClause = options.limit === void 0 ? "" : ` LIMIT ${Math.max(0, Math.floor(options.limit))}`;
|
|
4667
|
+
const claimedRows = transaction(this.db, () => {
|
|
4668
|
+
const candidates = this.db.prepare(
|
|
4669
|
+
`SELECT * FROM lt_thread_message_queue WHERE thread_id = ? AND status = 'pending'${filter.sql} ORDER BY priority DESC, sequence_order ASC${limitClause}`
|
|
4670
|
+
).all(threadId, ...filter.params);
|
|
4671
|
+
const startedAt = Date.now();
|
|
4672
|
+
const lockedAt = new Date(startedAt).toISOString();
|
|
4673
|
+
const leaseExpiresAt = new Date(startedAt + options.ttlMs).toISOString();
|
|
4674
|
+
const claimed = [];
|
|
4675
|
+
for (const row of candidates) {
|
|
4676
|
+
const changes = write(
|
|
4677
|
+
this.db,
|
|
4678
|
+
`UPDATE lt_thread_message_queue SET status = 'processing', locked_by = ?, locked_at = ?, lease_expires_at = ? WHERE id = ? AND status = 'pending'`,
|
|
4679
|
+
[options.owner, lockedAt, leaseExpiresAt, row.id]
|
|
4680
|
+
);
|
|
4681
|
+
if (changes > 0) claimed.push(row);
|
|
4682
|
+
}
|
|
4683
|
+
return claimed;
|
|
4684
|
+
});
|
|
4685
|
+
return claimedRows.map(rowToMessage);
|
|
4686
|
+
}
|
|
4687
|
+
async reclaimExpiredProcessing(threadId, scope) {
|
|
4688
|
+
const filter = scopeClause(scope);
|
|
4689
|
+
return transaction(this.db, () => write(
|
|
4690
|
+
this.db,
|
|
4691
|
+
`UPDATE lt_thread_message_queue SET status = 'pending', locked_by = NULL, locked_at = NULL, lease_expires_at = NULL WHERE thread_id = ? AND status = 'processing' AND lease_expires_at IS NOT NULL AND lease_expires_at <= ?${filter.sql}`,
|
|
4692
|
+
[threadId, nowISO(), ...filter.params]
|
|
4693
|
+
));
|
|
4694
|
+
}
|
|
4651
4695
|
async getQueueSize(threadId, scope) {
|
|
4652
4696
|
const filter = scopeClause(scope);
|
|
4653
4697
|
const row = this.db.prepare(
|
|
@@ -4689,7 +4733,7 @@ var LocalThreadMessageQueueStore = class {
|
|
|
4689
4733
|
async resetProcessingToPending(threadId, scope) {
|
|
4690
4734
|
const filter = scopeClause(scope);
|
|
4691
4735
|
const result = this.db.prepare(
|
|
4692
|
-
`UPDATE lt_thread_message_queue SET status = 'pending' WHERE thread_id = ? AND status = 'processing'${filter.sql}`
|
|
4736
|
+
`UPDATE lt_thread_message_queue SET status = 'pending', locked_by = NULL, locked_at = NULL, lease_expires_at = NULL WHERE thread_id = ? AND status = 'processing'${filter.sql}`
|
|
4693
4737
|
).run(threadId, ...filter.params);
|
|
4694
4738
|
return result.changes;
|
|
4695
4739
|
}
|
|
@@ -5075,6 +5119,27 @@ var LocalScheduleStorage = class {
|
|
|
5075
5119
|
`UPDATE lt_scheduled_tasks SET ${setClauses.join(", ")} WHERE task_id = ?`
|
|
5076
5120
|
).run(...values);
|
|
5077
5121
|
}
|
|
5122
|
+
async claimScheduledRun(taskId) {
|
|
5123
|
+
const result = this.db.prepare(
|
|
5124
|
+
`UPDATE lt_scheduled_tasks SET status = 'running', updated_at = ? WHERE task_id = ? AND status = 'pending'`
|
|
5125
|
+
).run((/* @__PURE__ */ new Date()).toISOString(), taskId);
|
|
5126
|
+
return result.changes > 0;
|
|
5127
|
+
}
|
|
5128
|
+
/**
|
|
5129
|
+
* Requeues `running` tasks whose `updated_at` is older than `olderThanMs` back
|
|
5130
|
+
* to `pending` (recovery after a crash that left a claim unsettled).
|
|
5131
|
+
*
|
|
5132
|
+
* @param olderThanMs Age threshold in milliseconds.
|
|
5133
|
+
* @returns The number of tasks requeued.
|
|
5134
|
+
*/
|
|
5135
|
+
async requeueStaleRunning(olderThanMs) {
|
|
5136
|
+
const now = /* @__PURE__ */ new Date();
|
|
5137
|
+
const cutoff = new Date(now.getTime() - olderThanMs).toISOString();
|
|
5138
|
+
const result = this.db.prepare(
|
|
5139
|
+
`UPDATE lt_scheduled_tasks SET status = 'pending', updated_at = ? WHERE status = 'running' AND updated_at < ?`
|
|
5140
|
+
).run(now.toISOString(), cutoff);
|
|
5141
|
+
return result.changes;
|
|
5142
|
+
}
|
|
5078
5143
|
async delete(taskId) {
|
|
5079
5144
|
this.db.prepare(`DELETE FROM lt_scheduled_tasks WHERE task_id = ?`).run(taskId);
|
|
5080
5145
|
}
|