@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.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
-
import { Pool as
|
|
2
|
+
import { Pool as Pool33 } from "pg";
|
|
3
3
|
|
|
4
4
|
// src/createPgStoreConfig.ts
|
|
5
|
-
import { Pool as
|
|
5
|
+
import { Pool as Pool30 } from "pg";
|
|
6
6
|
|
|
7
7
|
// src/migrations/migration.ts
|
|
8
8
|
var MigrationManager = class {
|
|
@@ -5210,6 +5210,44 @@ var addTrustedRunContextColumn = {
|
|
|
5210
5210
|
}
|
|
5211
5211
|
};
|
|
5212
5212
|
|
|
5213
|
+
// src/migrations/add_queue_claim_lease.ts
|
|
5214
|
+
var addQueueClaimLease = {
|
|
5215
|
+
version: 186,
|
|
5216
|
+
name: "add_queue_claim_lease_columns",
|
|
5217
|
+
up: async (client) => {
|
|
5218
|
+
await client.query(`
|
|
5219
|
+
ALTER TABLE lattice_thread_message_queue
|
|
5220
|
+
ADD COLUMN IF NOT EXISTS locked_by VARCHAR(255),
|
|
5221
|
+
ADD COLUMN IF NOT EXISTS locked_at TIMESTAMPTZ,
|
|
5222
|
+
ADD COLUMN IF NOT EXISTS lease_expires_at TIMESTAMPTZ
|
|
5223
|
+
`);
|
|
5224
|
+
await client.query(`
|
|
5225
|
+
CREATE INDEX IF NOT EXISTS idx_lattice_thread_message_queue_lease
|
|
5226
|
+
ON lattice_thread_message_queue(status, lease_expires_at)
|
|
5227
|
+
`);
|
|
5228
|
+
},
|
|
5229
|
+
down: async (client) => {
|
|
5230
|
+
await client.query("DROP INDEX IF EXISTS idx_lattice_thread_message_queue_lease");
|
|
5231
|
+
await client.query(`
|
|
5232
|
+
ALTER TABLE lattice_thread_message_queue
|
|
5233
|
+
DROP COLUMN IF EXISTS lease_expires_at,
|
|
5234
|
+
DROP COLUMN IF EXISTS locked_at,
|
|
5235
|
+
DROP COLUMN IF EXISTS locked_by
|
|
5236
|
+
`);
|
|
5237
|
+
}
|
|
5238
|
+
};
|
|
5239
|
+
|
|
5240
|
+
// src/migrations/queue_migrations.ts
|
|
5241
|
+
function registerThreadMessageQueueMigrations(manager) {
|
|
5242
|
+
manager.register(createThreadMessageQueueTable);
|
|
5243
|
+
manager.register(addPriorityAndCommandColumns);
|
|
5244
|
+
manager.register(alterMessageQueueIdColumn);
|
|
5245
|
+
manager.register(addCustomRunConfigColumn);
|
|
5246
|
+
manager.register(addWorkspaceProjectToQueue);
|
|
5247
|
+
manager.register(addTrustedRunContextColumn);
|
|
5248
|
+
manager.register(addQueueClaimLease);
|
|
5249
|
+
}
|
|
5250
|
+
|
|
5213
5251
|
// src/stores/ThreadMessageQueueStore.ts
|
|
5214
5252
|
var ThreadMessageQueueStore = class {
|
|
5215
5253
|
constructor(options) {
|
|
@@ -5230,12 +5268,7 @@ var ThreadMessageQueueStore = class {
|
|
|
5230
5268
|
throw new Error("Either pool or poolConfig must be provided");
|
|
5231
5269
|
}
|
|
5232
5270
|
this.migrationManager = new MigrationManager(this.pool);
|
|
5233
|
-
this.migrationManager
|
|
5234
|
-
this.migrationManager.register(addPriorityAndCommandColumns);
|
|
5235
|
-
this.migrationManager.register(addCustomRunConfigColumn);
|
|
5236
|
-
this.migrationManager.register(alterMessageQueueIdColumn);
|
|
5237
|
-
this.migrationManager.register(addWorkspaceProjectToQueue);
|
|
5238
|
-
this.migrationManager.register(addTrustedRunContextColumn);
|
|
5271
|
+
registerThreadMessageQueueMigrations(this.migrationManager);
|
|
5239
5272
|
if (options.autoMigrate !== false) {
|
|
5240
5273
|
this.initialize().catch((error) => {
|
|
5241
5274
|
console.error("Failed to initialize ThreadMessageQueueStore:", error);
|
|
@@ -5360,6 +5393,44 @@ var ThreadMessageQueueStore = class {
|
|
|
5360
5393
|
);
|
|
5361
5394
|
return result.rows.map((row) => this.rowToMessage(row));
|
|
5362
5395
|
}
|
|
5396
|
+
/**
|
|
5397
|
+
* Atomically claims pending messages for a replica, stamping a lease.
|
|
5398
|
+
*/
|
|
5399
|
+
async claimPendingMessages(threadId, scope, options) {
|
|
5400
|
+
const filter = scopeClause(scope, 4);
|
|
5401
|
+
const limitClause = options.limit === void 0 ? "" : ` LIMIT ${Math.max(0, Math.floor(options.limit))}`;
|
|
5402
|
+
const result = await this.pool.query(
|
|
5403
|
+
`UPDATE lattice_thread_message_queue q
|
|
5404
|
+
SET status = 'processing',
|
|
5405
|
+
locked_by = $2,
|
|
5406
|
+
locked_at = now(),
|
|
5407
|
+
lease_expires_at = now() + ($3::bigint || ' milliseconds')::interval
|
|
5408
|
+
WHERE q.id IN (
|
|
5409
|
+
SELECT id FROM lattice_thread_message_queue
|
|
5410
|
+
WHERE thread_id = $1 AND status = 'pending'${filter.sql}
|
|
5411
|
+
ORDER BY priority DESC, sequence_order ASC${limitClause}
|
|
5412
|
+
FOR UPDATE SKIP LOCKED
|
|
5413
|
+
)
|
|
5414
|
+
RETURNING q.*`,
|
|
5415
|
+
[threadId, options.owner, options.ttlMs, ...filter.params]
|
|
5416
|
+
);
|
|
5417
|
+
return result.rows.map((row) => this.rowToMessage(row));
|
|
5418
|
+
}
|
|
5419
|
+
/**
|
|
5420
|
+
* Returns expired processing rows to pending and clears their lease.
|
|
5421
|
+
*/
|
|
5422
|
+
async reclaimExpiredProcessing(threadId, scope) {
|
|
5423
|
+
const filter = scopeClause(scope, 2);
|
|
5424
|
+
const result = await this.pool.query(
|
|
5425
|
+
`UPDATE lattice_thread_message_queue
|
|
5426
|
+
SET status = 'pending', locked_by = NULL, locked_at = NULL, lease_expires_at = NULL
|
|
5427
|
+
WHERE thread_id = $1 AND status = 'processing'
|
|
5428
|
+
AND lease_expires_at IS NOT NULL AND lease_expires_at <= now()${filter.sql}
|
|
5429
|
+
RETURNING id`,
|
|
5430
|
+
[threadId, ...filter.params]
|
|
5431
|
+
);
|
|
5432
|
+
return result.rowCount ?? 0;
|
|
5433
|
+
}
|
|
5363
5434
|
/**
|
|
5364
5435
|
* Get processing messages for a thread
|
|
5365
5436
|
*/
|
|
@@ -5450,7 +5521,7 @@ var ThreadMessageQueueStore = class {
|
|
|
5450
5521
|
const filter = scopeClause(scope, 2);
|
|
5451
5522
|
const result = await this.pool.query(
|
|
5452
5523
|
`UPDATE lattice_thread_message_queue
|
|
5453
|
-
SET status = 'pending'
|
|
5524
|
+
SET status = 'pending', locked_by = NULL, locked_at = NULL, lease_expires_at = NULL
|
|
5454
5525
|
WHERE thread_id = $1 AND status = 'processing'${filter.sql}
|
|
5455
5526
|
RETURNING id`,
|
|
5456
5527
|
[threadId, ...filter.params]
|
|
@@ -5460,7 +5531,7 @@ var ThreadMessageQueueStore = class {
|
|
|
5460
5531
|
rowToMessage(row) {
|
|
5461
5532
|
return {
|
|
5462
5533
|
id: row.id,
|
|
5463
|
-
content:
|
|
5534
|
+
content: row.message_content,
|
|
5464
5535
|
type: row.message_type,
|
|
5465
5536
|
sequence: row.sequence_order,
|
|
5466
5537
|
createdAt: new Date(row.created_at),
|
|
@@ -7191,6 +7262,54 @@ var PostgreSQLScheduleStorage = class {
|
|
|
7191
7262
|
values
|
|
7192
7263
|
);
|
|
7193
7264
|
}
|
|
7265
|
+
/**
|
|
7266
|
+
* Atomically claims one due fire by transitioning the task `pending -> running`.
|
|
7267
|
+
*
|
|
7268
|
+
* Only one caller wins the `UPDATE ... WHERE status = 'pending'`; all others
|
|
7269
|
+
* observe zero affected rows and must skip execution.
|
|
7270
|
+
*
|
|
7271
|
+
* @param taskId Task to claim.
|
|
7272
|
+
* @returns True when this caller won the claim (task was `pending`); false if
|
|
7273
|
+
* another replica already claimed it or it is not pending.
|
|
7274
|
+
*/
|
|
7275
|
+
async claimScheduledRun(taskId) {
|
|
7276
|
+
await this.ensureInitialized();
|
|
7277
|
+
const result = await this.pool.query(
|
|
7278
|
+
`UPDATE lattice_scheduled_tasks
|
|
7279
|
+
SET status = $1, updated_at = $2
|
|
7280
|
+
WHERE task_id = $3 AND status = $4
|
|
7281
|
+
RETURNING task_id`,
|
|
7282
|
+
[
|
|
7283
|
+
ScheduledTaskStatus.RUNNING,
|
|
7284
|
+
/* @__PURE__ */ new Date(),
|
|
7285
|
+
taskId,
|
|
7286
|
+
ScheduledTaskStatus.PENDING
|
|
7287
|
+
]
|
|
7288
|
+
);
|
|
7289
|
+
return (result.rowCount ?? 0) > 0;
|
|
7290
|
+
}
|
|
7291
|
+
/**
|
|
7292
|
+
* Requeues `running` tasks whose `updated_at` is older than `olderThanMs` back
|
|
7293
|
+
* to `pending` (recovery after a crash that left a claim unsettled).
|
|
7294
|
+
*
|
|
7295
|
+
* @param olderThanMs Age threshold in milliseconds.
|
|
7296
|
+
* @returns The number of tasks requeued.
|
|
7297
|
+
*/
|
|
7298
|
+
async requeueStaleRunning(olderThanMs) {
|
|
7299
|
+
await this.ensureInitialized();
|
|
7300
|
+
const result = await this.pool.query(
|
|
7301
|
+
`UPDATE lattice_scheduled_tasks
|
|
7302
|
+
SET status = $1, updated_at = now()
|
|
7303
|
+
WHERE status = $2 AND updated_at < now() - ($3::bigint || ' milliseconds')::interval
|
|
7304
|
+
RETURNING task_id`,
|
|
7305
|
+
[
|
|
7306
|
+
ScheduledTaskStatus.PENDING,
|
|
7307
|
+
ScheduledTaskStatus.RUNNING,
|
|
7308
|
+
olderThanMs
|
|
7309
|
+
]
|
|
7310
|
+
);
|
|
7311
|
+
return result.rowCount ?? 0;
|
|
7312
|
+
}
|
|
7194
7313
|
/**
|
|
7195
7314
|
* Delete task
|
|
7196
7315
|
*/
|
|
@@ -9961,6 +10080,52 @@ var createUserPushSubscriptions = {
|
|
|
9961
10080
|
}
|
|
9962
10081
|
};
|
|
9963
10082
|
|
|
10083
|
+
// src/migrations/distributed_lease_migration.ts
|
|
10084
|
+
var createDistributedLeases = {
|
|
10085
|
+
version: 185,
|
|
10086
|
+
name: "create_lattice_distributed_leases",
|
|
10087
|
+
up: async (client) => {
|
|
10088
|
+
await client.query(`
|
|
10089
|
+
CREATE TABLE IF NOT EXISTS lattice_leases (
|
|
10090
|
+
key TEXT PRIMARY KEY,
|
|
10091
|
+
holder TEXT NOT NULL,
|
|
10092
|
+
acquired_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
10093
|
+
expires_at TIMESTAMPTZ NOT NULL
|
|
10094
|
+
)
|
|
10095
|
+
`);
|
|
10096
|
+
await client.query(`
|
|
10097
|
+
CREATE INDEX IF NOT EXISTS idx_lattice_leases_expires
|
|
10098
|
+
ON lattice_leases(expires_at)
|
|
10099
|
+
`);
|
|
10100
|
+
},
|
|
10101
|
+
down: async (client) => {
|
|
10102
|
+
await client.query("DROP INDEX IF EXISTS idx_lattice_leases_expires");
|
|
10103
|
+
await client.query("DROP TABLE IF EXISTS lattice_leases");
|
|
10104
|
+
}
|
|
10105
|
+
};
|
|
10106
|
+
|
|
10107
|
+
// src/migrations/outbox_migration.ts
|
|
10108
|
+
var createOutboxTable = {
|
|
10109
|
+
version: 187,
|
|
10110
|
+
name: "create_lattice_outbox",
|
|
10111
|
+
up: async (client) => {
|
|
10112
|
+
await client.query(`
|
|
10113
|
+
CREATE TABLE IF NOT EXISTS lattice_outbox (
|
|
10114
|
+
id BIGSERIAL PRIMARY KEY,
|
|
10115
|
+
topic TEXT NOT NULL,
|
|
10116
|
+
payload JSONB NOT NULL,
|
|
10117
|
+
origin_replica_id TEXT NOT NULL,
|
|
10118
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
10119
|
+
)
|
|
10120
|
+
`);
|
|
10121
|
+
await client.query(`CREATE INDEX IF NOT EXISTS idx_lattice_outbox_id ON lattice_outbox(id)`);
|
|
10122
|
+
},
|
|
10123
|
+
down: async (client) => {
|
|
10124
|
+
await client.query("DROP INDEX IF EXISTS idx_lattice_outbox_id");
|
|
10125
|
+
await client.query("DROP TABLE IF EXISTS lattice_outbox");
|
|
10126
|
+
}
|
|
10127
|
+
};
|
|
10128
|
+
|
|
9964
10129
|
// src/migrations/model_provider_migrations.ts
|
|
9965
10130
|
var createModelProvidersTable = {
|
|
9966
10131
|
version: 181,
|
|
@@ -11536,9 +11701,217 @@ var PostgreSQLModelProviderStore = class {
|
|
|
11536
11701
|
}
|
|
11537
11702
|
};
|
|
11538
11703
|
|
|
11704
|
+
// src/stores/PostgreSQLLeaseStore.ts
|
|
11705
|
+
import { Pool as Pool28 } from "pg";
|
|
11706
|
+
var PostgreSQLLeaseStore = class {
|
|
11707
|
+
/** @param options An externally managed pool, or a pool config/URL. */
|
|
11708
|
+
constructor(options) {
|
|
11709
|
+
this.ownsPool = true;
|
|
11710
|
+
this.initialized = false;
|
|
11711
|
+
this.initPromise = null;
|
|
11712
|
+
if (options.pool) {
|
|
11713
|
+
this.pool = options.pool;
|
|
11714
|
+
this.ownsPool = false;
|
|
11715
|
+
this.initialized = true;
|
|
11716
|
+
return;
|
|
11717
|
+
}
|
|
11718
|
+
if (typeof options.poolConfig === "string") {
|
|
11719
|
+
this.pool = new Pool28({ connectionString: options.poolConfig });
|
|
11720
|
+
} else if (options.poolConfig) {
|
|
11721
|
+
this.pool = new Pool28(options.poolConfig);
|
|
11722
|
+
} else {
|
|
11723
|
+
throw new Error("Either pool or poolConfig must be provided");
|
|
11724
|
+
}
|
|
11725
|
+
this.migrationManager = new MigrationManager(this.pool);
|
|
11726
|
+
this.migrationManager.register(createDistributedLeases);
|
|
11727
|
+
if (options.autoMigrate !== false) {
|
|
11728
|
+
this.initialize().catch((error) => {
|
|
11729
|
+
console.error("Failed to initialize PostgreSQLLeaseStore:", error);
|
|
11730
|
+
throw error;
|
|
11731
|
+
});
|
|
11732
|
+
}
|
|
11733
|
+
}
|
|
11734
|
+
/** Runs pending migrations once. */
|
|
11735
|
+
async initialize() {
|
|
11736
|
+
if (this.initialized) return;
|
|
11737
|
+
if (this.initPromise) return this.initPromise;
|
|
11738
|
+
this.initPromise = (async () => {
|
|
11739
|
+
try {
|
|
11740
|
+
await this.migrationManager.migrate();
|
|
11741
|
+
this.initialized = true;
|
|
11742
|
+
} finally {
|
|
11743
|
+
this.initPromise = null;
|
|
11744
|
+
}
|
|
11745
|
+
})();
|
|
11746
|
+
return this.initPromise;
|
|
11747
|
+
}
|
|
11748
|
+
/** Ends the pool when this store owns it. */
|
|
11749
|
+
async dispose() {
|
|
11750
|
+
if (this.ownsPool) await this.pool.end();
|
|
11751
|
+
}
|
|
11752
|
+
/** Atomically acquires a free/expired key (or re-acquires the caller's own). */
|
|
11753
|
+
async tryAcquire(key, holder, ttlMs) {
|
|
11754
|
+
const result = await this.pool.query(
|
|
11755
|
+
`INSERT INTO lattice_leases (key, holder, acquired_at, expires_at)
|
|
11756
|
+
VALUES ($1, $2, now(), now() + ($3::bigint || ' milliseconds')::interval)
|
|
11757
|
+
ON CONFLICT (key) DO UPDATE
|
|
11758
|
+
SET holder = EXCLUDED.holder,
|
|
11759
|
+
acquired_at = CASE WHEN lattice_leases.holder = EXCLUDED.holder
|
|
11760
|
+
THEN lattice_leases.acquired_at ELSE now() END,
|
|
11761
|
+
expires_at = EXCLUDED.expires_at
|
|
11762
|
+
WHERE lattice_leases.expires_at <= now()
|
|
11763
|
+
OR lattice_leases.holder = EXCLUDED.holder
|
|
11764
|
+
RETURNING holder`,
|
|
11765
|
+
[key, holder, ttlMs]
|
|
11766
|
+
);
|
|
11767
|
+
return result.rowCount === 1 && result.rows[0].holder === holder;
|
|
11768
|
+
}
|
|
11769
|
+
/** Extends a lease only while `holder` still owns it. */
|
|
11770
|
+
async renew(key, holder, ttlMs) {
|
|
11771
|
+
const result = await this.pool.query(
|
|
11772
|
+
`UPDATE lattice_leases
|
|
11773
|
+
SET expires_at = now() + ($3::bigint || ' milliseconds')::interval
|
|
11774
|
+
WHERE key = $1 AND holder = $2
|
|
11775
|
+
RETURNING key`,
|
|
11776
|
+
[key, holder, ttlMs]
|
|
11777
|
+
);
|
|
11778
|
+
return (result.rowCount ?? 0) > 0;
|
|
11779
|
+
}
|
|
11780
|
+
/** Deletes a lease only when `holder` owns it. */
|
|
11781
|
+
async release(key, holder) {
|
|
11782
|
+
const result = await this.pool.query(
|
|
11783
|
+
`DELETE FROM lattice_leases WHERE key = $1 AND holder = $2 RETURNING key`,
|
|
11784
|
+
[key, holder]
|
|
11785
|
+
);
|
|
11786
|
+
return (result.rowCount ?? 0) > 0;
|
|
11787
|
+
}
|
|
11788
|
+
/** Returns the live lease, or `null` when free/expired/absent. */
|
|
11789
|
+
async get(key) {
|
|
11790
|
+
const result = await this.pool.query(
|
|
11791
|
+
`SELECT key, holder, acquired_at, expires_at
|
|
11792
|
+
FROM lattice_leases
|
|
11793
|
+
WHERE key = $1 AND expires_at > now()`,
|
|
11794
|
+
[key]
|
|
11795
|
+
);
|
|
11796
|
+
if (result.rowCount === 0) return null;
|
|
11797
|
+
const row = result.rows[0];
|
|
11798
|
+
return {
|
|
11799
|
+
key: row.key,
|
|
11800
|
+
holder: row.holder,
|
|
11801
|
+
acquiredAt: new Date(row.acquired_at).toISOString(),
|
|
11802
|
+
expiresAt: new Date(row.expires_at).toISOString()
|
|
11803
|
+
};
|
|
11804
|
+
}
|
|
11805
|
+
};
|
|
11806
|
+
|
|
11807
|
+
// src/stores/PostgreSQLOutboxStore.ts
|
|
11808
|
+
import { Pool as Pool29 } from "pg";
|
|
11809
|
+
var PostgreSQLOutboxStore = class {
|
|
11810
|
+
/** @param options An externally managed pool, or a pool config/URL. */
|
|
11811
|
+
constructor(options) {
|
|
11812
|
+
this.ownsPool = true;
|
|
11813
|
+
this.initialized = false;
|
|
11814
|
+
this.initPromise = null;
|
|
11815
|
+
if (options.pool) {
|
|
11816
|
+
this.pool = options.pool;
|
|
11817
|
+
this.ownsPool = false;
|
|
11818
|
+
this.initialized = true;
|
|
11819
|
+
return;
|
|
11820
|
+
}
|
|
11821
|
+
if (typeof options.poolConfig === "string") {
|
|
11822
|
+
this.pool = new Pool29({ connectionString: options.poolConfig });
|
|
11823
|
+
} else if (options.poolConfig) {
|
|
11824
|
+
this.pool = new Pool29(options.poolConfig);
|
|
11825
|
+
} else {
|
|
11826
|
+
throw new Error("Either pool or poolConfig must be provided");
|
|
11827
|
+
}
|
|
11828
|
+
this.migrationManager = new MigrationManager(this.pool);
|
|
11829
|
+
this.migrationManager.register(createOutboxTable);
|
|
11830
|
+
if (options.autoMigrate !== false) {
|
|
11831
|
+
this.initialize().catch((error) => {
|
|
11832
|
+
console.error("Failed to initialize PostgreSQLOutboxStore:", error);
|
|
11833
|
+
throw error;
|
|
11834
|
+
});
|
|
11835
|
+
}
|
|
11836
|
+
}
|
|
11837
|
+
/** Runs pending migrations once. */
|
|
11838
|
+
async initialize() {
|
|
11839
|
+
if (this.initialized) return;
|
|
11840
|
+
if (this.initPromise) return this.initPromise;
|
|
11841
|
+
this.initPromise = (async () => {
|
|
11842
|
+
try {
|
|
11843
|
+
await this.migrationManager.migrate();
|
|
11844
|
+
this.initialized = true;
|
|
11845
|
+
} finally {
|
|
11846
|
+
this.initPromise = null;
|
|
11847
|
+
}
|
|
11848
|
+
})();
|
|
11849
|
+
return this.initPromise;
|
|
11850
|
+
}
|
|
11851
|
+
/** Ends the pool when this store owns it. */
|
|
11852
|
+
async dispose() {
|
|
11853
|
+
if (this.ownsPool) await this.pool.end();
|
|
11854
|
+
}
|
|
11855
|
+
/** Appends one event and returns it with its assigned id. */
|
|
11856
|
+
async append(input) {
|
|
11857
|
+
const result = await this.pool.query(
|
|
11858
|
+
`INSERT INTO lattice_outbox (topic, payload, origin_replica_id)
|
|
11859
|
+
VALUES ($1, $2::jsonb, $3)
|
|
11860
|
+
RETURNING id, topic, payload, origin_replica_id, created_at`,
|
|
11861
|
+
[input.topic, JSON.stringify(input.payload), input.originReplicaId]
|
|
11862
|
+
);
|
|
11863
|
+
return this.rowToEvent(result.rows[0]);
|
|
11864
|
+
}
|
|
11865
|
+
/** Lists up to `limit` events with `id > cursor`, ascending. */
|
|
11866
|
+
async listAfter(cursor, limit) {
|
|
11867
|
+
const result = await this.pool.query(
|
|
11868
|
+
`SELECT id, topic, payload, origin_replica_id, created_at
|
|
11869
|
+
FROM lattice_outbox
|
|
11870
|
+
WHERE id > $1
|
|
11871
|
+
ORDER BY id ASC
|
|
11872
|
+
LIMIT $2`,
|
|
11873
|
+
[cursor === null ? 0 : Number(cursor), limit]
|
|
11874
|
+
);
|
|
11875
|
+
return result.rows.map((row) => this.rowToEvent(row));
|
|
11876
|
+
}
|
|
11877
|
+
/** Returns the newest id, or `null` when the log is empty. */
|
|
11878
|
+
async latestId() {
|
|
11879
|
+
const result = await this.pool.query(
|
|
11880
|
+
`SELECT id FROM lattice_outbox ORDER BY id DESC LIMIT 1`
|
|
11881
|
+
);
|
|
11882
|
+
if (result.rowCount === 0) return null;
|
|
11883
|
+
return String(result.rows[0].id);
|
|
11884
|
+
}
|
|
11885
|
+
/**
|
|
11886
|
+
* Deletes events older than `olderThanMs` (evaluated against the database clock).
|
|
11887
|
+
*
|
|
11888
|
+
* @param olderThanMs Age threshold in milliseconds.
|
|
11889
|
+
* @returns The number of rows deleted.
|
|
11890
|
+
*/
|
|
11891
|
+
async pruneOlderThan(olderThanMs) {
|
|
11892
|
+
const result = await this.pool.query(
|
|
11893
|
+
`DELETE FROM lattice_outbox
|
|
11894
|
+
WHERE created_at < now() - ($1::bigint || ' milliseconds')::interval
|
|
11895
|
+
RETURNING id`,
|
|
11896
|
+
[olderThanMs]
|
|
11897
|
+
);
|
|
11898
|
+
return result.rowCount ?? 0;
|
|
11899
|
+
}
|
|
11900
|
+
/** Maps a `lattice_outbox` row to an {@link OutboxEvent}. */
|
|
11901
|
+
rowToEvent(row) {
|
|
11902
|
+
return {
|
|
11903
|
+
id: String(row.id),
|
|
11904
|
+
topic: row.topic,
|
|
11905
|
+
payload: row.payload,
|
|
11906
|
+
originReplicaId: row.origin_replica_id,
|
|
11907
|
+
createdAt: new Date(row.created_at).toISOString()
|
|
11908
|
+
};
|
|
11909
|
+
}
|
|
11910
|
+
};
|
|
11911
|
+
|
|
11539
11912
|
// src/createPgStoreConfig.ts
|
|
11540
11913
|
async function createPgStoreConfig(connectionString) {
|
|
11541
|
-
const pool = new
|
|
11914
|
+
const pool = new Pool30({ connectionString });
|
|
11542
11915
|
const mm = new MigrationManager(pool);
|
|
11543
11916
|
mm.register(createThreadsTable);
|
|
11544
11917
|
mm.register(createScheduledTasksTable);
|
|
@@ -11562,9 +11935,7 @@ async function createPgStoreConfig(connectionString) {
|
|
|
11562
11935
|
mm.register(createChannelInstallationsTable);
|
|
11563
11936
|
mm.register(addThreadTenantId);
|
|
11564
11937
|
mm.register(addProjectConfigColumn);
|
|
11565
|
-
mm
|
|
11566
|
-
mm.register(addPriorityAndCommandColumns);
|
|
11567
|
-
mm.register(alterMessageQueueIdColumn);
|
|
11938
|
+
registerThreadMessageQueueMigrations(mm);
|
|
11568
11939
|
mm.register(createWorkflowTrackingTables);
|
|
11569
11940
|
for (const m of evalMigrations) {
|
|
11570
11941
|
mm.register(m);
|
|
@@ -11574,9 +11945,7 @@ async function createPgStoreConfig(connectionString) {
|
|
|
11574
11945
|
mm.register(createDatabaseConfigsTable);
|
|
11575
11946
|
mm.register(changeAssistantPrimaryKey);
|
|
11576
11947
|
mm.register(addStepThreadId);
|
|
11577
|
-
mm.register(addCustomRunConfigColumn);
|
|
11578
11948
|
mm.register(createA2AApiKeysTable);
|
|
11579
|
-
mm.register(addWorkspaceProjectToQueue);
|
|
11580
11949
|
mm.register(addAssistantOwnerUserId);
|
|
11581
11950
|
for (const m of taskMigrations) {
|
|
11582
11951
|
mm.register(m);
|
|
@@ -11600,7 +11969,6 @@ async function createPgStoreConfig(connectionString) {
|
|
|
11600
11969
|
mm.register(createCapabilityBundlesTable);
|
|
11601
11970
|
mm.register(addTaskWorkItemPendingIndexesMigration);
|
|
11602
11971
|
mm.register(createProjectRoomTables);
|
|
11603
|
-
mm.register(addTrustedRunContextColumn);
|
|
11604
11972
|
mm.register(addProjectLifecycleEventIndex);
|
|
11605
11973
|
mm.register(addAgentWebAppsIdentity);
|
|
11606
11974
|
mm.register(createProjectRoomReadStates);
|
|
@@ -11610,6 +11978,8 @@ async function createPgStoreConfig(connectionString) {
|
|
|
11610
11978
|
mm.register(createModelProviderModelsTable);
|
|
11611
11979
|
mm.register(addModelProviderApiStyle);
|
|
11612
11980
|
mm.register(addModelProviderModelStale);
|
|
11981
|
+
mm.register(createDistributedLeases);
|
|
11982
|
+
mm.register(createOutboxTable);
|
|
11613
11983
|
await mm.migrate();
|
|
11614
11984
|
const checkpoint = PostgresSaver.fromConnString(connectionString);
|
|
11615
11985
|
checkpoint.setup().catch((err) => {
|
|
@@ -11651,13 +12021,15 @@ async function createPgStoreConfig(connectionString) {
|
|
|
11651
12021
|
projectRoomMessage: new PostgreSQLProjectRoomMessageStore(opts),
|
|
11652
12022
|
projectRoomReadState: new PostgreSQLProjectRoomReadStateStore(opts),
|
|
11653
12023
|
userPushSubscription: new PostgreSQLUserPushSubscriptionStore(opts),
|
|
12024
|
+
lease: new PostgreSQLLeaseStore(opts),
|
|
12025
|
+
outbox: new PostgreSQLOutboxStore(opts),
|
|
11654
12026
|
vectorStoreProvider: new PGVectorStoreProvider(pool, connectionString),
|
|
11655
12027
|
checkpoint
|
|
11656
12028
|
};
|
|
11657
12029
|
}
|
|
11658
12030
|
|
|
11659
12031
|
// src/stores/PostgreSQLSkillStore.ts
|
|
11660
|
-
import { Pool as
|
|
12032
|
+
import { Pool as Pool31 } from "pg";
|
|
11661
12033
|
var PostgreSQLSkillStore = class {
|
|
11662
12034
|
constructor(options) {
|
|
11663
12035
|
this.initialized = false;
|
|
@@ -11670,9 +12042,9 @@ var PostgreSQLSkillStore = class {
|
|
|
11670
12042
|
return;
|
|
11671
12043
|
}
|
|
11672
12044
|
if (typeof options.poolConfig === "string") {
|
|
11673
|
-
this.pool = new
|
|
12045
|
+
this.pool = new Pool31({ connectionString: options.poolConfig });
|
|
11674
12046
|
} else if (options.poolConfig) {
|
|
11675
|
-
this.pool = new
|
|
12047
|
+
this.pool = new Pool31(options.poolConfig);
|
|
11676
12048
|
} else {
|
|
11677
12049
|
throw new Error("Either pool or poolConfig must be provided");
|
|
11678
12050
|
}
|
|
@@ -11973,7 +12345,7 @@ var PostgreSQLSkillStore = class {
|
|
|
11973
12345
|
};
|
|
11974
12346
|
|
|
11975
12347
|
// src/stores/ChannelIdentityMappingStore.ts
|
|
11976
|
-
import { Pool as
|
|
12348
|
+
import { Pool as Pool32 } from "pg";
|
|
11977
12349
|
var ChannelIdentityMappingStore = class {
|
|
11978
12350
|
constructor(options) {
|
|
11979
12351
|
this.initialized = false;
|
|
@@ -11985,7 +12357,7 @@ var ChannelIdentityMappingStore = class {
|
|
|
11985
12357
|
this.initialized = true;
|
|
11986
12358
|
return;
|
|
11987
12359
|
}
|
|
11988
|
-
this.pool = typeof options.poolConfig === "string" ? new
|
|
12360
|
+
this.pool = typeof options.poolConfig === "string" ? new Pool32({ connectionString: options.poolConfig }) : options.poolConfig ? new Pool32(options.poolConfig) : (() => {
|
|
11989
12361
|
throw new Error("Either pool or poolConfig must be provided");
|
|
11990
12362
|
})();
|
|
11991
12363
|
this.migrationManager = new MigrationManager(this.pool);
|
|
@@ -12211,7 +12583,7 @@ export {
|
|
|
12211
12583
|
MenuStore,
|
|
12212
12584
|
MigrationManager,
|
|
12213
12585
|
PGVectorStoreProvider,
|
|
12214
|
-
|
|
12586
|
+
Pool33 as Pool,
|
|
12215
12587
|
PostgreSQLA2AApiKeyStore,
|
|
12216
12588
|
PostgreSQLAgentWebAppStore,
|
|
12217
12589
|
PostgreSQLAssistantStore,
|