@melandlabs/memory-store 0.1.0 → 0.1.2
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/README.md +4 -5
- package/dist/{config-MlK9f9CM.d.ts → config-D6MzksfT.d.ts} +20 -14
- package/dist/http.d.ts +1 -2
- package/dist/http.js +28 -39
- package/dist/http.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +28 -39
- package/dist/index.js.map +1 -1
- package/dist/mcp.d.ts +2 -2
- package/dist/mcp.js +28 -39
- package/dist/mcp.js.map +1 -1
- package/dist/search/unified-search.d.ts +1 -1
- package/dist/search/unified-search.js +28 -39
- package/dist/search/unified-search.js.map +1 -1
- package/dist/server/cli-http.js +28 -39
- package/dist/server/cli-http.js.map +1 -1
- package/dist/server/cli-mcp.js +28 -39
- package/dist/server/cli-mcp.js.map +1 -1
- package/dist/storage/postgres-raw-message-factory.d.ts +1 -1
- package/dist/storage/raw-message-store.d.ts +12 -5
- package/dist/storage/raw-message-store.js +28 -39
- package/dist/storage/raw-message-store.js.map +1 -1
- package/dist/storage/sqlite-raw-message-store.d.ts +21 -6
- package/dist/storage/sqlite-raw-message-store.js +12 -18
- package/dist/storage/sqlite-raw-message-store.js.map +1 -1
- package/dist/storage/sqlite-vector-index.d.ts +4 -3
- package/dist/storage/sqlite-vector-index.js +23 -16
- package/dist/storage/sqlite-vector-index.js.map +1 -1
- package/package.json +7 -5
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { mkdirSync } from 'fs';
|
|
2
|
-
import {
|
|
2
|
+
import { homedir } from 'os';
|
|
3
|
+
import { join, dirname } from 'path';
|
|
3
4
|
import { SQLiteRawMessageManager } from '@melandlabs/sqlite';
|
|
4
5
|
import '@melandlabs/ai/memory';
|
|
5
6
|
import '@melandlabs/rag';
|
|
@@ -25,16 +26,17 @@ function isRawMessageChromaEnabled() {
|
|
|
25
26
|
|
|
26
27
|
// src/storage/sqlite-raw-message-store.ts
|
|
27
28
|
var manager = null;
|
|
28
|
-
|
|
29
|
-
|
|
29
|
+
var DEFAULT_SQLITE_PATH = join(homedir(), ".opencontext", "memory", "store.db");
|
|
30
|
+
function resolveSQLiteRawMessageDbPath() {
|
|
31
|
+
const fromEnv = process.env.MEMORY_STORE_DB_PATH?.trim();
|
|
32
|
+
return fromEnv && fromEnv.length > 0 ? fromEnv : DEFAULT_SQLITE_PATH;
|
|
30
33
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
}
|
|
34
|
+
function isSQLiteRawMessageStorageAvailable(_env) {
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
async function getSQLiteRawMessageManager(_env) {
|
|
36
38
|
if (!manager) {
|
|
37
|
-
const dbPath =
|
|
39
|
+
const dbPath = resolveSQLiteRawMessageDbPath();
|
|
38
40
|
mkdirSync(dirname(dbPath), { recursive: true });
|
|
39
41
|
manager = new SQLiteRawMessageManager({
|
|
40
42
|
dbPath,
|
|
@@ -54,15 +56,7 @@ async function closeSQLiteRawMessageManager() {
|
|
|
54
56
|
function __resetSQLiteRawMessageManagerForTests() {
|
|
55
57
|
manager = null;
|
|
56
58
|
}
|
|
57
|
-
function resolveEnv(env) {
|
|
58
|
-
if (env) return env;
|
|
59
|
-
return {
|
|
60
|
-
isTauriMode: () => process.env.IS_TAURI === "true" || typeof process.env.TAURI_MODE === "string",
|
|
61
|
-
getTauriDbPath: () => process.env.TAURI_DB_PATH ?? "",
|
|
62
|
-
getTauriDataDir: () => process.env.TAURI_DATA_DIR ?? ""
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
59
|
|
|
66
|
-
export { __resetSQLiteRawMessageManagerForTests, closeSQLiteRawMessageManager, getSQLiteRawMessageManager, isSQLiteRawMessageStorageAvailable };
|
|
60
|
+
export { __resetSQLiteRawMessageManagerForTests, closeSQLiteRawMessageManager, getSQLiteRawMessageManager, isSQLiteRawMessageStorageAvailable, resolveSQLiteRawMessageDbPath };
|
|
67
61
|
//# sourceMappingURL=sqlite-raw-message-store.js.map
|
|
68
62
|
//# sourceMappingURL=sqlite-raw-message-store.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/storage/chroma-memory-index.ts","../../src/storage/sqlite-raw-message-store.ts"],"names":[],"mappings":";;;;;;;AAiBA,IAAM,2BAAA,GAA8B;AAAA,EACnC,kCAAA;AAAA,EACA,6BAAA;AAAA,EACA;AACD,CAAA;AA+CA,SAAS,WAAW,IAAA,EAAiC;AACpD,EAAA,KAAA,MAAW,OAAO,IAAA,EAAM;AACvB,IAAA,MAAM,QAAQ,OAAA,CAAQ,GAAA,CAAI,GAAG,CAAA,EAAG,IAAA,GAAO,WAAA,EAAY;AACnD,IAAA,IAAI,KAAA,EAAO;AACV,MAAA,OAAO,KAAA;AAAA,IACR;AAAA,EACD;AACA,EAAA,OAAO,EAAA;AACR;AAEO,SAAS,yBAAA,GAAqC;AACpD,EAAA,OAAO,UAAA,CAAW,2BAA2B,CAAA,KAAM,QAAA;AACpD;;;ACnEA,IAAI,OAAA,GAA0C,IAAA;AAEvC,SAAS,mCAAmC,GAAA,EAA+B;AACjF,EAAA,OAAO,UAAA,CAAW,GAAG,CAAA,CAAE,WAAA,EAAY;AACpC;AAEA,eAAsB,2BAA2B,GAAA,EAAwD;AACxG,EAAA,MAAM,CAAA,GAAI,WAAW,GAAG,CAAA;AACxB,EAAA,IAAI,CAAC,CAAA,CAAE,WAAA,EAAY,EAAG;AACrB,IAAA,MAAM,IAAI,MAAM,6DAA6D,CAAA;AAAA,EAC9E;AAEA,EAAA,IAAI,CAAC,OAAA,EAAS;AACb,IAAA,MAAM,SAAS,CAAA,CAAE,cAAA,IAAiB,IAAK,OAAA,CAAQ,IAAI,aAAA,IAAiB,EAAA;AACpE,IAAA,SAAA,CAAU,QAAQ,MAAM,CAAA,EAAG,EAAE,SAAA,EAAW,MAAM,CAAA;AAC9C,IAAA,OAAA,GAAU,IAAI,uBAAA,CAAwB;AAAA,MACrC,MAAA;AAAA,MACA,kBAAA,EAAoB,CAAC,yBAAA;AAA0B,KAC/C,CAAA;AACD,IAAA,MAAM,QAAQ,IAAA,EAAK;AAAA,EACpB;AAEA,EAAA,OAAO,OAAA;AACR;AAEA,eAAsB,4BAAA,GAA8C;AACnE,EAAA,IAAI,CAAC,OAAA,EAAS;AACb,IAAA;AAAA,EACD;AACA,EAAA,MAAM,QAAQ,KAAA,EAAM;AACpB,EAAA,OAAA,GAAU,IAAA;AACX;AAKO,SAAS,sCAAA,GAA+C;AAC9D,EAAA,OAAA,GAAU,IAAA;AACX;AAEA,SAAS,WAAW,GAAA,EAAsC;AACzD,EAAA,IAAI,KAAK,OAAO,GAAA;AAChB,EAAA,OAAO;AAAA,IACN,WAAA,EAAa,MAAM,OAAA,CAAQ,GAAA,CAAI,aAAa,MAAA,IAAU,OAAO,OAAA,CAAQ,GAAA,CAAI,UAAA,KAAe,QAAA;AAAA,IACxF,cAAA,EAAgB,MAAM,OAAA,CAAQ,GAAA,CAAI,aAAA,IAAiB,EAAA;AAAA,IACnD,eAAA,EAAiB,MAAM,OAAA,CAAQ,GAAA,CAAI,cAAA,IAAkB;AAAA,GACtD;AACD","file":"sqlite-raw-message-store.js","sourcesContent":["/**\n * Chroma-backed raw-message + insight vector index.\n *\n * Detects the active backend at call time (defaults to sqlite-vec if\n * no env override is present). The actual URL/collection values come\n * from env vars so the standalone memory daemon can pick them up\n * without an injected config.\n */\n\nimport { buildMemoryRecordEmbeddingDocument } from \"@melandlabs/ai/memory\";\nimport { ChromaVectorStore } from \"@melandlabs/rag\";\nimport type { DocumentChunk } from \"@melandlabs/rag/vector-service\";\n\nimport { type RawMessage, rawMessageToMemoryRecord } from \"../contracts\";\n\nconst DEFAULT_RAW_MESSAGES_COLLECTION = \"opencontext_raw_messages\";\nconst DEFAULT_INSIGHTS_COLLECTION = \"opencontext_insights\";\nconst RAW_VECTOR_BACKEND_ENV_KEYS = [\n\t\"RAW_MESSAGE_VECTOR_STORE_BACKEND\",\n\t\"MEMORY_VECTOR_STORE_BACKEND\",\n\t\"VECTOR_STORE_BACKEND\",\n] as const;\nconst INSIGHT_VECTOR_BACKEND_ENV_KEYS = [\n\t\"INSIGHT_VECTOR_STORE_BACKEND\",\n\t\"MEMORY_VECTOR_STORE_BACKEND\",\n\t\"VECTOR_STORE_BACKEND\",\n] as const;\n\nexport interface ChromaInsightVectorInput {\n\tinsightId: string;\n\tuserId: string;\n\tbotId: string;\n\tcontent: string;\n\tcontentHash: string;\n\tembedding: number[];\n\tembeddingModel: string;\n\tembeddingDimensions: number;\n\ttitle?: unknown;\n\tdescription?: unknown;\n\ttaskLabel?: unknown;\n\timportance?: unknown;\n\turgency?: unknown;\n\tplatform?: unknown;\n\taccount?: unknown;\n\ttime?: unknown;\n\tarchived?: unknown;\n}\n\nexport interface ChromaInsightSearchResult {\n\tid: string;\n\tcontent: string;\n\tsimilarity: number;\n\tmetadata: {\n\t\tbotId: string;\n\t\ttitle: string;\n\t\tdescription: string;\n\t\ttaskLabel: string;\n\t\timportance: string;\n\t\turgency: string;\n\t\tplatform: string | null;\n\t\taccount: string | null;\n\t\ttime: Date | null;\n\t\tembeddingModel: string;\n\t\tembeddingDimensions: number;\n\t\tcontentHash: string;\n\t};\n}\n\nfunction getBackend(keys: readonly string[]): string {\n\tfor (const key of keys) {\n\t\tconst value = process.env[key]?.trim().toLowerCase();\n\t\tif (value) {\n\t\t\treturn value;\n\t\t}\n\t}\n\treturn \"\";\n}\n\nexport function isRawMessageChromaEnabled(): boolean {\n\treturn getBackend(RAW_VECTOR_BACKEND_ENV_KEYS) === \"chroma\";\n}\n\nexport function isInsightChromaEnabled(): boolean {\n\treturn getBackend(INSIGHT_VECTOR_BACKEND_ENV_KEYS) === \"chroma\";\n}\n\nfunction getRawMessagesCollectionName(): string {\n\treturn (\n\t\tprocess.env.CHROMA_RAW_MESSAGES_COLLECTION ||\n\t\tprocess.env.CHROMA_MEMORY_COLLECTION ||\n\t\tDEFAULT_RAW_MESSAGES_COLLECTION\n\t);\n}\n\nfunction getInsightsCollectionName(): string {\n\treturn process.env.CHROMA_INSIGHTS_COLLECTION || DEFAULT_INSIGHTS_COLLECTION;\n}\n\nfunction getChromaStore(collectionName: string): ChromaVectorStore {\n\treturn new ChromaVectorStore({\n\t\turl: process.env.CHROMA_URL,\n\t\tcollectionName,\n\t});\n}\n\nfunction normalizeTimestampToMs(value: number | undefined): number {\n\tif (!Number.isFinite(value)) {\n\t\treturn 0;\n\t}\n\tif ((value as number) < 1e11) {\n\t\treturn Math.floor((value as number) * 1000);\n\t}\n\treturn Math.floor(value as number);\n}\n\nfunction toStringValue(value: unknown, fallback = \"\"): string {\n\treturn typeof value === \"string\" && value.trim().length > 0 ? value.trim() : fallback;\n}\n\nfunction toNullableString(value: unknown): string | null {\n\treturn typeof value === \"string\" && value.trim().length > 0 ? value.trim() : null;\n}\n\nfunction toBooleanValue(value: unknown): boolean {\n\treturn value === true || value === \"true\" || value === 1 || value === \"1\";\n}\n\nfunction toDateValue(value: unknown): Date | null {\n\tif (value instanceof Date) {\n\t\treturn Number.isNaN(value.getTime()) ? null : value;\n\t}\n\tif (typeof value === \"string\" || typeof value === \"number\") {\n\t\tconst date = new Date(value);\n\t\treturn Number.isNaN(date.getTime()) ? null : date;\n\t}\n\treturn null;\n}\n\nfunction rawMessageToChunk(message: RawMessage): DocumentChunk | null {\n\tif (!message.embedding || message.embedding.length === 0) {\n\t\treturn null;\n\t}\n\n\tconst record = rawMessageToMemoryRecord(message);\n\tconst document = buildMemoryRecordEmbeddingDocument(record);\n\tif (!document.content) {\n\t\treturn null;\n\t}\n\n\treturn {\n\t\tid: message.messageId,\n\t\tdocumentId: message.messageId,\n\t\tcontent: document.content,\n\t\tembedding: message.embedding,\n\t\tmetadata: {\n\t\t\tsourceType: \"raw_message\",\n\t\t\tuserId: message.userId,\n\t\t\tplatform: message.platform,\n\t\t\tbotId: message.botId,\n\t\t\tchannel: message.channel,\n\t\t\tperson: message.person,\n\t\t\ttimestamp: normalizeTimestampToMs(message.timestamp),\n\t\t\tmemoryStage: message.memoryStage,\n\t\t\tembeddingModel: message.embeddingModel,\n\t\t\tembeddingDimensions: message.embeddingDimensions,\n\t\t\tcontentHash: message.embeddingContentHash,\n\t\t\tarchived: Boolean(message.archivedAt),\n\t\t\trole: typeof message.metadata?.role === \"string\" ? message.metadata.role : undefined,\n\t\t},\n\t};\n}\n\nexport async function upsertRawMessagesToChroma(messages: RawMessage[]): Promise<number> {\n\tif (!isRawMessageChromaEnabled() || messages.length === 0) {\n\t\treturn 0;\n\t}\n\n\tconst chunks = messages.map(rawMessageToChunk).filter((chunk): chunk is DocumentChunk => chunk !== null);\n\tif (chunks.length === 0) {\n\t\treturn 0;\n\t}\n\n\tconst store = getChromaStore(getRawMessagesCollectionName());\n\tawait store.addChunks(chunks);\n\treturn chunks.length;\n}\n\nexport async function searchRawMessagesWithChroma(input: {\n\tuserId: string;\n\tqueryEmbedding: number[];\n\tlimit: number;\n\tthreshold: number;\n\tbotId?: string;\n}): Promise<\n\tArray<{\n\t\tid: string;\n\t\tcontent: string;\n\t\tsimilarity: number;\n\t\tmetadata: Record<string, unknown>;\n\t}>\n> {\n\tif (!isRawMessageChromaEnabled() || input.queryEmbedding.length === 0) {\n\t\treturn [];\n\t}\n\n\tconst store = getChromaStore(getRawMessagesCollectionName());\n\tconst results = await store.similaritySearch(\n\t\tinput.queryEmbedding,\n\t\tMath.max(input.limit * 5, input.limit),\n\t\tinput.userId,\n\t);\n\n\treturn results\n\t\t.filter((result) => {\n\t\t\tconst metadata = result.metadata ?? {};\n\t\t\tif (toBooleanValue(metadata.archived)) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tif (input.botId && metadata.botId !== input.botId) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\treturn result.score >= input.threshold;\n\t\t})\n\t\t.map((result) => ({\n\t\t\tid: result.id,\n\t\t\tcontent: result.content,\n\t\t\tsimilarity: result.score,\n\t\t\tmetadata: result.metadata ?? {},\n\t\t}))\n\t\t.slice(0, input.limit);\n}\n\nexport async function upsertInsightsToChroma(insights: ChromaInsightVectorInput[]): Promise<number> {\n\tif (!isInsightChromaEnabled() || insights.length === 0) {\n\t\treturn 0;\n\t}\n\n\tconst chunks = insights\n\t\t.filter(\n\t\t\t(item) => item.content.trim().length > 0 && Array.isArray(item.embedding) && item.embedding.length > 0,\n\t\t)\n\t\t.map<DocumentChunk>((item) => ({\n\t\t\tid: item.insightId,\n\t\t\tdocumentId: item.insightId,\n\t\t\tcontent: item.content,\n\t\t\tembedding: item.embedding,\n\t\t\tmetadata: {\n\t\t\t\tsourceType: \"insight\",\n\t\t\t\tinsightId: item.insightId,\n\t\t\t\tuserId: item.userId,\n\t\t\t\tbotId: item.botId,\n\t\t\t\ttitle: toStringValue(item.title),\n\t\t\t\tdescription: toStringValue(item.description),\n\t\t\t\ttaskLabel: toStringValue(item.taskLabel),\n\t\t\t\timportance: toStringValue(item.importance),\n\t\t\t\turgency: toStringValue(item.urgency),\n\t\t\t\tplatform: toNullableString(item.platform),\n\t\t\t\taccount: toNullableString(item.account),\n\t\t\t\ttime: toDateValue(item.time)?.toISOString(),\n\t\t\t\tembeddingModel: item.embeddingModel,\n\t\t\t\tembeddingDimensions: item.embeddingDimensions,\n\t\t\t\tcontentHash: item.contentHash,\n\t\t\t\tarchived: toBooleanValue(item.archived),\n\t\t\t},\n\t\t}));\n\n\tif (chunks.length === 0) {\n\t\treturn 0;\n\t}\n\n\tconst store = getChromaStore(getInsightsCollectionName());\n\tawait store.addChunks(chunks);\n\treturn chunks.length;\n}\n\nexport async function searchInsightsWithChroma(input: {\n\tuserId: string;\n\tqueryEmbedding: number[];\n\tlimit: number;\n\tthreshold: number;\n\tbotIds?: string[];\n\tincludeArchived?: boolean;\n}): Promise<ChromaInsightSearchResult[]> {\n\tif (!isInsightChromaEnabled() || input.queryEmbedding.length === 0) {\n\t\treturn [];\n\t}\n\n\tconst botIdSet = input.botIds && input.botIds.length > 0 ? new Set(input.botIds) : null;\n\tconst store = getChromaStore(getInsightsCollectionName());\n\tconst results = await store.similaritySearch(\n\t\tinput.queryEmbedding,\n\t\tMath.max(input.limit * 5, input.limit),\n\t\tinput.userId,\n\t);\n\n\treturn results\n\t\t.filter((result) => {\n\t\t\tconst metadata = result.metadata ?? {};\n\t\t\tif (!input.includeArchived && toBooleanValue(metadata.archived)) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tif (botIdSet && !botIdSet.has(String(metadata.botId ?? \"\"))) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\treturn result.score >= input.threshold;\n\t\t})\n\t\t.map((result) => {\n\t\t\tconst metadata = result.metadata ?? {};\n\t\t\treturn {\n\t\t\t\tid: result.id,\n\t\t\t\tcontent: result.content,\n\t\t\t\tsimilarity: result.score,\n\t\t\t\tmetadata: {\n\t\t\t\t\tbotId: toStringValue(metadata.botId),\n\t\t\t\t\ttitle: toStringValue(metadata.title),\n\t\t\t\t\tdescription: toStringValue(metadata.description),\n\t\t\t\t\ttaskLabel: toStringValue(metadata.taskLabel),\n\t\t\t\t\timportance: toStringValue(metadata.importance),\n\t\t\t\t\turgency: toStringValue(metadata.urgency),\n\t\t\t\t\tplatform: toNullableString(metadata.platform),\n\t\t\t\t\taccount: toNullableString(metadata.account),\n\t\t\t\t\ttime: toDateValue(metadata.time),\n\t\t\t\t\tembeddingModel: toStringValue(metadata.embeddingModel),\n\t\t\t\t\tembeddingDimensions: Number(metadata.embeddingDimensions ?? 0),\n\t\t\t\t\tcontentHash: toStringValue(metadata.contentHash),\n\t\t\t\t},\n\t\t\t};\n\t\t})\n\t\t.slice(0, input.limit);\n}\n","/**\n * SQLite-backed raw message store.\n *\n * Singleton manager used in Tauri (local) mode. The Tauri env is\n * sourced from the injected `MemoryStoreConfig.env`.\n */\n\nimport { mkdirSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\nimport { SQLiteRawMessageManager } from \"@melandlabs/sqlite\";\nimport type { MemoryStoreEnv } from \"../config\";\nimport { isRawMessageChromaEnabled } from \"./chroma-memory-index\";\n\nlet manager: SQLiteRawMessageManager | null = null;\n\nexport function isSQLiteRawMessageStorageAvailable(env?: MemoryStoreEnv): boolean {\n\treturn resolveEnv(env).isTauriMode();\n}\n\nexport async function getSQLiteRawMessageManager(env?: MemoryStoreEnv): Promise<SQLiteRawMessageManager> {\n\tconst e = resolveEnv(env);\n\tif (!e.isTauriMode()) {\n\t\tthrow new Error(\"SQLite raw message storage is only available in Tauri mode.\");\n\t}\n\n\tif (!manager) {\n\t\tconst dbPath = e.getTauriDbPath?.() ?? process.env.TAURI_DB_PATH ?? \"\";\n\t\tmkdirSync(dirname(dbPath), { recursive: true });\n\t\tmanager = new SQLiteRawMessageManager({\n\t\t\tdbPath,\n\t\t\tenableVectorSearch: !isRawMessageChromaEnabled(),\n\t\t});\n\t\tawait manager.init();\n\t}\n\n\treturn manager;\n}\n\nexport async function closeSQLiteRawMessageManager(): Promise<void> {\n\tif (!manager) {\n\t\treturn;\n\t}\n\tawait manager.close();\n\tmanager = null;\n}\n\n/**\n * Test-only: reset the singleton. Used by vitest between test cases.\n */\nexport function __resetSQLiteRawMessageManagerForTests(): void {\n\tmanager = null;\n}\n\nfunction resolveEnv(env?: MemoryStoreEnv): MemoryStoreEnv {\n\tif (env) return env;\n\treturn {\n\t\tisTauriMode: () => process.env.IS_TAURI === \"true\" || typeof process.env.TAURI_MODE === \"string\",\n\t\tgetTauriDbPath: () => process.env.TAURI_DB_PATH ?? \"\",\n\t\tgetTauriDataDir: () => process.env.TAURI_DATA_DIR ?? \"\",\n\t};\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/storage/chroma-memory-index.ts","../../src/storage/sqlite-raw-message-store.ts"],"names":[],"mappings":";;;;;;;;AAiBA,IAAM,2BAAA,GAA8B;AAAA,EACnC,kCAAA;AAAA,EACA,6BAAA;AAAA,EACA;AACD,CAAA;AA+CA,SAAS,WAAW,IAAA,EAAiC;AACpD,EAAA,KAAA,MAAW,OAAO,IAAA,EAAM;AACvB,IAAA,MAAM,QAAQ,OAAA,CAAQ,GAAA,CAAI,GAAG,CAAA,EAAG,IAAA,GAAO,WAAA,EAAY;AACnD,IAAA,IAAI,KAAA,EAAO;AACV,MAAA,OAAO,KAAA;AAAA,IACR;AAAA,EACD;AACA,EAAA,OAAO,EAAA;AACR;AAEO,SAAS,yBAAA,GAAqC;AACpD,EAAA,OAAO,UAAA,CAAW,2BAA2B,CAAA,KAAM,QAAA;AACpD;;;AC5DA,IAAI,OAAA,GAA0C,IAAA;AAE9C,IAAM,sBAAsB,IAAA,CAAK,OAAA,EAAQ,EAAG,cAAA,EAAgB,UAAU,UAAU,CAAA;AAMzE,SAAS,6BAAA,GAAwC;AACvD,EAAA,MAAM,OAAA,GAAU,OAAA,CAAQ,GAAA,CAAI,oBAAA,EAAsB,IAAA,EAAK;AACvD,EAAA,OAAO,OAAA,IAAW,OAAA,CAAQ,MAAA,GAAS,CAAA,GAAI,OAAA,GAAU,mBAAA;AAClD;AAMO,SAAS,mCAAmC,IAAA,EAAgC;AAClF,EAAA,OAAO,IAAA;AACR;AAEA,eAAsB,2BAA2B,IAAA,EAAyD;AACzG,EAAA,IAAI,CAAC,OAAA,EAAS;AACb,IAAA,MAAM,SAAS,6BAAA,EAA8B;AAC7C,IAAA,SAAA,CAAU,QAAQ,MAAM,CAAA,EAAG,EAAE,SAAA,EAAW,MAAM,CAAA;AAC9C,IAAA,OAAA,GAAU,IAAI,uBAAA,CAAwB;AAAA,MACrC,MAAA;AAAA,MACA,kBAAA,EAAoB,CAAC,yBAAA;AAA0B,KAC/C,CAAA;AACD,IAAA,MAAM,QAAQ,IAAA,EAAK;AAAA,EACpB;AAEA,EAAA,OAAO,OAAA;AACR;AAEA,eAAsB,4BAAA,GAA8C;AACnE,EAAA,IAAI,CAAC,OAAA,EAAS;AACb,IAAA;AAAA,EACD;AACA,EAAA,MAAM,QAAQ,KAAA,EAAM;AACpB,EAAA,OAAA,GAAU,IAAA;AACX;AAKO,SAAS,sCAAA,GAA+C;AAC9D,EAAA,OAAA,GAAU,IAAA;AACX","file":"sqlite-raw-message-store.js","sourcesContent":["/**\n * Chroma-backed raw-message + insight vector index.\n *\n * Detects the active backend at call time (defaults to sqlite-vec if\n * no env override is present). The actual URL/collection values come\n * from env vars so the standalone memory daemon can pick them up\n * without an injected config.\n */\n\nimport { buildMemoryRecordEmbeddingDocument } from \"@melandlabs/ai/memory\";\nimport { ChromaVectorStore } from \"@melandlabs/rag\";\nimport type { DocumentChunk } from \"@melandlabs/rag/vector-service\";\n\nimport { type RawMessage, rawMessageToMemoryRecord } from \"../contracts\";\n\nconst DEFAULT_RAW_MESSAGES_COLLECTION = \"opencontext_raw_messages\";\nconst DEFAULT_INSIGHTS_COLLECTION = \"opencontext_insights\";\nconst RAW_VECTOR_BACKEND_ENV_KEYS = [\n\t\"RAW_MESSAGE_VECTOR_STORE_BACKEND\",\n\t\"MEMORY_VECTOR_STORE_BACKEND\",\n\t\"VECTOR_STORE_BACKEND\",\n] as const;\nconst INSIGHT_VECTOR_BACKEND_ENV_KEYS = [\n\t\"INSIGHT_VECTOR_STORE_BACKEND\",\n\t\"MEMORY_VECTOR_STORE_BACKEND\",\n\t\"VECTOR_STORE_BACKEND\",\n] as const;\n\nexport interface ChromaInsightVectorInput {\n\tinsightId: string;\n\tuserId: string;\n\tbotId: string;\n\tcontent: string;\n\tcontentHash: string;\n\tembedding: number[];\n\tembeddingModel: string;\n\tembeddingDimensions: number;\n\ttitle?: unknown;\n\tdescription?: unknown;\n\ttaskLabel?: unknown;\n\timportance?: unknown;\n\turgency?: unknown;\n\tplatform?: unknown;\n\taccount?: unknown;\n\ttime?: unknown;\n\tarchived?: unknown;\n}\n\nexport interface ChromaInsightSearchResult {\n\tid: string;\n\tcontent: string;\n\tsimilarity: number;\n\tmetadata: {\n\t\tbotId: string;\n\t\ttitle: string;\n\t\tdescription: string;\n\t\ttaskLabel: string;\n\t\timportance: string;\n\t\turgency: string;\n\t\tplatform: string | null;\n\t\taccount: string | null;\n\t\ttime: Date | null;\n\t\tembeddingModel: string;\n\t\tembeddingDimensions: number;\n\t\tcontentHash: string;\n\t};\n}\n\nfunction getBackend(keys: readonly string[]): string {\n\tfor (const key of keys) {\n\t\tconst value = process.env[key]?.trim().toLowerCase();\n\t\tif (value) {\n\t\t\treturn value;\n\t\t}\n\t}\n\treturn \"\";\n}\n\nexport function isRawMessageChromaEnabled(): boolean {\n\treturn getBackend(RAW_VECTOR_BACKEND_ENV_KEYS) === \"chroma\";\n}\n\nexport function isInsightChromaEnabled(): boolean {\n\treturn getBackend(INSIGHT_VECTOR_BACKEND_ENV_KEYS) === \"chroma\";\n}\n\nfunction getRawMessagesCollectionName(): string {\n\treturn (\n\t\tprocess.env.CHROMA_RAW_MESSAGES_COLLECTION ||\n\t\tprocess.env.CHROMA_MEMORY_COLLECTION ||\n\t\tDEFAULT_RAW_MESSAGES_COLLECTION\n\t);\n}\n\nfunction getInsightsCollectionName(): string {\n\treturn process.env.CHROMA_INSIGHTS_COLLECTION || DEFAULT_INSIGHTS_COLLECTION;\n}\n\nfunction getChromaStore(collectionName: string): ChromaVectorStore {\n\treturn new ChromaVectorStore({\n\t\turl: process.env.CHROMA_URL,\n\t\tcollectionName,\n\t});\n}\n\nfunction normalizeTimestampToMs(value: number | undefined): number {\n\tif (!Number.isFinite(value)) {\n\t\treturn 0;\n\t}\n\tif ((value as number) < 1e11) {\n\t\treturn Math.floor((value as number) * 1000);\n\t}\n\treturn Math.floor(value as number);\n}\n\nfunction toStringValue(value: unknown, fallback = \"\"): string {\n\treturn typeof value === \"string\" && value.trim().length > 0 ? value.trim() : fallback;\n}\n\nfunction toNullableString(value: unknown): string | null {\n\treturn typeof value === \"string\" && value.trim().length > 0 ? value.trim() : null;\n}\n\nfunction toBooleanValue(value: unknown): boolean {\n\treturn value === true || value === \"true\" || value === 1 || value === \"1\";\n}\n\nfunction toDateValue(value: unknown): Date | null {\n\tif (value instanceof Date) {\n\t\treturn Number.isNaN(value.getTime()) ? null : value;\n\t}\n\tif (typeof value === \"string\" || typeof value === \"number\") {\n\t\tconst date = new Date(value);\n\t\treturn Number.isNaN(date.getTime()) ? null : date;\n\t}\n\treturn null;\n}\n\nfunction rawMessageToChunk(message: RawMessage): DocumentChunk | null {\n\tif (!message.embedding || message.embedding.length === 0) {\n\t\treturn null;\n\t}\n\n\tconst record = rawMessageToMemoryRecord(message);\n\tconst document = buildMemoryRecordEmbeddingDocument(record);\n\tif (!document.content) {\n\t\treturn null;\n\t}\n\n\treturn {\n\t\tid: message.messageId,\n\t\tdocumentId: message.messageId,\n\t\tcontent: document.content,\n\t\tembedding: message.embedding,\n\t\tmetadata: {\n\t\t\tsourceType: \"raw_message\",\n\t\t\tuserId: message.userId,\n\t\t\tplatform: message.platform,\n\t\t\tbotId: message.botId,\n\t\t\tchannel: message.channel,\n\t\t\tperson: message.person,\n\t\t\ttimestamp: normalizeTimestampToMs(message.timestamp),\n\t\t\tmemoryStage: message.memoryStage,\n\t\t\tembeddingModel: message.embeddingModel,\n\t\t\tembeddingDimensions: message.embeddingDimensions,\n\t\t\tcontentHash: message.embeddingContentHash,\n\t\t\tarchived: Boolean(message.archivedAt),\n\t\t\trole: typeof message.metadata?.role === \"string\" ? message.metadata.role : undefined,\n\t\t},\n\t};\n}\n\nexport async function upsertRawMessagesToChroma(messages: RawMessage[]): Promise<number> {\n\tif (!isRawMessageChromaEnabled() || messages.length === 0) {\n\t\treturn 0;\n\t}\n\n\tconst chunks = messages.map(rawMessageToChunk).filter((chunk): chunk is DocumentChunk => chunk !== null);\n\tif (chunks.length === 0) {\n\t\treturn 0;\n\t}\n\n\tconst store = getChromaStore(getRawMessagesCollectionName());\n\tawait store.addChunks(chunks);\n\treturn chunks.length;\n}\n\nexport async function searchRawMessagesWithChroma(input: {\n\tuserId: string;\n\tqueryEmbedding: number[];\n\tlimit: number;\n\tthreshold: number;\n\tbotId?: string;\n}): Promise<\n\tArray<{\n\t\tid: string;\n\t\tcontent: string;\n\t\tsimilarity: number;\n\t\tmetadata: Record<string, unknown>;\n\t}>\n> {\n\tif (!isRawMessageChromaEnabled() || input.queryEmbedding.length === 0) {\n\t\treturn [];\n\t}\n\n\tconst store = getChromaStore(getRawMessagesCollectionName());\n\tconst results = await store.similaritySearch(\n\t\tinput.queryEmbedding,\n\t\tMath.max(input.limit * 5, input.limit),\n\t\tinput.userId,\n\t);\n\n\treturn results\n\t\t.filter((result) => {\n\t\t\tconst metadata = result.metadata ?? {};\n\t\t\tif (toBooleanValue(metadata.archived)) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tif (input.botId && metadata.botId !== input.botId) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\treturn result.score >= input.threshold;\n\t\t})\n\t\t.map((result) => ({\n\t\t\tid: result.id,\n\t\t\tcontent: result.content,\n\t\t\tsimilarity: result.score,\n\t\t\tmetadata: result.metadata ?? {},\n\t\t}))\n\t\t.slice(0, input.limit);\n}\n\nexport async function upsertInsightsToChroma(insights: ChromaInsightVectorInput[]): Promise<number> {\n\tif (!isInsightChromaEnabled() || insights.length === 0) {\n\t\treturn 0;\n\t}\n\n\tconst chunks = insights\n\t\t.filter(\n\t\t\t(item) => item.content.trim().length > 0 && Array.isArray(item.embedding) && item.embedding.length > 0,\n\t\t)\n\t\t.map<DocumentChunk>((item) => ({\n\t\t\tid: item.insightId,\n\t\t\tdocumentId: item.insightId,\n\t\t\tcontent: item.content,\n\t\t\tembedding: item.embedding,\n\t\t\tmetadata: {\n\t\t\t\tsourceType: \"insight\",\n\t\t\t\tinsightId: item.insightId,\n\t\t\t\tuserId: item.userId,\n\t\t\t\tbotId: item.botId,\n\t\t\t\ttitle: toStringValue(item.title),\n\t\t\t\tdescription: toStringValue(item.description),\n\t\t\t\ttaskLabel: toStringValue(item.taskLabel),\n\t\t\t\timportance: toStringValue(item.importance),\n\t\t\t\turgency: toStringValue(item.urgency),\n\t\t\t\tplatform: toNullableString(item.platform),\n\t\t\t\taccount: toNullableString(item.account),\n\t\t\t\ttime: toDateValue(item.time)?.toISOString(),\n\t\t\t\tembeddingModel: item.embeddingModel,\n\t\t\t\tembeddingDimensions: item.embeddingDimensions,\n\t\t\t\tcontentHash: item.contentHash,\n\t\t\t\tarchived: toBooleanValue(item.archived),\n\t\t\t},\n\t\t}));\n\n\tif (chunks.length === 0) {\n\t\treturn 0;\n\t}\n\n\tconst store = getChromaStore(getInsightsCollectionName());\n\tawait store.addChunks(chunks);\n\treturn chunks.length;\n}\n\nexport async function searchInsightsWithChroma(input: {\n\tuserId: string;\n\tqueryEmbedding: number[];\n\tlimit: number;\n\tthreshold: number;\n\tbotIds?: string[];\n\tincludeArchived?: boolean;\n}): Promise<ChromaInsightSearchResult[]> {\n\tif (!isInsightChromaEnabled() || input.queryEmbedding.length === 0) {\n\t\treturn [];\n\t}\n\n\tconst botIdSet = input.botIds && input.botIds.length > 0 ? new Set(input.botIds) : null;\n\tconst store = getChromaStore(getInsightsCollectionName());\n\tconst results = await store.similaritySearch(\n\t\tinput.queryEmbedding,\n\t\tMath.max(input.limit * 5, input.limit),\n\t\tinput.userId,\n\t);\n\n\treturn results\n\t\t.filter((result) => {\n\t\t\tconst metadata = result.metadata ?? {};\n\t\t\tif (!input.includeArchived && toBooleanValue(metadata.archived)) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tif (botIdSet && !botIdSet.has(String(metadata.botId ?? \"\"))) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\treturn result.score >= input.threshold;\n\t\t})\n\t\t.map((result) => {\n\t\t\tconst metadata = result.metadata ?? {};\n\t\t\treturn {\n\t\t\t\tid: result.id,\n\t\t\t\tcontent: result.content,\n\t\t\t\tsimilarity: result.score,\n\t\t\t\tmetadata: {\n\t\t\t\t\tbotId: toStringValue(metadata.botId),\n\t\t\t\t\ttitle: toStringValue(metadata.title),\n\t\t\t\t\tdescription: toStringValue(metadata.description),\n\t\t\t\t\ttaskLabel: toStringValue(metadata.taskLabel),\n\t\t\t\t\timportance: toStringValue(metadata.importance),\n\t\t\t\t\turgency: toStringValue(metadata.urgency),\n\t\t\t\t\tplatform: toNullableString(metadata.platform),\n\t\t\t\t\taccount: toNullableString(metadata.account),\n\t\t\t\t\ttime: toDateValue(metadata.time),\n\t\t\t\t\tembeddingModel: toStringValue(metadata.embeddingModel),\n\t\t\t\t\tembeddingDimensions: Number(metadata.embeddingDimensions ?? 0),\n\t\t\t\t\tcontentHash: toStringValue(metadata.contentHash),\n\t\t\t\t},\n\t\t\t};\n\t\t})\n\t\t.slice(0, input.limit);\n}\n","/**\n * SQLite-backed raw message store.\n *\n * Singleton manager. Local SQLite is the default backend for the memory\n * store — no host-mode check, no env-var opt-in. The DB path is resolved\n * from (in order):\n * 1. `MEMORY_STORE_DB_PATH` — recommended knob\n * 2. `~/.opencontext/memory/store.db` — last-resort default\n *\n * The `env?` parameter on the exported functions is kept for back-compat\n * with the historical `MemoryStoreEnv` interface but is no longer read.\n */\n\nimport { mkdirSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport { SQLiteRawMessageManager } from \"@melandlabs/sqlite\";\nimport type { MemoryStoreEnv } from \"../config\";\nimport { isRawMessageChromaEnabled } from \"./chroma-memory-index\";\n\nlet manager: SQLiteRawMessageManager | null = null;\n\nconst DEFAULT_SQLITE_PATH = join(homedir(), \".opencontext\", \"memory\", \"store.db\");\n\n/**\n * Resolve the SQLite DB file path from env vars. Exposed so other storage\n * layers (vector index) can stay in sync with the same default.\n */\nexport function resolveSQLiteRawMessageDbPath(): string {\n\tconst fromEnv = process.env.MEMORY_STORE_DB_PATH?.trim();\n\treturn fromEnv && fromEnv.length > 0 ? fromEnv : DEFAULT_SQLITE_PATH;\n}\n\n/**\n * Local SQLite is the default. Every host (Tauri, server, CLI, tests) can\n * open the manager; the only failure mode is filesystem write permission.\n */\nexport function isSQLiteRawMessageStorageAvailable(_env?: MemoryStoreEnv): boolean {\n\treturn true;\n}\n\nexport async function getSQLiteRawMessageManager(_env?: MemoryStoreEnv): Promise<SQLiteRawMessageManager> {\n\tif (!manager) {\n\t\tconst dbPath = resolveSQLiteRawMessageDbPath();\n\t\tmkdirSync(dirname(dbPath), { recursive: true });\n\t\tmanager = new SQLiteRawMessageManager({\n\t\t\tdbPath,\n\t\t\tenableVectorSearch: !isRawMessageChromaEnabled(),\n\t\t});\n\t\tawait manager.init();\n\t}\n\n\treturn manager;\n}\n\nexport async function closeSQLiteRawMessageManager(): Promise<void> {\n\tif (!manager) {\n\t\treturn;\n\t}\n\tawait manager.close();\n\tmanager = null;\n}\n\n/**\n * Test-only: reset the singleton. Used by vitest between test cases.\n */\nexport function __resetSQLiteRawMessageManagerForTests(): void {\n\tmanager = null;\n}\n\n// Kept for back-compat with the historical `MemoryStoreEnv` interface.\n// The resolved env is no longer read by the SQLite layer, but the symbol\n// stays exported so external code that still calls `resolveEnv` compiles.\nfunction resolveEnv(_env?: MemoryStoreEnv): MemoryStoreEnv {\n\treturn {};\n}\n"]}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { VectorSearchResult } from '@melandlabs/rag/vector-service';
|
|
2
|
-
import { b as MemoryStoreEnv } from '../config-
|
|
2
|
+
import { b as MemoryStoreEnv } from '../config-D6MzksfT.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* SQLite-vec-backed raw-message + insight vector index.
|
|
6
6
|
*
|
|
7
7
|
* Mirrors the chroma-memory-index interface but uses the local
|
|
8
|
-
* sqlite-vec store. The DB path is resolved
|
|
8
|
+
* sqlite-vec store. The DB path is resolved from the same env-var chain
|
|
9
|
+
* the raw-message SQLite layer uses.
|
|
9
10
|
*/
|
|
10
11
|
|
|
11
12
|
interface SQLiteInsightVectorInput {
|
|
@@ -35,7 +36,7 @@ interface SQLiteInsightSearchInput {
|
|
|
35
36
|
botIds?: string[];
|
|
36
37
|
includeArchived?: boolean;
|
|
37
38
|
}
|
|
38
|
-
declare function isInsightSQLiteVecEnabled(
|
|
39
|
+
declare function isInsightSQLiteVecEnabled(_env?: MemoryStoreEnv): boolean;
|
|
39
40
|
declare function upsertInsightsToSQLiteVec(insights: SQLiteInsightVectorInput[], env?: MemoryStoreEnv): Promise<number>;
|
|
40
41
|
declare function searchInsightsWithSQLiteVec(input: SQLiteInsightSearchInput, env?: MemoryStoreEnv): Promise<VectorSearchResult[]>;
|
|
41
42
|
|
|
@@ -1,32 +1,39 @@
|
|
|
1
|
+
import 'fs';
|
|
2
|
+
import { homedir } from 'os';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import '@melandlabs/sqlite';
|
|
5
|
+
import '@melandlabs/ai/memory';
|
|
6
|
+
import '@melandlabs/rag';
|
|
7
|
+
|
|
8
|
+
// src/storage/sqlite-raw-message-store.ts
|
|
9
|
+
|
|
10
|
+
// src/storage/sqlite-raw-message-store.ts
|
|
11
|
+
var DEFAULT_SQLITE_PATH = join(homedir(), ".opencontext", "memory", "store.db");
|
|
12
|
+
function resolveSQLiteRawMessageDbPath() {
|
|
13
|
+
const fromEnv = process.env.MEMORY_STORE_DB_PATH?.trim();
|
|
14
|
+
return fromEnv && fromEnv.length > 0 ? fromEnv : DEFAULT_SQLITE_PATH;
|
|
15
|
+
}
|
|
16
|
+
|
|
1
17
|
// src/storage/sqlite-vector-index.ts
|
|
2
18
|
function getMemoryVectorStoreBackend() {
|
|
3
19
|
const backend = (process.env.INSIGHT_VECTOR_STORE_BACKEND || process.env.MEMORY_VECTOR_STORE_BACKEND || process.env.VECTOR_STORE_BACKEND || "sqlite-vec").toLowerCase();
|
|
4
20
|
return backend === "chroma" ? "chroma" : "sqlite-vec";
|
|
5
21
|
}
|
|
6
|
-
function isInsightSQLiteVecEnabled(
|
|
7
|
-
return
|
|
8
|
-
}
|
|
9
|
-
function resolveEnv(env) {
|
|
10
|
-
if (env) return env;
|
|
11
|
-
return {
|
|
12
|
-
isTauriMode: () => process.env.IS_TAURI === "true" || typeof process.env.TAURI_MODE === "string",
|
|
13
|
-
getTauriDbPath: () => process.env.TAURI_DB_PATH ?? "",
|
|
14
|
-
getTauriDataDir: () => process.env.TAURI_DATA_DIR ?? ""
|
|
15
|
-
};
|
|
22
|
+
function isInsightSQLiteVecEnabled(_env) {
|
|
23
|
+
return getMemoryVectorStoreBackend() === "sqlite-vec";
|
|
16
24
|
}
|
|
17
|
-
async function getInsightSQLiteVecStore(
|
|
18
|
-
const
|
|
19
|
-
const dbPath = e.getTauriDbPath?.() ?? process.env.TAURI_DB_PATH ?? "";
|
|
25
|
+
async function getInsightSQLiteVecStore(_env) {
|
|
26
|
+
const dbPath = resolveSQLiteRawMessageDbPath();
|
|
20
27
|
const { getSQLiteVecStore } = await import('@melandlabs/rag/sqlite-vec-store');
|
|
21
28
|
return await getSQLiteVecStore(dbPath, void 0, {
|
|
22
29
|
collectionName: process.env.SQLITE_VEC_INSIGHTS_COLLECTION || "opencontext_insights"
|
|
23
30
|
});
|
|
24
31
|
}
|
|
25
32
|
async function upsertInsightsToSQLiteVec(insights, env) {
|
|
26
|
-
if (!isInsightSQLiteVecEnabled(
|
|
33
|
+
if (!isInsightSQLiteVecEnabled() || insights.length === 0) {
|
|
27
34
|
return 0;
|
|
28
35
|
}
|
|
29
|
-
const store = await getInsightSQLiteVecStore(
|
|
36
|
+
const store = await getInsightSQLiteVecStore();
|
|
30
37
|
await store.addChunks(
|
|
31
38
|
insights.map((item) => ({
|
|
32
39
|
id: item.insightId,
|
|
@@ -54,7 +61,7 @@ async function upsertInsightsToSQLiteVec(insights, env) {
|
|
|
54
61
|
return insights.length;
|
|
55
62
|
}
|
|
56
63
|
async function searchInsightsWithSQLiteVec(input, env) {
|
|
57
|
-
const store = await getInsightSQLiteVecStore(
|
|
64
|
+
const store = await getInsightSQLiteVecStore();
|
|
58
65
|
const overfetchLimit = Math.max(input.limit * 8, input.limit);
|
|
59
66
|
const results = await store.similaritySearchWithOptions(input.queryEmbedding, {
|
|
60
67
|
limit: overfetchLimit,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/storage/sqlite-vector-index.ts"],"names":[],"mappings":"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","file":"sqlite-vector-index.js","sourcesContent":["/**\n * SQLite-vec-backed raw-message + insight vector index.\n *\n * Mirrors the chroma-memory-index interface but uses the local\n * sqlite-vec store. The DB path is resolved via `MemoryStoreEnv`.\n */\n\nimport type { VectorSearchResult } from \"@melandlabs/rag/vector-service\";\nimport type { MemoryStoreEnv } from \"../config\";\n\nexport interface SQLiteInsightVectorInput {\n\tinsightId: string;\n\tuserId: string;\n\tbotId: string;\n\tcontent: string;\n\tcontentHash: string;\n\tembedding: number[];\n\tembeddingModel: string;\n\tembeddingDimensions: number;\n\ttitle?: unknown;\n\tdescription?: unknown;\n\ttaskLabel?: unknown;\n\timportance?: unknown;\n\turgency?: unknown;\n\tplatform?: unknown;\n\taccount?: unknown;\n\ttime?: unknown;\n\tarchived?: unknown;\n}\n\nexport interface SQLiteInsightSearchInput {\n\tuserId: string;\n\tqueryEmbedding: number[];\n\tlimit: number;\n\tthreshold: number;\n\tbotIds?: string[];\n\tincludeArchived?: boolean;\n}\n\nfunction getMemoryVectorStoreBackend(): \"chroma\" | \"sqlite-vec\" {\n\tconst backend = (\n\t\tprocess.env.INSIGHT_VECTOR_STORE_BACKEND ||\n\t\tprocess.env.MEMORY_VECTOR_STORE_BACKEND ||\n\t\tprocess.env.VECTOR_STORE_BACKEND ||\n\t\t\"sqlite-vec\"\n\t).toLowerCase();\n\treturn backend === \"chroma\" ? \"chroma\" : \"sqlite-vec\";\n}\n\nexport function isInsightSQLiteVecEnabled(env?: MemoryStoreEnv): boolean {\n\treturn resolveEnv(env).isTauriMode() && getMemoryVectorStoreBackend() === \"sqlite-vec\";\n}\n\nfunction resolveEnv(env?: MemoryStoreEnv): MemoryStoreEnv {\n\tif (env) return env;\n\treturn {\n\t\tisTauriMode: () => process.env.IS_TAURI === \"true\" || typeof process.env.TAURI_MODE === \"string\",\n\t\tgetTauriDbPath: () => process.env.TAURI_DB_PATH ?? \"\",\n\t\tgetTauriDataDir: () => process.env.TAURI_DATA_DIR ?? \"\",\n\t};\n}\n\nasync function getInsightSQLiteVecStore(env: MemoryStoreEnv) {\n\tconst e = resolveEnv(env);\n\tconst dbPath = e.getTauriDbPath?.() ?? process.env.TAURI_DB_PATH ?? \"\";\n\tconst { getSQLiteVecStore } = await import(\"@melandlabs/rag/sqlite-vec-store\");\n\treturn await getSQLiteVecStore(dbPath, undefined, {\n\t\tcollectionName: process.env.SQLITE_VEC_INSIGHTS_COLLECTION || \"opencontext_insights\",\n\t});\n}\n\nexport async function upsertInsightsToSQLiteVec(\n\tinsights: SQLiteInsightVectorInput[],\n\tenv?: MemoryStoreEnv,\n): Promise<number> {\n\tif (!isInsightSQLiteVecEnabled(env) || insights.length === 0) {\n\t\treturn 0;\n\t}\n\n\tconst store = await getInsightSQLiteVecStore(resolveEnv(env));\n\tawait store.addChunks(\n\t\tinsights.map((item) => ({\n\t\t\tid: item.insightId,\n\t\t\tdocumentId: item.insightId,\n\t\t\tcontent: item.content,\n\t\t\tembedding: item.embedding,\n\t\t\tmetadata: {\n\t\t\t\tuserId: item.userId,\n\t\t\t\tbotId: item.botId,\n\t\t\t\tcontentHash: item.contentHash,\n\t\t\t\tembeddingModel: item.embeddingModel,\n\t\t\t\tembeddingDimensions: item.embeddingDimensions,\n\t\t\t\ttitle: toStringValue(item.title),\n\t\t\t\tdescription: toStringValue(item.description),\n\t\t\t\ttaskLabel: toStringValue(item.taskLabel),\n\t\t\t\timportance: toStringValue(item.importance),\n\t\t\t\turgency: toStringValue(item.urgency),\n\t\t\t\tplatform: toNullableString(item.platform),\n\t\t\t\taccount: toNullableString(item.account),\n\t\t\t\ttime: normalizeTime(item.time),\n\t\t\t\tarchived: toBooleanValue(item.archived),\n\t\t\t},\n\t\t})),\n\t);\n\treturn insights.length;\n}\n\nexport async function searchInsightsWithSQLiteVec(\n\tinput: SQLiteInsightSearchInput,\n\tenv?: MemoryStoreEnv,\n): Promise<VectorSearchResult[]> {\n\tconst store = await getInsightSQLiteVecStore(resolveEnv(env));\n\tconst overfetchLimit = Math.max(input.limit * 8, input.limit);\n\tconst results = await store.similaritySearchWithOptions(input.queryEmbedding, {\n\t\tlimit: overfetchLimit,\n\t\tfilter: { userId: input.userId },\n\t});\n\n\treturn results\n\t\t.filter((result) => result.score >= input.threshold)\n\t\t.filter((result) => {\n\t\t\tconst metadata = result.metadata ?? {};\n\t\t\tif (input.botIds?.length && !input.botIds.includes(String(metadata.botId ?? \"\"))) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\treturn input.includeArchived || metadata.archived !== true;\n\t\t})\n\t\t.slice(0, input.limit);\n}\n\nfunction normalizeTime(value: unknown): number | null {\n\tif (value instanceof Date) return value.getTime();\n\tif (typeof value === \"number\" && Number.isFinite(value)) return value;\n\tif (typeof value === \"string\") {\n\t\tconst parsed = Date.parse(value);\n\t\treturn Number.isFinite(parsed) ? parsed : null;\n\t}\n\treturn null;\n}\n\nfunction toStringValue(value: unknown): string {\n\treturn typeof value === \"string\" ? value : \"\";\n}\n\nfunction toNullableString(value: unknown): string | null {\n\treturn typeof value === \"string\" && value.length > 0 ? value : null;\n}\n\nfunction toBooleanValue(value: unknown): boolean {\n\treturn value === true || value === \"true\" || value === 1 || value === \"1\";\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/storage/sqlite-raw-message-store.ts","../../src/storage/sqlite-vector-index.ts"],"names":[],"mappings":"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","file":"sqlite-vector-index.js","sourcesContent":["/**\n * SQLite-backed raw message store.\n *\n * Singleton manager. Local SQLite is the default backend for the memory\n * store — no host-mode check, no env-var opt-in. The DB path is resolved\n * from (in order):\n * 1. `MEMORY_STORE_DB_PATH` — recommended knob\n * 2. `~/.opencontext/memory/store.db` — last-resort default\n *\n * The `env?` parameter on the exported functions is kept for back-compat\n * with the historical `MemoryStoreEnv` interface but is no longer read.\n */\n\nimport { mkdirSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport { SQLiteRawMessageManager } from \"@melandlabs/sqlite\";\nimport type { MemoryStoreEnv } from \"../config\";\nimport { isRawMessageChromaEnabled } from \"./chroma-memory-index\";\n\nlet manager: SQLiteRawMessageManager | null = null;\n\nconst DEFAULT_SQLITE_PATH = join(homedir(), \".opencontext\", \"memory\", \"store.db\");\n\n/**\n * Resolve the SQLite DB file path from env vars. Exposed so other storage\n * layers (vector index) can stay in sync with the same default.\n */\nexport function resolveSQLiteRawMessageDbPath(): string {\n\tconst fromEnv = process.env.MEMORY_STORE_DB_PATH?.trim();\n\treturn fromEnv && fromEnv.length > 0 ? fromEnv : DEFAULT_SQLITE_PATH;\n}\n\n/**\n * Local SQLite is the default. Every host (Tauri, server, CLI, tests) can\n * open the manager; the only failure mode is filesystem write permission.\n */\nexport function isSQLiteRawMessageStorageAvailable(_env?: MemoryStoreEnv): boolean {\n\treturn true;\n}\n\nexport async function getSQLiteRawMessageManager(_env?: MemoryStoreEnv): Promise<SQLiteRawMessageManager> {\n\tif (!manager) {\n\t\tconst dbPath = resolveSQLiteRawMessageDbPath();\n\t\tmkdirSync(dirname(dbPath), { recursive: true });\n\t\tmanager = new SQLiteRawMessageManager({\n\t\t\tdbPath,\n\t\t\tenableVectorSearch: !isRawMessageChromaEnabled(),\n\t\t});\n\t\tawait manager.init();\n\t}\n\n\treturn manager;\n}\n\nexport async function closeSQLiteRawMessageManager(): Promise<void> {\n\tif (!manager) {\n\t\treturn;\n\t}\n\tawait manager.close();\n\tmanager = null;\n}\n\n/**\n * Test-only: reset the singleton. Used by vitest between test cases.\n */\nexport function __resetSQLiteRawMessageManagerForTests(): void {\n\tmanager = null;\n}\n\n// Kept for back-compat with the historical `MemoryStoreEnv` interface.\n// The resolved env is no longer read by the SQLite layer, but the symbol\n// stays exported so external code that still calls `resolveEnv` compiles.\nfunction resolveEnv(_env?: MemoryStoreEnv): MemoryStoreEnv {\n\treturn {};\n}\n","/**\n * SQLite-vec-backed raw-message + insight vector index.\n *\n * Mirrors the chroma-memory-index interface but uses the local\n * sqlite-vec store. The DB path is resolved from the same env-var chain\n * the raw-message SQLite layer uses.\n */\n\nimport type { VectorSearchResult } from \"@melandlabs/rag/vector-service\";\nimport type { MemoryStoreEnv } from \"../config\";\nimport { resolveSQLiteRawMessageDbPath } from \"./sqlite-raw-message-store\";\n\nexport interface SQLiteInsightVectorInput {\n\tinsightId: string;\n\tuserId: string;\n\tbotId: string;\n\tcontent: string;\n\tcontentHash: string;\n\tembedding: number[];\n\tembeddingModel: string;\n\tembeddingDimensions: number;\n\ttitle?: unknown;\n\tdescription?: unknown;\n\ttaskLabel?: unknown;\n\timportance?: unknown;\n\turgency?: unknown;\n\tplatform?: unknown;\n\taccount?: unknown;\n\ttime?: unknown;\n\tarchived?: unknown;\n}\n\nexport interface SQLiteInsightSearchInput {\n\tuserId: string;\n\tqueryEmbedding: number[];\n\tlimit: number;\n\tthreshold: number;\n\tbotIds?: string[];\n\tincludeArchived?: boolean;\n}\n\nfunction getMemoryVectorStoreBackend(): \"chroma\" | \"sqlite-vec\" {\n\tconst backend = (\n\t\tprocess.env.INSIGHT_VECTOR_STORE_BACKEND ||\n\t\tprocess.env.MEMORY_VECTOR_STORE_BACKEND ||\n\t\tprocess.env.VECTOR_STORE_BACKEND ||\n\t\t\"sqlite-vec\"\n\t).toLowerCase();\n\treturn backend === \"chroma\" ? \"chroma\" : \"sqlite-vec\";\n}\n\nexport function isInsightSQLiteVecEnabled(_env?: MemoryStoreEnv): boolean {\n\treturn getMemoryVectorStoreBackend() === \"sqlite-vec\";\n}\n\nfunction resolveEnv(_env?: MemoryStoreEnv): MemoryStoreEnv {\n\treturn {};\n}\n\nasync function getInsightSQLiteVecStore(_env: MemoryStoreEnv) {\n\tconst dbPath = resolveSQLiteRawMessageDbPath();\n\tconst { getSQLiteVecStore } = await import(\"@melandlabs/rag/sqlite-vec-store\");\n\treturn await getSQLiteVecStore(dbPath, undefined, {\n\t\tcollectionName: process.env.SQLITE_VEC_INSIGHTS_COLLECTION || \"opencontext_insights\",\n\t});\n}\n\nexport async function upsertInsightsToSQLiteVec(\n\tinsights: SQLiteInsightVectorInput[],\n\tenv?: MemoryStoreEnv,\n): Promise<number> {\n\tif (!isInsightSQLiteVecEnabled(env) || insights.length === 0) {\n\t\treturn 0;\n\t}\n\n\tconst store = await getInsightSQLiteVecStore(resolveEnv(env));\n\tawait store.addChunks(\n\t\tinsights.map((item) => ({\n\t\t\tid: item.insightId,\n\t\t\tdocumentId: item.insightId,\n\t\t\tcontent: item.content,\n\t\t\tembedding: item.embedding,\n\t\t\tmetadata: {\n\t\t\t\tuserId: item.userId,\n\t\t\t\tbotId: item.botId,\n\t\t\t\tcontentHash: item.contentHash,\n\t\t\t\tembeddingModel: item.embeddingModel,\n\t\t\t\tembeddingDimensions: item.embeddingDimensions,\n\t\t\t\ttitle: toStringValue(item.title),\n\t\t\t\tdescription: toStringValue(item.description),\n\t\t\t\ttaskLabel: toStringValue(item.taskLabel),\n\t\t\t\timportance: toStringValue(item.importance),\n\t\t\t\turgency: toStringValue(item.urgency),\n\t\t\t\tplatform: toNullableString(item.platform),\n\t\t\t\taccount: toNullableString(item.account),\n\t\t\t\ttime: normalizeTime(item.time),\n\t\t\t\tarchived: toBooleanValue(item.archived),\n\t\t\t},\n\t\t})),\n\t);\n\treturn insights.length;\n}\n\nexport async function searchInsightsWithSQLiteVec(\n\tinput: SQLiteInsightSearchInput,\n\tenv?: MemoryStoreEnv,\n): Promise<VectorSearchResult[]> {\n\tconst store = await getInsightSQLiteVecStore(resolveEnv(env));\n\tconst overfetchLimit = Math.max(input.limit * 8, input.limit);\n\tconst results = await store.similaritySearchWithOptions(input.queryEmbedding, {\n\t\tlimit: overfetchLimit,\n\t\tfilter: { userId: input.userId },\n\t});\n\n\treturn results\n\t\t.filter((result) => result.score >= input.threshold)\n\t\t.filter((result) => {\n\t\t\tconst metadata = result.metadata ?? {};\n\t\t\tif (input.botIds?.length && !input.botIds.includes(String(metadata.botId ?? \"\"))) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\treturn input.includeArchived || metadata.archived !== true;\n\t\t})\n\t\t.slice(0, input.limit);\n}\n\nfunction normalizeTime(value: unknown): number | null {\n\tif (value instanceof Date) return value.getTime();\n\tif (typeof value === \"number\" && Number.isFinite(value)) return value;\n\tif (typeof value === \"string\") {\n\t\tconst parsed = Date.parse(value);\n\t\treturn Number.isFinite(parsed) ? parsed : null;\n\t}\n\treturn null;\n}\n\nfunction toStringValue(value: unknown): string {\n\treturn typeof value === \"string\" ? value : \"\";\n}\n\nfunction toNullableString(value: unknown): string | null {\n\treturn typeof value === \"string\" && value.length > 0 ? value : null;\n}\n\nfunction toBooleanValue(value: unknown): boolean {\n\treturn value === true || value === \"true\" || value === 1 || value === \"1\";\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@melandlabs/memory-store",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist",
|
|
@@ -63,10 +63,10 @@
|
|
|
63
63
|
"@melandlabs/ai": "0.1.0",
|
|
64
64
|
"@melandlabs/rag": "0.1.0",
|
|
65
65
|
"@melandlabs/shared": "0.1.0",
|
|
66
|
-
"@melandlabs/sqlite": "0.1.
|
|
66
|
+
"@melandlabs/sqlite": "0.1.1"
|
|
67
67
|
},
|
|
68
68
|
"peerDependencies": {
|
|
69
|
-
"@melandlabs/indexeddb": "0.1.
|
|
69
|
+
"@melandlabs/indexeddb": "0.1.1"
|
|
70
70
|
},
|
|
71
71
|
"peerDependenciesMeta": {
|
|
72
72
|
"@melandlabs/indexeddb": {
|
|
@@ -76,12 +76,14 @@
|
|
|
76
76
|
"devDependencies": {
|
|
77
77
|
"@types/node": "^22.13.10",
|
|
78
78
|
"typescript": "^5.6.3",
|
|
79
|
-
"@melandlabs/config": "0.1.
|
|
79
|
+
"@melandlabs/config": "0.1.1"
|
|
80
80
|
},
|
|
81
81
|
"scripts": {
|
|
82
82
|
"build": "tsup",
|
|
83
83
|
"typecheck": "tsc --noEmit",
|
|
84
84
|
"lint": "biome check .",
|
|
85
|
-
"lint:fix": "biome check --write ."
|
|
85
|
+
"lint:fix": "biome check --write .",
|
|
86
|
+
"test": "vitest run",
|
|
87
|
+
"test:watch": "vitest"
|
|
86
88
|
}
|
|
87
89
|
}
|