@axiom-lattice/pg-stores 3.2.1 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +10 -10
- package/CHANGELOG.md +87 -0
- package/dist/index.d.mts +109 -2
- package/dist/index.d.ts +109 -2
- package/dist/index.js +395 -23
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +395 -23
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
- package/src/__tests__/PostgreSQLLeaseStore.test.ts +43 -0
- package/src/__tests__/PostgreSQLOutboxStore.test.ts +77 -0
- package/src/__tests__/PostgreSQLScheduleStorage.test.ts +162 -0
- package/src/__tests__/ThreadMessageQueueStore.integration.test.ts +82 -0
- package/src/__tests__/ThreadMessageQueueStore.test.ts +71 -12
- package/src/__tests__/queue_migrations.test.ts +18 -0
- package/src/createPgStoreConfig.ts +10 -12
- package/src/migrations/add_queue_claim_lease.ts +35 -0
- package/src/migrations/distributed_lease_migration.ts +32 -0
- package/src/migrations/outbox_migration.ts +30 -0
- package/src/migrations/queue_migrations.ts +28 -0
- package/src/stores/PostgreSQLLeaseStore.ts +126 -0
- package/src/stores/PostgreSQLOutboxStore.ts +136 -0
- package/src/stores/PostgreSQLScheduleStorage.ts +54 -0
- package/src/stores/ThreadMessageQueueStore.ts +49 -16
package/dist/index.js
CHANGED
|
@@ -37,7 +37,7 @@ __export(index_exports, {
|
|
|
37
37
|
MenuStore: () => MenuStore,
|
|
38
38
|
MigrationManager: () => MigrationManager,
|
|
39
39
|
PGVectorStoreProvider: () => PGVectorStoreProvider,
|
|
40
|
-
Pool: () =>
|
|
40
|
+
Pool: () => import_pg33.Pool,
|
|
41
41
|
PostgreSQLA2AApiKeyStore: () => PostgreSQLA2AApiKeyStore,
|
|
42
42
|
PostgreSQLAgentWebAppStore: () => PostgreSQLAgentWebAppStore,
|
|
43
43
|
PostgreSQLAssistantStore: () => PostgreSQLAssistantStore,
|
|
@@ -127,10 +127,10 @@ __export(index_exports, {
|
|
|
127
127
|
safeParse: () => safeParse
|
|
128
128
|
});
|
|
129
129
|
module.exports = __toCommonJS(index_exports);
|
|
130
|
-
var
|
|
130
|
+
var import_pg33 = require("pg");
|
|
131
131
|
|
|
132
132
|
// src/createPgStoreConfig.ts
|
|
133
|
-
var
|
|
133
|
+
var import_pg30 = require("pg");
|
|
134
134
|
|
|
135
135
|
// src/migrations/migration.ts
|
|
136
136
|
var MigrationManager = class {
|
|
@@ -5333,6 +5333,44 @@ var addTrustedRunContextColumn = {
|
|
|
5333
5333
|
}
|
|
5334
5334
|
};
|
|
5335
5335
|
|
|
5336
|
+
// src/migrations/add_queue_claim_lease.ts
|
|
5337
|
+
var addQueueClaimLease = {
|
|
5338
|
+
version: 186,
|
|
5339
|
+
name: "add_queue_claim_lease_columns",
|
|
5340
|
+
up: async (client) => {
|
|
5341
|
+
await client.query(`
|
|
5342
|
+
ALTER TABLE lattice_thread_message_queue
|
|
5343
|
+
ADD COLUMN IF NOT EXISTS locked_by VARCHAR(255),
|
|
5344
|
+
ADD COLUMN IF NOT EXISTS locked_at TIMESTAMPTZ,
|
|
5345
|
+
ADD COLUMN IF NOT EXISTS lease_expires_at TIMESTAMPTZ
|
|
5346
|
+
`);
|
|
5347
|
+
await client.query(`
|
|
5348
|
+
CREATE INDEX IF NOT EXISTS idx_lattice_thread_message_queue_lease
|
|
5349
|
+
ON lattice_thread_message_queue(status, lease_expires_at)
|
|
5350
|
+
`);
|
|
5351
|
+
},
|
|
5352
|
+
down: async (client) => {
|
|
5353
|
+
await client.query("DROP INDEX IF EXISTS idx_lattice_thread_message_queue_lease");
|
|
5354
|
+
await client.query(`
|
|
5355
|
+
ALTER TABLE lattice_thread_message_queue
|
|
5356
|
+
DROP COLUMN IF EXISTS lease_expires_at,
|
|
5357
|
+
DROP COLUMN IF EXISTS locked_at,
|
|
5358
|
+
DROP COLUMN IF EXISTS locked_by
|
|
5359
|
+
`);
|
|
5360
|
+
}
|
|
5361
|
+
};
|
|
5362
|
+
|
|
5363
|
+
// src/migrations/queue_migrations.ts
|
|
5364
|
+
function registerThreadMessageQueueMigrations(manager) {
|
|
5365
|
+
manager.register(createThreadMessageQueueTable);
|
|
5366
|
+
manager.register(addPriorityAndCommandColumns);
|
|
5367
|
+
manager.register(alterMessageQueueIdColumn);
|
|
5368
|
+
manager.register(addCustomRunConfigColumn);
|
|
5369
|
+
manager.register(addWorkspaceProjectToQueue);
|
|
5370
|
+
manager.register(addTrustedRunContextColumn);
|
|
5371
|
+
manager.register(addQueueClaimLease);
|
|
5372
|
+
}
|
|
5373
|
+
|
|
5336
5374
|
// src/stores/ThreadMessageQueueStore.ts
|
|
5337
5375
|
var ThreadMessageQueueStore = class {
|
|
5338
5376
|
constructor(options) {
|
|
@@ -5353,12 +5391,7 @@ var ThreadMessageQueueStore = class {
|
|
|
5353
5391
|
throw new Error("Either pool or poolConfig must be provided");
|
|
5354
5392
|
}
|
|
5355
5393
|
this.migrationManager = new MigrationManager(this.pool);
|
|
5356
|
-
this.migrationManager
|
|
5357
|
-
this.migrationManager.register(addPriorityAndCommandColumns);
|
|
5358
|
-
this.migrationManager.register(addCustomRunConfigColumn);
|
|
5359
|
-
this.migrationManager.register(alterMessageQueueIdColumn);
|
|
5360
|
-
this.migrationManager.register(addWorkspaceProjectToQueue);
|
|
5361
|
-
this.migrationManager.register(addTrustedRunContextColumn);
|
|
5394
|
+
registerThreadMessageQueueMigrations(this.migrationManager);
|
|
5362
5395
|
if (options.autoMigrate !== false) {
|
|
5363
5396
|
this.initialize().catch((error) => {
|
|
5364
5397
|
console.error("Failed to initialize ThreadMessageQueueStore:", error);
|
|
@@ -5483,6 +5516,44 @@ var ThreadMessageQueueStore = class {
|
|
|
5483
5516
|
);
|
|
5484
5517
|
return result.rows.map((row) => this.rowToMessage(row));
|
|
5485
5518
|
}
|
|
5519
|
+
/**
|
|
5520
|
+
* Atomically claims pending messages for a replica, stamping a lease.
|
|
5521
|
+
*/
|
|
5522
|
+
async claimPendingMessages(threadId, scope, options) {
|
|
5523
|
+
const filter = scopeClause(scope, 4);
|
|
5524
|
+
const limitClause = options.limit === void 0 ? "" : ` LIMIT ${Math.max(0, Math.floor(options.limit))}`;
|
|
5525
|
+
const result = await this.pool.query(
|
|
5526
|
+
`UPDATE lattice_thread_message_queue q
|
|
5527
|
+
SET status = 'processing',
|
|
5528
|
+
locked_by = $2,
|
|
5529
|
+
locked_at = now(),
|
|
5530
|
+
lease_expires_at = now() + ($3::bigint || ' milliseconds')::interval
|
|
5531
|
+
WHERE q.id IN (
|
|
5532
|
+
SELECT id FROM lattice_thread_message_queue
|
|
5533
|
+
WHERE thread_id = $1 AND status = 'pending'${filter.sql}
|
|
5534
|
+
ORDER BY priority DESC, sequence_order ASC${limitClause}
|
|
5535
|
+
FOR UPDATE SKIP LOCKED
|
|
5536
|
+
)
|
|
5537
|
+
RETURNING q.*`,
|
|
5538
|
+
[threadId, options.owner, options.ttlMs, ...filter.params]
|
|
5539
|
+
);
|
|
5540
|
+
return result.rows.map((row) => this.rowToMessage(row));
|
|
5541
|
+
}
|
|
5542
|
+
/**
|
|
5543
|
+
* Returns expired processing rows to pending and clears their lease.
|
|
5544
|
+
*/
|
|
5545
|
+
async reclaimExpiredProcessing(threadId, scope) {
|
|
5546
|
+
const filter = scopeClause(scope, 2);
|
|
5547
|
+
const result = await this.pool.query(
|
|
5548
|
+
`UPDATE lattice_thread_message_queue
|
|
5549
|
+
SET status = 'pending', locked_by = NULL, locked_at = NULL, lease_expires_at = NULL
|
|
5550
|
+
WHERE thread_id = $1 AND status = 'processing'
|
|
5551
|
+
AND lease_expires_at IS NOT NULL AND lease_expires_at <= now()${filter.sql}
|
|
5552
|
+
RETURNING id`,
|
|
5553
|
+
[threadId, ...filter.params]
|
|
5554
|
+
);
|
|
5555
|
+
return result.rowCount ?? 0;
|
|
5556
|
+
}
|
|
5486
5557
|
/**
|
|
5487
5558
|
* Get processing messages for a thread
|
|
5488
5559
|
*/
|
|
@@ -5573,7 +5644,7 @@ var ThreadMessageQueueStore = class {
|
|
|
5573
5644
|
const filter = scopeClause(scope, 2);
|
|
5574
5645
|
const result = await this.pool.query(
|
|
5575
5646
|
`UPDATE lattice_thread_message_queue
|
|
5576
|
-
SET status = 'pending'
|
|
5647
|
+
SET status = 'pending', locked_by = NULL, locked_at = NULL, lease_expires_at = NULL
|
|
5577
5648
|
WHERE thread_id = $1 AND status = 'processing'${filter.sql}
|
|
5578
5649
|
RETURNING id`,
|
|
5579
5650
|
[threadId, ...filter.params]
|
|
@@ -5583,7 +5654,7 @@ var ThreadMessageQueueStore = class {
|
|
|
5583
5654
|
rowToMessage(row) {
|
|
5584
5655
|
return {
|
|
5585
5656
|
id: row.id,
|
|
5586
|
-
content:
|
|
5657
|
+
content: row.message_content,
|
|
5587
5658
|
type: row.message_type,
|
|
5588
5659
|
sequence: row.sequence_order,
|
|
5589
5660
|
createdAt: new Date(row.created_at),
|
|
@@ -7310,6 +7381,54 @@ var PostgreSQLScheduleStorage = class {
|
|
|
7310
7381
|
values
|
|
7311
7382
|
);
|
|
7312
7383
|
}
|
|
7384
|
+
/**
|
|
7385
|
+
* Atomically claims one due fire by transitioning the task `pending -> running`.
|
|
7386
|
+
*
|
|
7387
|
+
* Only one caller wins the `UPDATE ... WHERE status = 'pending'`; all others
|
|
7388
|
+
* observe zero affected rows and must skip execution.
|
|
7389
|
+
*
|
|
7390
|
+
* @param taskId Task to claim.
|
|
7391
|
+
* @returns True when this caller won the claim (task was `pending`); false if
|
|
7392
|
+
* another replica already claimed it or it is not pending.
|
|
7393
|
+
*/
|
|
7394
|
+
async claimScheduledRun(taskId) {
|
|
7395
|
+
await this.ensureInitialized();
|
|
7396
|
+
const result = await this.pool.query(
|
|
7397
|
+
`UPDATE lattice_scheduled_tasks
|
|
7398
|
+
SET status = $1, updated_at = $2
|
|
7399
|
+
WHERE task_id = $3 AND status = $4
|
|
7400
|
+
RETURNING task_id`,
|
|
7401
|
+
[
|
|
7402
|
+
import_protocols4.ScheduledTaskStatus.RUNNING,
|
|
7403
|
+
/* @__PURE__ */ new Date(),
|
|
7404
|
+
taskId,
|
|
7405
|
+
import_protocols4.ScheduledTaskStatus.PENDING
|
|
7406
|
+
]
|
|
7407
|
+
);
|
|
7408
|
+
return (result.rowCount ?? 0) > 0;
|
|
7409
|
+
}
|
|
7410
|
+
/**
|
|
7411
|
+
* Requeues `running` tasks whose `updated_at` is older than `olderThanMs` back
|
|
7412
|
+
* to `pending` (recovery after a crash that left a claim unsettled).
|
|
7413
|
+
*
|
|
7414
|
+
* @param olderThanMs Age threshold in milliseconds.
|
|
7415
|
+
* @returns The number of tasks requeued.
|
|
7416
|
+
*/
|
|
7417
|
+
async requeueStaleRunning(olderThanMs) {
|
|
7418
|
+
await this.ensureInitialized();
|
|
7419
|
+
const result = await this.pool.query(
|
|
7420
|
+
`UPDATE lattice_scheduled_tasks
|
|
7421
|
+
SET status = $1, updated_at = now()
|
|
7422
|
+
WHERE status = $2 AND updated_at < now() - ($3::bigint || ' milliseconds')::interval
|
|
7423
|
+
RETURNING task_id`,
|
|
7424
|
+
[
|
|
7425
|
+
import_protocols4.ScheduledTaskStatus.PENDING,
|
|
7426
|
+
import_protocols4.ScheduledTaskStatus.RUNNING,
|
|
7427
|
+
olderThanMs
|
|
7428
|
+
]
|
|
7429
|
+
);
|
|
7430
|
+
return result.rowCount ?? 0;
|
|
7431
|
+
}
|
|
7313
7432
|
/**
|
|
7314
7433
|
* Delete task
|
|
7315
7434
|
*/
|
|
@@ -10080,6 +10199,52 @@ var createUserPushSubscriptions = {
|
|
|
10080
10199
|
}
|
|
10081
10200
|
};
|
|
10082
10201
|
|
|
10202
|
+
// src/migrations/distributed_lease_migration.ts
|
|
10203
|
+
var createDistributedLeases = {
|
|
10204
|
+
version: 185,
|
|
10205
|
+
name: "create_lattice_distributed_leases",
|
|
10206
|
+
up: async (client) => {
|
|
10207
|
+
await client.query(`
|
|
10208
|
+
CREATE TABLE IF NOT EXISTS lattice_leases (
|
|
10209
|
+
key TEXT PRIMARY KEY,
|
|
10210
|
+
holder TEXT NOT NULL,
|
|
10211
|
+
acquired_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
10212
|
+
expires_at TIMESTAMPTZ NOT NULL
|
|
10213
|
+
)
|
|
10214
|
+
`);
|
|
10215
|
+
await client.query(`
|
|
10216
|
+
CREATE INDEX IF NOT EXISTS idx_lattice_leases_expires
|
|
10217
|
+
ON lattice_leases(expires_at)
|
|
10218
|
+
`);
|
|
10219
|
+
},
|
|
10220
|
+
down: async (client) => {
|
|
10221
|
+
await client.query("DROP INDEX IF EXISTS idx_lattice_leases_expires");
|
|
10222
|
+
await client.query("DROP TABLE IF EXISTS lattice_leases");
|
|
10223
|
+
}
|
|
10224
|
+
};
|
|
10225
|
+
|
|
10226
|
+
// src/migrations/outbox_migration.ts
|
|
10227
|
+
var createOutboxTable = {
|
|
10228
|
+
version: 187,
|
|
10229
|
+
name: "create_lattice_outbox",
|
|
10230
|
+
up: async (client) => {
|
|
10231
|
+
await client.query(`
|
|
10232
|
+
CREATE TABLE IF NOT EXISTS lattice_outbox (
|
|
10233
|
+
id BIGSERIAL PRIMARY KEY,
|
|
10234
|
+
topic TEXT NOT NULL,
|
|
10235
|
+
payload JSONB NOT NULL,
|
|
10236
|
+
origin_replica_id TEXT NOT NULL,
|
|
10237
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
10238
|
+
)
|
|
10239
|
+
`);
|
|
10240
|
+
await client.query(`CREATE INDEX IF NOT EXISTS idx_lattice_outbox_id ON lattice_outbox(id)`);
|
|
10241
|
+
},
|
|
10242
|
+
down: async (client) => {
|
|
10243
|
+
await client.query("DROP INDEX IF EXISTS idx_lattice_outbox_id");
|
|
10244
|
+
await client.query("DROP TABLE IF EXISTS lattice_outbox");
|
|
10245
|
+
}
|
|
10246
|
+
};
|
|
10247
|
+
|
|
10083
10248
|
// src/migrations/model_provider_migrations.ts
|
|
10084
10249
|
var createModelProvidersTable = {
|
|
10085
10250
|
version: 181,
|
|
@@ -11655,9 +11820,217 @@ var PostgreSQLModelProviderStore = class {
|
|
|
11655
11820
|
}
|
|
11656
11821
|
};
|
|
11657
11822
|
|
|
11823
|
+
// src/stores/PostgreSQLLeaseStore.ts
|
|
11824
|
+
var import_pg28 = require("pg");
|
|
11825
|
+
var PostgreSQLLeaseStore = class {
|
|
11826
|
+
/** @param options An externally managed pool, or a pool config/URL. */
|
|
11827
|
+
constructor(options) {
|
|
11828
|
+
this.ownsPool = true;
|
|
11829
|
+
this.initialized = false;
|
|
11830
|
+
this.initPromise = null;
|
|
11831
|
+
if (options.pool) {
|
|
11832
|
+
this.pool = options.pool;
|
|
11833
|
+
this.ownsPool = false;
|
|
11834
|
+
this.initialized = true;
|
|
11835
|
+
return;
|
|
11836
|
+
}
|
|
11837
|
+
if (typeof options.poolConfig === "string") {
|
|
11838
|
+
this.pool = new import_pg28.Pool({ connectionString: options.poolConfig });
|
|
11839
|
+
} else if (options.poolConfig) {
|
|
11840
|
+
this.pool = new import_pg28.Pool(options.poolConfig);
|
|
11841
|
+
} else {
|
|
11842
|
+
throw new Error("Either pool or poolConfig must be provided");
|
|
11843
|
+
}
|
|
11844
|
+
this.migrationManager = new MigrationManager(this.pool);
|
|
11845
|
+
this.migrationManager.register(createDistributedLeases);
|
|
11846
|
+
if (options.autoMigrate !== false) {
|
|
11847
|
+
this.initialize().catch((error) => {
|
|
11848
|
+
console.error("Failed to initialize PostgreSQLLeaseStore:", error);
|
|
11849
|
+
throw error;
|
|
11850
|
+
});
|
|
11851
|
+
}
|
|
11852
|
+
}
|
|
11853
|
+
/** Runs pending migrations once. */
|
|
11854
|
+
async initialize() {
|
|
11855
|
+
if (this.initialized) return;
|
|
11856
|
+
if (this.initPromise) return this.initPromise;
|
|
11857
|
+
this.initPromise = (async () => {
|
|
11858
|
+
try {
|
|
11859
|
+
await this.migrationManager.migrate();
|
|
11860
|
+
this.initialized = true;
|
|
11861
|
+
} finally {
|
|
11862
|
+
this.initPromise = null;
|
|
11863
|
+
}
|
|
11864
|
+
})();
|
|
11865
|
+
return this.initPromise;
|
|
11866
|
+
}
|
|
11867
|
+
/** Ends the pool when this store owns it. */
|
|
11868
|
+
async dispose() {
|
|
11869
|
+
if (this.ownsPool) await this.pool.end();
|
|
11870
|
+
}
|
|
11871
|
+
/** Atomically acquires a free/expired key (or re-acquires the caller's own). */
|
|
11872
|
+
async tryAcquire(key, holder, ttlMs) {
|
|
11873
|
+
const result = await this.pool.query(
|
|
11874
|
+
`INSERT INTO lattice_leases (key, holder, acquired_at, expires_at)
|
|
11875
|
+
VALUES ($1, $2, now(), now() + ($3::bigint || ' milliseconds')::interval)
|
|
11876
|
+
ON CONFLICT (key) DO UPDATE
|
|
11877
|
+
SET holder = EXCLUDED.holder,
|
|
11878
|
+
acquired_at = CASE WHEN lattice_leases.holder = EXCLUDED.holder
|
|
11879
|
+
THEN lattice_leases.acquired_at ELSE now() END,
|
|
11880
|
+
expires_at = EXCLUDED.expires_at
|
|
11881
|
+
WHERE lattice_leases.expires_at <= now()
|
|
11882
|
+
OR lattice_leases.holder = EXCLUDED.holder
|
|
11883
|
+
RETURNING holder`,
|
|
11884
|
+
[key, holder, ttlMs]
|
|
11885
|
+
);
|
|
11886
|
+
return result.rowCount === 1 && result.rows[0].holder === holder;
|
|
11887
|
+
}
|
|
11888
|
+
/** Extends a lease only while `holder` still owns it. */
|
|
11889
|
+
async renew(key, holder, ttlMs) {
|
|
11890
|
+
const result = await this.pool.query(
|
|
11891
|
+
`UPDATE lattice_leases
|
|
11892
|
+
SET expires_at = now() + ($3::bigint || ' milliseconds')::interval
|
|
11893
|
+
WHERE key = $1 AND holder = $2
|
|
11894
|
+
RETURNING key`,
|
|
11895
|
+
[key, holder, ttlMs]
|
|
11896
|
+
);
|
|
11897
|
+
return (result.rowCount ?? 0) > 0;
|
|
11898
|
+
}
|
|
11899
|
+
/** Deletes a lease only when `holder` owns it. */
|
|
11900
|
+
async release(key, holder) {
|
|
11901
|
+
const result = await this.pool.query(
|
|
11902
|
+
`DELETE FROM lattice_leases WHERE key = $1 AND holder = $2 RETURNING key`,
|
|
11903
|
+
[key, holder]
|
|
11904
|
+
);
|
|
11905
|
+
return (result.rowCount ?? 0) > 0;
|
|
11906
|
+
}
|
|
11907
|
+
/** Returns the live lease, or `null` when free/expired/absent. */
|
|
11908
|
+
async get(key) {
|
|
11909
|
+
const result = await this.pool.query(
|
|
11910
|
+
`SELECT key, holder, acquired_at, expires_at
|
|
11911
|
+
FROM lattice_leases
|
|
11912
|
+
WHERE key = $1 AND expires_at > now()`,
|
|
11913
|
+
[key]
|
|
11914
|
+
);
|
|
11915
|
+
if (result.rowCount === 0) return null;
|
|
11916
|
+
const row = result.rows[0];
|
|
11917
|
+
return {
|
|
11918
|
+
key: row.key,
|
|
11919
|
+
holder: row.holder,
|
|
11920
|
+
acquiredAt: new Date(row.acquired_at).toISOString(),
|
|
11921
|
+
expiresAt: new Date(row.expires_at).toISOString()
|
|
11922
|
+
};
|
|
11923
|
+
}
|
|
11924
|
+
};
|
|
11925
|
+
|
|
11926
|
+
// src/stores/PostgreSQLOutboxStore.ts
|
|
11927
|
+
var import_pg29 = require("pg");
|
|
11928
|
+
var PostgreSQLOutboxStore = class {
|
|
11929
|
+
/** @param options An externally managed pool, or a pool config/URL. */
|
|
11930
|
+
constructor(options) {
|
|
11931
|
+
this.ownsPool = true;
|
|
11932
|
+
this.initialized = false;
|
|
11933
|
+
this.initPromise = null;
|
|
11934
|
+
if (options.pool) {
|
|
11935
|
+
this.pool = options.pool;
|
|
11936
|
+
this.ownsPool = false;
|
|
11937
|
+
this.initialized = true;
|
|
11938
|
+
return;
|
|
11939
|
+
}
|
|
11940
|
+
if (typeof options.poolConfig === "string") {
|
|
11941
|
+
this.pool = new import_pg29.Pool({ connectionString: options.poolConfig });
|
|
11942
|
+
} else if (options.poolConfig) {
|
|
11943
|
+
this.pool = new import_pg29.Pool(options.poolConfig);
|
|
11944
|
+
} else {
|
|
11945
|
+
throw new Error("Either pool or poolConfig must be provided");
|
|
11946
|
+
}
|
|
11947
|
+
this.migrationManager = new MigrationManager(this.pool);
|
|
11948
|
+
this.migrationManager.register(createOutboxTable);
|
|
11949
|
+
if (options.autoMigrate !== false) {
|
|
11950
|
+
this.initialize().catch((error) => {
|
|
11951
|
+
console.error("Failed to initialize PostgreSQLOutboxStore:", error);
|
|
11952
|
+
throw error;
|
|
11953
|
+
});
|
|
11954
|
+
}
|
|
11955
|
+
}
|
|
11956
|
+
/** Runs pending migrations once. */
|
|
11957
|
+
async initialize() {
|
|
11958
|
+
if (this.initialized) return;
|
|
11959
|
+
if (this.initPromise) return this.initPromise;
|
|
11960
|
+
this.initPromise = (async () => {
|
|
11961
|
+
try {
|
|
11962
|
+
await this.migrationManager.migrate();
|
|
11963
|
+
this.initialized = true;
|
|
11964
|
+
} finally {
|
|
11965
|
+
this.initPromise = null;
|
|
11966
|
+
}
|
|
11967
|
+
})();
|
|
11968
|
+
return this.initPromise;
|
|
11969
|
+
}
|
|
11970
|
+
/** Ends the pool when this store owns it. */
|
|
11971
|
+
async dispose() {
|
|
11972
|
+
if (this.ownsPool) await this.pool.end();
|
|
11973
|
+
}
|
|
11974
|
+
/** Appends one event and returns it with its assigned id. */
|
|
11975
|
+
async append(input) {
|
|
11976
|
+
const result = await this.pool.query(
|
|
11977
|
+
`INSERT INTO lattice_outbox (topic, payload, origin_replica_id)
|
|
11978
|
+
VALUES ($1, $2::jsonb, $3)
|
|
11979
|
+
RETURNING id, topic, payload, origin_replica_id, created_at`,
|
|
11980
|
+
[input.topic, JSON.stringify(input.payload), input.originReplicaId]
|
|
11981
|
+
);
|
|
11982
|
+
return this.rowToEvent(result.rows[0]);
|
|
11983
|
+
}
|
|
11984
|
+
/** Lists up to `limit` events with `id > cursor`, ascending. */
|
|
11985
|
+
async listAfter(cursor, limit) {
|
|
11986
|
+
const result = await this.pool.query(
|
|
11987
|
+
`SELECT id, topic, payload, origin_replica_id, created_at
|
|
11988
|
+
FROM lattice_outbox
|
|
11989
|
+
WHERE id > $1
|
|
11990
|
+
ORDER BY id ASC
|
|
11991
|
+
LIMIT $2`,
|
|
11992
|
+
[cursor === null ? 0 : Number(cursor), limit]
|
|
11993
|
+
);
|
|
11994
|
+
return result.rows.map((row) => this.rowToEvent(row));
|
|
11995
|
+
}
|
|
11996
|
+
/** Returns the newest id, or `null` when the log is empty. */
|
|
11997
|
+
async latestId() {
|
|
11998
|
+
const result = await this.pool.query(
|
|
11999
|
+
`SELECT id FROM lattice_outbox ORDER BY id DESC LIMIT 1`
|
|
12000
|
+
);
|
|
12001
|
+
if (result.rowCount === 0) return null;
|
|
12002
|
+
return String(result.rows[0].id);
|
|
12003
|
+
}
|
|
12004
|
+
/**
|
|
12005
|
+
* Deletes events older than `olderThanMs` (evaluated against the database clock).
|
|
12006
|
+
*
|
|
12007
|
+
* @param olderThanMs Age threshold in milliseconds.
|
|
12008
|
+
* @returns The number of rows deleted.
|
|
12009
|
+
*/
|
|
12010
|
+
async pruneOlderThan(olderThanMs) {
|
|
12011
|
+
const result = await this.pool.query(
|
|
12012
|
+
`DELETE FROM lattice_outbox
|
|
12013
|
+
WHERE created_at < now() - ($1::bigint || ' milliseconds')::interval
|
|
12014
|
+
RETURNING id`,
|
|
12015
|
+
[olderThanMs]
|
|
12016
|
+
);
|
|
12017
|
+
return result.rowCount ?? 0;
|
|
12018
|
+
}
|
|
12019
|
+
/** Maps a `lattice_outbox` row to an {@link OutboxEvent}. */
|
|
12020
|
+
rowToEvent(row) {
|
|
12021
|
+
return {
|
|
12022
|
+
id: String(row.id),
|
|
12023
|
+
topic: row.topic,
|
|
12024
|
+
payload: row.payload,
|
|
12025
|
+
originReplicaId: row.origin_replica_id,
|
|
12026
|
+
createdAt: new Date(row.created_at).toISOString()
|
|
12027
|
+
};
|
|
12028
|
+
}
|
|
12029
|
+
};
|
|
12030
|
+
|
|
11658
12031
|
// src/createPgStoreConfig.ts
|
|
11659
12032
|
async function createPgStoreConfig(connectionString) {
|
|
11660
|
-
const pool = new
|
|
12033
|
+
const pool = new import_pg30.Pool({ connectionString });
|
|
11661
12034
|
const mm = new MigrationManager(pool);
|
|
11662
12035
|
mm.register(createThreadsTable);
|
|
11663
12036
|
mm.register(createScheduledTasksTable);
|
|
@@ -11681,9 +12054,7 @@ async function createPgStoreConfig(connectionString) {
|
|
|
11681
12054
|
mm.register(createChannelInstallationsTable);
|
|
11682
12055
|
mm.register(addThreadTenantId);
|
|
11683
12056
|
mm.register(addProjectConfigColumn);
|
|
11684
|
-
mm
|
|
11685
|
-
mm.register(addPriorityAndCommandColumns);
|
|
11686
|
-
mm.register(alterMessageQueueIdColumn);
|
|
12057
|
+
registerThreadMessageQueueMigrations(mm);
|
|
11687
12058
|
mm.register(createWorkflowTrackingTables);
|
|
11688
12059
|
for (const m of evalMigrations) {
|
|
11689
12060
|
mm.register(m);
|
|
@@ -11693,9 +12064,7 @@ async function createPgStoreConfig(connectionString) {
|
|
|
11693
12064
|
mm.register(createDatabaseConfigsTable);
|
|
11694
12065
|
mm.register(changeAssistantPrimaryKey);
|
|
11695
12066
|
mm.register(addStepThreadId);
|
|
11696
|
-
mm.register(addCustomRunConfigColumn);
|
|
11697
12067
|
mm.register(createA2AApiKeysTable);
|
|
11698
|
-
mm.register(addWorkspaceProjectToQueue);
|
|
11699
12068
|
mm.register(addAssistantOwnerUserId);
|
|
11700
12069
|
for (const m of taskMigrations) {
|
|
11701
12070
|
mm.register(m);
|
|
@@ -11719,7 +12088,6 @@ async function createPgStoreConfig(connectionString) {
|
|
|
11719
12088
|
mm.register(createCapabilityBundlesTable);
|
|
11720
12089
|
mm.register(addTaskWorkItemPendingIndexesMigration);
|
|
11721
12090
|
mm.register(createProjectRoomTables);
|
|
11722
|
-
mm.register(addTrustedRunContextColumn);
|
|
11723
12091
|
mm.register(addProjectLifecycleEventIndex);
|
|
11724
12092
|
mm.register(addAgentWebAppsIdentity);
|
|
11725
12093
|
mm.register(createProjectRoomReadStates);
|
|
@@ -11729,6 +12097,8 @@ async function createPgStoreConfig(connectionString) {
|
|
|
11729
12097
|
mm.register(createModelProviderModelsTable);
|
|
11730
12098
|
mm.register(addModelProviderApiStyle);
|
|
11731
12099
|
mm.register(addModelProviderModelStale);
|
|
12100
|
+
mm.register(createDistributedLeases);
|
|
12101
|
+
mm.register(createOutboxTable);
|
|
11732
12102
|
await mm.migrate();
|
|
11733
12103
|
const checkpoint = import_langgraph_checkpoint_postgres.PostgresSaver.fromConnString(connectionString);
|
|
11734
12104
|
checkpoint.setup().catch((err) => {
|
|
@@ -11770,13 +12140,15 @@ async function createPgStoreConfig(connectionString) {
|
|
|
11770
12140
|
projectRoomMessage: new PostgreSQLProjectRoomMessageStore(opts),
|
|
11771
12141
|
projectRoomReadState: new PostgreSQLProjectRoomReadStateStore(opts),
|
|
11772
12142
|
userPushSubscription: new PostgreSQLUserPushSubscriptionStore(opts),
|
|
12143
|
+
lease: new PostgreSQLLeaseStore(opts),
|
|
12144
|
+
outbox: new PostgreSQLOutboxStore(opts),
|
|
11773
12145
|
vectorStoreProvider: new PGVectorStoreProvider(pool, connectionString),
|
|
11774
12146
|
checkpoint
|
|
11775
12147
|
};
|
|
11776
12148
|
}
|
|
11777
12149
|
|
|
11778
12150
|
// src/stores/PostgreSQLSkillStore.ts
|
|
11779
|
-
var
|
|
12151
|
+
var import_pg31 = require("pg");
|
|
11780
12152
|
var PostgreSQLSkillStore = class {
|
|
11781
12153
|
constructor(options) {
|
|
11782
12154
|
this.initialized = false;
|
|
@@ -11789,9 +12161,9 @@ var PostgreSQLSkillStore = class {
|
|
|
11789
12161
|
return;
|
|
11790
12162
|
}
|
|
11791
12163
|
if (typeof options.poolConfig === "string") {
|
|
11792
|
-
this.pool = new
|
|
12164
|
+
this.pool = new import_pg31.Pool({ connectionString: options.poolConfig });
|
|
11793
12165
|
} else if (options.poolConfig) {
|
|
11794
|
-
this.pool = new
|
|
12166
|
+
this.pool = new import_pg31.Pool(options.poolConfig);
|
|
11795
12167
|
} else {
|
|
11796
12168
|
throw new Error("Either pool or poolConfig must be provided");
|
|
11797
12169
|
}
|
|
@@ -12092,7 +12464,7 @@ var PostgreSQLSkillStore = class {
|
|
|
12092
12464
|
};
|
|
12093
12465
|
|
|
12094
12466
|
// src/stores/ChannelIdentityMappingStore.ts
|
|
12095
|
-
var
|
|
12467
|
+
var import_pg32 = require("pg");
|
|
12096
12468
|
var ChannelIdentityMappingStore = class {
|
|
12097
12469
|
constructor(options) {
|
|
12098
12470
|
this.initialized = false;
|
|
@@ -12104,7 +12476,7 @@ var ChannelIdentityMappingStore = class {
|
|
|
12104
12476
|
this.initialized = true;
|
|
12105
12477
|
return;
|
|
12106
12478
|
}
|
|
12107
|
-
this.pool = typeof options.poolConfig === "string" ? new
|
|
12479
|
+
this.pool = typeof options.poolConfig === "string" ? new import_pg32.Pool({ connectionString: options.poolConfig }) : options.poolConfig ? new import_pg32.Pool(options.poolConfig) : (() => {
|
|
12108
12480
|
throw new Error("Either pool or poolConfig must be provided");
|
|
12109
12481
|
})();
|
|
12110
12482
|
this.migrationManager = new MigrationManager(this.pool);
|