@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/.turbo/turbo-build.log +10 -10
- package/CHANGELOG.md +30 -0
- package/dist/index.d.mts +26 -2
- package/dist/index.d.ts +26 -2
- package/dist/index.js +126 -12
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +125 -12
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
- package/src/__tests__/LocalMcpServerConfigStore.test.ts +77 -0
- package/src/__tests__/LocalProjectRoomReadStateStore.test.ts +101 -0
- package/src/__tests__/LocalProjectRoomStores.test.ts +39 -0
- package/src/createLocalStoreConfig.ts +2 -0
- package/src/index.ts +1 -0
- package/src/stores/LocalMcpServerConfigStore.ts +22 -0
- package/src/stores/LocalProjectRoomMessageStore.ts +14 -0
- package/src/stores/LocalProjectRoomReadStateStore.ts +50 -0
- package/src/stores/project-room-schema.ts +30 -0
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
|
|
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) ||
|
|
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 ?
|
|
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(
|
|
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(
|
|
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
|
|
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
|
|
3898
|
-
const names = new Set(
|
|
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,
|