@axiom-lattice/pg-stores 1.0.65 → 1.0.67

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.
@@ -102,11 +102,9 @@ async function examplePostgreSQL() {
102
102
  import { sqlDatabaseManager } from '@axiom-lattice/core';
103
103
 
104
104
  async function exampleWithSqlManager() {
105
- const store = await storeLatticeManager.getStoreLattice('default', 'database').store;
106
105
  const tenantId = 'tenant-123';
107
106
 
108
- // 设置配置存储以便按需加载
109
- sqlDatabaseManager.setConfigStore(store);
107
+ // Store is auto-read from StoreLatticeManager by SqlDatabaseManager — no setConfigStore needed
110
108
 
111
109
  // 现在可以使用注册的数据库(会自动从 store 加载)
112
110
  const db = await sqlDatabaseManager.getDatabase(tenantId, 'main-db');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@axiom-lattice/pg-stores",
3
- "version": "1.0.65",
3
+ "version": "1.0.67",
4
4
  "description": "PG stores implementation for Axiom Lattice framework",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -22,8 +22,8 @@
22
22
  "dependencies": {
23
23
  "pg": "^8.16.3",
24
24
  "uuid": "^9.0.1",
25
- "@axiom-lattice/core": "2.1.75",
26
- "@axiom-lattice/protocols": "2.1.38"
25
+ "@axiom-lattice/core": "2.1.76",
26
+ "@axiom-lattice/protocols": "2.1.39"
27
27
  },
28
28
  "devDependencies": {
29
29
  "@types/node": "^20.11.24",
@@ -19,21 +19,17 @@ jest.mock('pg', () => ({
19
19
 
20
20
  describe('ThreadMessageQueueStore', () => {
21
21
  let store: ThreadMessageQueueStore;
22
- const mockPool = {
23
- query: mockQuery,
24
- connect: mockConnect,
25
- };
26
22
 
27
23
  const mockThreadId = 'test-thread';
28
24
  const mockTenantId = 'test-tenant';
29
25
  const mockAssistantId = 'test-assistant';
30
26
 
31
27
  beforeEach(() => {
32
- // Reset singleton
33
- (ThreadMessageQueueStore as any)._instance = undefined;
34
- store = ThreadMessageQueueStore.getInstance();
35
- store.initialize(mockPool as any);
36
-
28
+ store = new ThreadMessageQueueStore({
29
+ poolConfig: { connectionString: "mock" },
30
+ autoMigrate: false,
31
+ });
32
+
37
33
  jest.clearAllMocks();
38
34
  });
39
35
 
@@ -333,19 +329,6 @@ describe('ThreadMessageQueueStore', () => {
333
329
  });
334
330
  });
335
331
 
336
- describe('markCompleted', () => {
337
- it('should mark message as completed', async () => {
338
- mockQuery.mockResolvedValueOnce({ rowCount: 1 });
339
-
340
- await store.markCompleted('msg-1');
341
-
342
- expect(mockQuery).toHaveBeenCalledWith(
343
- expect.stringContaining("UPDATE thread_message_queue"),
344
- ['msg-1']
345
- );
346
- });
347
- });
348
-
349
332
  describe('clearMessages', () => {
350
333
  it('should clear all messages for thread', async () => {
351
334
  mockQuery.mockResolvedValueOnce({ rowCount: 5 });
@@ -359,35 +342,4 @@ describe('ThreadMessageQueueStore', () => {
359
342
  });
360
343
  });
361
344
 
362
- describe('clearCompletedMessages', () => {
363
- it('should remove completed messages for thread', async () => {
364
- mockQuery.mockResolvedValueOnce({ rowCount: 3 });
365
-
366
- await store.clearCompletedMessages(mockThreadId);
367
-
368
- expect(mockQuery).toHaveBeenCalledWith(
369
- expect.stringContaining("DELETE FROM thread_message_queue"),
370
- [mockThreadId]
371
- );
372
- });
373
- });
374
-
375
- describe('singleton pattern', () => {
376
- it('should return same instance', () => {
377
- const instance1 = ThreadMessageQueueStore.getInstance();
378
- const instance2 = ThreadMessageQueueStore.getInstance();
379
-
380
- expect(instance1).toBe(instance2);
381
- });
382
-
383
- it('should throw error if not initialized', () => {
384
- // Reset instance
385
- (ThreadMessageQueueStore as any)._instance = undefined;
386
- const newStore = ThreadMessageQueueStore.getInstance();
387
-
388
- expect(() => {
389
- newStore.getQueueSize('thread-1');
390
- }).toThrow('not initialized');
391
- });
392
- });
393
345
  });
@@ -0,0 +1,47 @@
1
+ /**
2
+ * createPgStoreConfig
3
+ *
4
+ * Creates a map of all PG store instances from a single connection string,
5
+ * ready to pass directly to configureStores().
6
+ */
7
+
8
+ import { PostgreSQLThreadStore } from "./stores/PostgreSQLThreadStore";
9
+ import { PostgreSQLAssistantStore } from "./stores/PostgreSQLAssistantStore";
10
+ import { PostgreSQLDatabaseConfigStore } from "./stores/PostgreSQLDatabaseConfigStore";
11
+ import { PostgreSQLMetricsServerConfigStore } from "./stores/PostgreSQLMetricsServerConfigStore";
12
+ import { PostgreSQLMcpServerConfigStore } from "./stores/PostgreSQLMcpServerConfigStore";
13
+ import { PostgreSQLWorkspaceStore } from "./stores/PostgreSQLWorkspaceStore";
14
+ import { PostgreSQLProjectStore } from "./stores/PostgreSQLProjectStore";
15
+ import { PostgreSQLUserStore } from "./stores/PostgreSQLUserStore";
16
+ import { PostgreSQLTenantStore } from "./stores/PostgreSQLTenantStore";
17
+ import { PostgreSQLUserTenantLinkStore } from "./stores/PostgreSQLUserTenantLinkStore";
18
+ import { PostgreSQLWorkflowTrackingStore } from "./stores/PostgreSQLWorkflowTrackingStore";
19
+ import { PostgreSQLEvalStore } from "./stores/PostgreSQLEvalStore";
20
+ import { ThreadMessageQueueStore } from "./stores/ThreadMessageQueueStore";
21
+ import { ChannelBindingStore } from "./stores/ChannelBindingStore";
22
+ import { PostgreSQLChannelInstallationStore } from "./stores/PostgreSQLChannelInstallationStore";
23
+ import { PostgreSQLScheduleStorage } from "./stores/PostgreSQLScheduleStorage";
24
+
25
+ export function createPgStoreConfig(connectionString: string) {
26
+ const opts = { poolConfig: connectionString, autoMigrate: false } as const;
27
+ const optsAuto = { poolConfig: connectionString, autoMigrate: true } as const;
28
+
29
+ return {
30
+ workspace: new PostgreSQLWorkspaceStore(opts),
31
+ project: new PostgreSQLProjectStore(opts),
32
+ eval: new PostgreSQLEvalStore(optsAuto),
33
+ user: new PostgreSQLUserStore(opts),
34
+ tenant: new PostgreSQLTenantStore(opts),
35
+ userTenantLink: new PostgreSQLUserTenantLinkStore(opts),
36
+ channelBinding: new ChannelBindingStore(optsAuto),
37
+ channelInstallation: new PostgreSQLChannelInstallationStore(optsAuto),
38
+ thread: new PostgreSQLThreadStore(opts),
39
+ database: new PostgreSQLDatabaseConfigStore(opts),
40
+ metrics: new PostgreSQLMetricsServerConfigStore(opts),
41
+ mcp: new PostgreSQLMcpServerConfigStore(opts),
42
+ assistant: new PostgreSQLAssistantStore(opts),
43
+ workflowTracking: new PostgreSQLWorkflowTrackingStore(optsAuto),
44
+ threadMessageQueue: new ThreadMessageQueueStore(optsAuto),
45
+ schedule: new PostgreSQLScheduleStorage(opts),
46
+ };
47
+ }
package/src/index.ts CHANGED
@@ -8,6 +8,8 @@
8
8
  export { Pool } from "pg";
9
9
  export type { PoolConfig } from "pg";
10
10
 
11
+ export * from "./createPgStoreConfig";
12
+
11
13
  export * from "./stores/PostgreSQLThreadStore";
12
14
  export * from "./stores/PostgreSQLAssistantStore";
13
15
  export * from "./stores/PostgreSQLScheduleStorage";
@@ -9,7 +9,7 @@ import { Migration } from "./migration";
9
9
  * Migration: Add custom_run_config column
10
10
  */
11
11
  export const addCustomRunConfigColumn: Migration = {
12
- version: 102,
12
+ version: 114,
13
13
  name: "add_custom_run_config_column",
14
14
  up: async (client: PoolClient) => {
15
15
  await client.query(`
@@ -10,7 +10,7 @@ import { Migration } from "./migration";
10
10
  * Migration: Change primary key to support multi-tenancy
11
11
  */
12
12
  export const changeAssistantPrimaryKey: Migration = {
13
- version: 15,
13
+ version: 112,
14
14
  name: "change_assistant_primary_key",
15
15
  up: async (client: PoolClient) => {
16
16
  // Check if table exists and has the old primary key structure
@@ -9,7 +9,7 @@ import { Migration } from './migration';
9
9
  * Initial migration: Create database configs table
10
10
  */
11
11
  export const createDatabaseConfigsTable: Migration = {
12
- version: 4,
12
+ version: 111,
13
13
  name: 'create_database_configs_table',
14
14
  up: async (client: PoolClient) => {
15
15
  await client.query(`
@@ -10,7 +10,7 @@ import { Migration } from "./migration";
10
10
  * Migration: Add tenant_id column to threads table
11
11
  */
12
12
  export const addThreadTenantId: Migration = {
13
- version: 15,
13
+ version: 23,
14
14
  name: "add_thread_tenant_id",
15
15
  up: async (client: PoolClient) => {
16
16
  // Add tenant_id column
@@ -18,7 +18,7 @@ export const addUserStatusColumn: Migration = {
18
18
  // Create index on status for filtering
19
19
  await client.query(`
20
20
  CREATE INDEX IF NOT EXISTS idx_lattice_users_status
21
- ON lattice_users(tenant_id, status)
21
+ ON lattice_users(status)
22
22
  `);
23
23
 
24
24
  // Migrate existing users to active status
@@ -700,7 +700,7 @@ export class PostgreSQLEvalStore implements EvalStore {
700
700
  async deleteRun(tenantId: string, id: string): Promise<boolean> {
701
701
  await this.ensureInitialized();
702
702
  // Delete results first, then the run
703
- await this.pool.query(`DELETE FROM lattice_eval_run_results WHERE run_id = $1 AND tenant_id = $2`, [id, tenantId]);
703
+ await this.pool.query(`DELETE FROM lattice_eval_run_results WHERE run_id = $1 AND run_id IN (SELECT id FROM lattice_eval_runs WHERE tenant_id = $2)`, [id, tenantId]);
704
704
  const { rowCount } = await this.pool.query(
705
705
  `DELETE FROM lattice_eval_runs WHERE id = $1 AND tenant_id = $2`, [id, tenantId]
706
706
  );
@@ -201,7 +201,7 @@ export class PostgreSQLSkillStore implements SkillStore {
201
201
  `
202
202
  INSERT INTO lattice_skills (id, tenant_id, name, description, license, compatibility, metadata, content, sub_skills, created_at, updated_at)
203
203
  VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
204
- ON CONFLICT (id) DO UPDATE SET
204
+ ON CONFLICT (tenant_id, id) DO UPDATE SET
205
205
  tenant_id = EXCLUDED.tenant_id,
206
206
  name = EXCLUDED.name,
207
207
  description = EXCLUDED.description,
@@ -4,7 +4,7 @@
4
4
  * Provides PostgreSQL storage for thread message queues
5
5
  */
6
6
 
7
- import { Pool, PoolClient } from "pg";
7
+ import { Pool, type PoolConfig } from "pg";
8
8
  import crypto from "crypto";
9
9
  import type {
10
10
  IMessageQueueStore,
@@ -20,55 +20,69 @@ import { alterMessageQueueIdColumn } from "../migrations/alter_message_queue_id_
20
20
 
21
21
  export type { PendingMessage, AddMessageParams, ThreadInfo };
22
22
 
23
+ export interface PostgreSQLThreadMessageQueueStoreOptions {
24
+ poolConfig: string | PoolConfig;
25
+ autoMigrate?: boolean;
26
+ }
27
+
23
28
  export class ThreadMessageQueueStore implements IMessageQueueStore {
24
- private static _instance: ThreadMessageQueueStore;
25
- private pool: Pool | null = null;
29
+ private pool: Pool;
30
+ private ownsPool: boolean = true;
26
31
  private initialized: boolean = false;
32
+ private initPromise: Promise<void> | null = null;
33
+ private migrationManager: MigrationManager;
34
+
35
+ constructor(options: PostgreSQLThreadMessageQueueStoreOptions) {
36
+ // Create Pool from config
37
+ if (typeof options.poolConfig === "string") {
38
+ this.pool = new Pool({ connectionString: options.poolConfig });
39
+ } else {
40
+ this.pool = new Pool(options.poolConfig);
41
+ }
27
42
 
28
- public static getInstance(): ThreadMessageQueueStore {
29
- if (!ThreadMessageQueueStore._instance) {
30
- ThreadMessageQueueStore._instance = new ThreadMessageQueueStore();
43
+ this.migrationManager = new MigrationManager(this.pool);
44
+ this.migrationManager.register(createThreadMessageQueueTable);
45
+ this.migrationManager.register(addPriorityAndCommandColumns);
46
+ this.migrationManager.register(addCustomRunConfigColumn);
47
+ this.migrationManager.register(alterMessageQueueIdColumn);
48
+
49
+ if (options.autoMigrate !== false) {
50
+ this.initialize().catch((error) => {
51
+ console.error("Failed to initialize ThreadMessageQueueStore:", error);
52
+ throw error;
53
+ });
31
54
  }
32
- return ThreadMessageQueueStore._instance;
33
55
  }
34
56
 
35
- private constructor() {}
36
-
37
- /**
38
- * Initialize with PostgreSQL pool
39
- * @param pool - PostgreSQL connection pool
40
- * @param autoMigrate - Whether to run migrations automatically (default: true)
41
- */
42
- async initialize(pool: Pool, autoMigrate: boolean = true): Promise<void> {
43
- this.pool = pool;
44
-
45
- if (autoMigrate && !this.initialized) {
46
- const migrationManager = new MigrationManager(pool);
47
- migrationManager.register(createThreadMessageQueueTable);
48
- migrationManager.register(addPriorityAndCommandColumns);
49
- migrationManager.register(addCustomRunConfigColumn);
50
- migrationManager.register(alterMessageQueueIdColumn);
51
- await migrationManager.migrate();
52
- this.initialized = true;
53
- }
57
+ async initialize(): Promise<void> {
58
+ if (this.initialized) return;
59
+ if (this.initPromise) return this.initPromise;
60
+
61
+ this.initPromise = (async () => {
62
+ try {
63
+ await this.migrationManager.migrate();
64
+ this.initialized = true;
65
+ } finally {
66
+ this.initPromise = null;
67
+ }
68
+ })();
69
+ return this.initPromise;
54
70
  }
55
71
 
56
- private getPool(): Pool {
57
- if (!this.pool) {
58
- throw new Error("ThreadMessageQueueStore not initialized. Call initialize() first.");
72
+ async dispose(): Promise<void> {
73
+ if (this.ownsPool && this.pool) {
74
+ await this.pool.end();
59
75
  }
60
- return this.pool;
61
76
  }
62
77
 
63
78
  /**
64
79
  * Add message to queue
65
80
  */
66
81
  async addMessage(params: AddMessageParams): Promise<PendingMessage> {
67
- const pool = this.getPool();
68
82
  const { threadId, tenantId, assistantId, content, type = "human", priority = 0, command, custom_run_config, id } = params;
69
83
 
70
84
  // Get current max sequence (no lock needed without unique constraint)
71
- const seqResult = await pool.query(
85
+ const seqResult = await this.pool.query(
72
86
  `SELECT COALESCE(MAX(sequence_order), 0) + 1 as next_seq
73
87
  FROM lattice_thread_message_queue
74
88
  WHERE thread_id = $1`,
@@ -76,7 +90,7 @@ export class ThreadMessageQueueStore implements IMessageQueueStore {
76
90
  );
77
91
  const nextSeq = seqResult.rows[0].next_seq;
78
92
 
79
- const result = await pool.query(
93
+ const result = await this.pool.query(
80
94
  `INSERT INTO lattice_thread_message_queue
81
95
  (id, thread_id, tenant_id, assistant_id, message_content, message_type, sequence_order, priority, command, custom_run_config)
82
96
  VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
@@ -92,16 +106,13 @@ export class ThreadMessageQueueStore implements IMessageQueueStore {
92
106
  * Uses priority=100 to ensure message is processed first
93
107
  */
94
108
  async addMessageAtHead(params: AddMessageParams): Promise<PendingMessage> {
95
- const pool = this.getPool();
96
109
  const { threadId, tenantId, assistantId, content, type = "human", command, custom_run_config, id } = params;
97
110
 
98
- // Use provided tenantId and assistantId directly
99
- // These should always be provided by the caller (Agent)
100
111
  const resolvedTenantId = tenantId!;
101
112
  const resolvedAssistantId = assistantId!;
102
113
 
103
114
  // Get current max sequence for ordering within same priority
104
- const seqResult = await pool.query(
115
+ const seqResult = await this.pool.query(
105
116
  `SELECT COALESCE(MAX(sequence_order), 0) + 1 as next_seq
106
117
  FROM lattice_thread_message_queue
107
118
  WHERE thread_id = $1`,
@@ -110,7 +121,7 @@ export class ThreadMessageQueueStore implements IMessageQueueStore {
110
121
  const nextSeq = seqResult.rows[0].next_seq;
111
122
 
112
123
  // Insert with high priority (100)
113
- const result = await pool.query(
124
+ const result = await this.pool.query(
114
125
  `INSERT INTO lattice_thread_message_queue
115
126
  (id, thread_id, tenant_id, assistant_id, message_content, message_type, sequence_order, priority, command, custom_run_config)
116
127
  VALUES ($1, $2, $3, $4, $5, $6, $7, 100, $8, $9)
@@ -125,8 +136,7 @@ export class ThreadMessageQueueStore implements IMessageQueueStore {
125
136
  * Get pending messages for thread
126
137
  */
127
138
  async getPendingMessages(threadId: string): Promise<PendingMessage[]> {
128
- const pool = this.getPool();
129
- const result = await pool.query(
139
+ const result = await this.pool.query(
130
140
  `SELECT * FROM lattice_thread_message_queue
131
141
  WHERE thread_id = $1 AND status = 'pending'
132
142
  ORDER BY priority DESC, sequence_order ASC`,
@@ -140,8 +150,7 @@ export class ThreadMessageQueueStore implements IMessageQueueStore {
140
150
  * Get processing messages for a thread
141
151
  */
142
152
  async getProcessingMessages(threadId: string): Promise<PendingMessage[]> {
143
- const pool = this.getPool();
144
- const result = await pool.query(
153
+ const result = await this.pool.query(
145
154
  `SELECT * FROM lattice_thread_message_queue
146
155
  WHERE thread_id = $1 AND status = 'processing'
147
156
  ORDER BY priority DESC, sequence_order ASC`,
@@ -155,8 +164,7 @@ export class ThreadMessageQueueStore implements IMessageQueueStore {
155
164
  * Get queue size
156
165
  */
157
166
  async getQueueSize(threadId: string): Promise<number> {
158
- const pool = this.getPool();
159
- const result = await pool.query(
167
+ const result = await this.pool.query(
160
168
  `SELECT COUNT(*) as count FROM lattice_thread_message_queue
161
169
  WHERE thread_id = $1 AND status = 'pending'`,
162
170
  [threadId]
@@ -169,8 +177,7 @@ export class ThreadMessageQueueStore implements IMessageQueueStore {
169
177
  * Get all threads with pending or processing messages
170
178
  */
171
179
  async getThreadsWithPendingMessages(): Promise<ThreadInfo[]> {
172
- const pool = this.getPool();
173
- const result = await pool.query(
180
+ const result = await this.pool.query(
174
181
  `SELECT DISTINCT tenant_id, assistant_id, thread_id
175
182
  FROM lattice_thread_message_queue
176
183
  WHERE status IN ('pending', 'processing')
@@ -188,8 +195,7 @@ export class ThreadMessageQueueStore implements IMessageQueueStore {
188
195
  * Remove message
189
196
  */
190
197
  async removeMessage(messageId: string): Promise<boolean> {
191
- const pool = this.getPool();
192
- const result = await pool.query(
198
+ const result = await this.pool.query(
193
199
  `DELETE FROM lattice_thread_message_queue WHERE id = $1 RETURNING id`,
194
200
  [messageId]
195
201
  );
@@ -201,8 +207,7 @@ export class ThreadMessageQueueStore implements IMessageQueueStore {
201
207
  * Clear all messages for thread
202
208
  */
203
209
  async clearMessages(threadId: string): Promise<void> {
204
- const pool = this.getPool();
205
- await pool.query(
210
+ await this.pool.query(
206
211
  `DELETE FROM lattice_thread_message_queue WHERE thread_id = $1`,
207
212
  [threadId]
208
213
  );
@@ -212,43 +217,18 @@ export class ThreadMessageQueueStore implements IMessageQueueStore {
212
217
  * Mark message as processing
213
218
  */
214
219
  async markProcessing(messageId: string): Promise<void> {
215
- const pool = this.getPool();
216
- await pool.query(
220
+ await this.pool.query(
217
221
  `UPDATE lattice_thread_message_queue SET status = 'processing' WHERE id = $1`,
218
222
  [messageId]
219
223
  );
220
224
  }
221
225
 
222
- /**
223
- * Mark message as completed
224
- */
225
- async markCompleted(messageId: string): Promise<void> {
226
- const pool = this.getPool();
227
- await pool.query(
228
- `UPDATE lattice_thread_message_queue SET status = 'completed' WHERE id = $1`,
229
- [messageId]
230
- );
231
- }
232
-
233
- /**
234
- * Clear completed messages for thread
235
- */
236
- async clearCompletedMessages(threadId: string): Promise<void> {
237
- const pool = this.getPool();
238
- await pool.query(
239
- `DELETE FROM lattice_thread_message_queue
240
- WHERE thread_id = $1 AND status = 'completed'`,
241
- [threadId]
242
- );
243
- }
244
-
245
226
  /**
246
227
  * Reset all processing messages to pending state for a thread
247
228
  * Returns the number of messages reset
248
229
  */
249
230
  async resetProcessingToPending(threadId: string): Promise<number> {
250
- const pool = this.getPool();
251
- const result = await pool.query(
231
+ const result = await this.pool.query(
252
232
  `UPDATE lattice_thread_message_queue
253
233
  SET status = 'pending'
254
234
  WHERE thread_id = $1 AND status = 'processing'
@@ -275,5 +255,3 @@ export class ThreadMessageQueueStore implements IMessageQueueStore {
275
255
  };
276
256
  }
277
257
  }
278
-
279
- export const getThreadMessageQueueStore = () => ThreadMessageQueueStore.getInstance();