@axiom-lattice/pg-stores 1.0.21 → 1.0.23

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.
@@ -10,6 +10,8 @@ import {
10
10
  } from "@axiom-lattice/protocols";
11
11
  import { MigrationManager } from "../migrations/migration";
12
12
  import { createAssistantsTable } from "../migrations/assistant_migrations";
13
+ import { addAssistantTenantId } from "../migrations/assistant_tenant_migration";
14
+ import { changeAssistantPrimaryKey } from "../migrations/assistant_pk_migration";
13
15
 
14
16
  /**
15
17
  * PostgreSQL AssistantStore options
@@ -30,12 +32,16 @@ export interface PostgreSQLAssistantStoreOptions {
30
32
 
31
33
  /**
32
34
  * PostgreSQL implementation of AssistantStore
35
+ *
36
+ * Features:
37
+ * - Multi-tenant isolation via tenant_id
33
38
  */
34
39
  export class PostgreSQLAssistantStore implements AssistantStore {
35
40
  private pool: Pool;
36
41
  private migrationManager: MigrationManager;
37
42
  private initialized: boolean = false;
38
43
  private ownsPool: boolean = true;
44
+ private initPromise: Promise<void> | null = null;
39
45
 
40
46
  constructor(options: PostgreSQLAssistantStoreOptions) {
41
47
  // Create Pool from config
@@ -47,6 +53,8 @@ export class PostgreSQLAssistantStore implements AssistantStore {
47
53
 
48
54
  this.migrationManager = new MigrationManager(this.pool);
49
55
  this.migrationManager.register(createAssistantsTable);
56
+ this.migrationManager.register(addAssistantTenantId);
57
+ this.migrationManager.register(changeAssistantPrimaryKey);
50
58
 
51
59
  // Auto-migrate by default
52
60
  if (options.autoMigrate !== false) {
@@ -59,24 +67,38 @@ export class PostgreSQLAssistantStore implements AssistantStore {
59
67
 
60
68
  /**
61
69
  * Initialize the store and run migrations
70
+ * Uses a promise-based lock to prevent concurrent initialization
62
71
  */
63
72
  async initialize(): Promise<void> {
64
73
  if (this.initialized) {
65
74
  return;
66
75
  }
67
76
 
68
- await this.migrationManager.migrate();
69
- this.initialized = true;
77
+ if (this.initPromise) {
78
+ return this.initPromise;
79
+ }
80
+
81
+ this.initPromise = (async () => {
82
+ try {
83
+ await this.migrationManager.migrate();
84
+ this.initialized = true;
85
+ } finally {
86
+ this.initPromise = null;
87
+ }
88
+ })();
89
+
90
+ return this.initPromise;
70
91
  }
71
92
 
72
93
  /**
73
- * Get all assistants
94
+ * Get all assistants for a tenant
74
95
  */
75
- async getAllAssistants(): Promise<Assistant[]> {
96
+ async getAllAssistants(tenantId: string): Promise<Assistant[]> {
76
97
  await this.ensureInitialized();
77
98
 
78
99
  const result = await this.pool.query<{
79
100
  id: string;
101
+ tenant_id: string;
80
102
  name: string;
81
103
  description: string | null;
82
104
  graph_definition: any;
@@ -84,10 +106,12 @@ export class PostgreSQLAssistantStore implements AssistantStore {
84
106
  updated_at: Date;
85
107
  }>(
86
108
  `
87
- SELECT id, name, description, graph_definition, created_at, updated_at
109
+ SELECT id, tenant_id, name, description, graph_definition, created_at, updated_at
88
110
  FROM lattice_assistants
111
+ WHERE tenant_id = $1
89
112
  ORDER BY created_at DESC
90
- `
113
+ `,
114
+ [tenantId]
91
115
  );
92
116
 
93
117
  return result.rows.map(this.mapRowToAssistant);
@@ -96,11 +120,12 @@ export class PostgreSQLAssistantStore implements AssistantStore {
96
120
  /**
97
121
  * Get assistant by ID
98
122
  */
99
- async getAssistantById(id: string): Promise<Assistant | null> {
123
+ async getAssistantById(tenantId: string, id: string): Promise<Assistant | null> {
100
124
  await this.ensureInitialized();
101
125
 
102
126
  const result = await this.pool.query<{
103
127
  id: string;
128
+ tenant_id: string;
104
129
  name: string;
105
130
  description: string | null;
106
131
  graph_definition: any;
@@ -108,11 +133,11 @@ export class PostgreSQLAssistantStore implements AssistantStore {
108
133
  updated_at: Date;
109
134
  }>(
110
135
  `
111
- SELECT id, name, description, graph_definition, created_at, updated_at
136
+ SELECT id, tenant_id, name, description, graph_definition, created_at, updated_at
112
137
  FROM lattice_assistants
113
- WHERE id = $1
138
+ WHERE tenant_id = $1 AND id = $2
114
139
  `,
115
- [id]
140
+ [tenantId, id]
116
141
  );
117
142
 
118
143
  if (result.rows.length === 0) {
@@ -126,6 +151,7 @@ export class PostgreSQLAssistantStore implements AssistantStore {
126
151
  * Create a new assistant
127
152
  */
128
153
  async createAssistant(
154
+ tenantId: string,
129
155
  id: string,
130
156
  data: CreateAssistantRequest
131
157
  ): Promise<Assistant> {
@@ -135,9 +161,10 @@ export class PostgreSQLAssistantStore implements AssistantStore {
135
161
 
136
162
  await this.pool.query(
137
163
  `
138
- INSERT INTO lattice_assistants (id, name, description, graph_definition, created_at, updated_at)
139
- VALUES ($1, $2, $3, $4, $5, $6)
164
+ INSERT INTO lattice_assistants (id, tenant_id, name, description, graph_definition, created_at, updated_at)
165
+ VALUES ($1, $2, $3, $4, $5, $6, $7)
140
166
  ON CONFLICT (id) DO UPDATE SET
167
+ tenant_id = EXCLUDED.tenant_id,
141
168
  name = EXCLUDED.name,
142
169
  description = EXCLUDED.description,
143
170
  graph_definition = EXCLUDED.graph_definition,
@@ -145,6 +172,7 @@ export class PostgreSQLAssistantStore implements AssistantStore {
145
172
  `,
146
173
  [
147
174
  id,
175
+ tenantId,
148
176
  data.name,
149
177
  data.description || null,
150
178
  JSON.stringify(data.graphDefinition),
@@ -155,6 +183,7 @@ export class PostgreSQLAssistantStore implements AssistantStore {
155
183
 
156
184
  return {
157
185
  id,
186
+ tenantId,
158
187
  name: data.name,
159
188
  description: data.description,
160
189
  graphDefinition: data.graphDefinition,
@@ -167,13 +196,14 @@ export class PostgreSQLAssistantStore implements AssistantStore {
167
196
  * Update an existing assistant
168
197
  */
169
198
  async updateAssistant(
199
+ tenantId: string,
170
200
  id: string,
171
201
  updates: Partial<CreateAssistantRequest>
172
202
  ): Promise<Assistant | null> {
173
203
  await this.ensureInitialized();
174
204
 
175
205
  // Get existing assistant
176
- const existing = await this.getAssistantById(id);
206
+ const existing = await this.getAssistantById(tenantId, id);
177
207
  if (!existing) {
178
208
  return null;
179
209
  }
@@ -207,34 +237,35 @@ export class PostgreSQLAssistantStore implements AssistantStore {
207
237
  updateFields.push(`updated_at = $${paramIndex++}`);
208
238
  updateValues.push(new Date());
209
239
 
210
- // Add id for WHERE clause
240
+ // Add tenant_id and id for WHERE clause
241
+ updateValues.push(tenantId);
211
242
  updateValues.push(id);
212
243
 
213
244
  await this.pool.query(
214
245
  `
215
246
  UPDATE lattice_assistants
216
247
  SET ${updateFields.join(", ")}
217
- WHERE id = $${paramIndex}
248
+ WHERE tenant_id = $${paramIndex++} AND id = $${paramIndex}
218
249
  `,
219
250
  updateValues
220
251
  );
221
252
 
222
253
  // Return updated assistant
223
- return await this.getAssistantById(id);
254
+ return await this.getAssistantById(tenantId, id);
224
255
  }
225
256
 
226
257
  /**
227
258
  * Delete an assistant by ID
228
259
  */
229
- async deleteAssistant(id: string): Promise<boolean> {
260
+ async deleteAssistant(tenantId: string, id: string): Promise<boolean> {
230
261
  await this.ensureInitialized();
231
262
 
232
263
  const result = await this.pool.query(
233
264
  `
234
265
  DELETE FROM lattice_assistants
235
- WHERE id = $1
266
+ WHERE tenant_id = $1 AND id = $2
236
267
  `,
237
- [id]
268
+ [tenantId, id]
238
269
  );
239
270
 
240
271
  return result.rowCount !== null && result.rowCount > 0;
@@ -243,16 +274,16 @@ export class PostgreSQLAssistantStore implements AssistantStore {
243
274
  /**
244
275
  * Check if assistant exists
245
276
  */
246
- async hasAssistant(id: string): Promise<boolean> {
277
+ async hasAssistant(tenantId: string, id: string): Promise<boolean> {
247
278
  await this.ensureInitialized();
248
279
 
249
280
  const result = await this.pool.query(
250
281
  `
251
282
  SELECT 1 FROM lattice_assistants
252
- WHERE id = $1
283
+ WHERE tenant_id = $1 AND id = $2
253
284
  LIMIT 1
254
285
  `,
255
- [id]
286
+ [tenantId, id]
256
287
  );
257
288
 
258
289
  return result.rows.length > 0;
@@ -282,6 +313,7 @@ export class PostgreSQLAssistantStore implements AssistantStore {
282
313
  */
283
314
  private mapRowToAssistant(row: {
284
315
  id: string;
316
+ tenant_id: string;
285
317
  name: string;
286
318
  description: string | null;
287
319
  graph_definition: any;
@@ -290,6 +322,7 @@ export class PostgreSQLAssistantStore implements AssistantStore {
290
322
  }): Assistant {
291
323
  return {
292
324
  id: row.id,
325
+ tenantId: row.tenant_id,
293
326
  name: row.name,
294
327
  description: row.description || undefined,
295
328
  graphDefinition:
@@ -14,6 +14,7 @@ import {
14
14
  } from "@axiom-lattice/protocols";
15
15
  import { MigrationManager } from "../migrations/migration";
16
16
  import { createScheduledTasksTable } from "../migrations/schedule_migrations";
17
+ import { addScheduleTenantId } from "../migrations/schedule_tenant_migration";
17
18
 
18
19
  /**
19
20
  * PostgreSQL ScheduleStorage options
@@ -78,6 +79,7 @@ export class PostgreSQLScheduleStorage implements ScheduleStorage {
78
79
 
79
80
  this.migrationManager = new MigrationManager(this.pool);
80
81
  this.migrationManager.register(createScheduledTasksTable);
82
+ this.migrationManager.register(addScheduleTenantId);
81
83
 
82
84
  // Auto-migrate by default
83
85
  if (options.autoMigrate !== false) {
@@ -424,6 +426,7 @@ export class PostgreSQLScheduleStorage implements ScheduleStorage {
424
426
  * Get all tasks with optional filters
425
427
  */
426
428
  async getAllTasks(filters?: {
429
+ tenantId?: string;
427
430
  status?: ScheduledTaskStatus;
428
431
  executionType?: ScheduleExecutionType;
429
432
  taskType?: string;
@@ -438,6 +441,12 @@ export class PostgreSQLScheduleStorage implements ScheduleStorage {
438
441
  const values: any[] = [];
439
442
  let paramIndex = 1;
440
443
 
444
+ // Always filter by tenantId if provided
445
+ if (filters?.tenantId !== undefined) {
446
+ whereClauses.push(`tenant_id = $${paramIndex++}`);
447
+ values.push(filters.tenantId);
448
+ }
449
+
441
450
  if (filters?.status !== undefined) {
442
451
  whereClauses.push(`status = $${paramIndex++}`);
443
452
  values.push(filters.status);
@@ -483,6 +492,7 @@ export class PostgreSQLScheduleStorage implements ScheduleStorage {
483
492
  * Count tasks with optional filters
484
493
  */
485
494
  async countTasks(filters?: {
495
+ tenantId?: string;
486
496
  status?: ScheduledTaskStatus;
487
497
  executionType?: ScheduleExecutionType;
488
498
  taskType?: string;
@@ -495,6 +505,12 @@ export class PostgreSQLScheduleStorage implements ScheduleStorage {
495
505
  const values: any[] = [];
496
506
  let paramIndex = 1;
497
507
 
508
+ // Always filter by tenantId if provided
509
+ if (filters?.tenantId !== undefined) {
510
+ whereClauses.push(`tenant_id = $${paramIndex++}`);
511
+ values.push(filters.tenantId);
512
+ }
513
+
498
514
  if (filters?.status !== undefined) {
499
515
  whereClauses.push(`status = $${paramIndex++}`);
500
516
  values.push(filters.status);