@cmdoss/memwal-sdk 0.6.2 → 0.8.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 (247) hide show
  1. package/ARCHITECTURE.md +547 -547
  2. package/BENCHMARKS.md +238 -238
  3. package/README.md +310 -181
  4. package/dist/ai-sdk/tools.d.ts +2 -2
  5. package/dist/ai-sdk/tools.js +2 -2
  6. package/dist/client/ClientMemoryManager.js +2 -2
  7. package/dist/client/ClientMemoryManager.js.map +1 -1
  8. package/dist/client/PersonalDataWallet.d.ts.map +1 -1
  9. package/dist/client/SimplePDWClient.d.ts +29 -1
  10. package/dist/client/SimplePDWClient.d.ts.map +1 -1
  11. package/dist/client/SimplePDWClient.js +45 -13
  12. package/dist/client/SimplePDWClient.js.map +1 -1
  13. package/dist/client/namespaces/EmbeddingsNamespace.d.ts +1 -1
  14. package/dist/client/namespaces/EmbeddingsNamespace.js +1 -1
  15. package/dist/client/namespaces/MemoryNamespace.d.ts +31 -0
  16. package/dist/client/namespaces/MemoryNamespace.d.ts.map +1 -1
  17. package/dist/client/namespaces/MemoryNamespace.js +272 -39
  18. package/dist/client/namespaces/MemoryNamespace.js.map +1 -1
  19. package/dist/client/namespaces/consolidated/AINamespace.d.ts +2 -2
  20. package/dist/client/namespaces/consolidated/AINamespace.js +2 -2
  21. package/dist/client/namespaces/consolidated/BlockchainNamespace.d.ts +12 -2
  22. package/dist/client/namespaces/consolidated/BlockchainNamespace.d.ts.map +1 -1
  23. package/dist/client/namespaces/consolidated/BlockchainNamespace.js +62 -4
  24. package/dist/client/namespaces/consolidated/BlockchainNamespace.js.map +1 -1
  25. package/dist/client/namespaces/consolidated/StorageNamespace.d.ts +67 -2
  26. package/dist/client/namespaces/consolidated/StorageNamespace.d.ts.map +1 -1
  27. package/dist/client/namespaces/consolidated/StorageNamespace.js +549 -16
  28. package/dist/client/namespaces/consolidated/StorageNamespace.js.map +1 -1
  29. package/dist/config/ConfigurationHelper.js +61 -61
  30. package/dist/config/defaults.js +2 -2
  31. package/dist/config/defaults.js.map +1 -1
  32. package/dist/graph/GraphService.js +21 -21
  33. package/dist/graph/GraphService.js.map +1 -1
  34. package/dist/index.d.ts +3 -1
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +3 -1
  37. package/dist/index.js.map +1 -1
  38. package/dist/infrastructure/seal/EncryptionService.d.ts +9 -5
  39. package/dist/infrastructure/seal/EncryptionService.d.ts.map +1 -1
  40. package/dist/infrastructure/seal/EncryptionService.js +37 -15
  41. package/dist/infrastructure/seal/EncryptionService.js.map +1 -1
  42. package/dist/infrastructure/seal/SealService.d.ts +13 -5
  43. package/dist/infrastructure/seal/SealService.d.ts.map +1 -1
  44. package/dist/infrastructure/seal/SealService.js +36 -34
  45. package/dist/infrastructure/seal/SealService.js.map +1 -1
  46. package/dist/langchain/createPDWRAG.js +30 -30
  47. package/dist/retrieval/MemoryDecryptionPipeline.d.ts.map +1 -1
  48. package/dist/retrieval/MemoryDecryptionPipeline.js +2 -1
  49. package/dist/retrieval/MemoryDecryptionPipeline.js.map +1 -1
  50. package/dist/retrieval/MemoryRetrievalService.d.ts +31 -0
  51. package/dist/retrieval/MemoryRetrievalService.d.ts.map +1 -1
  52. package/dist/retrieval/MemoryRetrievalService.js +44 -4
  53. package/dist/retrieval/MemoryRetrievalService.js.map +1 -1
  54. package/dist/services/CapabilityService.d.ts.map +1 -1
  55. package/dist/services/CapabilityService.js +30 -14
  56. package/dist/services/CapabilityService.js.map +1 -1
  57. package/dist/services/CrossContextPermissionService.d.ts.map +1 -1
  58. package/dist/services/CrossContextPermissionService.js +9 -7
  59. package/dist/services/CrossContextPermissionService.js.map +1 -1
  60. package/dist/services/EmbeddingService.d.ts +28 -1
  61. package/dist/services/EmbeddingService.d.ts.map +1 -1
  62. package/dist/services/EmbeddingService.js +54 -0
  63. package/dist/services/EmbeddingService.js.map +1 -1
  64. package/dist/services/EncryptionService.d.ts.map +1 -1
  65. package/dist/services/EncryptionService.js +6 -5
  66. package/dist/services/EncryptionService.js.map +1 -1
  67. package/dist/services/GeminiAIService.js +309 -309
  68. package/dist/services/IndexManager.d.ts +5 -1
  69. package/dist/services/IndexManager.d.ts.map +1 -1
  70. package/dist/services/IndexManager.js +17 -40
  71. package/dist/services/IndexManager.js.map +1 -1
  72. package/dist/services/QueryService.js +1 -1
  73. package/dist/services/QueryService.js.map +1 -1
  74. package/dist/services/StorageService.d.ts +11 -0
  75. package/dist/services/StorageService.d.ts.map +1 -1
  76. package/dist/services/StorageService.js +73 -10
  77. package/dist/services/StorageService.js.map +1 -1
  78. package/dist/services/TransactionService.d.ts +20 -0
  79. package/dist/services/TransactionService.d.ts.map +1 -1
  80. package/dist/services/TransactionService.js +43 -0
  81. package/dist/services/TransactionService.js.map +1 -1
  82. package/dist/services/ViewService.js +2 -2
  83. package/dist/services/ViewService.js.map +1 -1
  84. package/dist/services/storage/QuiltBatchManager.d.ts +101 -1
  85. package/dist/services/storage/QuiltBatchManager.d.ts.map +1 -1
  86. package/dist/services/storage/QuiltBatchManager.js +410 -20
  87. package/dist/services/storage/QuiltBatchManager.js.map +1 -1
  88. package/dist/services/storage/index.d.ts +1 -1
  89. package/dist/services/storage/index.d.ts.map +1 -1
  90. package/dist/services/storage/index.js.map +1 -1
  91. package/dist/utils/LRUCache.d.ts +106 -0
  92. package/dist/utils/LRUCache.d.ts.map +1 -0
  93. package/dist/utils/LRUCache.js +281 -0
  94. package/dist/utils/LRUCache.js.map +1 -0
  95. package/dist/utils/index.d.ts +1 -0
  96. package/dist/utils/index.d.ts.map +1 -1
  97. package/dist/utils/index.js +2 -0
  98. package/dist/utils/index.js.map +1 -1
  99. package/dist/utils/memoryIndexOnChain.d.ts +212 -0
  100. package/dist/utils/memoryIndexOnChain.d.ts.map +1 -0
  101. package/dist/utils/memoryIndexOnChain.js +312 -0
  102. package/dist/utils/memoryIndexOnChain.js.map +1 -0
  103. package/dist/utils/rebuildIndexNode.d.ts +29 -0
  104. package/dist/utils/rebuildIndexNode.d.ts.map +1 -1
  105. package/dist/utils/rebuildIndexNode.js +366 -98
  106. package/dist/utils/rebuildIndexNode.js.map +1 -1
  107. package/dist/vector/HnswWasmService.d.ts +20 -5
  108. package/dist/vector/HnswWasmService.d.ts.map +1 -1
  109. package/dist/vector/HnswWasmService.js +73 -40
  110. package/dist/vector/HnswWasmService.js.map +1 -1
  111. package/dist/vector/IHnswService.d.ts +10 -1
  112. package/dist/vector/IHnswService.d.ts.map +1 -1
  113. package/dist/vector/IHnswService.js.map +1 -1
  114. package/dist/vector/NodeHnswService.d.ts +16 -0
  115. package/dist/vector/NodeHnswService.d.ts.map +1 -1
  116. package/dist/vector/NodeHnswService.js +84 -5
  117. package/dist/vector/NodeHnswService.js.map +1 -1
  118. package/dist/vector/createHnswService.d.ts +1 -1
  119. package/dist/vector/createHnswService.js +1 -1
  120. package/dist/vector/index.d.ts +1 -1
  121. package/dist/vector/index.js +1 -1
  122. package/package.json +157 -157
  123. package/src/access/PermissionService.ts +635 -635
  124. package/src/aggregation/AggregationService.ts +389 -389
  125. package/src/ai-sdk/PDWVectorStore.ts +715 -715
  126. package/src/ai-sdk/index.ts +65 -65
  127. package/src/ai-sdk/tools.ts +460 -460
  128. package/src/ai-sdk/types.ts +404 -404
  129. package/src/batch/BatchManager.ts +597 -597
  130. package/src/batch/BatchingService.ts +429 -429
  131. package/src/batch/MemoryProcessingCache.ts +492 -492
  132. package/src/batch/index.ts +30 -30
  133. package/src/browser.ts +200 -200
  134. package/src/client/ClientMemoryManager.ts +987 -987
  135. package/src/client/PersonalDataWallet.ts +345 -345
  136. package/src/client/SimplePDWClient.ts +1289 -1222
  137. package/src/client/factory.ts +154 -154
  138. package/src/client/namespaces/AnalyticsNamespace.ts +377 -377
  139. package/src/client/namespaces/BatchNamespace.ts +356 -356
  140. package/src/client/namespaces/CacheNamespace.ts +123 -123
  141. package/src/client/namespaces/CapabilityNamespace.ts +217 -217
  142. package/src/client/namespaces/ClassifyNamespace.ts +169 -169
  143. package/src/client/namespaces/ContextNamespace.ts +297 -297
  144. package/src/client/namespaces/EmbeddingsNamespace.ts +99 -99
  145. package/src/client/namespaces/EncryptionNamespace.ts +221 -221
  146. package/src/client/namespaces/GraphNamespace.ts +468 -468
  147. package/src/client/namespaces/IndexNamespace.ts +361 -361
  148. package/src/client/namespaces/MemoryNamespace.ts +1422 -1135
  149. package/src/client/namespaces/PermissionsNamespace.ts +254 -254
  150. package/src/client/namespaces/PipelineNamespace.ts +220 -220
  151. package/src/client/namespaces/SearchNamespace.ts +1049 -1049
  152. package/src/client/namespaces/StorageNamespace.ts +458 -458
  153. package/src/client/namespaces/TxNamespace.ts +260 -260
  154. package/src/client/namespaces/WalletNamespace.ts +243 -243
  155. package/src/client/namespaces/consolidated/AINamespace.ts +449 -449
  156. package/src/client/namespaces/consolidated/BlockchainNamespace.ts +607 -546
  157. package/src/client/namespaces/consolidated/SecurityNamespace.ts +648 -648
  158. package/src/client/namespaces/consolidated/StorageNamespace.ts +1141 -497
  159. package/src/client/namespaces/consolidated/index.ts +39 -39
  160. package/src/client/signers/KeypairSigner.ts +108 -108
  161. package/src/client/signers/UnifiedSigner.ts +110 -110
  162. package/src/client/signers/WalletAdapterSigner.ts +159 -159
  163. package/src/client/signers/index.ts +26 -26
  164. package/src/config/ConfigurationHelper.ts +412 -412
  165. package/src/config/defaults.ts +51 -51
  166. package/src/config/index.ts +8 -8
  167. package/src/config/validation.ts +70 -70
  168. package/src/core/index.ts +14 -14
  169. package/src/core/interfaces/IService.ts +307 -307
  170. package/src/core/interfaces/index.ts +8 -8
  171. package/src/core/types/capability.ts +297 -297
  172. package/src/core/types/index.ts +870 -870
  173. package/src/core/types/wallet.ts +270 -270
  174. package/src/core/types.ts +9 -9
  175. package/src/core/wallet.ts +222 -222
  176. package/src/embedding/index.ts +19 -19
  177. package/src/embedding/types.ts +357 -357
  178. package/src/errors/index.ts +602 -602
  179. package/src/errors/recovery.ts +461 -461
  180. package/src/errors/validation.ts +567 -567
  181. package/src/generated/pdw/capability.ts +319 -319
  182. package/src/graph/GraphService.ts +887 -887
  183. package/src/graph/KnowledgeGraphManager.ts +728 -728
  184. package/src/graph/index.ts +25 -25
  185. package/src/index.ts +498 -474
  186. package/src/infrastructure/index.ts +22 -22
  187. package/src/infrastructure/seal/EncryptionService.ts +628 -603
  188. package/src/infrastructure/seal/SealService.ts +613 -615
  189. package/src/infrastructure/seal/index.ts +9 -9
  190. package/src/infrastructure/sui/BlockchainManager.ts +627 -627
  191. package/src/infrastructure/sui/SuiService.ts +888 -888
  192. package/src/infrastructure/sui/index.ts +9 -9
  193. package/src/infrastructure/walrus/StorageManager.ts +604 -604
  194. package/src/infrastructure/walrus/WalrusStorageService.ts +612 -612
  195. package/src/infrastructure/walrus/index.ts +9 -9
  196. package/src/langchain/PDWEmbeddings.ts +145 -145
  197. package/src/langchain/PDWVectorStore.ts +456 -456
  198. package/src/langchain/createPDWRAG.ts +303 -303
  199. package/src/langchain/index.ts +47 -47
  200. package/src/permissions/ConsentRepository.browser.ts +249 -249
  201. package/src/permissions/ConsentRepository.ts +364 -364
  202. package/src/pipeline/MemoryPipeline.ts +862 -862
  203. package/src/pipeline/PipelineManager.ts +683 -683
  204. package/src/pipeline/index.ts +26 -26
  205. package/src/retrieval/AdvancedSearchService.ts +629 -629
  206. package/src/retrieval/MemoryAnalyticsService.ts +711 -711
  207. package/src/retrieval/MemoryDecryptionPipeline.ts +825 -824
  208. package/src/retrieval/MemoryRetrievalService.ts +904 -830
  209. package/src/retrieval/index.ts +42 -42
  210. package/src/services/BatchService.ts +352 -352
  211. package/src/services/CapabilityService.ts +464 -448
  212. package/src/services/ClassifierService.ts +465 -465
  213. package/src/services/CrossContextPermissionService.ts +486 -484
  214. package/src/services/EmbeddingService.ts +771 -706
  215. package/src/services/EncryptionService.ts +712 -711
  216. package/src/services/GeminiAIService.ts +753 -753
  217. package/src/services/IndexManager.ts +977 -1004
  218. package/src/services/MemoryIndexService.ts +1003 -1003
  219. package/src/services/MemoryService.ts +369 -369
  220. package/src/services/QueryService.ts +890 -890
  221. package/src/services/StorageService.ts +1182 -1111
  222. package/src/services/TransactionService.ts +838 -790
  223. package/src/services/VectorService.ts +462 -462
  224. package/src/services/ViewService.ts +484 -484
  225. package/src/services/index.ts +25 -25
  226. package/src/services/storage/BlobAttributesManager.ts +333 -333
  227. package/src/services/storage/KnowledgeGraphManager.ts +425 -425
  228. package/src/services/storage/MemorySearchManager.ts +387 -387
  229. package/src/services/storage/QuiltBatchManager.ts +1130 -660
  230. package/src/services/storage/WalrusMetadataManager.ts +268 -268
  231. package/src/services/storage/WalrusStorageManager.ts +287 -287
  232. package/src/services/storage/index.ts +57 -52
  233. package/src/types/index.ts +13 -13
  234. package/src/utils/LRUCache.ts +378 -0
  235. package/src/utils/index.ts +76 -68
  236. package/src/utils/memoryIndexOnChain.ts +507 -0
  237. package/src/utils/rebuildIndex.ts +290 -290
  238. package/src/utils/rebuildIndexNode.ts +771 -424
  239. package/src/vector/BrowserHnswIndexService.ts +758 -758
  240. package/src/vector/HnswWasmService.ts +731 -679
  241. package/src/vector/IHnswService.ts +233 -224
  242. package/src/vector/NodeHnswService.ts +833 -735
  243. package/src/vector/VectorManager.ts +478 -478
  244. package/src/vector/createHnswService.ts +135 -135
  245. package/src/vector/index.ts +56 -56
  246. package/src/wallet/ContextWalletService.ts +656 -656
  247. package/src/wallet/MainWalletService.ts +317 -317
@@ -1,1049 +1,1049 @@
1
- /**
2
- * Search Namespace - All Search Methods
3
- *
4
- * Provides comprehensive search capabilities:
5
- * - Vector similarity search
6
- * - Semantic search (AI-enhanced)
7
- * - Keyword search (metadata)
8
- * - Hybrid search (combined)
9
- * - Temporal search (date-based)
10
- * - Category filtering
11
- *
12
- * @module client/namespaces
13
- */
14
-
15
- import type { ServiceContainer } from '../SimplePDWClient';
16
-
17
- /**
18
- * Search result item
19
- *
20
- * NOTE: `content` is empty by default for privacy. Content is stored encrypted
21
- * on Walrus and not in the index. Use `pdw.search.withContent()` or
22
- * `pdw.memory.get(blobId)` to retrieve decrypted content.
23
- */
24
- export interface SearchResult {
25
- id: string;
26
- /** Empty by default - use withContent() to fetch from Walrus */
27
- content: string;
28
- score: number;
29
- similarity: number;
30
- category?: string;
31
- importance?: number;
32
- topic?: string;
33
- /** Use this to fetch content: pdw.memory.get(blobId) */
34
- blobId: string;
35
- metadata?: Record<string, any>;
36
- timestamp: number;
37
- }
38
-
39
- /**
40
- * Vector search options
41
- */
42
- export interface VectorSearchOptions {
43
- limit?: number;
44
- threshold?: number; // Minimum similarity score
45
- category?: string;
46
- includeEmbeddings?: boolean;
47
- /**
48
- * Fetch content from Walrus for each result (slower but includes content)
49
- * @default false
50
- */
51
- fetchContent?: boolean;
52
- }
53
-
54
- /**
55
- * Semantic search options
56
- */
57
- export interface SemanticSearchOptions extends VectorSearchOptions {
58
- rerank?: boolean; // Use AI to rerank results
59
- }
60
-
61
- /**
62
- * Keyword search options
63
- */
64
- export interface KeywordSearchOptions {
65
- limit?: number;
66
- category?: string;
67
- fields?: string[]; // Which metadata fields to search
68
- caseSensitive?: boolean;
69
- }
70
-
71
- /**
72
- * Hybrid search options
73
- */
74
- export interface HybridSearchOptions {
75
- limit?: number;
76
- vectorWeight?: number; // 0-1, weight for vector search
77
- keywordWeight?: number; // 0-1, weight for keyword search
78
- category?: string;
79
- }
80
-
81
- /**
82
- * Date range for temporal search
83
- */
84
- export interface DateRange {
85
- start: Date | string;
86
- end?: Date | string;
87
- }
88
-
89
- /**
90
- * Search Namespace
91
- *
92
- * Handles all types of search operations
93
- */
94
- export class SearchNamespace {
95
- constructor(private services: ServiceContainer) {}
96
-
97
- /**
98
- * Vector similarity search
99
- *
100
- * Searches memories by semantic similarity using embeddings.
101
- *
102
- * NOTE: Content is NOT included by default for privacy (content is encrypted on Walrus).
103
- * Use `fetchContent: true` option or call `pdw.memory.get(blobId)` to get content.
104
- *
105
- * @param query - Text query to search for
106
- * @param options - Search options
107
- * @returns Sorted array of results by similarity (content empty unless fetchContent=true)
108
- *
109
- * @example
110
- * ```typescript
111
- * // Fast search (no content)
112
- * const results = await pdw.search.vector('programming');
113
- *
114
- * // Search with content (slower - fetches from Walrus)
115
- * const results = await pdw.search.vector('programming', { fetchContent: true });
116
- *
117
- * // Or fetch content for specific result
118
- * const memory = await pdw.memory.get(results[0].blobId);
119
- * ```
120
- */
121
- async vector(query: string, options: VectorSearchOptions = {}): Promise<SearchResult[]> {
122
- const { limit = 10, threshold = 0.7, category, fetchContent = false } = options;
123
-
124
- try {
125
- // Generate query embedding
126
- if (!this.services.embedding) {
127
- throw new Error('Embedding service not configured. Please provide geminiApiKey in config.');
128
- }
129
-
130
- const embResult = await this.services.embedding.embedText({
131
- text: query
132
- });
133
-
134
- // Search using local HNSW index (VectorService)
135
- if (!this.services.vector) {
136
- throw new Error('Vector service not configured. Enable local indexing in config.');
137
- }
138
-
139
- const spaceId = this.services.config.userAddress;
140
-
141
- // Search in local HNSW index
142
- const searchResult = await this.services.vector.searchVectors(spaceId, embResult.vector, {
143
- k: limit
144
- });
145
-
146
- // Filter by threshold and category
147
- let results = searchResult.results.filter((r: any) => r.similarity >= threshold);
148
-
149
- if (category) {
150
- results = results.filter((r: any) =>
151
- r.metadata?.category === category
152
- );
153
- }
154
-
155
- // Convert to SearchResult format
156
- // Option A+: Content may be available from local index when encryption is OFF
157
- const searchResults: SearchResult[] = results.map((r: any) => {
158
- // blobId must be a valid Walrus blob ID, not a vectorId
159
- // Only use metadata.blobId if it's a non-empty string that looks like a Walrus blobId
160
- const rawBlobId = r.metadata?.blobId;
161
- const isValidBlobId = rawBlobId && typeof rawBlobId === 'string' && rawBlobId.length > 10 && !/^\d+$/.test(rawBlobId);
162
- const blobId = isValidBlobId ? rawBlobId : (r.metadata?.memoryObjectId || '');
163
-
164
- return {
165
- id: r.memoryId || r.vectorId.toString(),
166
- content: r.metadata?.content || r.content || '', // ✅ Get content from index metadata if available
167
- score: r.similarity,
168
- similarity: r.similarity,
169
- category: r.metadata?.category,
170
- importance: r.metadata?.importance || 5,
171
- topic: r.metadata?.topic,
172
- blobId,
173
- metadata: r.metadata || {},
174
- timestamp: r.metadata?.timestamp || Date.now()
175
- };
176
- });
177
-
178
- // Optionally fetch content from Walrus
179
- if (fetchContent) {
180
- await this.populateContent(searchResults);
181
- }
182
-
183
- return searchResults;
184
- } catch (error) {
185
- throw new Error(`Vector search failed: ${error instanceof Error ? error.message : String(error)}`);
186
- }
187
- }
188
-
189
- /**
190
- * Semantic search (AI-enhanced)
191
- *
192
- * Uses AI to understand query intent and rerank results
193
- *
194
- * @param query - Natural language query
195
- * @param options - Search options
196
- * @returns Semantically relevant results
197
- */
198
- async semantic(query: string, options: SemanticSearchOptions = {}): Promise<SearchResult[]> {
199
- try {
200
- if (!this.services.query) {
201
- throw new Error('Query service not available');
202
- }
203
-
204
- // Use QueryService for semantic search
205
- const results = await this.services.query.semanticSearch(
206
- {
207
- query,
208
- userAddress: this.services.config.userAddress,
209
- queryType: 'semantic',
210
- k: options.limit || 10,
211
- threshold: options.threshold || 0.6
212
- },
213
- {
214
- expandQuery: options.rerank ?? true // Use expandQuery instead of rerank
215
- }
216
- );
217
-
218
- return results.map((r: any) => ({
219
- id: r.id,
220
- content: r.content || '',
221
- score: r.similarity_score || 0,
222
- similarity: r.similarity_score || 0,
223
- category: r.category,
224
- importance: r.metadata?.importance,
225
- blobId: r.blobId || r.id,
226
- metadata: r.metadata || {},
227
- timestamp: r.timestamp ? new Date(r.timestamp).getTime() : Date.now()
228
- }));
229
- } catch (error) {
230
- throw new Error(`Semantic search failed: ${error instanceof Error ? error.message : String(error)}`);
231
- }
232
- }
233
-
234
- /**
235
- * Keyword search (metadata-based)
236
- *
237
- * Searches in metadata fields using keywords
238
- *
239
- * @param query - Keyword to search for
240
- * @param options - Search options
241
- * @returns Matching memories
242
- */
243
- async keyword(query: string, options: KeywordSearchOptions = {}): Promise<SearchResult[]> {
244
- try {
245
- const { limit = 10, category, fields = ['content', 'topic'], caseSensitive = false } = options;
246
-
247
- // Use QueryService for keyword search
248
- const results = await this.services.query.keywordSearch({
249
- query,
250
- userAddress: this.services.config.userAddress,
251
- queryType: 'keyword',
252
- categories: category ? [category] : undefined,
253
- limit
254
- });
255
-
256
- return results.map((r: any) => ({
257
- id: r.id,
258
- content: r.content || '',
259
- score: 1.0, // Keyword match = binary
260
- similarity: 1.0,
261
- category: r.category,
262
- importance: r.metadata?.importance,
263
- blobId: r.blobId || r.id,
264
- metadata: r.metadata || {},
265
- timestamp: r.timestamp ? new Date(r.timestamp).getTime() : Date.now()
266
- }));
267
- } catch (error) {
268
- throw new Error(`Keyword search failed: ${error instanceof Error ? error.message : String(error)}`);
269
- }
270
- }
271
-
272
- /**
273
- * Hybrid search (vector + keyword)
274
- *
275
- * Combines vector similarity and keyword matching
276
- *
277
- * @param query - Search query
278
- * @param options - Hybrid search options
279
- * @returns Ranked results from both methods
280
- */
281
- async hybrid(query: string, options: HybridSearchOptions = {}): Promise<SearchResult[]> {
282
- try {
283
- const {
284
- limit = 10,
285
- vectorWeight = 0.7,
286
- keywordWeight = 0.3,
287
- category
288
- } = options;
289
-
290
- // Use QueryService for hybrid search
291
- const results = await this.services.query.hybridSearch({
292
- query,
293
- userAddress: this.services.config.userAddress,
294
- queryType: 'hybrid',
295
- limit,
296
- categories: category ? [category] : undefined
297
- });
298
-
299
- return results.map((r: any) => ({
300
- id: r.id,
301
- content: r.content || '',
302
- score: r.similarity_score || 0,
303
- similarity: r.similarity_score || 0,
304
- category: r.category,
305
- importance: r.metadata?.importance,
306
- blobId: r.blobId || r.id,
307
- metadata: r.metadata || {},
308
- timestamp: r.timestamp ? new Date(r.timestamp).getTime() : Date.now()
309
- }));
310
- } catch (error) {
311
- throw new Error(`Hybrid search failed: ${error instanceof Error ? error.message : String(error)}`);
312
- }
313
- }
314
-
315
- /**
316
- * Search by category
317
- *
318
- * First tries local vector index (for recently created memories),
319
- * then queries blockchain for on-chain Memory objects.
320
- * Combines results and deduplicates by blobId.
321
- *
322
- * @param category - Category to filter by
323
- * @param options - Additional options
324
- * @returns Memories in category
325
- */
326
- async byCategory(category: string, options: VectorSearchOptions = {}): Promise<SearchResult[]> {
327
- try {
328
- const limit = options.limit || 50;
329
- const results: SearchResult[] = [];
330
- const seenIds = new Set<string>();
331
-
332
- // 1. First check local vector index (includes recently created memories)
333
- // Use VectorService.getVectorsByCategory() method
334
- if (this.services.vector) {
335
- try {
336
- const spaceId = this.services.config.userAddress;
337
- const localResults = this.services.vector.getVectorsByCategory(spaceId, category);
338
-
339
- for (const { vectorId, metadata } of localResults) {
340
- // blobId must be a valid Walrus blob ID, not a vectorId
341
- const rawBlobId = metadata?.blobId;
342
- const isValidBlobId = rawBlobId && typeof rawBlobId === 'string' && rawBlobId.length > 10 && !/^\d+$/.test(rawBlobId);
343
- const blobId = isValidBlobId ? rawBlobId : (metadata?.memoryObjectId || '');
344
- const id = blobId || metadata?.memoryId || vectorId?.toString();
345
-
346
- if (id && !seenIds.has(id)) {
347
- seenIds.add(id);
348
- results.push({
349
- id,
350
- content: metadata?.content || '',
351
- score: 1.0,
352
- similarity: 1.0,
353
- category: metadata?.category,
354
- importance: metadata?.importance || 5,
355
- topic: metadata?.topic,
356
- blobId,
357
- metadata: metadata || {},
358
- timestamp: metadata?.timestamp || Date.now()
359
- });
360
- }
361
- }
362
- } catch (localError) {
363
- // Local index access failed, continue with on-chain query
364
- console.log('Local index search skipped:', localError);
365
- }
366
- }
367
-
368
- // 2. Query on-chain Memory objects
369
- const viewService = this.services.viewService;
370
- if (viewService) {
371
- try {
372
- const response = await viewService.getUserMemories(
373
- this.services.config.userAddress,
374
- { limit, category }
375
- );
376
-
377
- for (const m of response.data) {
378
- const id = m.id || m.blobId;
379
- if (id && !seenIds.has(id)) {
380
- seenIds.add(id);
381
- results.push({
382
- id,
383
- content: '',
384
- score: 1.0,
385
- similarity: 1.0,
386
- category: m.category,
387
- importance: m.importance || 5,
388
- topic: m.topic,
389
- blobId: m.blobId || id,
390
- metadata: {
391
- category: m.category,
392
- importance: m.importance,
393
- topic: m.topic
394
- },
395
- timestamp: m.createdAt || Date.now()
396
- });
397
- }
398
- }
399
- } catch (viewError) {
400
- console.log('On-chain query failed:', viewError);
401
- }
402
- }
403
-
404
- return results.slice(0, limit);
405
- } catch (error) {
406
- throw new Error(`Category search failed: ${error instanceof Error ? error.message : String(error)}`);
407
- }
408
- }
409
-
410
- /**
411
- * Search by date range
412
- *
413
- * @param dateRange - Start and end dates
414
- * @param options - Additional options
415
- * @returns Memories within date range
416
- */
417
- async byDate(dateRange: DateRange, options: VectorSearchOptions = {}): Promise<SearchResult[]> {
418
- try {
419
- const start = typeof dateRange.start === 'string'
420
- ? new Date(dateRange.start)
421
- : dateRange.start;
422
-
423
- const end = dateRange.end
424
- ? (typeof dateRange.end === 'string' ? new Date(dateRange.end) : dateRange.end)
425
- : new Date();
426
-
427
- // Use QueryService for temporal search
428
- const results = await this.services.query.temporalSearch({
429
- userAddress: this.services.config.userAddress,
430
- queryType: 'temporal',
431
- dateRange: { start, end },
432
- limit: options.limit || 50,
433
- categories: options.category ? [options.category] : undefined
434
- });
435
-
436
- return results.map((r: any) => ({
437
- id: r.id,
438
- content: r.content || '',
439
- score: 1.0,
440
- similarity: 1.0,
441
- category: r.category,
442
- importance: r.metadata?.importance,
443
- blobId: r.blobId || r.id,
444
- metadata: r.metadata || {},
445
- timestamp: r.timestamp ? new Date(r.timestamp).getTime() : Date.now()
446
- }));
447
- } catch (error) {
448
- throw new Error(`Temporal search failed: ${error instanceof Error ? error.message : String(error)}`);
449
- }
450
- }
451
-
452
- /**
453
- * Search by importance range
454
- *
455
- * @param min - Minimum importance (1-10)
456
- * @param max - Maximum importance (1-10)
457
- * @param options - Additional options
458
- * @returns Memories within importance range
459
- */
460
- async byImportance(
461
- min: number,
462
- max: number = 10,
463
- options: VectorSearchOptions = {}
464
- ): Promise<SearchResult[]> {
465
- const memoriesResult = await this.services.viewService?.getUserMemories(
466
- this.services.config.userAddress
467
- );
468
- const allMemories = memoriesResult?.data || [];
469
-
470
- const filtered = allMemories.filter((m: any) => {
471
- const importance = m.importance || m.metadata?.importance || 5;
472
- return importance >= min && importance <= max;
473
- });
474
-
475
- const limit = options.limit || 50;
476
- return filtered.slice(0, limit).map((m: any) => ({
477
- id: m.id || m.blobId,
478
- content: m.content || '',
479
- score: 1.0,
480
- similarity: 1.0,
481
- category: m.category || m.metadata?.category,
482
- importance: m.importance || m.metadata?.importance,
483
- blobId: m.blobId || m.id,
484
- metadata: m.metadata,
485
- timestamp: m.timestamp || Date.now()
486
- }));
487
- }
488
-
489
- /**
490
- * Advanced search with complex filters
491
- *
492
- * @param query - Search query or filters
493
- * @returns Filtered and ranked results
494
- */
495
- async advanced(query: {
496
- text?: string;
497
- category?: string;
498
- importance?: { min: number; max: number };
499
- dateRange?: DateRange;
500
- limit?: number;
501
- }): Promise<SearchResult[]> {
502
- try {
503
- // Combine multiple search strategies
504
- let results: SearchResult[] = [];
505
-
506
- // Start with vector search if text provided
507
- if (query.text) {
508
- results = await this.vector(query.text, {
509
- limit: query.limit || 50,
510
- category: query.category
511
- });
512
- } else {
513
- // Get all memories
514
- const memoriesResult = await this.services.viewService?.getUserMemories(
515
- this.services.config.userAddress
516
- );
517
- const memories = memoriesResult?.data || [];
518
-
519
- results = memories.map((m: any) => ({
520
- id: m.id || m.blobId,
521
- content: m.content || '',
522
- score: 1.0,
523
- similarity: 1.0,
524
- category: m.category || m.metadata?.category,
525
- importance: m.importance || m.metadata?.importance,
526
- blobId: m.blobId || m.id,
527
- metadata: m.metadata,
528
- timestamp: m.timestamp || Date.now()
529
- }));
530
- }
531
-
532
- // Apply filters
533
- if (query.importance) {
534
- results = results.filter(r =>
535
- (r.importance || 5) >= query.importance!.min &&
536
- (r.importance || 5) <= query.importance!.max
537
- );
538
- }
539
-
540
- if (query.dateRange) {
541
- const start = typeof query.dateRange.start === 'string'
542
- ? new Date(query.dateRange.start).getTime()
543
- : query.dateRange.start.getTime();
544
-
545
- const end = query.dateRange.end
546
- ? (typeof query.dateRange.end === 'string'
547
- ? new Date(query.dateRange.end).getTime()
548
- : query.dateRange.end.getTime())
549
- : Date.now();
550
-
551
- results = results.filter(r =>
552
- r.timestamp >= start && r.timestamp <= end
553
- );
554
- }
555
-
556
- return results.slice(0, query.limit || 50);
557
- } catch (error) {
558
- throw new Error(`Advanced search failed: ${error instanceof Error ? error.message : String(error)}`);
559
- }
560
- }
561
-
562
- /**
563
- * Graph-based search
564
- *
565
- * Search using knowledge graph relationships
566
- *
567
- * @param query - Entity or concept to search
568
- * @param options - Search options
569
- * @returns Related memories via graph connections
570
- */
571
- async graph(query: string, options: VectorSearchOptions = {}): Promise<SearchResult[]> {
572
- try {
573
- if (!this.services.query) {
574
- throw new Error('Query service not available');
575
- }
576
-
577
- // Use QueryService.graphSearch - returns { memories, graphResults }
578
- const result = await this.services.query.graphSearch({
579
- query,
580
- userAddress: this.services.config.userAddress,
581
- queryType: 'graph',
582
- limit: options.limit || 10
583
- });
584
-
585
- // Extract memories array from result
586
- const memories = result.memories || [];
587
-
588
- return memories.map((r: any) => ({
589
- id: r.id,
590
- content: r.content || '',
591
- score: r.similarity_score || 0,
592
- similarity: r.similarity_score || 0,
593
- category: r.category,
594
- importance: r.metadata?.importance,
595
- blobId: r.blobId || r.id,
596
- metadata: r.metadata || {},
597
- timestamp: r.timestamp ? new Date(r.timestamp).getTime() : Date.now()
598
- }));
599
- } catch (error) {
600
- throw new Error(`Graph search failed: ${error instanceof Error ? error.message : String(error)}`);
601
- }
602
- }
603
-
604
- /**
605
- * Search with embedding vectors included
606
- *
607
- * Same as vector search but includes embedding vectors in results
608
- *
609
- * @param query - Search query
610
- * @param options - Search options
611
- * @returns Results with embedding vectors
612
- */
613
- async withEmbeddings(query: string, options: VectorSearchOptions = {}): Promise<SearchResult[]> {
614
- return this.vector(query, {
615
- ...options,
616
- includeEmbeddings: true
617
- });
618
- }
619
-
620
- /**
621
- * Multi-vector search
622
- *
623
- * Search using multiple query vectors/texts
624
- *
625
- * @param queries - Array of query texts
626
- * @param options - Search options
627
- * @returns Combined and deduplicated results
628
- */
629
- async multiVector(queries: string[], options: VectorSearchOptions = {}): Promise<SearchResult[]> {
630
- try {
631
- const allResults: SearchResult[] = [];
632
- const seenIds = new Set<string>();
633
-
634
- // Search for each query
635
- for (const query of queries) {
636
- const results = await this.vector(query, {
637
- ...options,
638
- limit: Math.ceil((options.limit || 10) / queries.length)
639
- });
640
-
641
- // Deduplicate
642
- results.forEach(r => {
643
- if (!seenIds.has(r.id)) {
644
- allResults.push(r);
645
- seenIds.add(r.id);
646
- }
647
- });
648
- }
649
-
650
- // Sort by score and limit
651
- allResults.sort((a, b) => b.score - a.score);
652
- return allResults.slice(0, options.limit || 10);
653
- } catch (error) {
654
- throw new Error(`Multi-vector search failed: ${error instanceof Error ? error.message : String(error)}`);
655
- }
656
- }
657
-
658
- /**
659
- * Rerank search results using AI
660
- *
661
- * Takes existing results and reranks them using AI for better relevance
662
- *
663
- * @param results - Initial search results
664
- * @param query - Original query for context
665
- * @param options - Rerank options
666
- * @returns Reranked results
667
- */
668
- async rerank(
669
- results: SearchResult[],
670
- query: string,
671
- options: { limit?: number } = {}
672
- ): Promise<SearchResult[]> {
673
- try {
674
- if (!this.services.classifier) {
675
- throw new Error('Classifier service not configured. Need geminiApiKey for AI reranking.');
676
- }
677
-
678
- // Simple reranking: boost results by importance and category match
679
- const queryLower = query.toLowerCase();
680
-
681
- const scored = results.map(r => {
682
- let boost = 0;
683
-
684
- // Importance boost
685
- boost += ((r.importance || 5) - 5) * 0.02;
686
-
687
- // Content relevance boost (simple keyword match)
688
- if (r.content.toLowerCase().includes(queryLower)) {
689
- boost += 0.1;
690
- }
691
-
692
- // Topic relevance boost
693
- if (r.topic?.toLowerCase().includes(queryLower)) {
694
- boost += 0.05;
695
- }
696
-
697
- return {
698
- ...r,
699
- score: Math.min(1.0, r.score + boost)
700
- };
701
- });
702
-
703
- // Sort by new score
704
- scored.sort((a, b) => b.score - a.score);
705
-
706
- return scored.slice(0, options.limit || results.length);
707
- } catch (error) {
708
- throw new Error(`Rerank failed: ${error instanceof Error ? error.message : String(error)}`);
709
- }
710
- }
711
-
712
- /**
713
- * Populate content for search results by fetching from Walrus
714
- *
715
- * Fetches and decrypts content for each result in parallel.
716
- * Modifies results in-place.
717
- *
718
- * @param results - Search results to populate
719
- */
720
- private async populateContent(results: SearchResult[]): Promise<void> {
721
- // Option A+: Skip Walrus fetch for results that already have content from local index
722
- const resultsNeedingFetch = results.filter(r => !r.content && r.blobId);
723
- const resultsWithLocalContent = results.length - resultsNeedingFetch.length;
724
-
725
- if (resultsWithLocalContent > 0) {
726
- console.log(`📦 ${resultsWithLocalContent}/${results.length} results already have content from local index (skipping Walrus fetch)`);
727
- }
728
-
729
- if (resultsNeedingFetch.length === 0) {
730
- console.log('✅ All content available locally - no Walrus fetch needed!');
731
- return;
732
- }
733
-
734
- console.log(`🐳 Fetching content from Walrus for ${resultsNeedingFetch.length} results...`);
735
-
736
- const fetchPromises = resultsNeedingFetch.map(async (result) => {
737
- try {
738
- if (result.blobId) {
739
- const memoryPackage = await this.services.storage.retrieveMemoryPackage(result.blobId);
740
- if (memoryPackage.memoryPackage?.content) {
741
- result.content = memoryPackage.memoryPackage.content;
742
- }
743
- }
744
- } catch (error) {
745
- // Log but don't fail - content fetch is best-effort
746
- console.warn(`Failed to fetch content for ${result.blobId}:`, error);
747
- }
748
- });
749
-
750
- await Promise.all(fetchPromises);
751
- }
752
-
753
- /**
754
- * Search and fetch content in one call
755
- *
756
- * Convenience method that combines vector search with content fetching.
757
- *
758
- * @param query - Search query
759
- * @param options - Search options
760
- * @returns Results with content populated
761
- *
762
- * @example
763
- * ```typescript
764
- * const results = await pdw.search.withContent('programming');
765
- * console.log(results[0].content); // "I love TypeScript..."
766
- * ```
767
- */
768
- async withContent(query: string, options: VectorSearchOptions = {}): Promise<SearchResult[]> {
769
- return this.vector(query, { ...options, fetchContent: true });
770
- }
771
-
772
- // ==========================================================================
773
- // Knowledge Graph Methods (from GraphNamespace)
774
- // ==========================================================================
775
-
776
- /**
777
- * Get all entities from knowledge graph
778
- *
779
- * @param options - Filter options
780
- * @returns Array of entities
781
- *
782
- * @example
783
- * ```typescript
784
- * const entities = await pdw.search.entities({ type: 'PERSON' });
785
- * ```
786
- */
787
- async entities(options: {
788
- type?: string;
789
- minConfidence?: number;
790
- limit?: number;
791
- } = {}): Promise<Array<{
792
- id: string;
793
- name: string;
794
- type: string;
795
- confidence: number;
796
- }>> {
797
- try {
798
- const graphData = await this.services.storage.searchKnowledgeGraph(
799
- this.services.config.userAddress,
800
- { limit: options.limit || 100 }
801
- );
802
-
803
- let entities = graphData.entities;
804
-
805
- if (options.type) {
806
- entities = entities.filter((e: any) => e.type === options.type);
807
- }
808
-
809
- if (options.minConfidence) {
810
- entities = entities.filter((e: any) => (e.confidence || 0) >= options.minConfidence!);
811
- }
812
-
813
- return entities.map((e: any) => ({
814
- id: e.id,
815
- name: e.label || '',
816
- type: e.type,
817
- confidence: e.confidence || 0
818
- }));
819
- } catch (error) {
820
- throw new Error(`Get entities failed: ${error instanceof Error ? error.message : String(error)}`);
821
- }
822
- }
823
-
824
- /**
825
- * Get all relationships from knowledge graph
826
- *
827
- * @param options - Filter options
828
- * @returns Array of relationships
829
- *
830
- * @example
831
- * ```typescript
832
- * const rels = await pdw.search.relationships({ type: 'WORKS_AT' });
833
- * ```
834
- */
835
- async relationships(options: {
836
- type?: string;
837
- sourceId?: string;
838
- targetId?: string;
839
- minConfidence?: number;
840
- limit?: number;
841
- } = {}): Promise<Array<{
842
- id: string;
843
- source: string;
844
- target: string;
845
- type: string;
846
- confidence: number;
847
- }>> {
848
- try {
849
- const graphData = await this.services.storage.searchKnowledgeGraph(
850
- this.services.config.userAddress,
851
- {
852
- searchText: options.sourceId || options.targetId || '',
853
- limit: options.limit || 100
854
- }
855
- );
856
-
857
- let relationships = graphData.relationships;
858
-
859
- if (options.type) {
860
- relationships = relationships.filter((r: any) => r.type === options.type);
861
- }
862
-
863
- if (options.sourceId) {
864
- relationships = relationships.filter((r: any) => r.source === options.sourceId);
865
- }
866
-
867
- if (options.targetId) {
868
- relationships = relationships.filter((r: any) => r.target === options.targetId);
869
- }
870
-
871
- if (options.minConfidence) {
872
- relationships = relationships.filter((r: any) =>
873
- (r.confidence || 0) >= options.minConfidence!
874
- );
875
- }
876
-
877
- return relationships.map((r: any) => ({
878
- id: `${r.source}-${r.target}`,
879
- source: r.source,
880
- target: r.target,
881
- type: r.type || 'related',
882
- confidence: r.confidence || 0
883
- }));
884
- } catch (error) {
885
- throw new Error(`Get relationships failed: ${error instanceof Error ? error.message : String(error)}`);
886
- }
887
- }
888
-
889
- // ==========================================================================
890
- // Index Management (from IndexNamespace)
891
- // ==========================================================================
892
-
893
- /**
894
- * Index management operations
895
- *
896
- * Access via pdw.search.index.*
897
- */
898
- get index(): IndexSubNamespace {
899
- if (!this._indexSubNamespace) {
900
- this._indexSubNamespace = new IndexSubNamespace(this.services);
901
- }
902
- return this._indexSubNamespace;
903
- }
904
-
905
- private _indexSubNamespace?: IndexSubNamespace;
906
- }
907
-
908
- /**
909
- * Index sub-namespace for HNSW index management
910
- */
911
- class IndexSubNamespace {
912
- constructor(private services: ServiceContainer) {}
913
-
914
- /**
915
- * Get the underlying index service
916
- */
917
- private getService() {
918
- if (this.services.memoryIndex) {
919
- return { type: 'memoryIndex' as const, service: this.services.memoryIndex };
920
- }
921
- if (this.services.vector) {
922
- return { type: 'vector' as const, service: this.services.vector };
923
- }
924
- throw new Error('No indexing service configured. Enable local indexing in config.');
925
- }
926
-
927
- /**
928
- * Save index to Walrus storage
929
- *
930
- * Persists the HNSW index binary to Walrus for durability.
931
- *
932
- * @param spaceId - Index space identifier (userAddress)
933
- * @returns Blob ID of saved index on Walrus, or null if no index exists
934
- *
935
- * @example
936
- * ```typescript
937
- * const blobId = await pdw.search.index.save(userAddress);
938
- * console.log('Index saved to Walrus:', blobId);
939
- * ```
940
- */
941
- async save(spaceId: string): Promise<void> {
942
- const { type, service } = this.getService();
943
-
944
- if (type === 'memoryIndex') {
945
- await service.saveIndex(spaceId);
946
- console.log(`Index saved for space: ${spaceId}`);
947
- } else {
948
- await service.saveIndex(spaceId);
949
- }
950
- }
951
-
952
- /**
953
- * Load index from Walrus storage
954
- *
955
- * Loads a previously saved HNSW index from Walrus.
956
- *
957
- * @param spaceId - Index space identifier (userAddress)
958
- * @param blobId - Blob ID of the saved index on Walrus
959
- *
960
- * @example
961
- * ```typescript
962
- * await pdw.search.index.load(userAddress, 'blobId123');
963
- * ```
964
- */
965
- async load(spaceId: string, blobId: string): Promise<void> {
966
- const { type, service } = this.getService();
967
-
968
- if (type === 'memoryIndex') {
969
- await service.loadIndex(spaceId, blobId);
970
- console.log(`Index loaded from Walrus: ${blobId}`);
971
- } else {
972
- await service.loadIndex(spaceId, blobId);
973
- }
974
- }
975
-
976
- /**
977
- * Get index statistics
978
- *
979
- * @param spaceId - Index space identifier
980
- * @returns Index statistics
981
- *
982
- * @example
983
- * ```typescript
984
- * const stats = pdw.search.index.stats(userAddress);
985
- * console.log('Total vectors:', stats.totalVectors);
986
- * ```
987
- */
988
- stats(spaceId: string): {
989
- totalVectors: number;
990
- dimension: number;
991
- spaceType: string;
992
- maxElements: number;
993
- currentCount: number;
994
- } {
995
- const { type, service } = this.getService();
996
-
997
- if (type === 'memoryIndex') {
998
- const stats = service.getIndexStats(spaceId);
999
- return {
1000
- totalVectors: stats.totalMemories || 0,
1001
- dimension: 3072,
1002
- spaceType: 'cosine',
1003
- maxElements: 10000,
1004
- currentCount: stats.indexSize || stats.totalMemories || 0
1005
- };
1006
- } else {
1007
- const entry = (service as any).indexCache?.get(spaceId);
1008
- if (!entry) {
1009
- throw new Error(`Index ${spaceId} not found`);
1010
- }
1011
- const currentCount = entry.index.getCurrentCount?.() || 0;
1012
- return {
1013
- totalVectors: currentCount,
1014
- dimension: 3072,
1015
- spaceType: 'cosine',
1016
- maxElements: 10000,
1017
- currentCount
1018
- };
1019
- }
1020
- }
1021
-
1022
- /**
1023
- * Clear index and remove all vectors
1024
- *
1025
- * @param spaceId - Index space identifier
1026
- */
1027
- clear(spaceId: string): void {
1028
- const { type, service } = this.getService();
1029
-
1030
- if (type === 'memoryIndex') {
1031
- service.clearUserIndex(spaceId);
1032
- } else {
1033
- (service as any).indexCache?.delete(spaceId);
1034
- }
1035
- }
1036
-
1037
- /**
1038
- * Force flush pending vectors
1039
- *
1040
- * @param spaceId - Index space identifier
1041
- */
1042
- async flush(spaceId: string): Promise<void> {
1043
- const { type, service } = this.getService();
1044
-
1045
- if (type === 'memoryIndex') {
1046
- await service.flush(spaceId);
1047
- }
1048
- }
1049
- }
1
+ /**
2
+ * Search Namespace - All Search Methods
3
+ *
4
+ * Provides comprehensive search capabilities:
5
+ * - Vector similarity search
6
+ * - Semantic search (AI-enhanced)
7
+ * - Keyword search (metadata)
8
+ * - Hybrid search (combined)
9
+ * - Temporal search (date-based)
10
+ * - Category filtering
11
+ *
12
+ * @module client/namespaces
13
+ */
14
+
15
+ import type { ServiceContainer } from '../SimplePDWClient';
16
+
17
+ /**
18
+ * Search result item
19
+ *
20
+ * NOTE: `content` is empty by default for privacy. Content is stored encrypted
21
+ * on Walrus and not in the index. Use `pdw.search.withContent()` or
22
+ * `pdw.memory.get(blobId)` to retrieve decrypted content.
23
+ */
24
+ export interface SearchResult {
25
+ id: string;
26
+ /** Empty by default - use withContent() to fetch from Walrus */
27
+ content: string;
28
+ score: number;
29
+ similarity: number;
30
+ category?: string;
31
+ importance?: number;
32
+ topic?: string;
33
+ /** Use this to fetch content: pdw.memory.get(blobId) */
34
+ blobId: string;
35
+ metadata?: Record<string, any>;
36
+ timestamp: number;
37
+ }
38
+
39
+ /**
40
+ * Vector search options
41
+ */
42
+ export interface VectorSearchOptions {
43
+ limit?: number;
44
+ threshold?: number; // Minimum similarity score
45
+ category?: string;
46
+ includeEmbeddings?: boolean;
47
+ /**
48
+ * Fetch content from Walrus for each result (slower but includes content)
49
+ * @default false
50
+ */
51
+ fetchContent?: boolean;
52
+ }
53
+
54
+ /**
55
+ * Semantic search options
56
+ */
57
+ export interface SemanticSearchOptions extends VectorSearchOptions {
58
+ rerank?: boolean; // Use AI to rerank results
59
+ }
60
+
61
+ /**
62
+ * Keyword search options
63
+ */
64
+ export interface KeywordSearchOptions {
65
+ limit?: number;
66
+ category?: string;
67
+ fields?: string[]; // Which metadata fields to search
68
+ caseSensitive?: boolean;
69
+ }
70
+
71
+ /**
72
+ * Hybrid search options
73
+ */
74
+ export interface HybridSearchOptions {
75
+ limit?: number;
76
+ vectorWeight?: number; // 0-1, weight for vector search
77
+ keywordWeight?: number; // 0-1, weight for keyword search
78
+ category?: string;
79
+ }
80
+
81
+ /**
82
+ * Date range for temporal search
83
+ */
84
+ export interface DateRange {
85
+ start: Date | string;
86
+ end?: Date | string;
87
+ }
88
+
89
+ /**
90
+ * Search Namespace
91
+ *
92
+ * Handles all types of search operations
93
+ */
94
+ export class SearchNamespace {
95
+ constructor(private services: ServiceContainer) {}
96
+
97
+ /**
98
+ * Vector similarity search
99
+ *
100
+ * Searches memories by semantic similarity using embeddings.
101
+ *
102
+ * NOTE: Content is NOT included by default for privacy (content is encrypted on Walrus).
103
+ * Use `fetchContent: true` option or call `pdw.memory.get(blobId)` to get content.
104
+ *
105
+ * @param query - Text query to search for
106
+ * @param options - Search options
107
+ * @returns Sorted array of results by similarity (content empty unless fetchContent=true)
108
+ *
109
+ * @example
110
+ * ```typescript
111
+ * // Fast search (no content)
112
+ * const results = await pdw.search.vector('programming');
113
+ *
114
+ * // Search with content (slower - fetches from Walrus)
115
+ * const results = await pdw.search.vector('programming', { fetchContent: true });
116
+ *
117
+ * // Or fetch content for specific result
118
+ * const memory = await pdw.memory.get(results[0].blobId);
119
+ * ```
120
+ */
121
+ async vector(query: string, options: VectorSearchOptions = {}): Promise<SearchResult[]> {
122
+ const { limit = 10, threshold = 0.7, category, fetchContent = false } = options;
123
+
124
+ try {
125
+ // Generate query embedding
126
+ if (!this.services.embedding) {
127
+ throw new Error('Embedding service not configured. Please provide geminiApiKey in config.');
128
+ }
129
+
130
+ const embResult = await this.services.embedding.embedText({
131
+ text: query
132
+ });
133
+
134
+ // Search using local HNSW index (VectorService)
135
+ if (!this.services.vector) {
136
+ throw new Error('Vector service not configured. Enable local indexing in config.');
137
+ }
138
+
139
+ const spaceId = this.services.config.userAddress;
140
+
141
+ // Search in local HNSW index
142
+ const searchResult = await this.services.vector.searchVectors(spaceId, embResult.vector, {
143
+ k: limit
144
+ });
145
+
146
+ // Filter by threshold and category
147
+ let results = searchResult.results.filter((r: any) => r.similarity >= threshold);
148
+
149
+ if (category) {
150
+ results = results.filter((r: any) =>
151
+ r.metadata?.category === category
152
+ );
153
+ }
154
+
155
+ // Convert to SearchResult format
156
+ // Option A+: Content may be available from local index when encryption is OFF
157
+ const searchResults: SearchResult[] = results.map((r: any) => {
158
+ // blobId must be a valid Walrus blob ID, not a vectorId
159
+ // Only use metadata.blobId if it's a non-empty string that looks like a Walrus blobId
160
+ const rawBlobId = r.metadata?.blobId;
161
+ const isValidBlobId = rawBlobId && typeof rawBlobId === 'string' && rawBlobId.length > 10 && !/^\d+$/.test(rawBlobId);
162
+ const blobId = isValidBlobId ? rawBlobId : (r.metadata?.memoryObjectId || '');
163
+
164
+ return {
165
+ id: r.memoryId || r.vectorId.toString(),
166
+ content: r.metadata?.content || r.content || '', // ✅ Get content from index metadata if available
167
+ score: r.similarity,
168
+ similarity: r.similarity,
169
+ category: r.metadata?.category,
170
+ importance: r.metadata?.importance || 5,
171
+ topic: r.metadata?.topic,
172
+ blobId,
173
+ metadata: r.metadata || {},
174
+ timestamp: r.metadata?.timestamp || Date.now()
175
+ };
176
+ });
177
+
178
+ // Optionally fetch content from Walrus
179
+ if (fetchContent) {
180
+ await this.populateContent(searchResults);
181
+ }
182
+
183
+ return searchResults;
184
+ } catch (error) {
185
+ throw new Error(`Vector search failed: ${error instanceof Error ? error.message : String(error)}`);
186
+ }
187
+ }
188
+
189
+ /**
190
+ * Semantic search (AI-enhanced)
191
+ *
192
+ * Uses AI to understand query intent and rerank results
193
+ *
194
+ * @param query - Natural language query
195
+ * @param options - Search options
196
+ * @returns Semantically relevant results
197
+ */
198
+ async semantic(query: string, options: SemanticSearchOptions = {}): Promise<SearchResult[]> {
199
+ try {
200
+ if (!this.services.query) {
201
+ throw new Error('Query service not available');
202
+ }
203
+
204
+ // Use QueryService for semantic search
205
+ const results = await this.services.query.semanticSearch(
206
+ {
207
+ query,
208
+ userAddress: this.services.config.userAddress,
209
+ queryType: 'semantic',
210
+ k: options.limit || 10,
211
+ threshold: options.threshold || 0.6
212
+ },
213
+ {
214
+ expandQuery: options.rerank ?? true // Use expandQuery instead of rerank
215
+ }
216
+ );
217
+
218
+ return results.map((r: any) => ({
219
+ id: r.id,
220
+ content: r.content || '',
221
+ score: r.similarity_score || 0,
222
+ similarity: r.similarity_score || 0,
223
+ category: r.category,
224
+ importance: r.metadata?.importance,
225
+ blobId: r.blobId || r.id,
226
+ metadata: r.metadata || {},
227
+ timestamp: r.timestamp ? new Date(r.timestamp).getTime() : Date.now()
228
+ }));
229
+ } catch (error) {
230
+ throw new Error(`Semantic search failed: ${error instanceof Error ? error.message : String(error)}`);
231
+ }
232
+ }
233
+
234
+ /**
235
+ * Keyword search (metadata-based)
236
+ *
237
+ * Searches in metadata fields using keywords
238
+ *
239
+ * @param query - Keyword to search for
240
+ * @param options - Search options
241
+ * @returns Matching memories
242
+ */
243
+ async keyword(query: string, options: KeywordSearchOptions = {}): Promise<SearchResult[]> {
244
+ try {
245
+ const { limit = 10, category, fields = ['content', 'topic'], caseSensitive = false } = options;
246
+
247
+ // Use QueryService for keyword search
248
+ const results = await this.services.query.keywordSearch({
249
+ query,
250
+ userAddress: this.services.config.userAddress,
251
+ queryType: 'keyword',
252
+ categories: category ? [category] : undefined,
253
+ limit
254
+ });
255
+
256
+ return results.map((r: any) => ({
257
+ id: r.id,
258
+ content: r.content || '',
259
+ score: 1.0, // Keyword match = binary
260
+ similarity: 1.0,
261
+ category: r.category,
262
+ importance: r.metadata?.importance,
263
+ blobId: r.blobId || r.id,
264
+ metadata: r.metadata || {},
265
+ timestamp: r.timestamp ? new Date(r.timestamp).getTime() : Date.now()
266
+ }));
267
+ } catch (error) {
268
+ throw new Error(`Keyword search failed: ${error instanceof Error ? error.message : String(error)}`);
269
+ }
270
+ }
271
+
272
+ /**
273
+ * Hybrid search (vector + keyword)
274
+ *
275
+ * Combines vector similarity and keyword matching
276
+ *
277
+ * @param query - Search query
278
+ * @param options - Hybrid search options
279
+ * @returns Ranked results from both methods
280
+ */
281
+ async hybrid(query: string, options: HybridSearchOptions = {}): Promise<SearchResult[]> {
282
+ try {
283
+ const {
284
+ limit = 10,
285
+ vectorWeight = 0.7,
286
+ keywordWeight = 0.3,
287
+ category
288
+ } = options;
289
+
290
+ // Use QueryService for hybrid search
291
+ const results = await this.services.query.hybridSearch({
292
+ query,
293
+ userAddress: this.services.config.userAddress,
294
+ queryType: 'hybrid',
295
+ limit,
296
+ categories: category ? [category] : undefined
297
+ });
298
+
299
+ return results.map((r: any) => ({
300
+ id: r.id,
301
+ content: r.content || '',
302
+ score: r.similarity_score || 0,
303
+ similarity: r.similarity_score || 0,
304
+ category: r.category,
305
+ importance: r.metadata?.importance,
306
+ blobId: r.blobId || r.id,
307
+ metadata: r.metadata || {},
308
+ timestamp: r.timestamp ? new Date(r.timestamp).getTime() : Date.now()
309
+ }));
310
+ } catch (error) {
311
+ throw new Error(`Hybrid search failed: ${error instanceof Error ? error.message : String(error)}`);
312
+ }
313
+ }
314
+
315
+ /**
316
+ * Search by category
317
+ *
318
+ * First tries local vector index (for recently created memories),
319
+ * then queries blockchain for on-chain Memory objects.
320
+ * Combines results and deduplicates by blobId.
321
+ *
322
+ * @param category - Category to filter by
323
+ * @param options - Additional options
324
+ * @returns Memories in category
325
+ */
326
+ async byCategory(category: string, options: VectorSearchOptions = {}): Promise<SearchResult[]> {
327
+ try {
328
+ const limit = options.limit || 50;
329
+ const results: SearchResult[] = [];
330
+ const seenIds = new Set<string>();
331
+
332
+ // 1. First check local vector index (includes recently created memories)
333
+ // Use VectorService.getVectorsByCategory() method
334
+ if (this.services.vector) {
335
+ try {
336
+ const spaceId = this.services.config.userAddress;
337
+ const localResults = this.services.vector.getVectorsByCategory(spaceId, category);
338
+
339
+ for (const { vectorId, metadata } of localResults) {
340
+ // blobId must be a valid Walrus blob ID, not a vectorId
341
+ const rawBlobId = metadata?.blobId;
342
+ const isValidBlobId = rawBlobId && typeof rawBlobId === 'string' && rawBlobId.length > 10 && !/^\d+$/.test(rawBlobId);
343
+ const blobId = isValidBlobId ? rawBlobId : (metadata?.memoryObjectId || '');
344
+ const id = blobId || metadata?.memoryId || vectorId?.toString();
345
+
346
+ if (id && !seenIds.has(id)) {
347
+ seenIds.add(id);
348
+ results.push({
349
+ id,
350
+ content: metadata?.content || '',
351
+ score: 1.0,
352
+ similarity: 1.0,
353
+ category: metadata?.category,
354
+ importance: metadata?.importance || 5,
355
+ topic: metadata?.topic,
356
+ blobId,
357
+ metadata: metadata || {},
358
+ timestamp: metadata?.timestamp || Date.now()
359
+ });
360
+ }
361
+ }
362
+ } catch (localError) {
363
+ // Local index access failed, continue with on-chain query
364
+ console.log('Local index search skipped:', localError);
365
+ }
366
+ }
367
+
368
+ // 2. Query on-chain Memory objects
369
+ const viewService = this.services.viewService;
370
+ if (viewService) {
371
+ try {
372
+ const response = await viewService.getUserMemories(
373
+ this.services.config.userAddress,
374
+ { limit, category }
375
+ );
376
+
377
+ for (const m of response.data) {
378
+ const id = m.id || m.blobId;
379
+ if (id && !seenIds.has(id)) {
380
+ seenIds.add(id);
381
+ results.push({
382
+ id,
383
+ content: '',
384
+ score: 1.0,
385
+ similarity: 1.0,
386
+ category: m.category,
387
+ importance: m.importance || 5,
388
+ topic: m.topic,
389
+ blobId: m.blobId || id,
390
+ metadata: {
391
+ category: m.category,
392
+ importance: m.importance,
393
+ topic: m.topic
394
+ },
395
+ timestamp: m.createdAt || Date.now()
396
+ });
397
+ }
398
+ }
399
+ } catch (viewError) {
400
+ console.log('On-chain query failed:', viewError);
401
+ }
402
+ }
403
+
404
+ return results.slice(0, limit);
405
+ } catch (error) {
406
+ throw new Error(`Category search failed: ${error instanceof Error ? error.message : String(error)}`);
407
+ }
408
+ }
409
+
410
+ /**
411
+ * Search by date range
412
+ *
413
+ * @param dateRange - Start and end dates
414
+ * @param options - Additional options
415
+ * @returns Memories within date range
416
+ */
417
+ async byDate(dateRange: DateRange, options: VectorSearchOptions = {}): Promise<SearchResult[]> {
418
+ try {
419
+ const start = typeof dateRange.start === 'string'
420
+ ? new Date(dateRange.start)
421
+ : dateRange.start;
422
+
423
+ const end = dateRange.end
424
+ ? (typeof dateRange.end === 'string' ? new Date(dateRange.end) : dateRange.end)
425
+ : new Date();
426
+
427
+ // Use QueryService for temporal search
428
+ const results = await this.services.query.temporalSearch({
429
+ userAddress: this.services.config.userAddress,
430
+ queryType: 'temporal',
431
+ dateRange: { start, end },
432
+ limit: options.limit || 50,
433
+ categories: options.category ? [options.category] : undefined
434
+ });
435
+
436
+ return results.map((r: any) => ({
437
+ id: r.id,
438
+ content: r.content || '',
439
+ score: 1.0,
440
+ similarity: 1.0,
441
+ category: r.category,
442
+ importance: r.metadata?.importance,
443
+ blobId: r.blobId || r.id,
444
+ metadata: r.metadata || {},
445
+ timestamp: r.timestamp ? new Date(r.timestamp).getTime() : Date.now()
446
+ }));
447
+ } catch (error) {
448
+ throw new Error(`Temporal search failed: ${error instanceof Error ? error.message : String(error)}`);
449
+ }
450
+ }
451
+
452
+ /**
453
+ * Search by importance range
454
+ *
455
+ * @param min - Minimum importance (1-10)
456
+ * @param max - Maximum importance (1-10)
457
+ * @param options - Additional options
458
+ * @returns Memories within importance range
459
+ */
460
+ async byImportance(
461
+ min: number,
462
+ max: number = 10,
463
+ options: VectorSearchOptions = {}
464
+ ): Promise<SearchResult[]> {
465
+ const memoriesResult = await this.services.viewService?.getUserMemories(
466
+ this.services.config.userAddress
467
+ );
468
+ const allMemories = memoriesResult?.data || [];
469
+
470
+ const filtered = allMemories.filter((m: any) => {
471
+ const importance = m.importance || m.metadata?.importance || 5;
472
+ return importance >= min && importance <= max;
473
+ });
474
+
475
+ const limit = options.limit || 50;
476
+ return filtered.slice(0, limit).map((m: any) => ({
477
+ id: m.id || m.blobId,
478
+ content: m.content || '',
479
+ score: 1.0,
480
+ similarity: 1.0,
481
+ category: m.category || m.metadata?.category,
482
+ importance: m.importance || m.metadata?.importance,
483
+ blobId: m.blobId || m.id,
484
+ metadata: m.metadata,
485
+ timestamp: m.timestamp || Date.now()
486
+ }));
487
+ }
488
+
489
+ /**
490
+ * Advanced search with complex filters
491
+ *
492
+ * @param query - Search query or filters
493
+ * @returns Filtered and ranked results
494
+ */
495
+ async advanced(query: {
496
+ text?: string;
497
+ category?: string;
498
+ importance?: { min: number; max: number };
499
+ dateRange?: DateRange;
500
+ limit?: number;
501
+ }): Promise<SearchResult[]> {
502
+ try {
503
+ // Combine multiple search strategies
504
+ let results: SearchResult[] = [];
505
+
506
+ // Start with vector search if text provided
507
+ if (query.text) {
508
+ results = await this.vector(query.text, {
509
+ limit: query.limit || 50,
510
+ category: query.category
511
+ });
512
+ } else {
513
+ // Get all memories
514
+ const memoriesResult = await this.services.viewService?.getUserMemories(
515
+ this.services.config.userAddress
516
+ );
517
+ const memories = memoriesResult?.data || [];
518
+
519
+ results = memories.map((m: any) => ({
520
+ id: m.id || m.blobId,
521
+ content: m.content || '',
522
+ score: 1.0,
523
+ similarity: 1.0,
524
+ category: m.category || m.metadata?.category,
525
+ importance: m.importance || m.metadata?.importance,
526
+ blobId: m.blobId || m.id,
527
+ metadata: m.metadata,
528
+ timestamp: m.timestamp || Date.now()
529
+ }));
530
+ }
531
+
532
+ // Apply filters
533
+ if (query.importance) {
534
+ results = results.filter(r =>
535
+ (r.importance || 5) >= query.importance!.min &&
536
+ (r.importance || 5) <= query.importance!.max
537
+ );
538
+ }
539
+
540
+ if (query.dateRange) {
541
+ const start = typeof query.dateRange.start === 'string'
542
+ ? new Date(query.dateRange.start).getTime()
543
+ : query.dateRange.start.getTime();
544
+
545
+ const end = query.dateRange.end
546
+ ? (typeof query.dateRange.end === 'string'
547
+ ? new Date(query.dateRange.end).getTime()
548
+ : query.dateRange.end.getTime())
549
+ : Date.now();
550
+
551
+ results = results.filter(r =>
552
+ r.timestamp >= start && r.timestamp <= end
553
+ );
554
+ }
555
+
556
+ return results.slice(0, query.limit || 50);
557
+ } catch (error) {
558
+ throw new Error(`Advanced search failed: ${error instanceof Error ? error.message : String(error)}`);
559
+ }
560
+ }
561
+
562
+ /**
563
+ * Graph-based search
564
+ *
565
+ * Search using knowledge graph relationships
566
+ *
567
+ * @param query - Entity or concept to search
568
+ * @param options - Search options
569
+ * @returns Related memories via graph connections
570
+ */
571
+ async graph(query: string, options: VectorSearchOptions = {}): Promise<SearchResult[]> {
572
+ try {
573
+ if (!this.services.query) {
574
+ throw new Error('Query service not available');
575
+ }
576
+
577
+ // Use QueryService.graphSearch - returns { memories, graphResults }
578
+ const result = await this.services.query.graphSearch({
579
+ query,
580
+ userAddress: this.services.config.userAddress,
581
+ queryType: 'graph',
582
+ limit: options.limit || 10
583
+ });
584
+
585
+ // Extract memories array from result
586
+ const memories = result.memories || [];
587
+
588
+ return memories.map((r: any) => ({
589
+ id: r.id,
590
+ content: r.content || '',
591
+ score: r.similarity_score || 0,
592
+ similarity: r.similarity_score || 0,
593
+ category: r.category,
594
+ importance: r.metadata?.importance,
595
+ blobId: r.blobId || r.id,
596
+ metadata: r.metadata || {},
597
+ timestamp: r.timestamp ? new Date(r.timestamp).getTime() : Date.now()
598
+ }));
599
+ } catch (error) {
600
+ throw new Error(`Graph search failed: ${error instanceof Error ? error.message : String(error)}`);
601
+ }
602
+ }
603
+
604
+ /**
605
+ * Search with embedding vectors included
606
+ *
607
+ * Same as vector search but includes embedding vectors in results
608
+ *
609
+ * @param query - Search query
610
+ * @param options - Search options
611
+ * @returns Results with embedding vectors
612
+ */
613
+ async withEmbeddings(query: string, options: VectorSearchOptions = {}): Promise<SearchResult[]> {
614
+ return this.vector(query, {
615
+ ...options,
616
+ includeEmbeddings: true
617
+ });
618
+ }
619
+
620
+ /**
621
+ * Multi-vector search
622
+ *
623
+ * Search using multiple query vectors/texts
624
+ *
625
+ * @param queries - Array of query texts
626
+ * @param options - Search options
627
+ * @returns Combined and deduplicated results
628
+ */
629
+ async multiVector(queries: string[], options: VectorSearchOptions = {}): Promise<SearchResult[]> {
630
+ try {
631
+ const allResults: SearchResult[] = [];
632
+ const seenIds = new Set<string>();
633
+
634
+ // Search for each query
635
+ for (const query of queries) {
636
+ const results = await this.vector(query, {
637
+ ...options,
638
+ limit: Math.ceil((options.limit || 10) / queries.length)
639
+ });
640
+
641
+ // Deduplicate
642
+ results.forEach(r => {
643
+ if (!seenIds.has(r.id)) {
644
+ allResults.push(r);
645
+ seenIds.add(r.id);
646
+ }
647
+ });
648
+ }
649
+
650
+ // Sort by score and limit
651
+ allResults.sort((a, b) => b.score - a.score);
652
+ return allResults.slice(0, options.limit || 10);
653
+ } catch (error) {
654
+ throw new Error(`Multi-vector search failed: ${error instanceof Error ? error.message : String(error)}`);
655
+ }
656
+ }
657
+
658
+ /**
659
+ * Rerank search results using AI
660
+ *
661
+ * Takes existing results and reranks them using AI for better relevance
662
+ *
663
+ * @param results - Initial search results
664
+ * @param query - Original query for context
665
+ * @param options - Rerank options
666
+ * @returns Reranked results
667
+ */
668
+ async rerank(
669
+ results: SearchResult[],
670
+ query: string,
671
+ options: { limit?: number } = {}
672
+ ): Promise<SearchResult[]> {
673
+ try {
674
+ if (!this.services.classifier) {
675
+ throw new Error('Classifier service not configured. Need geminiApiKey for AI reranking.');
676
+ }
677
+
678
+ // Simple reranking: boost results by importance and category match
679
+ const queryLower = query.toLowerCase();
680
+
681
+ const scored = results.map(r => {
682
+ let boost = 0;
683
+
684
+ // Importance boost
685
+ boost += ((r.importance || 5) - 5) * 0.02;
686
+
687
+ // Content relevance boost (simple keyword match)
688
+ if (r.content.toLowerCase().includes(queryLower)) {
689
+ boost += 0.1;
690
+ }
691
+
692
+ // Topic relevance boost
693
+ if (r.topic?.toLowerCase().includes(queryLower)) {
694
+ boost += 0.05;
695
+ }
696
+
697
+ return {
698
+ ...r,
699
+ score: Math.min(1.0, r.score + boost)
700
+ };
701
+ });
702
+
703
+ // Sort by new score
704
+ scored.sort((a, b) => b.score - a.score);
705
+
706
+ return scored.slice(0, options.limit || results.length);
707
+ } catch (error) {
708
+ throw new Error(`Rerank failed: ${error instanceof Error ? error.message : String(error)}`);
709
+ }
710
+ }
711
+
712
+ /**
713
+ * Populate content for search results by fetching from Walrus
714
+ *
715
+ * Fetches and decrypts content for each result in parallel.
716
+ * Modifies results in-place.
717
+ *
718
+ * @param results - Search results to populate
719
+ */
720
+ private async populateContent(results: SearchResult[]): Promise<void> {
721
+ // Option A+: Skip Walrus fetch for results that already have content from local index
722
+ const resultsNeedingFetch = results.filter(r => !r.content && r.blobId);
723
+ const resultsWithLocalContent = results.length - resultsNeedingFetch.length;
724
+
725
+ if (resultsWithLocalContent > 0) {
726
+ console.log(`📦 ${resultsWithLocalContent}/${results.length} results already have content from local index (skipping Walrus fetch)`);
727
+ }
728
+
729
+ if (resultsNeedingFetch.length === 0) {
730
+ console.log('✅ All content available locally - no Walrus fetch needed!');
731
+ return;
732
+ }
733
+
734
+ console.log(`🐳 Fetching content from Walrus for ${resultsNeedingFetch.length} results...`);
735
+
736
+ const fetchPromises = resultsNeedingFetch.map(async (result) => {
737
+ try {
738
+ if (result.blobId) {
739
+ const memoryPackage = await this.services.storage.retrieveMemoryPackage(result.blobId);
740
+ if (memoryPackage.memoryPackage?.content) {
741
+ result.content = memoryPackage.memoryPackage.content;
742
+ }
743
+ }
744
+ } catch (error) {
745
+ // Log but don't fail - content fetch is best-effort
746
+ console.warn(`Failed to fetch content for ${result.blobId}:`, error);
747
+ }
748
+ });
749
+
750
+ await Promise.all(fetchPromises);
751
+ }
752
+
753
+ /**
754
+ * Search and fetch content in one call
755
+ *
756
+ * Convenience method that combines vector search with content fetching.
757
+ *
758
+ * @param query - Search query
759
+ * @param options - Search options
760
+ * @returns Results with content populated
761
+ *
762
+ * @example
763
+ * ```typescript
764
+ * const results = await pdw.search.withContent('programming');
765
+ * console.log(results[0].content); // "I love TypeScript..."
766
+ * ```
767
+ */
768
+ async withContent(query: string, options: VectorSearchOptions = {}): Promise<SearchResult[]> {
769
+ return this.vector(query, { ...options, fetchContent: true });
770
+ }
771
+
772
+ // ==========================================================================
773
+ // Knowledge Graph Methods (from GraphNamespace)
774
+ // ==========================================================================
775
+
776
+ /**
777
+ * Get all entities from knowledge graph
778
+ *
779
+ * @param options - Filter options
780
+ * @returns Array of entities
781
+ *
782
+ * @example
783
+ * ```typescript
784
+ * const entities = await pdw.search.entities({ type: 'PERSON' });
785
+ * ```
786
+ */
787
+ async entities(options: {
788
+ type?: string;
789
+ minConfidence?: number;
790
+ limit?: number;
791
+ } = {}): Promise<Array<{
792
+ id: string;
793
+ name: string;
794
+ type: string;
795
+ confidence: number;
796
+ }>> {
797
+ try {
798
+ const graphData = await this.services.storage.searchKnowledgeGraph(
799
+ this.services.config.userAddress,
800
+ { limit: options.limit || 100 }
801
+ );
802
+
803
+ let entities = graphData.entities;
804
+
805
+ if (options.type) {
806
+ entities = entities.filter((e: any) => e.type === options.type);
807
+ }
808
+
809
+ if (options.minConfidence) {
810
+ entities = entities.filter((e: any) => (e.confidence || 0) >= options.minConfidence!);
811
+ }
812
+
813
+ return entities.map((e: any) => ({
814
+ id: e.id,
815
+ name: e.label || '',
816
+ type: e.type,
817
+ confidence: e.confidence || 0
818
+ }));
819
+ } catch (error) {
820
+ throw new Error(`Get entities failed: ${error instanceof Error ? error.message : String(error)}`);
821
+ }
822
+ }
823
+
824
+ /**
825
+ * Get all relationships from knowledge graph
826
+ *
827
+ * @param options - Filter options
828
+ * @returns Array of relationships
829
+ *
830
+ * @example
831
+ * ```typescript
832
+ * const rels = await pdw.search.relationships({ type: 'WORKS_AT' });
833
+ * ```
834
+ */
835
+ async relationships(options: {
836
+ type?: string;
837
+ sourceId?: string;
838
+ targetId?: string;
839
+ minConfidence?: number;
840
+ limit?: number;
841
+ } = {}): Promise<Array<{
842
+ id: string;
843
+ source: string;
844
+ target: string;
845
+ type: string;
846
+ confidence: number;
847
+ }>> {
848
+ try {
849
+ const graphData = await this.services.storage.searchKnowledgeGraph(
850
+ this.services.config.userAddress,
851
+ {
852
+ searchText: options.sourceId || options.targetId || '',
853
+ limit: options.limit || 100
854
+ }
855
+ );
856
+
857
+ let relationships = graphData.relationships;
858
+
859
+ if (options.type) {
860
+ relationships = relationships.filter((r: any) => r.type === options.type);
861
+ }
862
+
863
+ if (options.sourceId) {
864
+ relationships = relationships.filter((r: any) => r.source === options.sourceId);
865
+ }
866
+
867
+ if (options.targetId) {
868
+ relationships = relationships.filter((r: any) => r.target === options.targetId);
869
+ }
870
+
871
+ if (options.minConfidence) {
872
+ relationships = relationships.filter((r: any) =>
873
+ (r.confidence || 0) >= options.minConfidence!
874
+ );
875
+ }
876
+
877
+ return relationships.map((r: any) => ({
878
+ id: `${r.source}-${r.target}`,
879
+ source: r.source,
880
+ target: r.target,
881
+ type: r.type || 'related',
882
+ confidence: r.confidence || 0
883
+ }));
884
+ } catch (error) {
885
+ throw new Error(`Get relationships failed: ${error instanceof Error ? error.message : String(error)}`);
886
+ }
887
+ }
888
+
889
+ // ==========================================================================
890
+ // Index Management (from IndexNamespace)
891
+ // ==========================================================================
892
+
893
+ /**
894
+ * Index management operations
895
+ *
896
+ * Access via pdw.search.index.*
897
+ */
898
+ get index(): IndexSubNamespace {
899
+ if (!this._indexSubNamespace) {
900
+ this._indexSubNamespace = new IndexSubNamespace(this.services);
901
+ }
902
+ return this._indexSubNamespace;
903
+ }
904
+
905
+ private _indexSubNamespace?: IndexSubNamespace;
906
+ }
907
+
908
+ /**
909
+ * Index sub-namespace for HNSW index management
910
+ */
911
+ class IndexSubNamespace {
912
+ constructor(private services: ServiceContainer) {}
913
+
914
+ /**
915
+ * Get the underlying index service
916
+ */
917
+ private getService() {
918
+ if (this.services.memoryIndex) {
919
+ return { type: 'memoryIndex' as const, service: this.services.memoryIndex };
920
+ }
921
+ if (this.services.vector) {
922
+ return { type: 'vector' as const, service: this.services.vector };
923
+ }
924
+ throw new Error('No indexing service configured. Enable local indexing in config.');
925
+ }
926
+
927
+ /**
928
+ * Save index to Walrus storage
929
+ *
930
+ * Persists the HNSW index binary to Walrus for durability.
931
+ *
932
+ * @param spaceId - Index space identifier (userAddress)
933
+ * @returns Blob ID of saved index on Walrus, or null if no index exists
934
+ *
935
+ * @example
936
+ * ```typescript
937
+ * const blobId = await pdw.search.index.save(userAddress);
938
+ * console.log('Index saved to Walrus:', blobId);
939
+ * ```
940
+ */
941
+ async save(spaceId: string): Promise<void> {
942
+ const { type, service } = this.getService();
943
+
944
+ if (type === 'memoryIndex') {
945
+ await service.saveIndex(spaceId);
946
+ console.log(`Index saved for space: ${spaceId}`);
947
+ } else {
948
+ await service.saveIndex(spaceId);
949
+ }
950
+ }
951
+
952
+ /**
953
+ * Load index from Walrus storage
954
+ *
955
+ * Loads a previously saved HNSW index from Walrus.
956
+ *
957
+ * @param spaceId - Index space identifier (userAddress)
958
+ * @param blobId - Blob ID of the saved index on Walrus
959
+ *
960
+ * @example
961
+ * ```typescript
962
+ * await pdw.search.index.load(userAddress, 'blobId123');
963
+ * ```
964
+ */
965
+ async load(spaceId: string, blobId: string): Promise<void> {
966
+ const { type, service } = this.getService();
967
+
968
+ if (type === 'memoryIndex') {
969
+ await service.loadIndex(spaceId, blobId);
970
+ console.log(`Index loaded from Walrus: ${blobId}`);
971
+ } else {
972
+ await service.loadIndex(spaceId, blobId);
973
+ }
974
+ }
975
+
976
+ /**
977
+ * Get index statistics
978
+ *
979
+ * @param spaceId - Index space identifier
980
+ * @returns Index statistics
981
+ *
982
+ * @example
983
+ * ```typescript
984
+ * const stats = pdw.search.index.stats(userAddress);
985
+ * console.log('Total vectors:', stats.totalVectors);
986
+ * ```
987
+ */
988
+ stats(spaceId: string): {
989
+ totalVectors: number;
990
+ dimension: number;
991
+ spaceType: string;
992
+ maxElements: number;
993
+ currentCount: number;
994
+ } {
995
+ const { type, service } = this.getService();
996
+
997
+ if (type === 'memoryIndex') {
998
+ const stats = service.getIndexStats(spaceId);
999
+ return {
1000
+ totalVectors: stats.totalMemories || 0,
1001
+ dimension: 3072,
1002
+ spaceType: 'cosine',
1003
+ maxElements: 10000,
1004
+ currentCount: stats.indexSize || stats.totalMemories || 0
1005
+ };
1006
+ } else {
1007
+ const entry = (service as any).indexCache?.get(spaceId);
1008
+ if (!entry) {
1009
+ throw new Error(`Index ${spaceId} not found`);
1010
+ }
1011
+ const currentCount = entry.index.getCurrentCount?.() || 0;
1012
+ return {
1013
+ totalVectors: currentCount,
1014
+ dimension: 3072,
1015
+ spaceType: 'cosine',
1016
+ maxElements: 10000,
1017
+ currentCount
1018
+ };
1019
+ }
1020
+ }
1021
+
1022
+ /**
1023
+ * Clear index and remove all vectors
1024
+ *
1025
+ * @param spaceId - Index space identifier
1026
+ */
1027
+ clear(spaceId: string): void {
1028
+ const { type, service } = this.getService();
1029
+
1030
+ if (type === 'memoryIndex') {
1031
+ service.clearUserIndex(spaceId);
1032
+ } else {
1033
+ (service as any).indexCache?.delete(spaceId);
1034
+ }
1035
+ }
1036
+
1037
+ /**
1038
+ * Force flush pending vectors
1039
+ *
1040
+ * @param spaceId - Index space identifier
1041
+ */
1042
+ async flush(spaceId: string): Promise<void> {
1043
+ const { type, service } = this.getService();
1044
+
1045
+ if (type === 'memoryIndex') {
1046
+ await service.flush(spaceId);
1047
+ }
1048
+ }
1049
+ }