@melandlabs/memory-store 0.1.0

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.
Files changed (46) hide show
  1. package/LICENSE +17 -0
  2. package/README.md +377 -0
  3. package/dist/config-MlK9f9CM.d.ts +147 -0
  4. package/dist/contracts.d.ts +62 -0
  5. package/dist/contracts.js +27 -0
  6. package/dist/contracts.js.map +1 -0
  7. package/dist/http.d.ts +38 -0
  8. package/dist/http.js +545 -0
  9. package/dist/http.js.map +1 -0
  10. package/dist/index.d.ts +35 -0
  11. package/dist/index.js +442 -0
  12. package/dist/index.js.map +1 -0
  13. package/dist/mcp.d.ts +29 -0
  14. package/dist/mcp.js +589 -0
  15. package/dist/mcp.js.map +1 -0
  16. package/dist/policies/memory-graph-correction-policy.d.ts +14 -0
  17. package/dist/policies/memory-graph-correction-policy.js +41 -0
  18. package/dist/policies/memory-graph-correction-policy.js.map +1 -0
  19. package/dist/policies/memory-graph-write-policy.d.ts +35 -0
  20. package/dist/policies/memory-graph-write-policy.js +78 -0
  21. package/dist/policies/memory-graph-write-policy.js.map +1 -0
  22. package/dist/search/unified-search.d.ts +81 -0
  23. package/dist/search/unified-search.js +399 -0
  24. package/dist/search/unified-search.js.map +1 -0
  25. package/dist/server/cli-http.d.ts +1 -0
  26. package/dist/server/cli-http.js +584 -0
  27. package/dist/server/cli-http.js.map +1 -0
  28. package/dist/server/cli-mcp.d.ts +1 -0
  29. package/dist/server/cli-mcp.js +603 -0
  30. package/dist/server/cli-mcp.js.map +1 -0
  31. package/dist/storage/chroma-memory-index.d.ts +76 -0
  32. package/dist/storage/chroma-memory-index.js +249 -0
  33. package/dist/storage/chroma-memory-index.js.map +1 -0
  34. package/dist/storage/postgres-raw-message-factory.d.ts +48 -0
  35. package/dist/storage/postgres-raw-message-factory.js +19 -0
  36. package/dist/storage/postgres-raw-message-factory.js.map +1 -0
  37. package/dist/storage/raw-message-store.d.ts +47 -0
  38. package/dist/storage/raw-message-store.js +140 -0
  39. package/dist/storage/raw-message-store.js.map +1 -0
  40. package/dist/storage/sqlite-raw-message-store.d.ts +19 -0
  41. package/dist/storage/sqlite-raw-message-store.js +68 -0
  42. package/dist/storage/sqlite-raw-message-store.js.map +1 -0
  43. package/dist/storage/sqlite-vector-index.d.ts +42 -0
  44. package/dist/storage/sqlite-vector-index.js +92 -0
  45. package/dist/storage/sqlite-vector-index.js.map +1 -0
  46. package/package.json +87 -0
@@ -0,0 +1,140 @@
1
+ import { mkdirSync } from 'fs';
2
+ import { dirname } from 'path';
3
+ import { SQLiteRawMessageManager } from '@melandlabs/sqlite';
4
+ import '@melandlabs/ai/memory';
5
+ import '@melandlabs/rag';
6
+
7
+ // src/storage/postgres-raw-message-factory.ts
8
+ var factory = null;
9
+ async function resolvePostgresFactory(env) {
10
+ return null;
11
+ }
12
+ function hasPostgresFactory() {
13
+ return factory !== null;
14
+ }
15
+ var RAW_VECTOR_BACKEND_ENV_KEYS = [
16
+ "RAW_MESSAGE_VECTOR_STORE_BACKEND",
17
+ "MEMORY_VECTOR_STORE_BACKEND",
18
+ "VECTOR_STORE_BACKEND"
19
+ ];
20
+ function getBackend(keys) {
21
+ for (const key of keys) {
22
+ const value = process.env[key]?.trim().toLowerCase();
23
+ if (value) {
24
+ return value;
25
+ }
26
+ }
27
+ return "";
28
+ }
29
+ function isRawMessageChromaEnabled() {
30
+ return getBackend(RAW_VECTOR_BACKEND_ENV_KEYS) === "chroma";
31
+ }
32
+
33
+ // src/storage/sqlite-raw-message-store.ts
34
+ var manager = null;
35
+ function isSQLiteRawMessageStorageAvailable(env) {
36
+ return resolveEnv(env).isTauriMode();
37
+ }
38
+ async function getSQLiteRawMessageManager(env) {
39
+ const e = resolveEnv(env);
40
+ if (!e.isTauriMode()) {
41
+ throw new Error("SQLite raw message storage is only available in Tauri mode.");
42
+ }
43
+ if (!manager) {
44
+ const dbPath = e.getTauriDbPath?.() ?? process.env.TAURI_DB_PATH ?? "";
45
+ mkdirSync(dirname(dbPath), { recursive: true });
46
+ manager = new SQLiteRawMessageManager({
47
+ dbPath,
48
+ enableVectorSearch: !isRawMessageChromaEnabled()
49
+ });
50
+ await manager.init();
51
+ }
52
+ return manager;
53
+ }
54
+ async function closeSQLiteRawMessageManager() {
55
+ if (!manager) {
56
+ return;
57
+ }
58
+ await manager.close();
59
+ manager = null;
60
+ }
61
+ function resolveEnv(env) {
62
+ if (env) return env;
63
+ return {
64
+ isTauriMode: () => process.env.IS_TAURI === "true" || typeof process.env.TAURI_MODE === "string",
65
+ getTauriDbPath: () => process.env.TAURI_DB_PATH ?? "",
66
+ getTauriDataDir: () => process.env.TAURI_DATA_DIR ?? ""
67
+ };
68
+ }
69
+
70
+ // src/storage/raw-message-store.ts
71
+ function resolveEnv2(env) {
72
+ if (env) return env;
73
+ return {
74
+ isTauriMode: () => process.env.IS_TAURI === "true" || typeof process.env.TAURI_MODE === "string",
75
+ getTauriDbPath: () => process.env.TAURI_DB_PATH ?? "",
76
+ getTauriDataDir: () => process.env.TAURI_DATA_DIR ?? ""
77
+ };
78
+ }
79
+ function createRawMessageStore(options = {}) {
80
+ const env = resolveEnv2(options.env);
81
+ return {
82
+ getBackend() {
83
+ return isSQLiteRawMessageStorageAvailable(env) ? "sqlite" : "postgres";
84
+ },
85
+ isAvailable() {
86
+ return isSQLiteRawMessageStorageAvailable(env) || hasPostgresFactory();
87
+ },
88
+ async getManager() {
89
+ if (isSQLiteRawMessageStorageAvailable(env)) {
90
+ return await getSQLiteRawMessageManager(env);
91
+ }
92
+ const pg = await resolvePostgresFactory();
93
+ if (!pg) {
94
+ throw new Error(
95
+ "No raw-message backend available. SQLite is disabled (not Tauri) and no Postgres factory is registered."
96
+ );
97
+ }
98
+ return pg;
99
+ },
100
+ async close() {
101
+ if (isSQLiteRawMessageStorageAvailable(env)) {
102
+ await closeSQLiteRawMessageManager();
103
+ }
104
+ }
105
+ };
106
+ }
107
+ var moduleStore = null;
108
+ var moduleConfig = null;
109
+ function configureRawMessageStore(config) {
110
+ moduleConfig = config;
111
+ moduleStore = createRawMessageStore({ env: config.env });
112
+ return moduleStore;
113
+ }
114
+ function getRawMessageStorageBackend() {
115
+ if (!moduleStore) {
116
+ moduleStore = createRawMessageStore({ env: moduleConfig?.env });
117
+ }
118
+ return moduleStore.getBackend();
119
+ }
120
+ function isRawMessageStorageAvailable() {
121
+ if (!moduleStore) {
122
+ moduleStore = createRawMessageStore({ env: moduleConfig?.env });
123
+ }
124
+ return moduleStore.isAvailable();
125
+ }
126
+ async function getRawMessageManager() {
127
+ if (!moduleStore) {
128
+ moduleStore = createRawMessageStore({ env: moduleConfig?.env });
129
+ }
130
+ return moduleStore.getManager();
131
+ }
132
+ async function closeRawMessageStore() {
133
+ if (!moduleStore) return;
134
+ await moduleStore.close();
135
+ moduleStore = null;
136
+ }
137
+
138
+ export { closeRawMessageStore, configureRawMessageStore, createRawMessageStore, getRawMessageManager, getRawMessageStorageBackend, isRawMessageStorageAvailable };
139
+ //# sourceMappingURL=raw-message-store.js.map
140
+ //# sourceMappingURL=raw-message-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/storage/postgres-raw-message-factory.ts","../../src/storage/chroma-memory-index.ts","../../src/storage/sqlite-raw-message-store.ts","../../src/storage/raw-message-store.ts"],"names":["resolveEnv"],"mappings":"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","file":"raw-message-store.js","sourcesContent":["/**\n * Lazy registration slot for the host's Postgres-backed raw message\n * manager. The actual `PostgresRawMessageManager` implementation lives\n * in the host application because it owns the schema-bound Drizzle\n * table references.\n *\n * The package ships this factory so consumers can wire up their own\n * postgres implementation without forcing the package to take a\n * hard dependency on Drizzle schema files.\n */\n\nimport type { MemoryStoreEnv } from \"../config\";\n\nexport interface PostgresRawMessageManagerLike {\n\tinit?(): Promise<void>;\n\tclose?(): Promise<void>;\n\tupsertRawMessages?(input: {\n\t\tuserId: string;\n\t\tmessages: unknown[];\n\t}): Promise<unknown>;\n\tgetMessageById?(input: {\n\t\tuserId: string;\n\t\tmessageId: string;\n\t}): Promise<unknown>;\n\tsearchMessagesSemantically?(input: {\n\t\tuserId: string;\n\t\tqueryEmbedding: number[];\n\t\tembeddingModel?: string;\n\t\tlimit?: number;\n\t\tscanLimit?: number;\n\t\tthreshold?: number;\n\t\tincludeArchived?: boolean;\n\t\tincludeDeprecated?: boolean;\n\t\tplatform?: string;\n\t\tbotId?: string;\n\t\tchannel?: string;\n\t\tperson?: string;\n\t\tstartTime?: number;\n\t\tendTime?: number;\n\t}): Promise<unknown[]>;\n}\n\nexport type PostgresFactoryFn = (env?: MemoryStoreEnv) => Promise<PostgresRawMessageManagerLike>;\n\nlet factory: PostgresFactoryFn | null = null;\n\nexport function registerPostgresFactory(fn: PostgresFactoryFn): void {\n\tfactory = fn;\n}\n\nexport function clearPostgresFactory(): void {\n\tfactory = null;\n}\n\nexport async function resolvePostgresFactory(\n\tenv?: MemoryStoreEnv,\n): Promise<PostgresRawMessageManagerLike | null> {\n\tif (!factory) return null;\n\treturn await factory(env);\n}\n\nexport function hasPostgresFactory(): boolean {\n\treturn factory !== null;\n}\n","/**\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","/**\n * Top-level raw-message store facade.\n *\n * Selects between sqlite (Tauri) and postgres (host-registered)\n * backends based on `MemoryStoreEnv.isTauriMode()`. The facade exposes\n * the common `RawMessageStorageManager` contract plus a typed\n * `searchMessagesSemantically` extension when present.\n */\n\nimport type { RawMessageStorageManager } from \"@melandlabs/indexeddb/storage\";\nimport type { MemoryStoreConfig, MemoryStoreEnv } from \"../config\";\nimport { hasPostgresFactory, resolvePostgresFactory } from \"./postgres-raw-message-factory\";\nimport {\n\tcloseSQLiteRawMessageManager,\n\tgetSQLiteRawMessageManager,\n\tisSQLiteRawMessageStorageAvailable,\n} from \"./sqlite-raw-message-store\";\n\nexport type RawMessageStorageBackend = \"sqlite\" | \"postgres\";\n\nexport type RawMessageStorageManagerWithSearch = RawMessageStorageManager & {\n\tsearchMessagesSemantically?: (input: {\n\t\tuserId: string;\n\t\tqueryEmbedding: number[];\n\t\tembeddingModel?: string;\n\t\tlimit?: number;\n\t\tscanLimit?: number;\n\t\tthreshold?: number;\n\t\tincludeArchived?: boolean;\n\t\tplatform?: string;\n\t\tbotId?: string;\n\t\tchannel?: string;\n\t\tperson?: string;\n\t\tstartTime?: number;\n\t\tendTime?: number;\n\t}) => Promise<unknown[]>;\n};\n\nexport interface CreateRawMessageStoreOptions {\n\tenv?: MemoryStoreEnv;\n}\n\nexport interface RawMessageStore {\n\tgetManager(): Promise<RawMessageStorageManagerWithSearch>;\n\tgetBackend(): RawMessageStorageBackend;\n\tisAvailable(): boolean;\n\tclose(): Promise<void>;\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\nexport function createRawMessageStore(options: CreateRawMessageStoreOptions = {}): RawMessageStore {\n\tconst env = resolveEnv(options.env);\n\n\treturn {\n\t\tgetBackend(): RawMessageStorageBackend {\n\t\t\treturn isSQLiteRawMessageStorageAvailable(env) ? \"sqlite\" : \"postgres\";\n\t\t},\n\t\tisAvailable(): boolean {\n\t\t\treturn isSQLiteRawMessageStorageAvailable(env) || hasPostgresFactory();\n\t\t},\n\t\tasync getManager(): Promise<RawMessageStorageManagerWithSearch> {\n\t\t\tif (isSQLiteRawMessageStorageAvailable(env)) {\n\t\t\t\treturn (await getSQLiteRawMessageManager(env)) as unknown as RawMessageStorageManagerWithSearch;\n\t\t\t}\n\t\t\tconst pg = await resolvePostgresFactory(env);\n\t\t\tif (!pg) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t\"No raw-message backend available. SQLite is disabled (not Tauri) and no Postgres factory is registered.\",\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn pg as unknown as RawMessageStorageManagerWithSearch;\n\t\t},\n\t\tasync close(): Promise<void> {\n\t\t\tif (isSQLiteRawMessageStorageAvailable(env)) {\n\t\t\t\tawait closeSQLiteRawMessageManager();\n\t\t\t}\n\t\t},\n\t};\n}\n\n// Module-level singleton so existing call sites that imported the\n// legacy `getRawMessageManager()` keep working without a refactor.\nlet moduleStore: RawMessageStore | null = null;\nlet moduleConfig: MemoryStoreConfig | null = null;\n\nexport function configureRawMessageStore(config: MemoryStoreConfig): RawMessageStore {\n\tmoduleConfig = config;\n\tmoduleStore = createRawMessageStore({ env: config.env });\n\treturn moduleStore;\n}\n\nexport function getRawMessageStorageBackend(): RawMessageStorageBackend {\n\tif (!moduleStore) {\n\t\tmoduleStore = createRawMessageStore({ env: moduleConfig?.env });\n\t}\n\treturn moduleStore.getBackend();\n}\n\nexport function isRawMessageStorageAvailable(): boolean {\n\tif (!moduleStore) {\n\t\tmoduleStore = createRawMessageStore({ env: moduleConfig?.env });\n\t}\n\treturn moduleStore.isAvailable();\n}\n\nexport async function getRawMessageManager(): Promise<RawMessageStorageManagerWithSearch> {\n\tif (!moduleStore) {\n\t\tmoduleStore = createRawMessageStore({ env: moduleConfig?.env });\n\t}\n\treturn moduleStore.getManager();\n}\n\nexport async function closeRawMessageStore(): Promise<void> {\n\tif (!moduleStore) return;\n\tawait moduleStore.close();\n\tmoduleStore = null;\n}\n"]}
@@ -0,0 +1,19 @@
1
+ import { SQLiteRawMessageManager } from '@melandlabs/sqlite';
2
+ import { b as MemoryStoreEnv } from '../config-MlK9f9CM.js';
3
+
4
+ /**
5
+ * SQLite-backed raw message store.
6
+ *
7
+ * Singleton manager used in Tauri (local) mode. The Tauri env is
8
+ * sourced from the injected `MemoryStoreConfig.env`.
9
+ */
10
+
11
+ declare function isSQLiteRawMessageStorageAvailable(env?: MemoryStoreEnv): boolean;
12
+ declare function getSQLiteRawMessageManager(env?: MemoryStoreEnv): Promise<SQLiteRawMessageManager>;
13
+ declare function closeSQLiteRawMessageManager(): Promise<void>;
14
+ /**
15
+ * Test-only: reset the singleton. Used by vitest between test cases.
16
+ */
17
+ declare function __resetSQLiteRawMessageManagerForTests(): void;
18
+
19
+ export { __resetSQLiteRawMessageManagerForTests, closeSQLiteRawMessageManager, getSQLiteRawMessageManager, isSQLiteRawMessageStorageAvailable };
@@ -0,0 +1,68 @@
1
+ import { mkdirSync } from 'fs';
2
+ import { dirname } from 'path';
3
+ import { SQLiteRawMessageManager } from '@melandlabs/sqlite';
4
+ import '@melandlabs/ai/memory';
5
+ import '@melandlabs/rag';
6
+
7
+ // src/storage/sqlite-raw-message-store.ts
8
+ var RAW_VECTOR_BACKEND_ENV_KEYS = [
9
+ "RAW_MESSAGE_VECTOR_STORE_BACKEND",
10
+ "MEMORY_VECTOR_STORE_BACKEND",
11
+ "VECTOR_STORE_BACKEND"
12
+ ];
13
+ function getBackend(keys) {
14
+ for (const key of keys) {
15
+ const value = process.env[key]?.trim().toLowerCase();
16
+ if (value) {
17
+ return value;
18
+ }
19
+ }
20
+ return "";
21
+ }
22
+ function isRawMessageChromaEnabled() {
23
+ return getBackend(RAW_VECTOR_BACKEND_ENV_KEYS) === "chroma";
24
+ }
25
+
26
+ // src/storage/sqlite-raw-message-store.ts
27
+ var manager = null;
28
+ function isSQLiteRawMessageStorageAvailable(env) {
29
+ return resolveEnv(env).isTauriMode();
30
+ }
31
+ async function getSQLiteRawMessageManager(env) {
32
+ const e = resolveEnv(env);
33
+ if (!e.isTauriMode()) {
34
+ throw new Error("SQLite raw message storage is only available in Tauri mode.");
35
+ }
36
+ if (!manager) {
37
+ const dbPath = e.getTauriDbPath?.() ?? process.env.TAURI_DB_PATH ?? "";
38
+ mkdirSync(dirname(dbPath), { recursive: true });
39
+ manager = new SQLiteRawMessageManager({
40
+ dbPath,
41
+ enableVectorSearch: !isRawMessageChromaEnabled()
42
+ });
43
+ await manager.init();
44
+ }
45
+ return manager;
46
+ }
47
+ async function closeSQLiteRawMessageManager() {
48
+ if (!manager) {
49
+ return;
50
+ }
51
+ await manager.close();
52
+ manager = null;
53
+ }
54
+ function __resetSQLiteRawMessageManagerForTests() {
55
+ manager = null;
56
+ }
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
+
66
+ export { __resetSQLiteRawMessageManagerForTests, closeSQLiteRawMessageManager, getSQLiteRawMessageManager, isSQLiteRawMessageStorageAvailable };
67
+ //# sourceMappingURL=sqlite-raw-message-store.js.map
68
+ //# sourceMappingURL=sqlite-raw-message-store.js.map
@@ -0,0 +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"]}
@@ -0,0 +1,42 @@
1
+ import { VectorSearchResult } from '@melandlabs/rag/vector-service';
2
+ import { b as MemoryStoreEnv } from '../config-MlK9f9CM.js';
3
+
4
+ /**
5
+ * SQLite-vec-backed raw-message + insight vector index.
6
+ *
7
+ * Mirrors the chroma-memory-index interface but uses the local
8
+ * sqlite-vec store. The DB path is resolved via `MemoryStoreEnv`.
9
+ */
10
+
11
+ interface SQLiteInsightVectorInput {
12
+ insightId: string;
13
+ userId: string;
14
+ botId: string;
15
+ content: string;
16
+ contentHash: string;
17
+ embedding: number[];
18
+ embeddingModel: string;
19
+ embeddingDimensions: number;
20
+ title?: unknown;
21
+ description?: unknown;
22
+ taskLabel?: unknown;
23
+ importance?: unknown;
24
+ urgency?: unknown;
25
+ platform?: unknown;
26
+ account?: unknown;
27
+ time?: unknown;
28
+ archived?: unknown;
29
+ }
30
+ interface SQLiteInsightSearchInput {
31
+ userId: string;
32
+ queryEmbedding: number[];
33
+ limit: number;
34
+ threshold: number;
35
+ botIds?: string[];
36
+ includeArchived?: boolean;
37
+ }
38
+ declare function isInsightSQLiteVecEnabled(env?: MemoryStoreEnv): boolean;
39
+ declare function upsertInsightsToSQLiteVec(insights: SQLiteInsightVectorInput[], env?: MemoryStoreEnv): Promise<number>;
40
+ declare function searchInsightsWithSQLiteVec(input: SQLiteInsightSearchInput, env?: MemoryStoreEnv): Promise<VectorSearchResult[]>;
41
+
42
+ export { type SQLiteInsightSearchInput, type SQLiteInsightVectorInput, isInsightSQLiteVecEnabled, searchInsightsWithSQLiteVec, upsertInsightsToSQLiteVec };
@@ -0,0 +1,92 @@
1
+ // src/storage/sqlite-vector-index.ts
2
+ function getMemoryVectorStoreBackend() {
3
+ const backend = (process.env.INSIGHT_VECTOR_STORE_BACKEND || process.env.MEMORY_VECTOR_STORE_BACKEND || process.env.VECTOR_STORE_BACKEND || "sqlite-vec").toLowerCase();
4
+ return backend === "chroma" ? "chroma" : "sqlite-vec";
5
+ }
6
+ function isInsightSQLiteVecEnabled(env) {
7
+ return resolveEnv(env).isTauriMode() && getMemoryVectorStoreBackend() === "sqlite-vec";
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
+ };
16
+ }
17
+ async function getInsightSQLiteVecStore(env) {
18
+ const e = resolveEnv(env);
19
+ const dbPath = e.getTauriDbPath?.() ?? process.env.TAURI_DB_PATH ?? "";
20
+ const { getSQLiteVecStore } = await import('@melandlabs/rag/sqlite-vec-store');
21
+ return await getSQLiteVecStore(dbPath, void 0, {
22
+ collectionName: process.env.SQLITE_VEC_INSIGHTS_COLLECTION || "opencontext_insights"
23
+ });
24
+ }
25
+ async function upsertInsightsToSQLiteVec(insights, env) {
26
+ if (!isInsightSQLiteVecEnabled(env) || insights.length === 0) {
27
+ return 0;
28
+ }
29
+ const store = await getInsightSQLiteVecStore(resolveEnv(env));
30
+ await store.addChunks(
31
+ insights.map((item) => ({
32
+ id: item.insightId,
33
+ documentId: item.insightId,
34
+ content: item.content,
35
+ embedding: item.embedding,
36
+ metadata: {
37
+ userId: item.userId,
38
+ botId: item.botId,
39
+ contentHash: item.contentHash,
40
+ embeddingModel: item.embeddingModel,
41
+ embeddingDimensions: item.embeddingDimensions,
42
+ title: toStringValue(item.title),
43
+ description: toStringValue(item.description),
44
+ taskLabel: toStringValue(item.taskLabel),
45
+ importance: toStringValue(item.importance),
46
+ urgency: toStringValue(item.urgency),
47
+ platform: toNullableString(item.platform),
48
+ account: toNullableString(item.account),
49
+ time: normalizeTime(item.time),
50
+ archived: toBooleanValue(item.archived)
51
+ }
52
+ }))
53
+ );
54
+ return insights.length;
55
+ }
56
+ async function searchInsightsWithSQLiteVec(input, env) {
57
+ const store = await getInsightSQLiteVecStore(resolveEnv(env));
58
+ const overfetchLimit = Math.max(input.limit * 8, input.limit);
59
+ const results = await store.similaritySearchWithOptions(input.queryEmbedding, {
60
+ limit: overfetchLimit,
61
+ filter: { userId: input.userId }
62
+ });
63
+ return results.filter((result) => result.score >= input.threshold).filter((result) => {
64
+ const metadata = result.metadata ?? {};
65
+ if (input.botIds?.length && !input.botIds.includes(String(metadata.botId ?? ""))) {
66
+ return false;
67
+ }
68
+ return input.includeArchived || metadata.archived !== true;
69
+ }).slice(0, input.limit);
70
+ }
71
+ function normalizeTime(value) {
72
+ if (value instanceof Date) return value.getTime();
73
+ if (typeof value === "number" && Number.isFinite(value)) return value;
74
+ if (typeof value === "string") {
75
+ const parsed = Date.parse(value);
76
+ return Number.isFinite(parsed) ? parsed : null;
77
+ }
78
+ return null;
79
+ }
80
+ function toStringValue(value) {
81
+ return typeof value === "string" ? value : "";
82
+ }
83
+ function toNullableString(value) {
84
+ return typeof value === "string" && value.length > 0 ? value : null;
85
+ }
86
+ function toBooleanValue(value) {
87
+ return value === true || value === "true" || value === 1 || value === "1";
88
+ }
89
+
90
+ export { isInsightSQLiteVecEnabled, searchInsightsWithSQLiteVec, upsertInsightsToSQLiteVec };
91
+ //# sourceMappingURL=sqlite-vector-index.js.map
92
+ //# sourceMappingURL=sqlite-vector-index.js.map
@@ -0,0 +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"]}
package/package.json ADDED
@@ -0,0 +1,87 @@
1
+ {
2
+ "name": "@melandlabs/memory-store",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "files": [
6
+ "dist",
7
+ "README.md",
8
+ "LICENSE"
9
+ ],
10
+ "main": "./dist/index.js",
11
+ "types": "./dist/index.d.ts",
12
+ "exports": {
13
+ ".": {
14
+ "types": "./dist/index.d.ts",
15
+ "import": "./dist/index.js"
16
+ },
17
+ "./contracts": "./dist/contracts.js",
18
+ "./http": {
19
+ "types": "./dist/http.d.ts",
20
+ "import": "./dist/http.js"
21
+ },
22
+ "./mcp": {
23
+ "types": "./dist/mcp.d.ts",
24
+ "import": "./dist/mcp.js"
25
+ },
26
+ "./raw-message-store": "./dist/storage/raw-message-store.js",
27
+ "./sqlite-raw-message-store": "./dist/storage/sqlite-raw-message-store.js",
28
+ "./postgres-raw-message-factory": "./dist/storage/postgres-raw-message-factory.js",
29
+ "./sqlite-vector-index": "./dist/storage/sqlite-vector-index.js",
30
+ "./chroma-memory-index": "./dist/storage/chroma-memory-index.js",
31
+ "./unified-search": "./dist/search/unified-search.js",
32
+ "./memory-graph-write-policy": "./dist/policies/memory-graph-write-policy.js",
33
+ "./memory-graph-correction-policy": "./dist/policies/memory-graph-correction-policy.js"
34
+ },
35
+ "bin": {
36
+ "opencontext-memory-http": "./dist/server/cli-http.js",
37
+ "opencontext-memory-mcp": "./dist/server/cli-mcp.js"
38
+ },
39
+ "repository": {
40
+ "type": "git",
41
+ "url": "git+https://github.com/melandlabs/opencontext.git",
42
+ "directory": "packages/memory-store"
43
+ },
44
+ "license": "Apache-2.0",
45
+ "description": "OpenContext memory storage + search SDK with HTTP and MCP server entry points.",
46
+ "keywords": [
47
+ "opencontext",
48
+ "memory",
49
+ "storage",
50
+ "search",
51
+ "sdk",
52
+ "mcp",
53
+ "http"
54
+ ],
55
+ "publishConfig": {
56
+ "access": "public"
57
+ },
58
+ "dependencies": {
59
+ "@hono/node-server": "^1.13.7",
60
+ "@modelcontextprotocol/sdk": "^1.25.3",
61
+ "hono": "^4.6.14",
62
+ "zod": "^4.3.6",
63
+ "@melandlabs/ai": "0.1.0",
64
+ "@melandlabs/rag": "0.1.0",
65
+ "@melandlabs/shared": "0.1.0",
66
+ "@melandlabs/sqlite": "0.1.0"
67
+ },
68
+ "peerDependencies": {
69
+ "@melandlabs/indexeddb": "0.1.0"
70
+ },
71
+ "peerDependenciesMeta": {
72
+ "@melandlabs/indexeddb": {
73
+ "optional": true
74
+ }
75
+ },
76
+ "devDependencies": {
77
+ "@types/node": "^22.13.10",
78
+ "typescript": "^5.6.3",
79
+ "@melandlabs/config": "0.1.0"
80
+ },
81
+ "scripts": {
82
+ "build": "tsup",
83
+ "typecheck": "tsc --noEmit",
84
+ "lint": "biome check .",
85
+ "lint:fix": "biome check --write ."
86
+ }
87
+ }