@axiom-lattice/local-stores 3.1.5 → 3.1.8

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
@@ -808,12 +808,20 @@ CREATE UNIQUE INDEX IF NOT EXISTS uq_lt_project_room_messages_idempotency
808
808
  ON lt_project_room_messages (tenant_id, room_id, idempotency_key) WHERE idempotency_key IS NOT NULL;
809
809
  CREATE INDEX IF NOT EXISTS idx_lt_project_room_messages_room_created
810
810
  ON lt_project_room_messages (tenant_id, room_id, created_at DESC, id DESC);
811
+ CREATE TABLE IF NOT EXISTS lt_project_room_read_states (
812
+ tenant_id TEXT NOT NULL, room_id TEXT NOT NULL, user_id TEXT NOT NULL,
813
+ last_read_at TEXT NOT NULL, updated_at TEXT NOT NULL,
814
+ PRIMARY KEY (tenant_id, room_id, user_id)
815
+ );
816
+ CREATE INDEX IF NOT EXISTS idx_lt_project_room_read_states_user
817
+ ON lt_project_room_read_states (tenant_id, user_id);
811
818
  `;
812
819
  var tableColumns = {
813
820
  lt_project_rooms: ["id", "tenant_id", "workspace_id", "project_id", "type", "name", "created_at", "updated_at"],
814
821
  lt_project_memberships: ["id", "tenant_id", "project_id", "user_id", "role", "status", "joined_at", "updated_at"],
815
822
  lt_project_bot_memberships: ["id", "tenant_id", "workspace_id", "project_id", "room_id", "assistant_id", "role", "title", "responsibility", "mention_name", "status", "room_thread_id", "joined_at", "updated_at"],
816
- lt_project_room_messages: ["id", "tenant_id", "workspace_id", "project_id", "room_id", "author", "content", "mentions", "reply_to_message_id", "source", "source_id", "idempotency_key", "created_at"]
823
+ lt_project_room_messages: ["id", "tenant_id", "workspace_id", "project_id", "room_id", "author", "content", "mentions", "reply_to_message_id", "source", "source_id", "idempotency_key", "created_at"],
824
+ lt_project_room_read_states: ["tenant_id", "room_id", "user_id", "last_read_at", "updated_at"]
817
825
  };
818
826
  var indexes = {
819
827
  idx_lt_project_memberships_project: { table: "lt_project_memberships", columns: ["tenant_id", "project_id"], unique: false, partial: false },
@@ -822,13 +830,15 @@ var indexes = {
822
830
  idx_lt_project_bot_memberships_project: { table: "lt_project_bot_memberships", columns: ["tenant_id", "project_id"], unique: false, partial: false },
823
831
  idx_lt_project_bot_memberships_room: { table: "lt_project_bot_memberships", columns: ["tenant_id", "room_id"], unique: false, partial: false },
824
832
  uq_lt_project_room_messages_idempotency: { table: "lt_project_room_messages", columns: ["tenant_id", "room_id", "idempotency_key"], unique: true, partial: true, sql: ["idempotency_key is not null"] },
825
- idx_lt_project_room_messages_room_created: { table: "lt_project_room_messages", columns: ["tenant_id", "room_id", "created_at", "id"], unique: false, partial: false, sql: ["created_at desc", "id desc"] }
833
+ idx_lt_project_room_messages_room_created: { table: "lt_project_room_messages", columns: ["tenant_id", "room_id", "created_at", "id"], unique: false, partial: false, sql: ["created_at desc", "id desc"] },
834
+ idx_lt_project_room_read_states_user: { table: "lt_project_room_read_states", columns: ["tenant_id", "user_id"], unique: false, partial: false }
826
835
  };
827
836
  var uniqueConstraints = {
828
837
  lt_project_rooms: [["tenant_id", "id"], ["tenant_id", "project_id", "type"]],
829
838
  lt_project_memberships: [["tenant_id", "id"], ["tenant_id", "project_id", "user_id"]],
830
839
  lt_project_bot_memberships: [["tenant_id", "id"], ["tenant_id", "project_id", "assistant_id"]],
831
- lt_project_room_messages: [["tenant_id", "id"]]
840
+ lt_project_room_messages: [["tenant_id", "id"]],
841
+ lt_project_room_read_states: [["tenant_id", "room_id", "user_id"]]
832
842
  };
833
843
  var checkConstraints = {
834
844
  lt_project_rooms: [{ name: "type", fragment: "type='main'" }],
@@ -840,7 +850,8 @@ var checkConstraints = {
840
850
  { name: "role", fragment: "role in ('coordinator','specialist')" },
841
851
  { name: "status", fragment: "status in ('active','paused','removed')" }
842
852
  ],
843
- lt_project_room_messages: [{ name: "source", fragment: "source in ('user','agent','task','routine','system')" }]
853
+ lt_project_room_messages: [{ name: "source", fragment: "source in ('user','agent','task','routine','system')" }],
854
+ lt_project_room_read_states: []
844
855
  };
845
856
  function rows(db, sql) {
846
857
  const result = db.getRawDb().exec(sql);
@@ -912,10 +923,10 @@ function validateProjectRoomSchema(db) {
912
923
  }
913
924
  for (const [name, expected] of Object.entries(indexes)) {
914
925
  const listed = rows(db, `PRAGMA index_list(${expected.table})`).find((index) => index.name === name);
915
- const columns5 = rows(db, `PRAGMA index_info(${name})`).map((column) => column.name);
926
+ const columns6 = rows(db, `PRAGMA index_info(${name})`).map((column) => column.name);
916
927
  const definition = rows(db, `SELECT sql FROM sqlite_master WHERE type = 'index' AND name = '${name}'`)[0]?.sql;
917
928
  const normalized = typeof definition === "string" ? definition.toLowerCase().replace(/\s+/g, " ") : "";
918
- if (!listed || listed.unique !== Number(expected.unique) || listed.partial !== Number(expected.partial) || columns5.join(",") !== expected.columns.join(",") || expected.sql?.some((fragment) => !normalized.includes(fragment))) {
929
+ if (!listed || listed.unique !== Number(expected.unique) || listed.partial !== Number(expected.partial) || columns6.join(",") !== expected.columns.join(",") || expected.sql?.some((fragment) => !normalized.includes(fragment))) {
919
930
  throw new Error(`Invalid project room schema: index '${name}' is incompatible`);
920
931
  }
921
932
  }
@@ -928,6 +939,30 @@ function ensureProjectRoomSchema(db) {
928
939
  const applied = hasMigrationTable ? rows(db, `SELECT name, version FROM lattice_schema_migrations WHERE store_name = 'project_room' AND name = '${migrationName}'`)[0] : void 0;
929
940
  if (applied) {
930
941
  if (applied.version !== 1) throw new Error(`Invalid project room schema migration version: ${String(applied.version)}`);
942
+ const hasReadStates = rows(db, "SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'lt_project_room_read_states'").length > 0;
943
+ if (!hasReadStates) {
944
+ raw.exec("BEGIN IMMEDIATE");
945
+ try {
946
+ raw.exec(`CREATE TABLE IF NOT EXISTS lt_project_room_read_states (
947
+ tenant_id TEXT NOT NULL, room_id TEXT NOT NULL, user_id TEXT NOT NULL,
948
+ last_read_at TEXT NOT NULL, updated_at TEXT NOT NULL,
949
+ PRIMARY KEY (tenant_id, room_id, user_id)
950
+ ); CREATE INDEX IF NOT EXISTS idx_lt_project_room_read_states_user
951
+ ON lt_project_room_read_states (tenant_id, user_id);`);
952
+ raw.run(
953
+ "INSERT INTO lattice_schema_migrations (store_name, name, version, applied_at) VALUES (?, ?, ?, ?)",
954
+ ["project_room", "create_project_room_read_states", 2, (/* @__PURE__ */ new Date()).toISOString()]
955
+ );
956
+ raw.exec("COMMIT");
957
+ db.save();
958
+ } catch (error) {
959
+ try {
960
+ raw.exec("ROLLBACK");
961
+ } catch {
962
+ }
963
+ throw error;
964
+ }
965
+ }
931
966
  validateProjectRoomSchema(db);
932
967
  marked[schemaMarker] = true;
933
968
  return;
@@ -1367,6 +1402,18 @@ var LocalProjectRoomMessageStore = class {
1367
1402
  const row = this.byId(tenantId, id);
1368
1403
  return row ? mapRow4(row) : null;
1369
1404
  }
1405
+ async countAfter(input) {
1406
+ if (!validDate(input.after)) throw new RangeError("Project room message count horizon date is invalid");
1407
+ const rows2 = this.db.prepare(
1408
+ `SELECT author FROM lt_project_room_messages
1409
+ WHERE tenant_id = ? AND room_id = ? AND created_at > ?`
1410
+ ).all(input.tenantId, input.roomId, input.after.toISOString());
1411
+ return rows2.filter((row) => {
1412
+ if (input.excludeAuthorUserId === void 0) return true;
1413
+ const parsed = parseJson(row.author);
1414
+ return !(record(parsed) && parsed.type === "human" && parsed.userId === input.excludeAuthorUserId);
1415
+ }).length;
1416
+ }
1370
1417
  insert(input, idempotent) {
1371
1418
  return transaction(this.db, () => {
1372
1419
  if (idempotent && input.idempotencyKey) {
@@ -1397,6 +1444,50 @@ var LocalProjectRoomMessageStore = class {
1397
1444
  }
1398
1445
  };
1399
1446
 
1447
+ // src/stores/LocalProjectRoomReadStateStore.ts
1448
+ var columns5 = "tenant_id, room_id, user_id, last_read_at, updated_at";
1449
+ function mapRow5(row) {
1450
+ const lastReadAt = new Date(row.last_read_at);
1451
+ const updatedAt = new Date(row.updated_at);
1452
+ if (Number.isNaN(lastReadAt.getTime()) || Number.isNaN(updatedAt.getTime())) throw new Error("Invalid project room read state row");
1453
+ return { tenantId: row.tenant_id, roomId: row.room_id, userId: row.user_id, lastReadAt, updatedAt };
1454
+ }
1455
+ var LocalProjectRoomReadStateStore = class {
1456
+ /** Creates a store over a managed local database wrapper. */
1457
+ constructor(db) {
1458
+ this.db = db;
1459
+ ensureProjectRoomSchema(db);
1460
+ }
1461
+ /** Returns the user's read marker for a room, or null when never reported. */
1462
+ async get(tenantId, roomId, userId) {
1463
+ const row = this.byKey(tenantId, roomId, userId);
1464
+ return row ? mapRow5(row) : null;
1465
+ }
1466
+ /** Upserts the marker monotonically (an older report never regresses the horizon). */
1467
+ async markRead(input) {
1468
+ if (!validDate(input.lastReadAt)) throw new RangeError("Project room read state lastReadAt is invalid");
1469
+ return transaction(this.db, () => {
1470
+ const existing = this.byKey(input.tenantId, input.roomId, input.userId);
1471
+ const lastReadAt = existing !== void 0 && new Date(existing.last_read_at).getTime() > input.lastReadAt.getTime() ? new Date(existing.last_read_at) : input.lastReadAt;
1472
+ write(
1473
+ this.db,
1474
+ `INSERT INTO lt_project_room_read_states (${columns5})
1475
+ VALUES (?, ?, ?, ?, ?)
1476
+ ON CONFLICT (tenant_id, room_id, user_id) DO UPDATE
1477
+ SET last_read_at = excluded.last_read_at, updated_at = excluded.updated_at`,
1478
+ [input.tenantId, input.roomId, input.userId, lastReadAt.toISOString(), timestamp()]
1479
+ );
1480
+ const row = this.byKey(input.tenantId, input.roomId, input.userId);
1481
+ return mapRow5(row);
1482
+ });
1483
+ }
1484
+ byKey(tenantId, roomId, userId) {
1485
+ return this.db.prepare(
1486
+ `SELECT ${columns5} FROM lt_project_room_read_states WHERE tenant_id = ? AND room_id = ? AND user_id = ?`
1487
+ ).get(tenantId, roomId, userId);
1488
+ }
1489
+ };
1490
+
1400
1491
  // src/stores/LocalUserStore.ts
1401
1492
  var DDL5 = `
1402
1493
  CREATE TABLE IF NOT EXISTS lt_users (
@@ -2224,6 +2315,18 @@ function mapRowToEntry3(row) {
2224
2315
  throw new Error("Failed to decrypt MCP server configuration");
2225
2316
  }
2226
2317
  }
2318
+ if (config.headers && row.is_env_encrypted) {
2319
+ try {
2320
+ const decryptedHeaders = {};
2321
+ for (const [key, value] of Object.entries(config.headers)) {
2322
+ decryptedHeaders[key] = decrypt2(value);
2323
+ }
2324
+ config.headers = decryptedHeaders;
2325
+ } catch (error) {
2326
+ console.error("Failed to decrypt MCP server headers:", error);
2327
+ throw new Error("Failed to decrypt MCP server configuration");
2328
+ }
2329
+ }
2227
2330
  return {
2228
2331
  id: row.id,
2229
2332
  tenantId: row.tenant_id,
@@ -2249,6 +2352,14 @@ function encryptEnvInConfig(config) {
2249
2352
  configCopy.env = encryptedEnv;
2250
2353
  isEnvEncrypted = true;
2251
2354
  }
2355
+ if (configCopy.headers && Object.keys(configCopy.headers).length > 0) {
2356
+ const encryptedHeaders = {};
2357
+ for (const [key, value] of Object.entries(configCopy.headers)) {
2358
+ encryptedHeaders[key] = encrypt2(value);
2359
+ }
2360
+ configCopy.headers = encryptedHeaders;
2361
+ isEnvEncrypted = true;
2362
+ }
2252
2363
  return { config: configCopy, isEnvEncrypted };
2253
2364
  }
2254
2365
 
@@ -3562,7 +3673,7 @@ var LocalChannelInstallationStore = class {
3562
3673
  const row = this.db.prepare(
3563
3674
  `SELECT * FROM lt_channel_installations WHERE id = ?`
3564
3675
  ).get(installationId);
3565
- return row ? mapRow5(row) : null;
3676
+ return row ? mapRow6(row) : null;
3566
3677
  }
3567
3678
  async getInstallationsByTenant(tenantId, channel) {
3568
3679
  let rows2;
@@ -3575,7 +3686,7 @@ var LocalChannelInstallationStore = class {
3575
3686
  `SELECT * FROM lt_channel_installations WHERE tenant_id = ? ORDER BY created_at DESC`
3576
3687
  ).all(tenantId);
3577
3688
  }
3578
- return rows2.map(mapRow5);
3689
+ return rows2.map(mapRow6);
3579
3690
  }
3580
3691
  async getAllInstallations(channel) {
3581
3692
  let rows2;
@@ -3588,7 +3699,7 @@ var LocalChannelInstallationStore = class {
3588
3699
  `SELECT * FROM lt_channel_installations ORDER BY created_at DESC`
3589
3700
  ).all();
3590
3701
  }
3591
- return rows2.map(mapRow5);
3702
+ return rows2.map(mapRow6);
3592
3703
  }
3593
3704
  async createInstallation(tenantId, installationId, data) {
3594
3705
  const now = nowISO();
@@ -3664,7 +3775,7 @@ var LocalChannelInstallationStore = class {
3664
3775
  return result.changes > 0;
3665
3776
  }
3666
3777
  };
3667
- function mapRow5(row) {
3778
+ function mapRow6(row) {
3668
3779
  return {
3669
3780
  id: row.id,
3670
3781
  tenantId: row.tenant_id,
@@ -3894,8 +4005,8 @@ var LocalThreadMessageQueueStore = class {
3894
4005
  this.capacityTail = Promise.resolve();
3895
4006
  this.db = db;
3896
4007
  ensureTable(db, DDL17);
3897
- const columns5 = db.prepare("PRAGMA table_info(lt_thread_message_queue)").all();
3898
- const names = new Set(columns5.map(({ name }) => name));
4008
+ const columns6 = db.prepare("PRAGMA table_info(lt_thread_message_queue)").all();
4009
+ const names = new Set(columns6.map(({ name }) => name));
3899
4010
  if (!names.has("trusted_run_context")) {
3900
4011
  db.run("ALTER TABLE lt_thread_message_queue ADD COLUMN trusted_run_context TEXT");
3901
4012
  }
@@ -5746,6 +5857,7 @@ async function createLocalStoreConfig(options = {}) {
5746
5857
  projectMembership: new LocalProjectMembershipStore(db),
5747
5858
  projectBotMembership: new LocalProjectBotMembershipStore(db),
5748
5859
  projectRoomMessage: new LocalProjectRoomMessageStore(db),
5860
+ projectRoomReadState: new LocalProjectRoomReadStateStore(db),
5749
5861
  user: new LocalUserStore(db),
5750
5862
  tenant: new LocalTenantStore(db),
5751
5863
  userTenantLink: new LocalUserTenantLinkStore(db),
@@ -5822,6 +5934,7 @@ export {
5822
5934
  LocalProjectBotMembershipStore,
5823
5935
  LocalProjectMembershipStore,
5824
5936
  LocalProjectRoomMessageStore,
5937
+ LocalProjectRoomReadStateStore,
5825
5938
  LocalProjectRoomStore,
5826
5939
  LocalProjectStore,
5827
5940
  LocalScheduleStorage,