@claude-flow/memory 3.0.0-alpha.1

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 (214) hide show
  1. package/.agentic-flow/intelligence.json +16 -0
  2. package/README.md +249 -0
  3. package/__tests__/coverage/base.css +224 -0
  4. package/__tests__/coverage/block-navigation.js +87 -0
  5. package/__tests__/coverage/coverage-final.json +19 -0
  6. package/__tests__/coverage/favicon.png +0 -0
  7. package/__tests__/coverage/index.html +206 -0
  8. package/__tests__/coverage/lcov-report/base.css +224 -0
  9. package/__tests__/coverage/lcov-report/block-navigation.js +87 -0
  10. package/__tests__/coverage/lcov-report/favicon.png +0 -0
  11. package/__tests__/coverage/lcov-report/index.html +206 -0
  12. package/__tests__/coverage/lcov-report/prettify.css +1 -0
  13. package/__tests__/coverage/lcov-report/prettify.js +2 -0
  14. package/__tests__/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  15. package/__tests__/coverage/lcov-report/sorter.js +210 -0
  16. package/__tests__/coverage/lcov-report/src/agentdb-adapter.ts.html +2737 -0
  17. package/__tests__/coverage/lcov-report/src/agentdb-backend.ts.html +3130 -0
  18. package/__tests__/coverage/lcov-report/src/application/commands/delete-memory.command.ts.html +601 -0
  19. package/__tests__/coverage/lcov-report/src/application/commands/index.html +131 -0
  20. package/__tests__/coverage/lcov-report/src/application/commands/store-memory.command.ts.html +394 -0
  21. package/__tests__/coverage/lcov-report/src/application/queries/index.html +116 -0
  22. package/__tests__/coverage/lcov-report/src/application/queries/search-memory.query.ts.html +796 -0
  23. package/__tests__/coverage/lcov-report/src/application/services/index.html +116 -0
  24. package/__tests__/coverage/lcov-report/src/application/services/memory-application-service.ts.html +793 -0
  25. package/__tests__/coverage/lcov-report/src/cache-manager.ts.html +1633 -0
  26. package/__tests__/coverage/lcov-report/src/database-provider.ts.html +1618 -0
  27. package/__tests__/coverage/lcov-report/src/domain/entities/index.html +116 -0
  28. package/__tests__/coverage/lcov-report/src/domain/entities/memory-entry.ts.html +952 -0
  29. package/__tests__/coverage/lcov-report/src/domain/services/index.html +116 -0
  30. package/__tests__/coverage/lcov-report/src/domain/services/memory-domain-service.ts.html +1294 -0
  31. package/__tests__/coverage/lcov-report/src/hnsw-index.ts.html +3124 -0
  32. package/__tests__/coverage/lcov-report/src/hybrid-backend.ts.html +2167 -0
  33. package/__tests__/coverage/lcov-report/src/index.html +266 -0
  34. package/__tests__/coverage/lcov-report/src/infrastructure/repositories/hybrid-memory-repository.ts.html +1633 -0
  35. package/__tests__/coverage/lcov-report/src/infrastructure/repositories/index.html +116 -0
  36. package/__tests__/coverage/lcov-report/src/migration.ts.html +2092 -0
  37. package/__tests__/coverage/lcov-report/src/query-builder.ts.html +1711 -0
  38. package/__tests__/coverage/lcov-report/src/sqlite-backend.ts.html +2281 -0
  39. package/__tests__/coverage/lcov-report/src/sqljs-backend.ts.html +2374 -0
  40. package/__tests__/coverage/lcov-report/src/types.ts.html +2266 -0
  41. package/__tests__/coverage/lcov.info +10238 -0
  42. package/__tests__/coverage/prettify.css +1 -0
  43. package/__tests__/coverage/prettify.js +2 -0
  44. package/__tests__/coverage/sort-arrow-sprite.png +0 -0
  45. package/__tests__/coverage/sorter.js +210 -0
  46. package/__tests__/coverage/src/agentdb-adapter.ts.html +2737 -0
  47. package/__tests__/coverage/src/agentdb-backend.ts.html +3130 -0
  48. package/__tests__/coverage/src/application/commands/delete-memory.command.ts.html +601 -0
  49. package/__tests__/coverage/src/application/commands/index.html +131 -0
  50. package/__tests__/coverage/src/application/commands/store-memory.command.ts.html +394 -0
  51. package/__tests__/coverage/src/application/queries/index.html +116 -0
  52. package/__tests__/coverage/src/application/queries/search-memory.query.ts.html +796 -0
  53. package/__tests__/coverage/src/application/services/index.html +116 -0
  54. package/__tests__/coverage/src/application/services/memory-application-service.ts.html +793 -0
  55. package/__tests__/coverage/src/cache-manager.ts.html +1633 -0
  56. package/__tests__/coverage/src/database-provider.ts.html +1618 -0
  57. package/__tests__/coverage/src/domain/entities/index.html +116 -0
  58. package/__tests__/coverage/src/domain/entities/memory-entry.ts.html +952 -0
  59. package/__tests__/coverage/src/domain/services/index.html +116 -0
  60. package/__tests__/coverage/src/domain/services/memory-domain-service.ts.html +1294 -0
  61. package/__tests__/coverage/src/hnsw-index.ts.html +3124 -0
  62. package/__tests__/coverage/src/hybrid-backend.ts.html +2167 -0
  63. package/__tests__/coverage/src/index.html +266 -0
  64. package/__tests__/coverage/src/infrastructure/repositories/hybrid-memory-repository.ts.html +1633 -0
  65. package/__tests__/coverage/src/infrastructure/repositories/index.html +116 -0
  66. package/__tests__/coverage/src/migration.ts.html +2092 -0
  67. package/__tests__/coverage/src/query-builder.ts.html +1711 -0
  68. package/__tests__/coverage/src/sqlite-backend.ts.html +2281 -0
  69. package/__tests__/coverage/src/sqljs-backend.ts.html +2374 -0
  70. package/__tests__/coverage/src/types.ts.html +2266 -0
  71. package/benchmarks/cache-hit-rate.bench.ts +535 -0
  72. package/benchmarks/hnsw-indexing.bench.ts +552 -0
  73. package/benchmarks/memory-write.bench.ts +469 -0
  74. package/benchmarks/vector-search.bench.ts +449 -0
  75. package/dist/agentdb-adapter.d.ts +146 -0
  76. package/dist/agentdb-adapter.d.ts.map +1 -0
  77. package/dist/agentdb-adapter.js +679 -0
  78. package/dist/agentdb-adapter.js.map +1 -0
  79. package/dist/agentdb-backend.d.ts +214 -0
  80. package/dist/agentdb-backend.d.ts.map +1 -0
  81. package/dist/agentdb-backend.js +827 -0
  82. package/dist/agentdb-backend.js.map +1 -0
  83. package/dist/agentdb-backend.test.d.ts +7 -0
  84. package/dist/agentdb-backend.test.d.ts.map +1 -0
  85. package/dist/agentdb-backend.test.js +258 -0
  86. package/dist/agentdb-backend.test.js.map +1 -0
  87. package/dist/application/commands/delete-memory.command.d.ts +65 -0
  88. package/dist/application/commands/delete-memory.command.d.ts.map +1 -0
  89. package/dist/application/commands/delete-memory.command.js +129 -0
  90. package/dist/application/commands/delete-memory.command.js.map +1 -0
  91. package/dist/application/commands/store-memory.command.d.ts +48 -0
  92. package/dist/application/commands/store-memory.command.d.ts.map +1 -0
  93. package/dist/application/commands/store-memory.command.js +72 -0
  94. package/dist/application/commands/store-memory.command.js.map +1 -0
  95. package/dist/application/index.d.ts +12 -0
  96. package/dist/application/index.d.ts.map +1 -0
  97. package/dist/application/index.js +15 -0
  98. package/dist/application/index.js.map +1 -0
  99. package/dist/application/queries/search-memory.query.d.ts +72 -0
  100. package/dist/application/queries/search-memory.query.d.ts.map +1 -0
  101. package/dist/application/queries/search-memory.query.js +143 -0
  102. package/dist/application/queries/search-memory.query.js.map +1 -0
  103. package/dist/application/services/memory-application-service.d.ts +121 -0
  104. package/dist/application/services/memory-application-service.d.ts.map +1 -0
  105. package/dist/application/services/memory-application-service.js +190 -0
  106. package/dist/application/services/memory-application-service.js.map +1 -0
  107. package/dist/cache-manager.d.ts +134 -0
  108. package/dist/cache-manager.d.ts.map +1 -0
  109. package/dist/cache-manager.js +407 -0
  110. package/dist/cache-manager.js.map +1 -0
  111. package/dist/database-provider.d.ts +86 -0
  112. package/dist/database-provider.d.ts.map +1 -0
  113. package/dist/database-provider.js +385 -0
  114. package/dist/database-provider.js.map +1 -0
  115. package/dist/database-provider.test.d.ts +7 -0
  116. package/dist/database-provider.test.d.ts.map +1 -0
  117. package/dist/database-provider.test.js +285 -0
  118. package/dist/database-provider.test.js.map +1 -0
  119. package/dist/domain/entities/memory-entry.d.ts +143 -0
  120. package/dist/domain/entities/memory-entry.d.ts.map +1 -0
  121. package/dist/domain/entities/memory-entry.js +226 -0
  122. package/dist/domain/entities/memory-entry.js.map +1 -0
  123. package/dist/domain/index.d.ts +11 -0
  124. package/dist/domain/index.d.ts.map +1 -0
  125. package/dist/domain/index.js +12 -0
  126. package/dist/domain/index.js.map +1 -0
  127. package/dist/domain/repositories/memory-repository.interface.d.ts +102 -0
  128. package/dist/domain/repositories/memory-repository.interface.d.ts.map +1 -0
  129. package/dist/domain/repositories/memory-repository.interface.js +11 -0
  130. package/dist/domain/repositories/memory-repository.interface.js.map +1 -0
  131. package/dist/domain/services/memory-domain-service.d.ts +105 -0
  132. package/dist/domain/services/memory-domain-service.d.ts.map +1 -0
  133. package/dist/domain/services/memory-domain-service.js +297 -0
  134. package/dist/domain/services/memory-domain-service.js.map +1 -0
  135. package/dist/hnsw-index.d.ts +111 -0
  136. package/dist/hnsw-index.d.ts.map +1 -0
  137. package/dist/hnsw-index.js +781 -0
  138. package/dist/hnsw-index.js.map +1 -0
  139. package/dist/hybrid-backend.d.ts +217 -0
  140. package/dist/hybrid-backend.d.ts.map +1 -0
  141. package/dist/hybrid-backend.js +491 -0
  142. package/dist/hybrid-backend.js.map +1 -0
  143. package/dist/hybrid-backend.test.d.ts +8 -0
  144. package/dist/hybrid-backend.test.d.ts.map +1 -0
  145. package/dist/hybrid-backend.test.js +320 -0
  146. package/dist/hybrid-backend.test.js.map +1 -0
  147. package/dist/index.d.ts +188 -0
  148. package/dist/index.d.ts.map +1 -0
  149. package/dist/index.js +345 -0
  150. package/dist/index.js.map +1 -0
  151. package/dist/infrastructure/index.d.ts +17 -0
  152. package/dist/infrastructure/index.d.ts.map +1 -0
  153. package/dist/infrastructure/index.js +16 -0
  154. package/dist/infrastructure/index.js.map +1 -0
  155. package/dist/infrastructure/repositories/hybrid-memory-repository.d.ts +66 -0
  156. package/dist/infrastructure/repositories/hybrid-memory-repository.d.ts.map +1 -0
  157. package/dist/infrastructure/repositories/hybrid-memory-repository.js +409 -0
  158. package/dist/infrastructure/repositories/hybrid-memory-repository.js.map +1 -0
  159. package/dist/migration.d.ts +68 -0
  160. package/dist/migration.d.ts.map +1 -0
  161. package/dist/migration.js +513 -0
  162. package/dist/migration.js.map +1 -0
  163. package/dist/query-builder.d.ts +211 -0
  164. package/dist/query-builder.d.ts.map +1 -0
  165. package/dist/query-builder.js +438 -0
  166. package/dist/query-builder.js.map +1 -0
  167. package/dist/sqlite-backend.d.ts +121 -0
  168. package/dist/sqlite-backend.d.ts.map +1 -0
  169. package/dist/sqlite-backend.js +564 -0
  170. package/dist/sqlite-backend.js.map +1 -0
  171. package/dist/sqljs-backend.d.ts +128 -0
  172. package/dist/sqljs-backend.d.ts.map +1 -0
  173. package/dist/sqljs-backend.js +598 -0
  174. package/dist/sqljs-backend.js.map +1 -0
  175. package/dist/types.d.ts +481 -0
  176. package/dist/types.d.ts.map +1 -0
  177. package/dist/types.js +58 -0
  178. package/dist/types.js.map +1 -0
  179. package/docs/AGENTDB-INTEGRATION.md +388 -0
  180. package/docs/CROSS_PLATFORM.md +505 -0
  181. package/docs/WINDOWS_SUPPORT.md +422 -0
  182. package/examples/agentdb-example.ts +345 -0
  183. package/examples/cross-platform-usage.ts +326 -0
  184. package/framework/benchmark.ts +112 -0
  185. package/package.json +31 -0
  186. package/src/agentdb-adapter.ts +884 -0
  187. package/src/agentdb-backend.test.ts +339 -0
  188. package/src/agentdb-backend.ts +1016 -0
  189. package/src/application/commands/delete-memory.command.ts +172 -0
  190. package/src/application/commands/store-memory.command.ts +103 -0
  191. package/src/application/index.ts +36 -0
  192. package/src/application/queries/search-memory.query.ts +237 -0
  193. package/src/application/services/memory-application-service.ts +236 -0
  194. package/src/cache-manager.ts +516 -0
  195. package/src/database-provider.test.ts +364 -0
  196. package/src/database-provider.ts +511 -0
  197. package/src/domain/entities/memory-entry.ts +289 -0
  198. package/src/domain/index.ts +35 -0
  199. package/src/domain/repositories/memory-repository.interface.ts +120 -0
  200. package/src/domain/services/memory-domain-service.ts +403 -0
  201. package/src/hnsw-index.ts +1013 -0
  202. package/src/hybrid-backend.test.ts +399 -0
  203. package/src/hybrid-backend.ts +694 -0
  204. package/src/index.ts +515 -0
  205. package/src/infrastructure/index.ts +23 -0
  206. package/src/infrastructure/repositories/hybrid-memory-repository.ts +516 -0
  207. package/src/migration.ts +669 -0
  208. package/src/query-builder.ts +542 -0
  209. package/src/sqlite-backend.ts +732 -0
  210. package/src/sqljs-backend.ts +763 -0
  211. package/src/types.ts +727 -0
  212. package/tsconfig.json +9 -0
  213. package/tsconfig.tsbuildinfo +1 -0
  214. package/verify-cross-platform.ts +170 -0
@@ -0,0 +1,694 @@
1
+ /**
2
+ * HybridBackend - Combines SQLite (structured queries) + AgentDB (vector search)
3
+ *
4
+ * Per ADR-009: "HybridBackend (SQLite + AgentDB) as default"
5
+ * - SQLite for: Structured queries, ACID transactions, exact matches
6
+ * - AgentDB for: Semantic search, vector similarity, RAG
7
+ *
8
+ * @module v3/memory/hybrid-backend
9
+ */
10
+
11
+ import { EventEmitter } from 'node:events';
12
+ import {
13
+ IMemoryBackend,
14
+ MemoryEntry,
15
+ MemoryEntryInput,
16
+ MemoryEntryUpdate,
17
+ MemoryQuery,
18
+ SearchOptions,
19
+ SearchResult,
20
+ BackendStats,
21
+ HealthCheckResult,
22
+ ComponentHealth,
23
+ EmbeddingGenerator,
24
+ createDefaultEntry,
25
+ QueryType,
26
+ } from './types.js';
27
+ import { SQLiteBackend, SQLiteBackendConfig } from './sqlite-backend.js';
28
+ import { AgentDBBackend, AgentDBBackendConfig } from './agentdb-backend.js';
29
+
30
+ /**
31
+ * Configuration for HybridBackend
32
+ */
33
+ export interface HybridBackendConfig {
34
+ /** SQLite configuration */
35
+ sqlite?: Partial<SQLiteBackendConfig>;
36
+
37
+ /** AgentDB configuration */
38
+ agentdb?: Partial<AgentDBBackendConfig>;
39
+
40
+ /** Default namespace */
41
+ defaultNamespace?: string;
42
+
43
+ /** Embedding generator function */
44
+ embeddingGenerator?: EmbeddingGenerator;
45
+
46
+ /** Query routing strategy */
47
+ routingStrategy?: 'auto' | 'sqlite-first' | 'agentdb-first';
48
+
49
+ /** Enable dual-write (write to both backends) */
50
+ dualWrite?: boolean;
51
+
52
+ /** Semantic search threshold for hybrid queries */
53
+ semanticThreshold?: number;
54
+
55
+ /** Maximum results to fetch from each backend in hybrid queries */
56
+ hybridMaxResults?: number;
57
+ }
58
+
59
+ /**
60
+ * Default configuration
61
+ */
62
+ const DEFAULT_CONFIG: Required<HybridBackendConfig> = {
63
+ sqlite: {},
64
+ agentdb: {},
65
+ defaultNamespace: 'default',
66
+ embeddingGenerator: undefined as any,
67
+ routingStrategy: 'auto',
68
+ dualWrite: true,
69
+ semanticThreshold: 0.7,
70
+ hybridMaxResults: 100,
71
+ };
72
+
73
+ /**
74
+ * Structured Query Interface
75
+ * Optimized for SQLite's strengths
76
+ */
77
+ export interface StructuredQuery {
78
+ /** Exact key match */
79
+ key?: string;
80
+
81
+ /** Key prefix match */
82
+ keyPrefix?: string;
83
+
84
+ /** Namespace filter */
85
+ namespace?: string;
86
+
87
+ /** Owner filter */
88
+ ownerId?: string;
89
+
90
+ /** Type filter */
91
+ type?: string;
92
+
93
+ /** Time range filters */
94
+ createdAfter?: number;
95
+ createdBefore?: number;
96
+ updatedAfter?: number;
97
+ updatedBefore?: number;
98
+
99
+ /** Pagination */
100
+ limit?: number;
101
+ offset?: number;
102
+ }
103
+
104
+ /**
105
+ * Semantic Query Interface
106
+ * Optimized for AgentDB's vector search
107
+ */
108
+ export interface SemanticQuery {
109
+ /** Content to search for (will be embedded) */
110
+ content?: string;
111
+
112
+ /** Pre-computed embedding */
113
+ embedding?: Float32Array;
114
+
115
+ /** Number of results */
116
+ k?: number;
117
+
118
+ /** Similarity threshold (0-1) */
119
+ threshold?: number;
120
+
121
+ /** Additional filters */
122
+ filters?: Partial<MemoryQuery>;
123
+ }
124
+
125
+ /**
126
+ * Hybrid Query Interface
127
+ * Combines structured + semantic search
128
+ */
129
+ export interface HybridQuery {
130
+ /** Semantic component */
131
+ semantic: SemanticQuery;
132
+
133
+ /** Structured component */
134
+ structured?: StructuredQuery;
135
+
136
+ /** How to combine results */
137
+ combineStrategy?: 'union' | 'intersection' | 'semantic-first' | 'structured-first';
138
+
139
+ /** Weights for score combination */
140
+ weights?: {
141
+ semantic: number;
142
+ structured: number;
143
+ };
144
+ }
145
+
146
+ /**
147
+ * HybridBackend Implementation
148
+ *
149
+ * Intelligently routes queries between SQLite and AgentDB:
150
+ * - Exact matches, prefix queries → SQLite
151
+ * - Semantic search, similarity → AgentDB
152
+ * - Complex hybrid queries → Both backends with intelligent merging
153
+ */
154
+ export class HybridBackend extends EventEmitter implements IMemoryBackend {
155
+ private sqlite: SQLiteBackend;
156
+ private agentdb: AgentDBBackend;
157
+ private config: Required<HybridBackendConfig>;
158
+ private initialized: boolean = false;
159
+
160
+ // Performance tracking
161
+ private stats = {
162
+ sqliteQueries: 0,
163
+ agentdbQueries: 0,
164
+ hybridQueries: 0,
165
+ totalQueryTime: 0,
166
+ };
167
+
168
+ constructor(config: HybridBackendConfig = {}) {
169
+ super();
170
+ this.config = { ...DEFAULT_CONFIG, ...config };
171
+
172
+ // Initialize SQLite backend
173
+ this.sqlite = new SQLiteBackend({
174
+ ...this.config.sqlite,
175
+ defaultNamespace: this.config.defaultNamespace,
176
+ embeddingGenerator: this.config.embeddingGenerator,
177
+ });
178
+
179
+ // Initialize AgentDB backend
180
+ this.agentdb = new AgentDBBackend({
181
+ ...this.config.agentdb,
182
+ namespace: this.config.defaultNamespace,
183
+ embeddingGenerator: this.config.embeddingGenerator,
184
+ });
185
+
186
+ // Forward events from both backends
187
+ this.sqlite.on('entry:stored', (data) => this.emit('sqlite:stored', data));
188
+ this.sqlite.on('entry:updated', (data) => this.emit('sqlite:updated', data));
189
+ this.sqlite.on('entry:deleted', (data) => this.emit('sqlite:deleted', data));
190
+
191
+ this.agentdb.on('entry:stored', (data) => this.emit('agentdb:stored', data));
192
+ this.agentdb.on('entry:updated', (data) => this.emit('agentdb:updated', data));
193
+ this.agentdb.on('entry:deleted', (data) => this.emit('agentdb:deleted', data));
194
+ this.agentdb.on('cache:hit', (data) => this.emit('cache:hit', data));
195
+ this.agentdb.on('cache:miss', (data) => this.emit('cache:miss', data));
196
+ }
197
+
198
+ /**
199
+ * Initialize both backends
200
+ */
201
+ async initialize(): Promise<void> {
202
+ if (this.initialized) return;
203
+
204
+ await Promise.all([this.sqlite.initialize(), this.agentdb.initialize()]);
205
+
206
+ this.initialized = true;
207
+ this.emit('initialized');
208
+ }
209
+
210
+ /**
211
+ * Shutdown both backends
212
+ */
213
+ async shutdown(): Promise<void> {
214
+ if (!this.initialized) return;
215
+
216
+ await Promise.all([this.sqlite.shutdown(), this.agentdb.shutdown()]);
217
+
218
+ this.initialized = false;
219
+ this.emit('shutdown');
220
+ }
221
+
222
+ /**
223
+ * Store in both backends (dual-write for consistency)
224
+ */
225
+ async store(entry: MemoryEntry): Promise<void> {
226
+ if (this.config.dualWrite) {
227
+ // Write to both backends in parallel
228
+ await Promise.all([this.sqlite.store(entry), this.agentdb.store(entry)]);
229
+ } else {
230
+ // Write to primary backend only (AgentDB has vector search)
231
+ await this.agentdb.store(entry);
232
+ }
233
+
234
+ this.emit('entry:stored', { id: entry.id });
235
+ }
236
+
237
+ /**
238
+ * Get from AgentDB (has caching enabled)
239
+ */
240
+ async get(id: string): Promise<MemoryEntry | null> {
241
+ return this.agentdb.get(id);
242
+ }
243
+
244
+ /**
245
+ * Get by key (SQLite optimized for exact matches)
246
+ */
247
+ async getByKey(namespace: string, key: string): Promise<MemoryEntry | null> {
248
+ return this.sqlite.getByKey(namespace, key);
249
+ }
250
+
251
+ /**
252
+ * Update in both backends
253
+ */
254
+ async update(id: string, update: MemoryEntryUpdate): Promise<MemoryEntry | null> {
255
+ if (this.config.dualWrite) {
256
+ // Update both backends
257
+ const [sqliteResult, agentdbResult] = await Promise.all([
258
+ this.sqlite.update(id, update),
259
+ this.agentdb.update(id, update),
260
+ ]);
261
+ return agentdbResult || sqliteResult;
262
+ } else {
263
+ return this.agentdb.update(id, update);
264
+ }
265
+ }
266
+
267
+ /**
268
+ * Delete from both backends
269
+ */
270
+ async delete(id: string): Promise<boolean> {
271
+ if (this.config.dualWrite) {
272
+ const [sqliteResult, agentdbResult] = await Promise.all([
273
+ this.sqlite.delete(id),
274
+ this.agentdb.delete(id),
275
+ ]);
276
+ return sqliteResult || agentdbResult;
277
+ } else {
278
+ return this.agentdb.delete(id);
279
+ }
280
+ }
281
+
282
+ /**
283
+ * Query routing - semantic goes to AgentDB, structured to SQLite
284
+ */
285
+ async query(query: MemoryQuery): Promise<MemoryEntry[]> {
286
+ const startTime = performance.now();
287
+
288
+ let results: MemoryEntry[];
289
+
290
+ // Route based on query type
291
+ switch (query.type) {
292
+ case 'exact':
293
+ // SQLite optimized for exact matches
294
+ this.stats.sqliteQueries++;
295
+ results = await this.sqlite.query(query);
296
+ break;
297
+
298
+ case 'prefix':
299
+ // SQLite optimized for prefix queries
300
+ this.stats.sqliteQueries++;
301
+ results = await this.sqlite.query(query);
302
+ break;
303
+
304
+ case 'tag':
305
+ // Both can handle tags, use SQLite for structured filtering
306
+ this.stats.sqliteQueries++;
307
+ results = await this.sqlite.query(query);
308
+ break;
309
+
310
+ case 'semantic':
311
+ // AgentDB optimized for semantic search
312
+ this.stats.agentdbQueries++;
313
+ results = await this.agentdb.query(query);
314
+ break;
315
+
316
+ case 'hybrid':
317
+ // Use hybrid query combining both backends
318
+ this.stats.hybridQueries++;
319
+ results = await this.queryHybridInternal(query);
320
+ break;
321
+
322
+ default:
323
+ // Auto-routing based on query properties
324
+ results = await this.autoRoute(query);
325
+ }
326
+
327
+ const duration = performance.now() - startTime;
328
+ this.stats.totalQueryTime += duration;
329
+
330
+ this.emit('query:completed', { type: query.type, duration, count: results.length });
331
+ return results;
332
+ }
333
+
334
+ /**
335
+ * Structured queries (SQL)
336
+ * Routes to SQLite for optimal performance
337
+ */
338
+ async queryStructured(query: StructuredQuery): Promise<MemoryEntry[]> {
339
+ this.stats.sqliteQueries++;
340
+
341
+ const memoryQuery: MemoryQuery = {
342
+ type: query.key ? 'exact' : query.keyPrefix ? 'prefix' : 'hybrid',
343
+ key: query.key,
344
+ keyPrefix: query.keyPrefix,
345
+ namespace: query.namespace,
346
+ ownerId: query.ownerId,
347
+ memoryType: query.type as any,
348
+ createdAfter: query.createdAfter,
349
+ createdBefore: query.createdBefore,
350
+ updatedAfter: query.updatedAfter,
351
+ updatedBefore: query.updatedBefore,
352
+ limit: query.limit || 100,
353
+ offset: query.offset || 0,
354
+ };
355
+
356
+ return this.sqlite.query(memoryQuery);
357
+ }
358
+
359
+ /**
360
+ * Semantic queries (vector)
361
+ * Routes to AgentDB for HNSW-based vector search
362
+ */
363
+ async querySemantic(query: SemanticQuery): Promise<MemoryEntry[]> {
364
+ this.stats.agentdbQueries++;
365
+
366
+ let embedding = query.embedding;
367
+
368
+ // Generate embedding if content provided
369
+ if (!embedding && query.content && this.config.embeddingGenerator) {
370
+ embedding = await this.config.embeddingGenerator(query.content);
371
+ }
372
+
373
+ if (!embedding) {
374
+ throw new Error('SemanticQuery requires either content or embedding');
375
+ }
376
+
377
+ const searchResults = await this.agentdb.search(embedding, {
378
+ k: query.k || 10,
379
+ threshold: query.threshold || this.config.semanticThreshold,
380
+ filters: query.filters as MemoryQuery | undefined,
381
+ });
382
+
383
+ return searchResults.map((r) => r.entry);
384
+ }
385
+
386
+ /**
387
+ * Hybrid queries (combine both)
388
+ * Intelligently merges results from both backends
389
+ */
390
+ async queryHybrid(query: HybridQuery): Promise<MemoryEntry[]> {
391
+ this.stats.hybridQueries++;
392
+
393
+ const strategy = query.combineStrategy || 'semantic-first';
394
+ const weights = query.weights || { semantic: 0.7, structured: 0.3 };
395
+
396
+ // Execute both queries in parallel
397
+ const [semanticResults, structuredResults] = await Promise.all([
398
+ this.querySemantic(query.semantic),
399
+ query.structured ? this.queryStructured(query.structured) : Promise.resolve([]),
400
+ ]);
401
+
402
+ // Combine results based on strategy
403
+ switch (strategy) {
404
+ case 'union':
405
+ return this.combineUnion(semanticResults, structuredResults);
406
+
407
+ case 'intersection':
408
+ return this.combineIntersection(semanticResults, structuredResults);
409
+
410
+ case 'semantic-first':
411
+ return this.combineSemanticFirst(semanticResults, structuredResults);
412
+
413
+ case 'structured-first':
414
+ return this.combineStructuredFirst(semanticResults, structuredResults);
415
+
416
+ default:
417
+ return this.combineUnion(semanticResults, structuredResults);
418
+ }
419
+ }
420
+
421
+ /**
422
+ * Semantic vector search (routes to AgentDB)
423
+ */
424
+ async search(embedding: Float32Array, options: SearchOptions): Promise<SearchResult[]> {
425
+ this.stats.agentdbQueries++;
426
+ return this.agentdb.search(embedding, options);
427
+ }
428
+
429
+ /**
430
+ * Bulk insert to both backends
431
+ */
432
+ async bulkInsert(entries: MemoryEntry[]): Promise<void> {
433
+ if (this.config.dualWrite) {
434
+ await Promise.all([this.sqlite.bulkInsert(entries), this.agentdb.bulkInsert(entries)]);
435
+ } else {
436
+ await this.agentdb.bulkInsert(entries);
437
+ }
438
+ }
439
+
440
+ /**
441
+ * Bulk delete from both backends
442
+ */
443
+ async bulkDelete(ids: string[]): Promise<number> {
444
+ if (this.config.dualWrite) {
445
+ const [sqliteCount, agentdbCount] = await Promise.all([
446
+ this.sqlite.bulkDelete(ids),
447
+ this.agentdb.bulkDelete(ids),
448
+ ]);
449
+ return Math.max(sqliteCount, agentdbCount);
450
+ } else {
451
+ return this.agentdb.bulkDelete(ids);
452
+ }
453
+ }
454
+
455
+ /**
456
+ * Count entries (use SQLite for efficiency)
457
+ */
458
+ async count(namespace?: string): Promise<number> {
459
+ return this.sqlite.count(namespace);
460
+ }
461
+
462
+ /**
463
+ * List namespaces (use SQLite)
464
+ */
465
+ async listNamespaces(): Promise<string[]> {
466
+ return this.sqlite.listNamespaces();
467
+ }
468
+
469
+ /**
470
+ * Clear namespace in both backends
471
+ */
472
+ async clearNamespace(namespace: string): Promise<number> {
473
+ if (this.config.dualWrite) {
474
+ const [sqliteCount, agentdbCount] = await Promise.all([
475
+ this.sqlite.clearNamespace(namespace),
476
+ this.agentdb.clearNamespace(namespace),
477
+ ]);
478
+ return Math.max(sqliteCount, agentdbCount);
479
+ } else {
480
+ return this.agentdb.clearNamespace(namespace);
481
+ }
482
+ }
483
+
484
+ /**
485
+ * Get combined statistics from both backends
486
+ */
487
+ async getStats(): Promise<BackendStats> {
488
+ const [sqliteStats, agentdbStats] = await Promise.all([
489
+ this.sqlite.getStats(),
490
+ this.agentdb.getStats(),
491
+ ]);
492
+
493
+ return {
494
+ totalEntries: Math.max(sqliteStats.totalEntries, agentdbStats.totalEntries),
495
+ entriesByNamespace: agentdbStats.entriesByNamespace,
496
+ entriesByType: agentdbStats.entriesByType,
497
+ memoryUsage: sqliteStats.memoryUsage + agentdbStats.memoryUsage,
498
+ hnswStats: agentdbStats.hnswStats,
499
+ cacheStats: agentdbStats.cacheStats,
500
+ avgQueryTime:
501
+ this.stats.hybridQueries + this.stats.sqliteQueries + this.stats.agentdbQueries > 0
502
+ ? this.stats.totalQueryTime /
503
+ (this.stats.hybridQueries + this.stats.sqliteQueries + this.stats.agentdbQueries)
504
+ : 0,
505
+ avgSearchTime: agentdbStats.avgSearchTime,
506
+ };
507
+ }
508
+
509
+ /**
510
+ * Health check for both backends
511
+ */
512
+ async healthCheck(): Promise<HealthCheckResult> {
513
+ const [sqliteHealth, agentdbHealth] = await Promise.all([
514
+ this.sqlite.healthCheck(),
515
+ this.agentdb.healthCheck(),
516
+ ]);
517
+
518
+ const allIssues = [...sqliteHealth.issues, ...agentdbHealth.issues];
519
+ const allRecommendations = [
520
+ ...sqliteHealth.recommendations,
521
+ ...agentdbHealth.recommendations,
522
+ ];
523
+
524
+ // Determine overall status
525
+ let status: 'healthy' | 'degraded' | 'unhealthy' = 'healthy';
526
+ if (
527
+ sqliteHealth.status === 'unhealthy' ||
528
+ agentdbHealth.status === 'unhealthy'
529
+ ) {
530
+ status = 'unhealthy';
531
+ } else if (
532
+ sqliteHealth.status === 'degraded' ||
533
+ agentdbHealth.status === 'degraded'
534
+ ) {
535
+ status = 'degraded';
536
+ }
537
+
538
+ return {
539
+ status,
540
+ components: {
541
+ storage: sqliteHealth.components.storage,
542
+ index: agentdbHealth.components.index,
543
+ cache: agentdbHealth.components.cache,
544
+ },
545
+ timestamp: Date.now(),
546
+ issues: allIssues,
547
+ recommendations: allRecommendations,
548
+ };
549
+ }
550
+
551
+ // ===== Private Methods =====
552
+
553
+ /**
554
+ * Auto-route queries based on properties
555
+ */
556
+ private async autoRoute(query: MemoryQuery): Promise<MemoryEntry[]> {
557
+ // If has embedding or content, use semantic search (AgentDB)
558
+ const hasEmbeddingGenerator = typeof this.config.embeddingGenerator === 'function';
559
+ if (query.embedding || (query.content && hasEmbeddingGenerator)) {
560
+ this.stats.agentdbQueries++;
561
+ return this.agentdb.query(query);
562
+ }
563
+
564
+ // If has exact key or prefix, use structured search (SQLite)
565
+ if (query.key || query.keyPrefix) {
566
+ this.stats.sqliteQueries++;
567
+ return this.sqlite.query(query);
568
+ }
569
+
570
+ // For other filters, use routing strategy
571
+ switch (this.config.routingStrategy) {
572
+ case 'sqlite-first':
573
+ this.stats.sqliteQueries++;
574
+ return this.sqlite.query(query);
575
+
576
+ case 'agentdb-first':
577
+ this.stats.agentdbQueries++;
578
+ return this.agentdb.query(query);
579
+
580
+ case 'auto':
581
+ default:
582
+ // Default to AgentDB (has caching)
583
+ this.stats.agentdbQueries++;
584
+ return this.agentdb.query(query);
585
+ }
586
+ }
587
+
588
+ /**
589
+ * Internal hybrid query implementation
590
+ */
591
+ private async queryHybridInternal(query: MemoryQuery): Promise<MemoryEntry[]> {
592
+ // If semantic component exists, use hybrid
593
+ if (query.embedding || query.content) {
594
+ const semanticQuery: SemanticQuery = {
595
+ content: query.content,
596
+ embedding: query.embedding,
597
+ k: query.limit || 10,
598
+ threshold: query.threshold,
599
+ filters: query,
600
+ };
601
+
602
+ const structuredQuery: StructuredQuery = {
603
+ namespace: query.namespace,
604
+ key: query.key,
605
+ keyPrefix: query.keyPrefix,
606
+ ownerId: query.ownerId,
607
+ type: query.memoryType,
608
+ createdAfter: query.createdAfter,
609
+ createdBefore: query.createdBefore,
610
+ updatedAfter: query.updatedAfter,
611
+ updatedBefore: query.updatedBefore,
612
+ limit: query.limit,
613
+ offset: query.offset,
614
+ };
615
+
616
+ return this.queryHybrid({
617
+ semantic: semanticQuery,
618
+ structured: structuredQuery,
619
+ combineStrategy: 'semantic-first',
620
+ });
621
+ }
622
+
623
+ // Otherwise, route to structured
624
+ return this.autoRoute(query);
625
+ }
626
+
627
+ /**
628
+ * Combine results using union (all unique results)
629
+ */
630
+ private combineUnion(
631
+ semanticResults: MemoryEntry[],
632
+ structuredResults: MemoryEntry[]
633
+ ): MemoryEntry[] {
634
+ const seen = new Set<string>();
635
+ const combined: MemoryEntry[] = [];
636
+
637
+ for (const entry of [...semanticResults, ...structuredResults]) {
638
+ if (!seen.has(entry.id)) {
639
+ seen.add(entry.id);
640
+ combined.push(entry);
641
+ }
642
+ }
643
+
644
+ return combined;
645
+ }
646
+
647
+ /**
648
+ * Combine results using intersection (only common results)
649
+ */
650
+ private combineIntersection(
651
+ semanticResults: MemoryEntry[],
652
+ structuredResults: MemoryEntry[]
653
+ ): MemoryEntry[] {
654
+ const semanticIds = new Set(semanticResults.map((e) => e.id));
655
+ return structuredResults.filter((e) => semanticIds.has(e.id));
656
+ }
657
+
658
+ /**
659
+ * Semantic-first: Prefer semantic results, add structured if not present
660
+ */
661
+ private combineSemanticFirst(
662
+ semanticResults: MemoryEntry[],
663
+ structuredResults: MemoryEntry[]
664
+ ): MemoryEntry[] {
665
+ const semanticIds = new Set(semanticResults.map((e) => e.id));
666
+ const additional = structuredResults.filter((e) => !semanticIds.has(e.id));
667
+ return [...semanticResults, ...additional];
668
+ }
669
+
670
+ /**
671
+ * Structured-first: Prefer structured results, add semantic if not present
672
+ */
673
+ private combineStructuredFirst(
674
+ semanticResults: MemoryEntry[],
675
+ structuredResults: MemoryEntry[]
676
+ ): MemoryEntry[] {
677
+ const structuredIds = new Set(structuredResults.map((e) => e.id));
678
+ const additional = semanticResults.filter((e) => !structuredIds.has(e.id));
679
+ return [...structuredResults, ...additional];
680
+ }
681
+
682
+ /**
683
+ * Get underlying backends for advanced operations
684
+ */
685
+ getSQLiteBackend(): SQLiteBackend {
686
+ return this.sqlite;
687
+ }
688
+
689
+ getAgentDBBackend(): AgentDBBackend {
690
+ return this.agentdb;
691
+ }
692
+ }
693
+
694
+ export default HybridBackend;