@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.
package/dist/index.mjs CHANGED
@@ -175,9 +175,111 @@ var createThreadsTable = {
175
175
  }
176
176
  };
177
177
 
178
+ // src/migrations/thread_tenant_migration.ts
179
+ var addThreadTenantId = {
180
+ version: 15,
181
+ name: "add_thread_tenant_id",
182
+ up: async (client) => {
183
+ await client.query(`
184
+ ALTER TABLE lattice_threads
185
+ ADD COLUMN IF NOT EXISTS tenant_id VARCHAR(255) DEFAULT 'default'
186
+ `);
187
+ await client.query(`
188
+ UPDATE lattice_threads
189
+ SET tenant_id = 'default'
190
+ WHERE tenant_id IS NULL
191
+ `);
192
+ await client.query(`
193
+ ALTER TABLE lattice_threads
194
+ ALTER COLUMN tenant_id SET NOT NULL
195
+ `);
196
+ await client.query(`
197
+ ALTER TABLE lattice_threads
198
+ ALTER COLUMN tenant_id DROP DEFAULT
199
+ `);
200
+ await client.query(`
201
+ CREATE INDEX IF NOT EXISTS idx_lattice_threads_tenant_id
202
+ ON lattice_threads(tenant_id)
203
+ `);
204
+ },
205
+ down: async (client) => {
206
+ await client.query(
207
+ "DROP INDEX IF EXISTS idx_lattice_threads_tenant_id"
208
+ );
209
+ await client.query(
210
+ "ALTER TABLE lattice_threads DROP COLUMN IF EXISTS tenant_id"
211
+ );
212
+ }
213
+ };
214
+
215
+ // src/migrations/thread_pk_migration.ts
216
+ var changeThreadPrimaryKey = {
217
+ version: 19,
218
+ name: "change_thread_primary_key",
219
+ up: async (client) => {
220
+ const tableExists = await client.query(`
221
+ SELECT EXISTS (
222
+ SELECT FROM information_schema.tables
223
+ WHERE table_name = 'lattice_threads'
224
+ )
225
+ `);
226
+ if (!tableExists.rows[0].exists) {
227
+ return;
228
+ }
229
+ const pkResult = await client.query(`
230
+ SELECT a.attname as column_name
231
+ FROM pg_index i
232
+ JOIN pg_attribute a ON a.attrelid = i.indrelid AND a.attnum = ANY(i.indkey)
233
+ WHERE i.indrelid = 'lattice_threads'::regclass
234
+ AND i.indisprimary
235
+ `);
236
+ const pkColumns = pkResult.rows.map((row) => row.column_name);
237
+ if (pkColumns.includes("tenant_id") && pkColumns.includes("id") && pkColumns.length === 2) {
238
+ console.log("Primary key already correct (id, tenant_id)");
239
+ return;
240
+ }
241
+ if (pkColumns.includes("id") && pkColumns.includes("assistant_id")) {
242
+ console.log("Changing primary key from (id, assistant_id) to (id, tenant_id)");
243
+ const tenantIdExists = await client.query(`
244
+ SELECT EXISTS (
245
+ SELECT FROM information_schema.columns
246
+ WHERE table_name = 'lattice_threads'
247
+ AND column_name = 'tenant_id'
248
+ )
249
+ `);
250
+ if (!tenantIdExists.rows[0].exists) {
251
+ await client.query(`
252
+ ALTER TABLE lattice_threads
253
+ ADD COLUMN tenant_id VARCHAR(255) DEFAULT 'default'
254
+ `);
255
+ }
256
+ await client.query(`
257
+ UPDATE lattice_threads
258
+ SET tenant_id = 'default'
259
+ WHERE tenant_id IS NULL
260
+ `);
261
+ await client.query(`
262
+ ALTER TABLE lattice_threads
263
+ ALTER COLUMN tenant_id SET NOT NULL
264
+ `);
265
+ await client.query(`
266
+ ALTER TABLE lattice_threads
267
+ DROP CONSTRAINT IF EXISTS lattice_threads_pkey
268
+ `);
269
+ await client.query(`
270
+ ALTER TABLE lattice_threads
271
+ ADD PRIMARY KEY (id, tenant_id)
272
+ `);
273
+ console.log("Primary key changed successfully to (id, tenant_id)");
274
+ }
275
+ },
276
+ down: async (client) => {
277
+ console.warn("Down migration for changeThreadPrimaryKey is not supported");
278
+ }
279
+ };
280
+
178
281
  // src/stores/PostgreSQLThreadStore.ts
179
282
  var PostgreSQLThreadStore = class {
180
- // Promise-based lock to prevent concurrent initialization
181
283
  constructor(options) {
182
284
  this.initialized = false;
183
285
  this.ownsPool = true;
@@ -189,6 +291,8 @@ var PostgreSQLThreadStore = class {
189
291
  }
190
292
  this.migrationManager = new MigrationManager(this.pool);
191
293
  this.migrationManager.register(createThreadsTable);
294
+ this.migrationManager.register(addThreadTenantId);
295
+ this.migrationManager.register(changeThreadPrimaryKey);
192
296
  if (options.autoMigrate !== false) {
193
297
  this.initialize().catch((error) => {
194
298
  console.error("Failed to initialize PostgreSQLThreadStore:", error);
@@ -227,33 +331,33 @@ var PostgreSQLThreadStore = class {
227
331
  return this.initPromise;
228
332
  }
229
333
  /**
230
- * Get all threads for a specific assistant
334
+ * Get all threads for a specific tenant and assistant
231
335
  */
232
- async getThreadsByAssistantId(assistantId) {
336
+ async getThreadsByAssistantId(tenantId, assistantId) {
233
337
  await this.ensureInitialized();
234
338
  const result = await this.pool.query(
235
339
  `
236
- SELECT id, assistant_id, metadata, created_at, updated_at
340
+ SELECT id, tenant_id, assistant_id, metadata, created_at, updated_at
237
341
  FROM lattice_threads
238
- WHERE assistant_id = $1
342
+ WHERE tenant_id = $1 AND assistant_id = $2
239
343
  ORDER BY created_at DESC
240
344
  `,
241
- [assistantId]
345
+ [tenantId, assistantId]
242
346
  );
243
347
  return result.rows.map(this.mapRowToThread);
244
348
  }
245
349
  /**
246
- * Get a thread by ID for a specific assistant
350
+ * Get a thread by ID for a specific tenant
247
351
  */
248
- async getThreadById(assistantId, threadId) {
352
+ async getThreadById(tenantId, threadId) {
249
353
  await this.ensureInitialized();
250
354
  const result = await this.pool.query(
251
355
  `
252
- SELECT id, assistant_id, metadata, created_at, updated_at
356
+ SELECT id, tenant_id, assistant_id, metadata, created_at, updated_at
253
357
  FROM lattice_threads
254
- WHERE id = $1 AND assistant_id = $2
358
+ WHERE tenant_id = $1 AND id = $2
255
359
  `,
256
- [threadId, assistantId]
360
+ [tenantId, threadId]
257
361
  );
258
362
  if (result.rows.length === 0) {
259
363
  return void 0;
@@ -261,24 +365,26 @@ var PostgreSQLThreadStore = class {
261
365
  return this.mapRowToThread(result.rows[0]);
262
366
  }
263
367
  /**
264
- * Create a new thread for an assistant
368
+ * Create a new thread for a tenant and assistant
265
369
  */
266
- async createThread(assistantId, threadId, data) {
370
+ async createThread(tenantId, assistantId, threadId, data) {
267
371
  await this.ensureInitialized();
268
372
  const now = /* @__PURE__ */ new Date();
269
373
  const metadata = data.metadata || {};
270
374
  await this.pool.query(
271
375
  `
272
- INSERT INTO lattice_threads (id, assistant_id, metadata, created_at, updated_at)
273
- VALUES ($1, $2, $3, $4, $5)
274
- ON CONFLICT (id, assistant_id) DO UPDATE SET
376
+ INSERT INTO lattice_threads (id, tenant_id, assistant_id, metadata, created_at, updated_at)
377
+ VALUES ($1, $2, $3, $4, $5, $6)
378
+ ON CONFLICT (id, tenant_id) DO UPDATE SET
379
+ assistant_id = EXCLUDED.assistant_id,
275
380
  metadata = EXCLUDED.metadata,
276
381
  updated_at = EXCLUDED.updated_at
277
382
  `,
278
- [threadId, assistantId, JSON.stringify(metadata), now, now]
383
+ [threadId, tenantId, assistantId, JSON.stringify(metadata), now, now]
279
384
  );
280
385
  return {
281
386
  id: threadId,
387
+ tenantId,
282
388
  assistantId,
283
389
  metadata,
284
390
  createdAt: now,
@@ -288,9 +394,9 @@ var PostgreSQLThreadStore = class {
288
394
  /**
289
395
  * Update an existing thread
290
396
  */
291
- async updateThread(assistantId, threadId, updates) {
397
+ async updateThread(tenantId, threadId, updates) {
292
398
  await this.ensureInitialized();
293
- const existing = await this.getThreadById(assistantId, threadId);
399
+ const existing = await this.getThreadById(tenantId, threadId);
294
400
  if (!existing) {
295
401
  return null;
296
402
  }
@@ -303,9 +409,9 @@ var PostgreSQLThreadStore = class {
303
409
  `
304
410
  UPDATE lattice_threads
305
411
  SET metadata = $1, updated_at = $2
306
- WHERE id = $3 AND assistant_id = $4
412
+ WHERE tenant_id = $3 AND id = $4
307
413
  `,
308
- [JSON.stringify(updatedMetadata), now, threadId, assistantId]
414
+ [JSON.stringify(updatedMetadata), now, tenantId, threadId]
309
415
  );
310
416
  return {
311
417
  ...existing,
@@ -316,29 +422,29 @@ var PostgreSQLThreadStore = class {
316
422
  /**
317
423
  * Delete a thread by ID
318
424
  */
319
- async deleteThread(assistantId, threadId) {
425
+ async deleteThread(tenantId, threadId) {
320
426
  await this.ensureInitialized();
321
427
  const result = await this.pool.query(
322
428
  `
323
429
  DELETE FROM lattice_threads
324
- WHERE id = $1 AND assistant_id = $2
430
+ WHERE tenant_id = $1 AND id = $2
325
431
  `,
326
- [threadId, assistantId]
432
+ [tenantId, threadId]
327
433
  );
328
434
  return result.rowCount !== null && result.rowCount > 0;
329
435
  }
330
436
  /**
331
437
  * Check if thread exists
332
438
  */
333
- async hasThread(assistantId, threadId) {
439
+ async hasThread(tenantId, threadId) {
334
440
  await this.ensureInitialized();
335
441
  const result = await this.pool.query(
336
442
  `
337
443
  SELECT 1 FROM lattice_threads
338
- WHERE id = $1 AND assistant_id = $2
444
+ WHERE tenant_id = $1 AND id = $2
339
445
  LIMIT 1
340
446
  `,
341
- [threadId, assistantId]
447
+ [tenantId, threadId]
342
448
  );
343
449
  return result.rows.length > 0;
344
450
  }
@@ -356,6 +462,7 @@ var PostgreSQLThreadStore = class {
356
462
  mapRowToThread(row) {
357
463
  return {
358
464
  id: row.id,
465
+ tenantId: row.tenant_id,
359
466
  assistantId: row.assistant_id,
360
467
  metadata: typeof row.metadata === "string" ? JSON.parse(row.metadata) : row.metadata || {},
361
468
  createdAt: row.created_at,
@@ -374,12 +481,14 @@ var createAssistantsTable = {
374
481
  up: async (client) => {
375
482
  await client.query(`
376
483
  CREATE TABLE IF NOT EXISTS lattice_assistants (
377
- id VARCHAR(255) PRIMARY KEY,
484
+ id VARCHAR(255) NOT NULL,
485
+ tenant_id VARCHAR(255) NOT NULL DEFAULT 'default',
378
486
  name VARCHAR(255) NOT NULL,
379
487
  description TEXT,
380
488
  graph_definition JSONB NOT NULL,
381
489
  created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
382
- updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
490
+ updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
491
+ PRIMARY KEY (tenant_id, id)
383
492
  )
384
493
  `);
385
494
  await client.query(`
@@ -400,11 +509,95 @@ var createAssistantsTable = {
400
509
  }
401
510
  };
402
511
 
512
+ // src/migrations/assistant_tenant_migration.ts
513
+ var addAssistantTenantId = {
514
+ version: 14,
515
+ name: "add_assistant_tenant_id",
516
+ up: async (client) => {
517
+ await client.query(`
518
+ CREATE INDEX IF NOT EXISTS idx_lattice_assistants_tenant_id
519
+ ON lattice_assistants(tenant_id)
520
+ `);
521
+ },
522
+ down: async (client) => {
523
+ await client.query(
524
+ "DROP INDEX IF EXISTS idx_lattice_assistants_tenant_id"
525
+ );
526
+ }
527
+ };
528
+
529
+ // src/migrations/assistant_pk_migration.ts
530
+ var changeAssistantPrimaryKey = {
531
+ version: 15,
532
+ name: "change_assistant_primary_key",
533
+ up: async (client) => {
534
+ const tableExists = await client.query(`
535
+ SELECT EXISTS (
536
+ SELECT FROM information_schema.tables
537
+ WHERE table_name = 'lattice_assistants'
538
+ )
539
+ `);
540
+ if (!tableExists.rows[0].exists) {
541
+ return;
542
+ }
543
+ const pkResult = await client.query(`
544
+ SELECT a.attname as column_name
545
+ FROM pg_index i
546
+ JOIN pg_attribute a ON a.attrelid = i.indrelid AND a.attnum = ANY(i.indkey)
547
+ WHERE i.indrelid = 'lattice_assistants'::regclass
548
+ AND i.indisprimary
549
+ `);
550
+ const pkColumns = pkResult.rows.map((row) => row.column_name);
551
+ if (pkColumns.includes("tenant_id") && pkColumns.includes("id")) {
552
+ console.log("Primary key already correct (tenant_id, id)");
553
+ return;
554
+ }
555
+ if (pkColumns.length === 1 && pkColumns[0] === "id") {
556
+ console.log("Changing primary key from (id) to (tenant_id, id)");
557
+ const tenantIdExists = await client.query(`
558
+ SELECT EXISTS (
559
+ SELECT FROM information_schema.columns
560
+ WHERE table_name = 'lattice_assistants'
561
+ AND column_name = 'tenant_id'
562
+ )
563
+ `);
564
+ if (!tenantIdExists.rows[0].exists) {
565
+ await client.query(`
566
+ ALTER TABLE lattice_assistants
567
+ ADD COLUMN tenant_id VARCHAR(255) DEFAULT 'default'
568
+ `);
569
+ }
570
+ await client.query(`
571
+ UPDATE lattice_assistants
572
+ SET tenant_id = 'default'
573
+ WHERE tenant_id IS NULL
574
+ `);
575
+ await client.query(`
576
+ ALTER TABLE lattice_assistants
577
+ ALTER COLUMN tenant_id SET NOT NULL
578
+ `);
579
+ await client.query(`
580
+ ALTER TABLE lattice_assistants
581
+ DROP CONSTRAINT IF EXISTS lattice_assistants_pkey
582
+ `);
583
+ await client.query(`
584
+ ALTER TABLE lattice_assistants
585
+ ADD PRIMARY KEY (tenant_id, id)
586
+ `);
587
+ console.log("Primary key changed successfully to (tenant_id, id)");
588
+ }
589
+ },
590
+ down: async (client) => {
591
+ console.warn("Down migration for changeAssistantPrimaryKey is not supported");
592
+ }
593
+ };
594
+
403
595
  // src/stores/PostgreSQLAssistantStore.ts
404
596
  var PostgreSQLAssistantStore = class {
405
597
  constructor(options) {
406
598
  this.initialized = false;
407
599
  this.ownsPool = true;
600
+ this.initPromise = null;
408
601
  if (typeof options.poolConfig === "string") {
409
602
  this.pool = new Pool2({ connectionString: options.poolConfig });
410
603
  } else {
@@ -412,6 +605,8 @@ var PostgreSQLAssistantStore = class {
412
605
  }
413
606
  this.migrationManager = new MigrationManager(this.pool);
414
607
  this.migrationManager.register(createAssistantsTable);
608
+ this.migrationManager.register(addAssistantTenantId);
609
+ this.migrationManager.register(changeAssistantPrimaryKey);
415
610
  if (options.autoMigrate !== false) {
416
611
  this.initialize().catch((error) => {
417
612
  console.error("Failed to initialize PostgreSQLAssistantStore:", error);
@@ -421,40 +616,53 @@ var PostgreSQLAssistantStore = class {
421
616
  }
422
617
  /**
423
618
  * Initialize the store and run migrations
619
+ * Uses a promise-based lock to prevent concurrent initialization
424
620
  */
425
621
  async initialize() {
426
622
  if (this.initialized) {
427
623
  return;
428
624
  }
429
- await this.migrationManager.migrate();
430
- this.initialized = true;
625
+ if (this.initPromise) {
626
+ return this.initPromise;
627
+ }
628
+ this.initPromise = (async () => {
629
+ try {
630
+ await this.migrationManager.migrate();
631
+ this.initialized = true;
632
+ } finally {
633
+ this.initPromise = null;
634
+ }
635
+ })();
636
+ return this.initPromise;
431
637
  }
432
638
  /**
433
- * Get all assistants
639
+ * Get all assistants for a tenant
434
640
  */
435
- async getAllAssistants() {
641
+ async getAllAssistants(tenantId) {
436
642
  await this.ensureInitialized();
437
643
  const result = await this.pool.query(
438
644
  `
439
- SELECT id, name, description, graph_definition, created_at, updated_at
645
+ SELECT id, tenant_id, name, description, graph_definition, created_at, updated_at
440
646
  FROM lattice_assistants
647
+ WHERE tenant_id = $1
441
648
  ORDER BY created_at DESC
442
- `
649
+ `,
650
+ [tenantId]
443
651
  );
444
652
  return result.rows.map(this.mapRowToAssistant);
445
653
  }
446
654
  /**
447
655
  * Get assistant by ID
448
656
  */
449
- async getAssistantById(id) {
657
+ async getAssistantById(tenantId, id) {
450
658
  await this.ensureInitialized();
451
659
  const result = await this.pool.query(
452
660
  `
453
- SELECT id, name, description, graph_definition, created_at, updated_at
661
+ SELECT id, tenant_id, name, description, graph_definition, created_at, updated_at
454
662
  FROM lattice_assistants
455
- WHERE id = $1
663
+ WHERE tenant_id = $1 AND id = $2
456
664
  `,
457
- [id]
665
+ [tenantId, id]
458
666
  );
459
667
  if (result.rows.length === 0) {
460
668
  return null;
@@ -464,14 +672,15 @@ var PostgreSQLAssistantStore = class {
464
672
  /**
465
673
  * Create a new assistant
466
674
  */
467
- async createAssistant(id, data) {
675
+ async createAssistant(tenantId, id, data) {
468
676
  await this.ensureInitialized();
469
677
  const now = /* @__PURE__ */ new Date();
470
678
  await this.pool.query(
471
679
  `
472
- INSERT INTO lattice_assistants (id, name, description, graph_definition, created_at, updated_at)
473
- VALUES ($1, $2, $3, $4, $5, $6)
680
+ INSERT INTO lattice_assistants (id, tenant_id, name, description, graph_definition, created_at, updated_at)
681
+ VALUES ($1, $2, $3, $4, $5, $6, $7)
474
682
  ON CONFLICT (id) DO UPDATE SET
683
+ tenant_id = EXCLUDED.tenant_id,
475
684
  name = EXCLUDED.name,
476
685
  description = EXCLUDED.description,
477
686
  graph_definition = EXCLUDED.graph_definition,
@@ -479,6 +688,7 @@ var PostgreSQLAssistantStore = class {
479
688
  `,
480
689
  [
481
690
  id,
691
+ tenantId,
482
692
  data.name,
483
693
  data.description || null,
484
694
  JSON.stringify(data.graphDefinition),
@@ -488,6 +698,7 @@ var PostgreSQLAssistantStore = class {
488
698
  );
489
699
  return {
490
700
  id,
701
+ tenantId,
491
702
  name: data.name,
492
703
  description: data.description,
493
704
  graphDefinition: data.graphDefinition,
@@ -498,9 +709,9 @@ var PostgreSQLAssistantStore = class {
498
709
  /**
499
710
  * Update an existing assistant
500
711
  */
501
- async updateAssistant(id, updates) {
712
+ async updateAssistant(tenantId, id, updates) {
502
713
  await this.ensureInitialized();
503
- const existing = await this.getAssistantById(id);
714
+ const existing = await this.getAssistantById(tenantId, id);
504
715
  if (!existing) {
505
716
  return null;
506
717
  }
@@ -524,43 +735,44 @@ var PostgreSQLAssistantStore = class {
524
735
  }
525
736
  updateFields.push(`updated_at = $${paramIndex++}`);
526
737
  updateValues.push(/* @__PURE__ */ new Date());
738
+ updateValues.push(tenantId);
527
739
  updateValues.push(id);
528
740
  await this.pool.query(
529
741
  `
530
742
  UPDATE lattice_assistants
531
743
  SET ${updateFields.join(", ")}
532
- WHERE id = $${paramIndex}
744
+ WHERE tenant_id = $${paramIndex++} AND id = $${paramIndex}
533
745
  `,
534
746
  updateValues
535
747
  );
536
- return await this.getAssistantById(id);
748
+ return await this.getAssistantById(tenantId, id);
537
749
  }
538
750
  /**
539
751
  * Delete an assistant by ID
540
752
  */
541
- async deleteAssistant(id) {
753
+ async deleteAssistant(tenantId, id) {
542
754
  await this.ensureInitialized();
543
755
  const result = await this.pool.query(
544
756
  `
545
757
  DELETE FROM lattice_assistants
546
- WHERE id = $1
758
+ WHERE tenant_id = $1 AND id = $2
547
759
  `,
548
- [id]
760
+ [tenantId, id]
549
761
  );
550
762
  return result.rowCount !== null && result.rowCount > 0;
551
763
  }
552
764
  /**
553
765
  * Check if assistant exists
554
766
  */
555
- async hasAssistant(id) {
767
+ async hasAssistant(tenantId, id) {
556
768
  await this.ensureInitialized();
557
769
  const result = await this.pool.query(
558
770
  `
559
771
  SELECT 1 FROM lattice_assistants
560
- WHERE id = $1
772
+ WHERE tenant_id = $1 AND id = $2
561
773
  LIMIT 1
562
774
  `,
563
- [id]
775
+ [tenantId, id]
564
776
  );
565
777
  return result.rows.length > 0;
566
778
  }
@@ -587,6 +799,7 @@ var PostgreSQLAssistantStore = class {
587
799
  mapRowToAssistant(row) {
588
800
  return {
589
801
  id: row.id,
802
+ tenantId: row.tenant_id,
590
803
  name: row.name,
591
804
  description: row.description || void 0,
592
805
  graphDefinition: typeof row.graph_definition === "string" ? JSON.parse(row.graph_definition) : row.graph_definition,
@@ -709,6 +922,59 @@ var createScheduledTasksTable = {
709
922
  }
710
923
  };
711
924
 
925
+ // src/migrations/schedule_tenant_migration.ts
926
+ var addScheduleTenantId = {
927
+ version: 18,
928
+ name: "add_schedule_tenant_id",
929
+ up: async (client) => {
930
+ await client.query(`
931
+ ALTER TABLE lattice_scheduled_tasks
932
+ ADD COLUMN IF NOT EXISTS tenant_id VARCHAR(255) DEFAULT 'default'
933
+ `);
934
+ await client.query(`
935
+ UPDATE lattice_scheduled_tasks
936
+ SET tenant_id = 'default'
937
+ WHERE tenant_id IS NULL
938
+ `);
939
+ await client.query(`
940
+ ALTER TABLE lattice_scheduled_tasks
941
+ ALTER COLUMN tenant_id SET NOT NULL
942
+ `);
943
+ await client.query(`
944
+ ALTER TABLE lattice_scheduled_tasks
945
+ ALTER COLUMN tenant_id DROP DEFAULT
946
+ `);
947
+ await client.query(`
948
+ CREATE INDEX IF NOT EXISTS idx_scheduled_tasks_tenant_id
949
+ ON lattice_scheduled_tasks(tenant_id)
950
+ `);
951
+ await client.query(`
952
+ CREATE INDEX IF NOT EXISTS idx_scheduled_tasks_tenant_assistant
953
+ ON lattice_scheduled_tasks(tenant_id, assistant_id)
954
+ WHERE assistant_id IS NOT NULL
955
+ `);
956
+ await client.query(`
957
+ CREATE INDEX IF NOT EXISTS idx_scheduled_tasks_tenant_thread
958
+ ON lattice_scheduled_tasks(tenant_id, thread_id)
959
+ WHERE thread_id IS NOT NULL
960
+ `);
961
+ },
962
+ down: async (client) => {
963
+ await client.query(
964
+ "DROP INDEX IF EXISTS idx_scheduled_tasks_tenant_thread"
965
+ );
966
+ await client.query(
967
+ "DROP INDEX IF EXISTS idx_scheduled_tasks_tenant_assistant"
968
+ );
969
+ await client.query(
970
+ "DROP INDEX IF EXISTS idx_scheduled_tasks_tenant_id"
971
+ );
972
+ await client.query(
973
+ "ALTER TABLE lattice_scheduled_tasks DROP COLUMN IF EXISTS tenant_id"
974
+ );
975
+ }
976
+ };
977
+
712
978
  // src/stores/PostgreSQLScheduleStorage.ts
713
979
  var PostgreSQLScheduleStorage = class {
714
980
  constructor(options) {
@@ -720,6 +986,7 @@ var PostgreSQLScheduleStorage = class {
720
986
  }
721
987
  this.migrationManager = new MigrationManager(this.pool);
722
988
  this.migrationManager.register(createScheduledTasksTable);
989
+ this.migrationManager.register(addScheduleTenantId);
723
990
  if (options.autoMigrate !== false) {
724
991
  this.initialize().catch((error) => {
725
992
  console.error("Failed to initialize PostgreSQLScheduleStorage:", error);
@@ -1016,6 +1283,10 @@ var PostgreSQLScheduleStorage = class {
1016
1283
  const whereClauses = [];
1017
1284
  const values = [];
1018
1285
  let paramIndex = 1;
1286
+ if (filters?.tenantId !== void 0) {
1287
+ whereClauses.push(`tenant_id = $${paramIndex++}`);
1288
+ values.push(filters.tenantId);
1289
+ }
1019
1290
  if (filters?.status !== void 0) {
1020
1291
  whereClauses.push(`status = $${paramIndex++}`);
1021
1292
  values.push(filters.status);
@@ -1060,6 +1331,10 @@ var PostgreSQLScheduleStorage = class {
1060
1331
  const whereClauses = [];
1061
1332
  const values = [];
1062
1333
  let paramIndex = 1;
1334
+ if (filters?.tenantId !== void 0) {
1335
+ whereClauses.push(`tenant_id = $${paramIndex++}`);
1336
+ values.push(filters.tenantId);
1337
+ }
1063
1338
  if (filters?.status !== void 0) {
1064
1339
  whereClauses.push(`status = $${paramIndex++}`);
1065
1340
  values.push(filters.status);
@@ -1192,11 +1467,115 @@ var createSkillsTable = {
1192
1467
  }
1193
1468
  };
1194
1469
 
1470
+ // src/migrations/skill_tenant_migration.ts
1471
+ var addSkillTenantId = {
1472
+ version: 16,
1473
+ name: "add_skill_tenant_id",
1474
+ up: async (client) => {
1475
+ await client.query(`
1476
+ ALTER TABLE lattice_skills
1477
+ ADD COLUMN IF NOT EXISTS tenant_id VARCHAR(255) DEFAULT 'default'
1478
+ `);
1479
+ await client.query(`
1480
+ UPDATE lattice_skills
1481
+ SET tenant_id = 'default'
1482
+ WHERE tenant_id IS NULL
1483
+ `);
1484
+ await client.query(`
1485
+ ALTER TABLE lattice_skills
1486
+ ALTER COLUMN tenant_id SET NOT NULL
1487
+ `);
1488
+ await client.query(`
1489
+ ALTER TABLE lattice_skills
1490
+ ALTER COLUMN tenant_id DROP DEFAULT
1491
+ `);
1492
+ await client.query(`
1493
+ CREATE INDEX IF NOT EXISTS idx_lattice_skills_tenant_id
1494
+ ON lattice_skills(tenant_id)
1495
+ `);
1496
+ },
1497
+ down: async (client) => {
1498
+ await client.query(
1499
+ "DROP INDEX IF EXISTS idx_lattice_skills_tenant_id"
1500
+ );
1501
+ await client.query(
1502
+ "ALTER TABLE lattice_skills DROP COLUMN IF EXISTS tenant_id"
1503
+ );
1504
+ }
1505
+ };
1506
+
1507
+ // src/migrations/skill_pk_migration.ts
1508
+ var changeSkillPrimaryKey = {
1509
+ version: 17,
1510
+ name: "change_skill_primary_key",
1511
+ up: async (client) => {
1512
+ const tableExists = await client.query(`
1513
+ SELECT EXISTS (
1514
+ SELECT FROM information_schema.tables
1515
+ WHERE table_name = 'lattice_skills'
1516
+ )
1517
+ `);
1518
+ if (!tableExists.rows[0].exists) {
1519
+ return;
1520
+ }
1521
+ const pkResult = await client.query(`
1522
+ SELECT a.attname as column_name
1523
+ FROM pg_index i
1524
+ JOIN pg_attribute a ON a.attrelid = i.indrelid AND a.attnum = ANY(i.indkey)
1525
+ WHERE i.indrelid = 'lattice_skills'::regclass
1526
+ AND i.indisprimary
1527
+ `);
1528
+ const pkColumns = pkResult.rows.map((row) => row.column_name);
1529
+ if (pkColumns.includes("tenant_id") && pkColumns.includes("id")) {
1530
+ console.log("Skill table primary key already correct (tenant_id, id)");
1531
+ return;
1532
+ }
1533
+ if (pkColumns.length === 1 && pkColumns[0] === "id") {
1534
+ console.log("Changing skill table primary key from (id) to (tenant_id, id)");
1535
+ const tenantIdExists = await client.query(`
1536
+ SELECT EXISTS (
1537
+ SELECT FROM information_schema.columns
1538
+ WHERE table_name = 'lattice_skills'
1539
+ AND column_name = 'tenant_id'
1540
+ )
1541
+ `);
1542
+ if (!tenantIdExists.rows[0].exists) {
1543
+ await client.query(`
1544
+ ALTER TABLE lattice_skills
1545
+ ADD COLUMN tenant_id VARCHAR(255) DEFAULT 'default'
1546
+ `);
1547
+ }
1548
+ await client.query(`
1549
+ UPDATE lattice_skills
1550
+ SET tenant_id = 'default'
1551
+ WHERE tenant_id IS NULL
1552
+ `);
1553
+ await client.query(`
1554
+ ALTER TABLE lattice_skills
1555
+ ALTER COLUMN tenant_id SET NOT NULL
1556
+ `);
1557
+ await client.query(`
1558
+ ALTER TABLE lattice_skills
1559
+ DROP CONSTRAINT IF EXISTS lattice_skills_pkey
1560
+ `);
1561
+ await client.query(`
1562
+ ALTER TABLE lattice_skills
1563
+ ADD PRIMARY KEY (tenant_id, id)
1564
+ `);
1565
+ console.log("Skill table primary key changed successfully to (tenant_id, id)");
1566
+ }
1567
+ },
1568
+ down: async (client) => {
1569
+ console.warn("Down migration for changeSkillPrimaryKey is not supported");
1570
+ }
1571
+ };
1572
+
1195
1573
  // src/stores/PostgreSQLSkillStore.ts
1196
1574
  var PostgreSQLSkillStore = class {
1197
1575
  constructor(options) {
1198
1576
  this.initialized = false;
1199
1577
  this.ownsPool = true;
1578
+ this.initPromise = null;
1200
1579
  if (typeof options.poolConfig === "string") {
1201
1580
  this.pool = new Pool4({ connectionString: options.poolConfig });
1202
1581
  } else {
@@ -1204,6 +1583,8 @@ var PostgreSQLSkillStore = class {
1204
1583
  }
1205
1584
  this.migrationManager = new MigrationManager(this.pool);
1206
1585
  this.migrationManager.register(createSkillsTable);
1586
+ this.migrationManager.register(addSkillTenantId);
1587
+ this.migrationManager.register(changeSkillPrimaryKey);
1207
1588
  if (options.autoMigrate !== false) {
1208
1589
  this.initialize().catch((error) => {
1209
1590
  console.error("Failed to initialize PostgreSQLSkillStore:", error);
@@ -1211,28 +1592,32 @@ var PostgreSQLSkillStore = class {
1211
1592
  });
1212
1593
  }
1213
1594
  }
1214
- /**
1215
- * Dispose resources and close the connection pool
1216
- * Should be called when the store is no longer needed
1217
- */
1218
- async dispose() {
1219
- if (this.ownsPool && this.pool) {
1220
- await this.pool.end();
1221
- }
1222
- }
1223
1595
  /**
1224
1596
  * Initialize the store and run migrations
1597
+ * Uses a promise-based lock to prevent concurrent initialization
1225
1598
  */
1226
1599
  async initialize() {
1227
1600
  if (this.initialized) {
1228
1601
  return;
1229
1602
  }
1230
- await this.migrationManager.migrate();
1231
- this.initialized = true;
1603
+ if (this.initPromise) {
1604
+ return this.initPromise;
1605
+ }
1606
+ this.initPromise = (async () => {
1607
+ try {
1608
+ await this.migrationManager.migrate();
1609
+ this.initialized = true;
1610
+ } finally {
1611
+ this.initPromise = null;
1612
+ }
1613
+ })();
1614
+ return this.initPromise;
1615
+ }
1616
+ async dispose() {
1617
+ if (this.ownsPool && this.pool) {
1618
+ await this.pool.end();
1619
+ }
1232
1620
  }
1233
- /**
1234
- * Ensure store is initialized
1235
- */
1236
1621
  async ensureInitialized() {
1237
1622
  if (!this.initialized) {
1238
1623
  await this.initialize();
@@ -1244,43 +1629,46 @@ var PostgreSQLSkillStore = class {
1244
1629
  mapRowToSkill(row) {
1245
1630
  return {
1246
1631
  id: row.id,
1632
+ tenantId: row.tenant_id,
1247
1633
  name: row.name,
1248
1634
  description: row.description,
1249
1635
  license: row.license || void 0,
1250
1636
  compatibility: row.compatibility || void 0,
1251
1637
  metadata: row.metadata || {},
1252
1638
  content: row.content || void 0,
1253
- subSkills: Array.isArray(row.sub_skills) && row.sub_skills.length > 0 ? row.sub_skills : void 0,
1639
+ subSkills: Array.isArray(row.sub_skills) ? row.sub_skills : void 0,
1254
1640
  createdAt: row.created_at,
1255
1641
  updatedAt: row.updated_at
1256
1642
  };
1257
1643
  }
1258
1644
  /**
1259
- * Get all skills
1645
+ * Get all skills for a tenant
1260
1646
  */
1261
- async getAllSkills() {
1647
+ async getAllSkills(tenantId) {
1262
1648
  await this.ensureInitialized();
1263
1649
  const result = await this.pool.query(
1264
1650
  `
1265
- SELECT id, name, description, license, compatibility, metadata, content, sub_skills, created_at, updated_at
1651
+ SELECT id, tenant_id, name, description, license, compatibility, metadata, content, sub_skills, created_at, updated_at
1266
1652
  FROM lattice_skills
1653
+ WHERE tenant_id = $1
1267
1654
  ORDER BY created_at DESC
1268
- `
1655
+ `,
1656
+ [tenantId]
1269
1657
  );
1270
1658
  return result.rows.map(this.mapRowToSkill);
1271
1659
  }
1272
1660
  /**
1273
- * Get skill by ID
1661
+ * Get skill by ID for a tenant
1274
1662
  */
1275
- async getSkillById(id) {
1663
+ async getSkillById(tenantId, id) {
1276
1664
  await this.ensureInitialized();
1277
1665
  const result = await this.pool.query(
1278
1666
  `
1279
- SELECT id, name, description, license, compatibility, metadata, content, sub_skills, created_at, updated_at
1667
+ SELECT id, tenant_id, name, description, license, compatibility, metadata, content, sub_skills, created_at, updated_at
1280
1668
  FROM lattice_skills
1281
- WHERE id = $1
1669
+ WHERE tenant_id = $1 AND id = $2
1282
1670
  `,
1283
- [id]
1671
+ [tenantId, id]
1284
1672
  );
1285
1673
  if (result.rows.length === 0) {
1286
1674
  return null;
@@ -1288,17 +1676,17 @@ var PostgreSQLSkillStore = class {
1288
1676
  return this.mapRowToSkill(result.rows[0]);
1289
1677
  }
1290
1678
  /**
1291
- * Create a new skill
1679
+ * Create a new skill for a tenant
1292
1680
  */
1293
- async createSkill(id, data) {
1681
+ async createSkill(tenantId, id, data) {
1294
1682
  await this.ensureInitialized();
1295
1683
  const now = /* @__PURE__ */ new Date();
1296
- const metadata = data.metadata || {};
1297
1684
  await this.pool.query(
1298
1685
  `
1299
- INSERT INTO lattice_skills (id, name, description, license, compatibility, metadata, content, sub_skills, created_at, updated_at)
1300
- VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
1686
+ INSERT INTO lattice_skills (id, tenant_id, name, description, license, compatibility, metadata, content, sub_skills, created_at, updated_at)
1687
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
1301
1688
  ON CONFLICT (id) DO UPDATE SET
1689
+ tenant_id = EXCLUDED.tenant_id,
1302
1690
  name = EXCLUDED.name,
1303
1691
  description = EXCLUDED.description,
1304
1692
  license = EXCLUDED.license,
@@ -1310,166 +1698,180 @@ var PostgreSQLSkillStore = class {
1310
1698
  `,
1311
1699
  [
1312
1700
  id,
1701
+ tenantId,
1313
1702
  data.name,
1314
1703
  data.description,
1315
1704
  data.license || null,
1316
1705
  data.compatibility || null,
1317
- JSON.stringify(metadata),
1706
+ JSON.stringify(data.metadata || {}),
1318
1707
  data.content || null,
1319
1708
  JSON.stringify(data.subSkills || []),
1320
1709
  now,
1321
1710
  now
1322
1711
  ]
1323
1712
  );
1324
- return this.getSkillById(id);
1713
+ return {
1714
+ id,
1715
+ tenantId,
1716
+ name: data.name,
1717
+ description: data.description,
1718
+ license: data.license,
1719
+ compatibility: data.compatibility,
1720
+ metadata: data.metadata || {},
1721
+ content: data.content,
1722
+ subSkills: data.subSkills,
1723
+ createdAt: now,
1724
+ updatedAt: now
1725
+ };
1325
1726
  }
1326
1727
  /**
1327
- * Update an existing skill
1728
+ * Update an existing skill for a tenant
1328
1729
  */
1329
- async updateSkill(id, updates) {
1730
+ async updateSkill(tenantId, id, updates) {
1330
1731
  await this.ensureInitialized();
1732
+ const existing = await this.getSkillById(tenantId, id);
1733
+ if (!existing) {
1734
+ return null;
1735
+ }
1331
1736
  const updateFields = [];
1332
- const values = [];
1737
+ const updateValues = [];
1333
1738
  let paramIndex = 1;
1334
1739
  if (updates.name !== void 0) {
1335
1740
  updateFields.push(`name = $${paramIndex++}`);
1336
- values.push(updates.name);
1741
+ updateValues.push(updates.name);
1337
1742
  }
1338
1743
  if (updates.description !== void 0) {
1339
1744
  updateFields.push(`description = $${paramIndex++}`);
1340
- values.push(updates.description);
1745
+ updateValues.push(updates.description);
1341
1746
  }
1342
1747
  if (updates.license !== void 0) {
1343
1748
  updateFields.push(`license = $${paramIndex++}`);
1344
- values.push(updates.license || null);
1749
+ updateValues.push(updates.license || null);
1345
1750
  }
1346
1751
  if (updates.compatibility !== void 0) {
1347
1752
  updateFields.push(`compatibility = $${paramIndex++}`);
1348
- values.push(updates.compatibility || null);
1753
+ updateValues.push(updates.compatibility || null);
1349
1754
  }
1350
1755
  if (updates.metadata !== void 0) {
1351
1756
  updateFields.push(`metadata = $${paramIndex++}`);
1352
- values.push(JSON.stringify(updates.metadata || {}));
1757
+ updateValues.push(JSON.stringify(updates.metadata || {}));
1353
1758
  }
1354
1759
  if (updates.content !== void 0) {
1355
1760
  updateFields.push(`content = $${paramIndex++}`);
1356
- values.push(updates.content || null);
1761
+ updateValues.push(updates.content || null);
1357
1762
  }
1358
1763
  if (updates.subSkills !== void 0) {
1359
1764
  updateFields.push(`sub_skills = $${paramIndex++}`);
1360
- values.push(JSON.stringify(updates.subSkills || []));
1765
+ updateValues.push(JSON.stringify(updates.subSkills || []));
1361
1766
  }
1362
1767
  if (updateFields.length === 0) {
1363
- return this.getSkillById(id);
1768
+ return existing;
1364
1769
  }
1365
1770
  updateFields.push(`updated_at = $${paramIndex++}`);
1366
- values.push(/* @__PURE__ */ new Date());
1367
- values.push(id);
1771
+ updateValues.push(/* @__PURE__ */ new Date());
1772
+ updateValues.push(tenantId);
1773
+ updateValues.push(id);
1368
1774
  await this.pool.query(
1369
1775
  `
1370
1776
  UPDATE lattice_skills
1371
1777
  SET ${updateFields.join(", ")}
1372
- WHERE id = $${paramIndex}
1778
+ WHERE tenant_id = $${paramIndex++} AND id = $${paramIndex}
1373
1779
  `,
1374
- values
1780
+ updateValues
1375
1781
  );
1376
- return this.getSkillById(id);
1782
+ return await this.getSkillById(tenantId, id);
1377
1783
  }
1378
1784
  /**
1379
- * Delete a skill by ID
1785
+ * Delete a skill by ID for a tenant
1380
1786
  */
1381
- async deleteSkill(id) {
1787
+ async deleteSkill(tenantId, id) {
1382
1788
  await this.ensureInitialized();
1383
1789
  const result = await this.pool.query(
1384
1790
  `
1385
1791
  DELETE FROM lattice_skills
1386
- WHERE id = $1
1792
+ WHERE tenant_id = $1 AND id = $2
1387
1793
  `,
1388
- [id]
1794
+ [tenantId, id]
1389
1795
  );
1390
1796
  return result.rowCount !== null && result.rowCount > 0;
1391
1797
  }
1392
1798
  /**
1393
- * Check if skill exists
1799
+ * Check if skill exists for a tenant
1394
1800
  */
1395
- async hasSkill(id) {
1801
+ async hasSkill(tenantId, id) {
1396
1802
  await this.ensureInitialized();
1397
1803
  const result = await this.pool.query(
1398
1804
  `
1399
- SELECT COUNT(*) as count
1400
- FROM lattice_skills
1401
- WHERE id = $1
1805
+ SELECT 1 FROM lattice_skills
1806
+ WHERE tenant_id = $1 AND id = $2
1807
+ LIMIT 1
1402
1808
  `,
1403
- [id]
1809
+ [tenantId, id]
1404
1810
  );
1405
- return parseInt(result.rows[0].count, 10) > 0;
1811
+ return result.rows.length > 0;
1406
1812
  }
1407
1813
  /**
1408
- * Search skills by metadata
1814
+ * Search skills by metadata within a tenant
1409
1815
  */
1410
- async searchByMetadata(metadataKey, metadataValue) {
1816
+ async searchByMetadata(tenantId, metadataKey, metadataValue) {
1411
1817
  await this.ensureInitialized();
1412
1818
  const result = await this.pool.query(
1413
1819
  `
1414
- SELECT id, name, description, license, compatibility, metadata, content, sub_skills, created_at, updated_at
1820
+ SELECT id, tenant_id, name, description, license, compatibility, metadata, content, sub_skills, created_at, updated_at
1415
1821
  FROM lattice_skills
1416
- WHERE metadata->>$1 = $2
1822
+ WHERE tenant_id = $1 AND metadata ->> $2 = $3
1417
1823
  ORDER BY created_at DESC
1418
1824
  `,
1419
- [metadataKey, metadataValue]
1825
+ [tenantId, metadataKey, metadataValue]
1420
1826
  );
1421
1827
  return result.rows.map(this.mapRowToSkill);
1422
1828
  }
1423
1829
  /**
1424
- * Filter skills by compatibility
1830
+ * Filter skills by compatibility within a tenant
1425
1831
  */
1426
- async filterByCompatibility(compatibility) {
1832
+ async filterByCompatibility(tenantId, compatibility) {
1427
1833
  await this.ensureInitialized();
1428
1834
  const result = await this.pool.query(
1429
1835
  `
1430
- SELECT id, name, description, license, compatibility, metadata, content, sub_skills, created_at, updated_at
1836
+ SELECT id, tenant_id, name, description, license, compatibility, metadata, content, sub_skills, created_at, updated_at
1431
1837
  FROM lattice_skills
1432
- WHERE compatibility = $1
1838
+ WHERE tenant_id = $1 AND compatibility = $2
1433
1839
  ORDER BY created_at DESC
1434
1840
  `,
1435
- [compatibility]
1841
+ [tenantId, compatibility]
1436
1842
  );
1437
1843
  return result.rows.map(this.mapRowToSkill);
1438
1844
  }
1439
1845
  /**
1440
- * Filter skills by license
1846
+ * Filter skills by license within a tenant
1441
1847
  */
1442
- async filterByLicense(license) {
1848
+ async filterByLicense(tenantId, license) {
1443
1849
  await this.ensureInitialized();
1444
1850
  const result = await this.pool.query(
1445
1851
  `
1446
- SELECT id, name, description, license, compatibility, metadata, content, sub_skills, created_at, updated_at
1852
+ SELECT id, tenant_id, name, description, license, compatibility, metadata, content, sub_skills, created_at, updated_at
1447
1853
  FROM lattice_skills
1448
- WHERE license = $1
1854
+ WHERE tenant_id = $1 AND license = $2
1449
1855
  ORDER BY created_at DESC
1450
1856
  `,
1451
- [license]
1857
+ [tenantId, license]
1452
1858
  );
1453
1859
  return result.rows.map(this.mapRowToSkill);
1454
1860
  }
1455
1861
  /**
1456
- * Get sub-skills of a parent skill
1862
+ * Get sub-skills of a parent skill within a tenant
1863
+ * Note: This searches for skills that have the parent skill name in their subSkills array
1457
1864
  */
1458
- async getSubSkills(parentSkillName) {
1865
+ async getSubSkills(tenantId, parentSkillName) {
1459
1866
  await this.ensureInitialized();
1460
- const parentSkill = await this.getSkillById(parentSkillName);
1461
- if (!parentSkill || !parentSkill.subSkills || parentSkill.subSkills.length === 0) {
1462
- return [];
1463
- }
1464
- const placeholders = parentSkill.subSkills.map((_, index) => `$${index + 1}`).join(", ");
1465
1867
  const result = await this.pool.query(
1466
1868
  `
1467
- SELECT id, name, description, license, compatibility, metadata, content, sub_skills, created_at, updated_at
1869
+ SELECT id, tenant_id, name, description, license, compatibility, metadata, content, sub_skills, created_at, updated_at
1468
1870
  FROM lattice_skills
1469
- WHERE name IN (${placeholders})
1871
+ WHERE tenant_id = $1 AND sub_skills @> to_jsonb($2::text)
1470
1872
  ORDER BY created_at DESC
1471
1873
  `,
1472
- parentSkill.subSkills
1874
+ [tenantId, JSON.stringify([parentSkillName])]
1473
1875
  );
1474
1876
  return result.rows.map(this.mapRowToSkill);
1475
1877
  }
@@ -3568,6 +3970,13 @@ export {
3568
3970
  PostgreSQLUserStore,
3569
3971
  PostgreSQLUserTenantLinkStore,
3570
3972
  PostgreSQLWorkspaceStore,
3973
+ addAssistantTenantId,
3974
+ addScheduleTenantId,
3975
+ addSkillTenantId,
3976
+ addThreadTenantId,
3977
+ changeAssistantPrimaryKey,
3978
+ changeSkillPrimaryKey,
3979
+ changeThreadPrimaryKey,
3571
3980
  createAssistantsTable,
3572
3981
  createDatabaseConfigsTable,
3573
3982
  createMcpServerConfigsTable,