@axiom-lattice/pg-stores 1.0.37 → 1.0.39

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
@@ -1,5 +1,5 @@
1
1
  // src/index.ts
2
- import { Pool as Pool13, PoolConfig as PoolConfig13 } from "pg";
2
+ import { Pool as Pool15, PoolConfig as PoolConfig15 } from "pg";
3
3
 
4
4
  // src/stores/PostgreSQLThreadStore.ts
5
5
  import { Pool } from "pg";
@@ -4014,6 +4014,90 @@ var createThreadMessageQueueTable = {
4014
4014
  }
4015
4015
  };
4016
4016
 
4017
+ // src/migrations/channel_identity_mapping_migration.ts
4018
+ var createChannelIdentityMappingTables = {
4019
+ version: 20,
4020
+ name: "create_channel_identity_mapping_tables",
4021
+ up: async (client) => {
4022
+ await client.query(`
4023
+ CREATE TABLE IF NOT EXISTS channel_identity_mappings (
4024
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
4025
+ channel VARCHAR(50) NOT NULL,
4026
+ channel_app_id VARCHAR(255) NOT NULL,
4027
+ tenant_id VARCHAR(255) NOT NULL,
4028
+ assistant_id VARCHAR(255) NOT NULL,
4029
+ mapping_mode VARCHAR(20) NOT NULL CHECK (mapping_mode IN ('user', 'group', 'hybrid')),
4030
+ external_subject_type VARCHAR(20) NOT NULL CHECK (external_subject_type IN ('user', 'chat')),
4031
+ external_subject_key VARCHAR(512) NOT NULL,
4032
+ lark_open_id VARCHAR(255),
4033
+ lark_chat_id VARCHAR(255) NOT NULL,
4034
+ lark_message_id VARCHAR(255),
4035
+ thread_id VARCHAR(255) NOT NULL,
4036
+ created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
4037
+ updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
4038
+ last_activity_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
4039
+ UNIQUE (channel, channel_app_id, tenant_id, assistant_id, external_subject_key)
4040
+ )
4041
+ `);
4042
+ await client.query(`
4043
+ CREATE INDEX IF NOT EXISTS idx_channel_identity_lookup
4044
+ ON channel_identity_mappings(channel, channel_app_id, tenant_id, assistant_id, external_subject_key)
4045
+ `);
4046
+ await client.query(`
4047
+ CREATE INDEX IF NOT EXISTS idx_channel_identity_thread
4048
+ ON channel_identity_mappings(thread_id, tenant_id)
4049
+ `);
4050
+ await client.query(`
4051
+ CREATE TABLE IF NOT EXISTS channel_inbound_message_receipts (
4052
+ channel VARCHAR(50) NOT NULL,
4053
+ channel_app_id VARCHAR(255) NOT NULL,
4054
+ external_message_id VARCHAR(255) NOT NULL,
4055
+ tenant_id VARCHAR(255) NOT NULL,
4056
+ status VARCHAR(20) NOT NULL DEFAULT 'processing' CHECK (status IN ('processing', 'completed', 'failed')),
4057
+ thread_id VARCHAR(255),
4058
+ created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
4059
+ updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
4060
+ PRIMARY KEY (channel, channel_app_id, external_message_id)
4061
+ )
4062
+ `);
4063
+ },
4064
+ down: async (client) => {
4065
+ await client.query("DROP TABLE IF EXISTS channel_inbound_message_receipts");
4066
+ await client.query("DROP INDEX IF EXISTS idx_channel_identity_thread");
4067
+ await client.query("DROP INDEX IF EXISTS idx_channel_identity_lookup");
4068
+ await client.query("DROP TABLE IF EXISTS channel_identity_mappings");
4069
+ }
4070
+ };
4071
+
4072
+ // src/migrations/channel_installation_migrations.ts
4073
+ var createChannelInstallationsTable = {
4074
+ version: 21,
4075
+ name: "create_channel_installations_table",
4076
+ up: async (client) => {
4077
+ await client.query(`
4078
+ CREATE TABLE IF NOT EXISTS lattice_channel_installations (
4079
+ id UUID PRIMARY KEY,
4080
+ tenant_id VARCHAR(255) NOT NULL,
4081
+ channel VARCHAR(50) NOT NULL,
4082
+ name VARCHAR(255),
4083
+ config JSONB NOT NULL,
4084
+ created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
4085
+ updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
4086
+ )
4087
+ `);
4088
+ await client.query(`
4089
+ CREATE INDEX IF NOT EXISTS idx_channel_installations_tenant_channel
4090
+ ON lattice_channel_installations(tenant_id, channel)
4091
+ `);
4092
+ },
4093
+ down: async (client) => {
4094
+ await client.query(
4095
+ "DROP INDEX IF EXISTS idx_channel_installations_tenant_channel"
4096
+ );
4097
+ await client.query("DROP TABLE IF EXISTS lattice_channel_installations");
4098
+ }
4099
+ };
4100
+
4017
4101
  // src/stores/ThreadMessageQueueStore.ts
4018
4102
  import crypto from "crypto";
4019
4103
  import {
@@ -4307,13 +4391,422 @@ var ThreadMessageQueueStore = class _ThreadMessageQueueStore {
4307
4391
  }
4308
4392
  };
4309
4393
  var getThreadMessageQueueStore = () => ThreadMessageQueueStore.getInstance();
4394
+
4395
+ // src/stores/ChannelIdentityMappingStore.ts
4396
+ import { Pool as Pool13 } from "pg";
4397
+ var ChannelIdentityMappingStore = class {
4398
+ constructor(options) {
4399
+ this.initialized = false;
4400
+ this.initPromise = null;
4401
+ this.pool = typeof options.poolConfig === "string" ? new Pool13({ connectionString: options.poolConfig }) : new Pool13(options.poolConfig);
4402
+ this.migrationManager = new MigrationManager(this.pool);
4403
+ this.migrationManager.register(createChannelIdentityMappingTables);
4404
+ if (options.autoMigrate !== false) {
4405
+ this.initialize().catch((error) => {
4406
+ console.error("Failed to initialize ChannelIdentityMappingStore:", error);
4407
+ throw error;
4408
+ });
4409
+ }
4410
+ }
4411
+ async initialize() {
4412
+ if (this.initialized) {
4413
+ return;
4414
+ }
4415
+ if (this.initPromise) {
4416
+ return this.initPromise;
4417
+ }
4418
+ this.initPromise = (async () => {
4419
+ try {
4420
+ await this.migrationManager.migrate();
4421
+ this.initialized = true;
4422
+ } finally {
4423
+ this.initPromise = null;
4424
+ }
4425
+ })();
4426
+ return this.initPromise;
4427
+ }
4428
+ async createMapping(input) {
4429
+ await this.ensureInitialized();
4430
+ const result = await this.pool.query(
4431
+ `
4432
+ INSERT INTO channel_identity_mappings (
4433
+ channel,
4434
+ channel_app_id,
4435
+ tenant_id,
4436
+ assistant_id,
4437
+ mapping_mode,
4438
+ external_subject_type,
4439
+ external_subject_key,
4440
+ lark_open_id,
4441
+ lark_chat_id,
4442
+ lark_message_id,
4443
+ thread_id
4444
+ ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
4445
+ RETURNING *
4446
+ `,
4447
+ [
4448
+ input.channel,
4449
+ input.channelAppId,
4450
+ input.tenantId,
4451
+ input.assistantId,
4452
+ input.mappingMode,
4453
+ input.externalSubjectType,
4454
+ input.externalSubjectKey,
4455
+ input.larkOpenId || null,
4456
+ input.larkChatId,
4457
+ input.larkMessageId || null,
4458
+ input.threadId
4459
+ ]
4460
+ );
4461
+ return mapRowToChannelIdentityMapping(result.rows[0]);
4462
+ }
4463
+ async getMappingBySubject(input) {
4464
+ await this.ensureInitialized();
4465
+ const result = await this.pool.query(
4466
+ `
4467
+ SELECT *
4468
+ FROM channel_identity_mappings
4469
+ WHERE channel = $1
4470
+ AND channel_app_id = $2
4471
+ AND tenant_id = $3
4472
+ AND assistant_id = $4
4473
+ AND external_subject_key = $5
4474
+ LIMIT 1
4475
+ `,
4476
+ [
4477
+ input.channel,
4478
+ input.channelAppId,
4479
+ input.tenantId,
4480
+ input.assistantId,
4481
+ input.externalSubjectKey
4482
+ ]
4483
+ );
4484
+ if (result.rows.length === 0) {
4485
+ return null;
4486
+ }
4487
+ return mapRowToChannelIdentityMapping(result.rows[0]);
4488
+ }
4489
+ async claimInboundReceipt(input) {
4490
+ await this.ensureInitialized();
4491
+ const result = await this.pool.query(
4492
+ `
4493
+ WITH existing AS (
4494
+ SELECT status
4495
+ FROM channel_inbound_message_receipts
4496
+ WHERE channel = $1
4497
+ AND channel_app_id = $2
4498
+ AND external_message_id = $3
4499
+ AND tenant_id = $4
4500
+ ),
4501
+ inserted AS (
4502
+ INSERT INTO channel_inbound_message_receipts (
4503
+ channel,
4504
+ channel_app_id,
4505
+ external_message_id,
4506
+ tenant_id,
4507
+ status
4508
+ )
4509
+ SELECT $1, $2, $3, $4, 'processing'
4510
+ WHERE NOT EXISTS (SELECT 1 FROM existing)
4511
+ RETURNING 'inserted'::text AS source, status
4512
+ ),
4513
+ retried AS (
4514
+ UPDATE channel_inbound_message_receipts
4515
+ SET status = 'processing', updated_at = CURRENT_TIMESTAMP
4516
+ WHERE channel = $1
4517
+ AND channel_app_id = $2
4518
+ AND external_message_id = $3
4519
+ AND tenant_id = $4
4520
+ AND EXISTS (SELECT 1 FROM existing WHERE status = 'failed')
4521
+ RETURNING 'retried'::text AS source, status
4522
+ )
4523
+ SELECT source, status FROM inserted
4524
+ UNION ALL
4525
+ SELECT source, status FROM retried
4526
+ UNION ALL
4527
+ SELECT 'existing'::text AS source, status FROM existing
4528
+ WHERE EXISTS (SELECT 1 FROM existing WHERE status IN ('processing', 'completed'))
4529
+ LIMIT 1
4530
+ `,
4531
+ [
4532
+ input.channel,
4533
+ input.channelAppId,
4534
+ input.externalMessageId,
4535
+ input.tenantId
4536
+ ]
4537
+ );
4538
+ const source = result.rows[0]?.source;
4539
+ const status = result.rows[0]?.status || "processing";
4540
+ if (source === "inserted" || source === "retried") {
4541
+ return { accepted: true, status: "processing" };
4542
+ }
4543
+ if (status === "completed") {
4544
+ return { accepted: false, status: "completed" };
4545
+ }
4546
+ return { accepted: false, status: "processing" };
4547
+ }
4548
+ async markInboundReceiptCompleted(input) {
4549
+ await this.ensureInitialized();
4550
+ await this.pool.query(
4551
+ `
4552
+ UPDATE channel_inbound_message_receipts
4553
+ SET status = 'completed', thread_id = $1, updated_at = CURRENT_TIMESTAMP
4554
+ WHERE channel = $2
4555
+ AND channel_app_id = $3
4556
+ AND external_message_id = $4
4557
+ AND tenant_id = $5
4558
+ `,
4559
+ [
4560
+ input.threadId,
4561
+ input.channel,
4562
+ input.channelAppId,
4563
+ input.externalMessageId,
4564
+ input.tenantId
4565
+ ]
4566
+ );
4567
+ }
4568
+ async markInboundReceiptFailed(input) {
4569
+ await this.ensureInitialized();
4570
+ await this.pool.query(
4571
+ `
4572
+ UPDATE channel_inbound_message_receipts
4573
+ SET status = 'failed', updated_at = CURRENT_TIMESTAMP
4574
+ WHERE channel = $1
4575
+ AND channel_app_id = $2
4576
+ AND external_message_id = $3
4577
+ AND tenant_id = $4
4578
+ `,
4579
+ [
4580
+ input.channel,
4581
+ input.channelAppId,
4582
+ input.externalMessageId,
4583
+ input.tenantId
4584
+ ]
4585
+ );
4586
+ }
4587
+ async ensureInitialized() {
4588
+ if (!this.initialized) {
4589
+ await this.initialize();
4590
+ }
4591
+ }
4592
+ };
4593
+ function mapRowToChannelIdentityMapping(row) {
4594
+ return {
4595
+ id: row.id,
4596
+ channel: row.channel,
4597
+ channelAppId: row.channel_app_id,
4598
+ tenantId: row.tenant_id,
4599
+ assistantId: row.assistant_id,
4600
+ mappingMode: row.mapping_mode,
4601
+ externalSubjectType: row.external_subject_type,
4602
+ externalSubjectKey: row.external_subject_key,
4603
+ larkOpenId: row.lark_open_id || void 0,
4604
+ larkChatId: row.lark_chat_id,
4605
+ larkMessageId: row.lark_message_id || void 0,
4606
+ threadId: row.thread_id,
4607
+ createdAt: row.created_at,
4608
+ updatedAt: row.updated_at,
4609
+ lastActivityAt: row.last_activity_at
4610
+ };
4611
+ }
4612
+
4613
+ // src/stores/PostgreSQLChannelInstallationStore.ts
4614
+ import { Pool as Pool14 } from "pg";
4615
+ import { decrypt as decrypt4, encrypt as encrypt4 } from "@axiom-lattice/core";
4616
+ var PostgreSQLChannelInstallationStore = class {
4617
+ constructor(options) {
4618
+ this.initialized = false;
4619
+ this.initPromise = null;
4620
+ this.pool = typeof options.poolConfig === "string" ? new Pool14({ connectionString: options.poolConfig }) : new Pool14(options.poolConfig);
4621
+ this.migrationManager = new MigrationManager(this.pool);
4622
+ this.migrationManager.register(createChannelInstallationsTable);
4623
+ if (options.autoMigrate !== false) {
4624
+ this.initialize().catch((error) => {
4625
+ console.error(
4626
+ "Failed to initialize PostgreSQLChannelInstallationStore:",
4627
+ error
4628
+ );
4629
+ throw error;
4630
+ });
4631
+ }
4632
+ }
4633
+ async initialize() {
4634
+ if (this.initialized) {
4635
+ return;
4636
+ }
4637
+ if (this.initPromise) {
4638
+ return this.initPromise;
4639
+ }
4640
+ this.initPromise = (async () => {
4641
+ try {
4642
+ await this.migrationManager.migrate();
4643
+ this.initialized = true;
4644
+ } finally {
4645
+ this.initPromise = null;
4646
+ }
4647
+ })();
4648
+ return this.initPromise;
4649
+ }
4650
+ async getInstallationById(installationId) {
4651
+ await this.ensureInitialized();
4652
+ const result = await this.pool.query(
4653
+ `
4654
+ SELECT id, tenant_id, channel, name, config, created_at, updated_at
4655
+ FROM lattice_channel_installations
4656
+ WHERE id = $1
4657
+ LIMIT 1
4658
+ `,
4659
+ [installationId]
4660
+ );
4661
+ if (result.rows.length === 0) {
4662
+ return null;
4663
+ }
4664
+ return this.mapRowToInstallation(result.rows[0]);
4665
+ }
4666
+ async getInstallationsByTenant(tenantId, channel) {
4667
+ await this.ensureInitialized();
4668
+ const result = channel ? await this.pool.query(
4669
+ `
4670
+ SELECT id, tenant_id, channel, name, config, created_at, updated_at
4671
+ FROM lattice_channel_installations
4672
+ WHERE tenant_id = $1 AND channel = $2
4673
+ ORDER BY created_at DESC
4674
+ `,
4675
+ [tenantId, channel]
4676
+ ) : await this.pool.query(
4677
+ `
4678
+ SELECT id, tenant_id, channel, name, config, created_at, updated_at
4679
+ FROM lattice_channel_installations
4680
+ WHERE tenant_id = $1
4681
+ ORDER BY created_at DESC
4682
+ `,
4683
+ [tenantId]
4684
+ );
4685
+ return result.rows.map((row) => this.mapRowToInstallation(row));
4686
+ }
4687
+ async createInstallation(tenantId, installationId, data) {
4688
+ await this.ensureInitialized();
4689
+ const now = /* @__PURE__ */ new Date();
4690
+ const encryptedConfig = this.encryptSecrets(data.config);
4691
+ await this.pool.query(
4692
+ `
4693
+ INSERT INTO lattice_channel_installations (
4694
+ id, tenant_id, channel, name, config, created_at, updated_at
4695
+ ) VALUES ($1, $2, $3, $4, $5, $6, $7)
4696
+ `,
4697
+ [
4698
+ installationId,
4699
+ tenantId,
4700
+ data.channel,
4701
+ data.name || null,
4702
+ JSON.stringify(encryptedConfig),
4703
+ now,
4704
+ now
4705
+ ]
4706
+ );
4707
+ return {
4708
+ id: installationId,
4709
+ tenantId,
4710
+ channel: data.channel,
4711
+ name: data.name,
4712
+ config: data.config,
4713
+ createdAt: now,
4714
+ updatedAt: now
4715
+ };
4716
+ }
4717
+ async updateInstallation(tenantId, installationId, updates) {
4718
+ await this.ensureInitialized();
4719
+ const existing = await this.getInstallationById(installationId);
4720
+ if (!existing || existing.tenantId !== tenantId) {
4721
+ return null;
4722
+ }
4723
+ const mergedConfig = {
4724
+ ...existing.config,
4725
+ ...updates.config || {}
4726
+ };
4727
+ const now = /* @__PURE__ */ new Date();
4728
+ await this.pool.query(
4729
+ `
4730
+ UPDATE lattice_channel_installations
4731
+ SET name = $1,
4732
+ config = $2,
4733
+ updated_at = $3
4734
+ WHERE tenant_id = $4 AND id = $5
4735
+ `,
4736
+ [
4737
+ updates.name ?? existing.name ?? null,
4738
+ JSON.stringify(this.encryptSecrets(mergedConfig)),
4739
+ now,
4740
+ tenantId,
4741
+ installationId
4742
+ ]
4743
+ );
4744
+ return {
4745
+ ...existing,
4746
+ name: updates.name ?? existing.name,
4747
+ config: mergedConfig,
4748
+ updatedAt: now
4749
+ };
4750
+ }
4751
+ async deleteInstallation(tenantId, installationId) {
4752
+ await this.ensureInitialized();
4753
+ const result = await this.pool.query(
4754
+ `
4755
+ DELETE FROM lattice_channel_installations
4756
+ WHERE tenant_id = $1 AND id = $2
4757
+ `,
4758
+ [tenantId, installationId]
4759
+ );
4760
+ return (result.rowCount || 0) > 0;
4761
+ }
4762
+ async ensureInitialized() {
4763
+ if (!this.initialized) {
4764
+ await this.initialize();
4765
+ }
4766
+ }
4767
+ mapRowToInstallation(row) {
4768
+ return {
4769
+ id: row.id,
4770
+ tenantId: row.tenant_id,
4771
+ channel: row.channel,
4772
+ name: row.name || void 0,
4773
+ config: this.decryptSecrets(
4774
+ typeof row.config === "string" ? JSON.parse(row.config) : row.config
4775
+ ),
4776
+ createdAt: row.created_at,
4777
+ updatedAt: row.updated_at
4778
+ };
4779
+ }
4780
+ encryptSecrets(config) {
4781
+ return {
4782
+ ...config,
4783
+ appSecret: typeof config.appSecret === "string" ? encrypt4(config.appSecret) : config.appSecret,
4784
+ verificationToken: typeof config.verificationToken === "string" ? encrypt4(config.verificationToken) : config.verificationToken,
4785
+ encryptKey: typeof config.encryptKey === "string" ? encrypt4(config.encryptKey) : config.encryptKey
4786
+ };
4787
+ }
4788
+ decryptSecrets(config) {
4789
+ return {
4790
+ appId: String(config.appId || ""),
4791
+ appSecret: typeof config.appSecret === "string" ? decrypt4(config.appSecret) : "",
4792
+ verificationToken: typeof config.verificationToken === "string" ? decrypt4(config.verificationToken) : void 0,
4793
+ encryptKey: typeof config.encryptKey === "string" ? decrypt4(config.encryptKey) : void 0,
4794
+ mappingMode: config.mappingMode === "user" || config.mappingMode === "group" || config.mappingMode === "hybrid" ? config.mappingMode : "hybrid",
4795
+ assistantId: String(config.assistantId || ""),
4796
+ workspaceId: typeof config.workspaceId === "string" ? config.workspaceId : void 0,
4797
+ projectId: typeof config.projectId === "string" ? config.projectId : void 0
4798
+ };
4799
+ }
4800
+ };
4310
4801
  export {
4311
4802
  AddMessageParams,
4803
+ ChannelIdentityMappingStore,
4312
4804
  MigrationManager,
4313
4805
  PendingMessage,
4314
- Pool13 as Pool,
4315
- PoolConfig13 as PoolConfig,
4806
+ Pool15 as Pool,
4807
+ PoolConfig15 as PoolConfig,
4316
4808
  PostgreSQLAssistantStore,
4809
+ PostgreSQLChannelInstallationStore,
4317
4810
  PostgreSQLDatabaseConfigStore,
4318
4811
  PostgreSQLMcpServerConfigStore,
4319
4812
  PostgreSQLMetricsServerConfigStore,
@@ -4335,6 +4828,8 @@ export {
4335
4828
  changeSkillPrimaryKey,
4336
4829
  changeThreadPrimaryKey,
4337
4830
  createAssistantsTable,
4831
+ createChannelIdentityMappingTables,
4832
+ createChannelInstallationsTable,
4338
4833
  createDatabaseConfigsTable,
4339
4834
  createMcpServerConfigsTable,
4340
4835
  createMetricsConfigsTable,