@soulcraft/brainy 0.41.0 → 0.44.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 (205) hide show
  1. package/README.md +605 -194
  2. package/dist/augmentationFactory.d.ts.map +1 -0
  3. package/dist/augmentationFactory.js +342 -0
  4. package/dist/augmentationFactory.js.map +1 -0
  5. package/dist/augmentationPipeline.d.ts.map +1 -0
  6. package/dist/augmentationPipeline.js +472 -0
  7. package/dist/augmentationPipeline.js.map +1 -0
  8. package/dist/augmentationRegistry.d.ts.map +1 -0
  9. package/dist/augmentationRegistry.js +105 -0
  10. package/dist/augmentationRegistry.js.map +1 -0
  11. package/dist/augmentationRegistryLoader.d.ts.map +1 -0
  12. package/dist/augmentationRegistryLoader.js +213 -0
  13. package/dist/augmentationRegistryLoader.js.map +1 -0
  14. package/dist/augmentations/conduitAugmentations.js +1158 -0
  15. package/dist/augmentations/conduitAugmentations.js.map +1 -0
  16. package/dist/augmentations/memoryAugmentations.d.ts +2 -0
  17. package/dist/augmentations/memoryAugmentations.d.ts.map +1 -1
  18. package/dist/augmentations/memoryAugmentations.js +270 -0
  19. package/dist/augmentations/memoryAugmentations.js.map +1 -0
  20. package/dist/augmentations/serverSearchAugmentations.js +531 -0
  21. package/dist/augmentations/serverSearchAugmentations.js.map +1 -0
  22. package/dist/brainyData.d.ts.map +1 -0
  23. package/dist/brainyData.js +3999 -0
  24. package/dist/brainyData.js.map +1 -0
  25. package/dist/browserFramework.d.ts +15 -0
  26. package/dist/browserFramework.d.ts.map +1 -0
  27. package/dist/browserFramework.js +31 -0
  28. package/dist/browserFramework.js.map +1 -0
  29. package/dist/coreTypes.d.ts.map +1 -0
  30. package/dist/coreTypes.js +5 -0
  31. package/dist/coreTypes.js.map +1 -0
  32. package/dist/demo.d.ts +106 -0
  33. package/dist/demo.d.ts.map +1 -0
  34. package/dist/demo.js +201 -0
  35. package/dist/demo.js.map +1 -0
  36. package/dist/distributed/configManager.d.ts.map +1 -0
  37. package/dist/distributed/configManager.js +322 -0
  38. package/dist/distributed/configManager.js.map +1 -0
  39. package/dist/distributed/domainDetector.d.ts.map +1 -0
  40. package/dist/distributed/domainDetector.js +307 -0
  41. package/dist/distributed/domainDetector.js.map +1 -0
  42. package/dist/distributed/hashPartitioner.d.ts.map +1 -0
  43. package/dist/distributed/hashPartitioner.js +146 -0
  44. package/dist/distributed/hashPartitioner.js.map +1 -0
  45. package/dist/distributed/healthMonitor.d.ts.map +1 -0
  46. package/dist/distributed/healthMonitor.js +244 -0
  47. package/dist/distributed/healthMonitor.js.map +1 -0
  48. package/dist/distributed/index.d.ts.map +1 -0
  49. package/dist/distributed/index.js +9 -0
  50. package/dist/distributed/index.js.map +1 -0
  51. package/dist/distributed/operationalModes.d.ts.map +1 -0
  52. package/dist/distributed/operationalModes.js +201 -0
  53. package/dist/distributed/operationalModes.js.map +1 -0
  54. package/dist/errors/brainyError.d.ts.map +1 -0
  55. package/dist/errors/brainyError.js +113 -0
  56. package/dist/errors/brainyError.js.map +1 -0
  57. package/dist/examples/basicUsage.js +118 -0
  58. package/dist/examples/basicUsage.js.map +1 -0
  59. package/dist/hnsw/distributedSearch.js +452 -0
  60. package/dist/hnsw/distributedSearch.js.map +1 -0
  61. package/dist/hnsw/hnswIndex.js +602 -0
  62. package/dist/hnsw/hnswIndex.js.map +1 -0
  63. package/dist/hnsw/hnswIndexOptimized.js +471 -0
  64. package/dist/hnsw/hnswIndexOptimized.js.map +1 -0
  65. package/dist/hnsw/optimizedHNSWIndex.js +313 -0
  66. package/dist/hnsw/optimizedHNSWIndex.js.map +1 -0
  67. package/dist/hnsw/partitionedHNSWIndex.js +304 -0
  68. package/dist/hnsw/partitionedHNSWIndex.js.map +1 -0
  69. package/dist/hnsw/scaledHNSWSystem.js +559 -0
  70. package/dist/hnsw/scaledHNSWSystem.js.map +1 -0
  71. package/dist/index.d.ts +3 -2
  72. package/dist/index.d.ts.map +1 -0
  73. package/dist/index.js +81 -0
  74. package/dist/index.js.map +1 -0
  75. package/dist/mcp/brainyMCPAdapter.js +142 -0
  76. package/dist/mcp/brainyMCPAdapter.js.map +1 -0
  77. package/dist/mcp/brainyMCPService.js +248 -0
  78. package/dist/mcp/brainyMCPService.js.map +1 -0
  79. package/dist/mcp/index.js +17 -0
  80. package/dist/mcp/index.js.map +1 -0
  81. package/dist/mcp/mcpAugmentationToolset.js +180 -0
  82. package/dist/mcp/mcpAugmentationToolset.js.map +1 -0
  83. package/dist/pipeline.d.ts.map +1 -0
  84. package/dist/pipeline.js +590 -0
  85. package/dist/pipeline.js.map +1 -0
  86. package/dist/sequentialPipeline.d.ts.map +1 -0
  87. package/dist/sequentialPipeline.js +417 -0
  88. package/dist/sequentialPipeline.js.map +1 -0
  89. package/dist/setup.d.ts.map +1 -0
  90. package/dist/setup.js +46 -0
  91. package/dist/setup.js.map +1 -0
  92. package/dist/storage/adapters/baseStorageAdapter.js +349 -0
  93. package/dist/storage/adapters/baseStorageAdapter.js.map +1 -0
  94. package/dist/storage/adapters/batchS3Operations.js +287 -0
  95. package/dist/storage/adapters/batchS3Operations.js.map +1 -0
  96. package/dist/storage/adapters/fileSystemStorage.js +846 -0
  97. package/dist/storage/adapters/fileSystemStorage.js.map +1 -0
  98. package/dist/storage/adapters/memoryStorage.js +532 -0
  99. package/dist/storage/adapters/memoryStorage.js.map +1 -0
  100. package/dist/storage/adapters/opfsStorage.d.ts.map +1 -1
  101. package/dist/storage/adapters/opfsStorage.js +1118 -0
  102. package/dist/storage/adapters/opfsStorage.js.map +1 -0
  103. package/dist/storage/adapters/optimizedS3Search.js +248 -0
  104. package/dist/storage/adapters/optimizedS3Search.js.map +1 -0
  105. package/dist/storage/adapters/s3CompatibleStorage.js +2026 -0
  106. package/dist/storage/adapters/s3CompatibleStorage.js.map +1 -0
  107. package/dist/storage/baseStorage.js +603 -0
  108. package/dist/storage/baseStorage.js.map +1 -0
  109. package/dist/storage/cacheManager.js +1306 -0
  110. package/dist/storage/cacheManager.js.map +1 -0
  111. package/dist/storage/enhancedCacheManager.js +520 -0
  112. package/dist/storage/enhancedCacheManager.js.map +1 -0
  113. package/dist/storage/readOnlyOptimizations.js +425 -0
  114. package/dist/storage/readOnlyOptimizations.js.map +1 -0
  115. package/dist/storage/storageFactory.d.ts +0 -1
  116. package/dist/storage/storageFactory.d.ts.map +1 -1
  117. package/dist/storage/storageFactory.js +227 -0
  118. package/dist/storage/storageFactory.js.map +1 -0
  119. package/dist/types/augmentations.js +16 -0
  120. package/dist/types/augmentations.js.map +1 -0
  121. package/dist/types/brainyDataInterface.js +8 -0
  122. package/dist/types/brainyDataInterface.js.map +1 -0
  123. package/dist/types/distributedTypes.js +6 -0
  124. package/dist/types/distributedTypes.js.map +1 -0
  125. package/dist/types/fileSystemTypes.js +8 -0
  126. package/dist/types/fileSystemTypes.js.map +1 -0
  127. package/dist/types/graphTypes.js +247 -0
  128. package/dist/types/graphTypes.js.map +1 -0
  129. package/dist/types/mcpTypes.js +22 -0
  130. package/dist/types/mcpTypes.js.map +1 -0
  131. package/dist/types/paginationTypes.js +5 -0
  132. package/dist/types/paginationTypes.js.map +1 -0
  133. package/dist/types/pipelineTypes.js +7 -0
  134. package/dist/types/pipelineTypes.js.map +1 -0
  135. package/dist/types/tensorflowTypes.js +6 -0
  136. package/dist/types/tensorflowTypes.js.map +1 -0
  137. package/dist/unified.d.ts.map +1 -0
  138. package/dist/unified.js +52 -128251
  139. package/dist/unified.js.map +1 -0
  140. package/dist/utils/autoConfiguration.js +341 -0
  141. package/dist/utils/autoConfiguration.js.map +1 -0
  142. package/dist/utils/cacheAutoConfig.js +261 -0
  143. package/dist/utils/cacheAutoConfig.js.map +1 -0
  144. package/dist/utils/crypto.js +45 -0
  145. package/dist/utils/crypto.js.map +1 -0
  146. package/dist/utils/distance.js +239 -0
  147. package/dist/utils/distance.js.map +1 -0
  148. package/dist/utils/embedding.d.ts.map +1 -1
  149. package/dist/utils/embedding.js +702 -0
  150. package/dist/utils/embedding.js.map +1 -0
  151. package/dist/utils/environment.js +75 -0
  152. package/dist/utils/environment.js.map +1 -0
  153. package/dist/utils/fieldNameTracking.js +90 -0
  154. package/dist/utils/fieldNameTracking.js.map +1 -0
  155. package/dist/utils/index.d.ts +1 -0
  156. package/dist/utils/index.d.ts.map +1 -1
  157. package/dist/utils/index.js +8 -0
  158. package/dist/utils/index.js.map +1 -0
  159. package/dist/utils/jsonProcessing.js +179 -0
  160. package/dist/utils/jsonProcessing.js.map +1 -0
  161. package/dist/utils/logger.js +129 -0
  162. package/dist/utils/logger.js.map +1 -0
  163. package/dist/utils/operationUtils.js +126 -0
  164. package/dist/utils/operationUtils.js.map +1 -0
  165. package/dist/utils/robustModelLoader.d.ts +14 -0
  166. package/dist/utils/robustModelLoader.d.ts.map +1 -1
  167. package/dist/utils/robustModelLoader.js +537 -0
  168. package/dist/utils/robustModelLoader.js.map +1 -0
  169. package/dist/utils/searchCache.js +248 -0
  170. package/dist/utils/searchCache.js.map +1 -0
  171. package/dist/utils/statistics.js +25 -0
  172. package/dist/utils/statistics.js.map +1 -0
  173. package/dist/utils/statisticsCollector.js +224 -0
  174. package/dist/utils/statisticsCollector.js.map +1 -0
  175. package/dist/utils/textEncoding.js +309 -0
  176. package/dist/utils/textEncoding.js.map +1 -0
  177. package/dist/utils/typeUtils.js +40 -0
  178. package/dist/utils/typeUtils.js.map +1 -0
  179. package/dist/utils/version.d.ts +15 -3
  180. package/dist/utils/version.d.ts.map +1 -1
  181. package/dist/utils/version.js +24 -0
  182. package/dist/utils/version.js.map +1 -0
  183. package/dist/utils/workerUtils.js +458 -0
  184. package/dist/utils/workerUtils.js.map +1 -0
  185. package/dist/worker.d.ts.map +1 -0
  186. package/dist/worker.js +54 -0
  187. package/dist/worker.js.map +1 -0
  188. package/package.json +30 -29
  189. package/dist/brainy.js +0 -90220
  190. package/dist/brainy.min.js +0 -12511
  191. package/dist/patched-platform-node.d.ts +0 -17
  192. package/dist/statistics/statisticsManager.d.ts +0 -121
  193. package/dist/storage/fileSystemStorage.d.ts +0 -73
  194. package/dist/storage/fileSystemStorage.d.ts.map +0 -1
  195. package/dist/storage/opfsStorage.d.ts +0 -236
  196. package/dist/storage/opfsStorage.d.ts.map +0 -1
  197. package/dist/storage/s3CompatibleStorage.d.ts +0 -157
  198. package/dist/storage/s3CompatibleStorage.d.ts.map +0 -1
  199. package/dist/testing/prettyReporter.d.ts +0 -23
  200. package/dist/testing/prettySummaryReporter.d.ts +0 -22
  201. package/dist/unified.min.js +0 -16153
  202. package/dist/utils/environmentDetection.d.ts +0 -47
  203. package/dist/utils/environmentDetection.d.ts.map +0 -1
  204. package/dist/utils/tensorflowUtils.d.ts +0 -17
  205. package/dist/utils/tensorflowUtils.d.ts.map +0 -1
@@ -0,0 +1,1118 @@
1
+ /**
2
+ * OPFS (Origin Private File System) Storage Adapter
3
+ * Provides persistent storage for the vector database using the Origin Private File System API
4
+ */
5
+ import { BaseStorage, NOUNS_DIR, VERBS_DIR, METADATA_DIR, NOUN_METADATA_DIR, VERB_METADATA_DIR, INDEX_DIR } from '../baseStorage.js';
6
+ import '../../types/fileSystemTypes.js';
7
+ /**
8
+ * Helper function to safely get a file from a FileSystemHandle
9
+ * This is needed because TypeScript doesn't recognize that a FileSystemHandle
10
+ * can be a FileSystemFileHandle which has the getFile method
11
+ */
12
+ async function safeGetFile(handle) {
13
+ // Type cast to any to avoid TypeScript error
14
+ return handle.getFile();
15
+ }
16
+ // Root directory name for OPFS storage
17
+ const ROOT_DIR = 'opfs-vector-db';
18
+ /**
19
+ * OPFS storage adapter for browser environments
20
+ * Uses the Origin Private File System API to store data persistently
21
+ */
22
+ export class OPFSStorage extends BaseStorage {
23
+ constructor() {
24
+ super();
25
+ this.rootDir = null;
26
+ this.nounsDir = null;
27
+ this.verbsDir = null;
28
+ this.metadataDir = null;
29
+ this.nounMetadataDir = null;
30
+ this.verbMetadataDir = null;
31
+ this.indexDir = null;
32
+ this.isAvailable = false;
33
+ this.isPersistentRequested = false;
34
+ this.isPersistentGranted = false;
35
+ this.statistics = null;
36
+ this.activeLocks = new Set();
37
+ this.lockPrefix = 'opfs-lock-';
38
+ // Check if OPFS is available
39
+ this.isAvailable =
40
+ typeof navigator !== 'undefined' &&
41
+ 'storage' in navigator &&
42
+ 'getDirectory' in navigator.storage;
43
+ }
44
+ /**
45
+ * Initialize the storage adapter
46
+ */
47
+ async init() {
48
+ if (this.isInitialized) {
49
+ return;
50
+ }
51
+ if (!this.isAvailable) {
52
+ throw new Error('Origin Private File System is not available in this environment');
53
+ }
54
+ try {
55
+ // Get the root directory
56
+ const root = await navigator.storage.getDirectory();
57
+ // Create or get our app's root directory
58
+ this.rootDir = await root.getDirectoryHandle(ROOT_DIR, { create: true });
59
+ // Create or get nouns directory
60
+ this.nounsDir = await this.rootDir.getDirectoryHandle(NOUNS_DIR, {
61
+ create: true
62
+ });
63
+ // Create or get verbs directory
64
+ this.verbsDir = await this.rootDir.getDirectoryHandle(VERBS_DIR, {
65
+ create: true
66
+ });
67
+ // Create or get metadata directory
68
+ this.metadataDir = await this.rootDir.getDirectoryHandle(METADATA_DIR, {
69
+ create: true
70
+ });
71
+ // Create or get noun metadata directory
72
+ this.nounMetadataDir = await this.rootDir.getDirectoryHandle(NOUN_METADATA_DIR, {
73
+ create: true
74
+ });
75
+ // Create or get verb metadata directory
76
+ this.verbMetadataDir = await this.rootDir.getDirectoryHandle(VERB_METADATA_DIR, {
77
+ create: true
78
+ });
79
+ // Create or get index directory
80
+ this.indexDir = await this.rootDir.getDirectoryHandle(INDEX_DIR, {
81
+ create: true
82
+ });
83
+ this.isInitialized = true;
84
+ }
85
+ catch (error) {
86
+ console.error('Failed to initialize OPFS storage:', error);
87
+ throw new Error(`Failed to initialize OPFS storage: ${error}`);
88
+ }
89
+ }
90
+ /**
91
+ * Check if OPFS is available in the current environment
92
+ */
93
+ isOPFSAvailable() {
94
+ return this.isAvailable;
95
+ }
96
+ /**
97
+ * Request persistent storage permission from the user
98
+ * @returns Promise that resolves to true if permission was granted, false otherwise
99
+ */
100
+ async requestPersistentStorage() {
101
+ if (!this.isAvailable) {
102
+ console.warn('Cannot request persistent storage: OPFS is not available');
103
+ return false;
104
+ }
105
+ try {
106
+ // Check if persistence is already granted
107
+ this.isPersistentGranted = await navigator.storage.persisted();
108
+ if (!this.isPersistentGranted) {
109
+ // Request permission for persistent storage
110
+ this.isPersistentGranted = await navigator.storage.persist();
111
+ }
112
+ this.isPersistentRequested = true;
113
+ return this.isPersistentGranted;
114
+ }
115
+ catch (error) {
116
+ console.warn('Failed to request persistent storage:', error);
117
+ return false;
118
+ }
119
+ }
120
+ /**
121
+ * Check if persistent storage is granted
122
+ * @returns Promise that resolves to true if persistent storage is granted, false otherwise
123
+ */
124
+ async isPersistent() {
125
+ if (!this.isAvailable) {
126
+ return false;
127
+ }
128
+ try {
129
+ this.isPersistentGranted = await navigator.storage.persisted();
130
+ return this.isPersistentGranted;
131
+ }
132
+ catch (error) {
133
+ console.warn('Failed to check persistent storage status:', error);
134
+ return false;
135
+ }
136
+ }
137
+ /**
138
+ * Save a noun to storage
139
+ */
140
+ async saveNoun_internal(noun) {
141
+ await this.ensureInitialized();
142
+ try {
143
+ // Convert connections Map to a serializable format
144
+ const serializableNoun = {
145
+ ...noun,
146
+ connections: this.mapToObject(noun.connections, (set) => Array.from(set))
147
+ };
148
+ // Create or get the file for this noun
149
+ const fileHandle = await this.nounsDir.getFileHandle(noun.id, {
150
+ create: true
151
+ });
152
+ // Write the noun data to the file
153
+ const writable = await fileHandle.createWritable();
154
+ await writable.write(JSON.stringify(serializableNoun));
155
+ await writable.close();
156
+ }
157
+ catch (error) {
158
+ console.error(`Failed to save noun ${noun.id}:`, error);
159
+ throw new Error(`Failed to save noun ${noun.id}: ${error}`);
160
+ }
161
+ }
162
+ /**
163
+ * Get a noun from storage
164
+ */
165
+ async getNoun_internal(id) {
166
+ await this.ensureInitialized();
167
+ try {
168
+ // Get the file handle for this noun
169
+ const fileHandle = await this.nounsDir.getFileHandle(id);
170
+ // Read the noun data from the file
171
+ const file = await fileHandle.getFile();
172
+ const text = await file.text();
173
+ const data = JSON.parse(text);
174
+ // Convert serialized connections back to Map<number, Set<string>>
175
+ const connections = new Map();
176
+ for (const [level, nounIds] of Object.entries(data.connections)) {
177
+ connections.set(Number(level), new Set(nounIds));
178
+ }
179
+ return {
180
+ id: data.id,
181
+ vector: data.vector,
182
+ connections,
183
+ level: data.level || 0
184
+ };
185
+ }
186
+ catch (error) {
187
+ // Noun not found or other error
188
+ return null;
189
+ }
190
+ }
191
+ /**
192
+ * Get all nouns from storage
193
+ */
194
+ async getAllNouns_internal() {
195
+ await this.ensureInitialized();
196
+ const allNouns = [];
197
+ try {
198
+ // Iterate through all files in the nouns directory
199
+ for await (const [name, handle] of this.nounsDir.entries()) {
200
+ if (handle.kind === 'file') {
201
+ try {
202
+ // Read the noun data from the file
203
+ const file = await safeGetFile(handle);
204
+ const text = await file.text();
205
+ const data = JSON.parse(text);
206
+ // Convert serialized connections back to Map<number, Set<string>>
207
+ const connections = new Map();
208
+ for (const [level, nounIds] of Object.entries(data.connections)) {
209
+ connections.set(Number(level), new Set(nounIds));
210
+ }
211
+ allNouns.push({
212
+ id: data.id,
213
+ vector: data.vector,
214
+ connections,
215
+ level: data.level || 0
216
+ });
217
+ }
218
+ catch (error) {
219
+ console.error(`Error reading noun file ${name}:`, error);
220
+ }
221
+ }
222
+ }
223
+ }
224
+ catch (error) {
225
+ console.error('Error reading nouns directory:', error);
226
+ }
227
+ return allNouns;
228
+ }
229
+ /**
230
+ * Get nouns by noun type (internal implementation)
231
+ * @param nounType The noun type to filter by
232
+ * @returns Promise that resolves to an array of nouns of the specified noun type
233
+ */
234
+ async getNounsByNounType_internal(nounType) {
235
+ return this.getNodesByNounType(nounType);
236
+ }
237
+ /**
238
+ * Get nodes by noun type
239
+ * @param nounType The noun type to filter by
240
+ * @returns Promise that resolves to an array of nodes of the specified noun type
241
+ */
242
+ async getNodesByNounType(nounType) {
243
+ await this.ensureInitialized();
244
+ const nodes = [];
245
+ try {
246
+ // Iterate through all files in the nouns directory
247
+ for await (const [name, handle] of this.nounsDir.entries()) {
248
+ if (handle.kind === 'file') {
249
+ try {
250
+ // Read the node data from the file
251
+ const file = await safeGetFile(handle);
252
+ const text = await file.text();
253
+ const data = JSON.parse(text);
254
+ // Get the metadata to check the noun type
255
+ const metadata = await this.getMetadata(data.id);
256
+ // Include the node if its noun type matches the requested type
257
+ if (metadata && metadata.noun === nounType) {
258
+ // Convert serialized connections back to Map<number, Set<string>>
259
+ const connections = new Map();
260
+ for (const [level, nodeIds] of Object.entries(data.connections)) {
261
+ connections.set(Number(level), new Set(nodeIds));
262
+ }
263
+ nodes.push({
264
+ id: data.id,
265
+ vector: data.vector,
266
+ connections,
267
+ level: data.level || 0
268
+ });
269
+ }
270
+ }
271
+ catch (error) {
272
+ console.error(`Error reading node file ${name}:`, error);
273
+ }
274
+ }
275
+ }
276
+ }
277
+ catch (error) {
278
+ console.error('Error reading nouns directory:', error);
279
+ }
280
+ return nodes;
281
+ }
282
+ /**
283
+ * Delete a noun from storage (internal implementation)
284
+ */
285
+ async deleteNoun_internal(id) {
286
+ return this.deleteNode(id);
287
+ }
288
+ /**
289
+ * Delete a node from storage
290
+ */
291
+ async deleteNode(id) {
292
+ await this.ensureInitialized();
293
+ try {
294
+ await this.nounsDir.removeEntry(id);
295
+ }
296
+ catch (error) {
297
+ // Ignore NotFoundError, which means the file doesn't exist
298
+ if (error.name !== 'NotFoundError') {
299
+ console.error(`Error deleting node ${id}:`, error);
300
+ throw error;
301
+ }
302
+ }
303
+ }
304
+ /**
305
+ * Save a verb to storage (internal implementation)
306
+ */
307
+ async saveVerb_internal(verb) {
308
+ return this.saveEdge(verb);
309
+ }
310
+ /**
311
+ * Save an edge to storage
312
+ */
313
+ async saveEdge(edge) {
314
+ await this.ensureInitialized();
315
+ try {
316
+ // Convert connections Map to a serializable format
317
+ const serializableEdge = {
318
+ ...edge,
319
+ connections: this.mapToObject(edge.connections, (set) => Array.from(set))
320
+ };
321
+ // Create or get the file for this verb
322
+ const fileHandle = await this.verbsDir.getFileHandle(edge.id, {
323
+ create: true
324
+ });
325
+ // Write the verb data to the file
326
+ const writable = await fileHandle.createWritable();
327
+ await writable.write(JSON.stringify(serializableEdge));
328
+ await writable.close();
329
+ }
330
+ catch (error) {
331
+ console.error(`Failed to save edge ${edge.id}:`, error);
332
+ throw new Error(`Failed to save edge ${edge.id}: ${error}`);
333
+ }
334
+ }
335
+ /**
336
+ * Get a verb from storage (internal implementation)
337
+ */
338
+ async getVerb_internal(id) {
339
+ return this.getEdge(id);
340
+ }
341
+ /**
342
+ * Get an edge from storage
343
+ */
344
+ async getEdge(id) {
345
+ await this.ensureInitialized();
346
+ try {
347
+ // Get the file handle for this edge
348
+ const fileHandle = await this.verbsDir.getFileHandle(id);
349
+ // Read the edge data from the file
350
+ const file = await fileHandle.getFile();
351
+ const text = await file.text();
352
+ const data = JSON.parse(text);
353
+ // Convert serialized connections back to Map<number, Set<string>>
354
+ const connections = new Map();
355
+ for (const [level, nodeIds] of Object.entries(data.connections)) {
356
+ connections.set(Number(level), new Set(nodeIds));
357
+ }
358
+ // Create default timestamp if not present
359
+ const defaultTimestamp = {
360
+ seconds: Math.floor(Date.now() / 1000),
361
+ nanoseconds: (Date.now() % 1000) * 1000000
362
+ };
363
+ // Create default createdBy if not present
364
+ const defaultCreatedBy = {
365
+ augmentation: 'unknown',
366
+ version: '1.0'
367
+ };
368
+ return {
369
+ id: data.id,
370
+ vector: data.vector,
371
+ connections
372
+ };
373
+ }
374
+ catch (error) {
375
+ // Edge not found or other error
376
+ return null;
377
+ }
378
+ }
379
+ /**
380
+ * Get all verbs from storage (internal implementation)
381
+ */
382
+ async getAllVerbs_internal() {
383
+ return this.getAllEdges();
384
+ }
385
+ /**
386
+ * Get all edges from storage
387
+ */
388
+ async getAllEdges() {
389
+ await this.ensureInitialized();
390
+ const allEdges = [];
391
+ try {
392
+ // Iterate through all files in the verbs directory
393
+ for await (const [name, handle] of this.verbsDir.entries()) {
394
+ if (handle.kind === 'file') {
395
+ try {
396
+ // Read the edge data from the file
397
+ const file = await safeGetFile(handle);
398
+ const text = await file.text();
399
+ const data = JSON.parse(text);
400
+ // Convert serialized connections back to Map<number, Set<string>>
401
+ const connections = new Map();
402
+ for (const [level, nodeIds] of Object.entries(data.connections)) {
403
+ connections.set(Number(level), new Set(nodeIds));
404
+ }
405
+ // Create default timestamp if not present
406
+ const defaultTimestamp = {
407
+ seconds: Math.floor(Date.now() / 1000),
408
+ nanoseconds: (Date.now() % 1000) * 1000000
409
+ };
410
+ // Create default createdBy if not present
411
+ const defaultCreatedBy = {
412
+ augmentation: 'unknown',
413
+ version: '1.0'
414
+ };
415
+ allEdges.push({
416
+ id: data.id,
417
+ vector: data.vector,
418
+ connections
419
+ });
420
+ }
421
+ catch (error) {
422
+ console.error(`Error reading edge file ${name}:`, error);
423
+ }
424
+ }
425
+ }
426
+ }
427
+ catch (error) {
428
+ console.error('Error reading verbs directory:', error);
429
+ }
430
+ return allEdges;
431
+ }
432
+ /**
433
+ * Get verbs by source (internal implementation)
434
+ */
435
+ async getVerbsBySource_internal(sourceId) {
436
+ // This method is deprecated and would require loading metadata for each edge
437
+ // For now, return empty array since this is not efficiently implementable with new storage pattern
438
+ console.warn('getVerbsBySource_internal is deprecated and not efficiently supported in new storage pattern');
439
+ return [];
440
+ }
441
+ /**
442
+ * Get edges by source
443
+ */
444
+ async getEdgesBySource(sourceId) {
445
+ // This method is deprecated and would require loading metadata for each edge
446
+ // For now, return empty array since this is not efficiently implementable with new storage pattern
447
+ console.warn('getEdgesBySource is deprecated and not efficiently supported in new storage pattern');
448
+ return [];
449
+ }
450
+ /**
451
+ * Get verbs by target (internal implementation)
452
+ */
453
+ async getVerbsByTarget_internal(targetId) {
454
+ // This method is deprecated and would require loading metadata for each edge
455
+ // For now, return empty array since this is not efficiently implementable with new storage pattern
456
+ console.warn('getVerbsByTarget_internal is deprecated and not efficiently supported in new storage pattern');
457
+ return [];
458
+ }
459
+ /**
460
+ * Get edges by target
461
+ */
462
+ async getEdgesByTarget(targetId) {
463
+ // This method is deprecated and would require loading metadata for each edge
464
+ // For now, return empty array since this is not efficiently implementable with new storage pattern
465
+ console.warn('getEdgesByTarget is deprecated and not efficiently supported in new storage pattern');
466
+ return [];
467
+ }
468
+ /**
469
+ * Get verbs by type (internal implementation)
470
+ */
471
+ async getVerbsByType_internal(type) {
472
+ // This method is deprecated and would require loading metadata for each edge
473
+ // For now, return empty array since this is not efficiently implementable with new storage pattern
474
+ console.warn('getVerbsByType_internal is deprecated and not efficiently supported in new storage pattern');
475
+ return [];
476
+ }
477
+ /**
478
+ * Get edges by type
479
+ */
480
+ async getEdgesByType(type) {
481
+ // This method is deprecated and would require loading metadata for each edge
482
+ // For now, return empty array since this is not efficiently implementable with new storage pattern
483
+ console.warn('getEdgesByType is deprecated and not efficiently supported in new storage pattern');
484
+ return [];
485
+ }
486
+ /**
487
+ * Delete a verb from storage (internal implementation)
488
+ */
489
+ async deleteVerb_internal(id) {
490
+ return this.deleteEdge(id);
491
+ }
492
+ /**
493
+ * Delete an edge from storage
494
+ */
495
+ async deleteEdge(id) {
496
+ await this.ensureInitialized();
497
+ try {
498
+ await this.verbsDir.removeEntry(id);
499
+ }
500
+ catch (error) {
501
+ // Ignore NotFoundError, which means the file doesn't exist
502
+ if (error.name !== 'NotFoundError') {
503
+ console.error(`Error deleting edge ${id}:`, error);
504
+ throw error;
505
+ }
506
+ }
507
+ }
508
+ /**
509
+ * Save metadata to storage
510
+ */
511
+ async saveMetadata(id, metadata) {
512
+ await this.ensureInitialized();
513
+ try {
514
+ // Create or get the file for this metadata
515
+ const fileHandle = await this.metadataDir.getFileHandle(id, {
516
+ create: true
517
+ });
518
+ // Write the metadata to the file
519
+ const writable = await fileHandle.createWritable();
520
+ await writable.write(JSON.stringify(metadata));
521
+ await writable.close();
522
+ }
523
+ catch (error) {
524
+ console.error(`Failed to save metadata ${id}:`, error);
525
+ throw new Error(`Failed to save metadata ${id}: ${error}`);
526
+ }
527
+ }
528
+ /**
529
+ * Get metadata from storage
530
+ */
531
+ async getMetadata(id) {
532
+ await this.ensureInitialized();
533
+ try {
534
+ // Get the file handle for this metadata
535
+ const fileHandle = await this.metadataDir.getFileHandle(id);
536
+ // Read the metadata from the file
537
+ const file = await fileHandle.getFile();
538
+ const text = await file.text();
539
+ return JSON.parse(text);
540
+ }
541
+ catch (error) {
542
+ // Metadata not found or other error
543
+ return null;
544
+ }
545
+ }
546
+ /**
547
+ * Save verb metadata to storage
548
+ */
549
+ async saveVerbMetadata(id, metadata) {
550
+ await this.ensureInitialized();
551
+ const fileName = `${id}.json`;
552
+ const fileHandle = await this.verbMetadataDir.getFileHandle(fileName, { create: true });
553
+ const writable = await fileHandle.createWritable();
554
+ await writable.write(JSON.stringify(metadata, null, 2));
555
+ await writable.close();
556
+ }
557
+ /**
558
+ * Get verb metadata from storage
559
+ */
560
+ async getVerbMetadata(id) {
561
+ await this.ensureInitialized();
562
+ const fileName = `${id}.json`;
563
+ try {
564
+ const fileHandle = await this.verbMetadataDir.getFileHandle(fileName);
565
+ const file = await safeGetFile(fileHandle);
566
+ const text = await file.text();
567
+ return JSON.parse(text);
568
+ }
569
+ catch (error) {
570
+ if (error.name !== 'NotFoundError') {
571
+ console.error(`Error reading verb metadata ${id}:`, error);
572
+ }
573
+ return null;
574
+ }
575
+ }
576
+ /**
577
+ * Save noun metadata to storage
578
+ */
579
+ async saveNounMetadata(id, metadata) {
580
+ await this.ensureInitialized();
581
+ const fileName = `${id}.json`;
582
+ const fileHandle = await this.nounMetadataDir.getFileHandle(fileName, { create: true });
583
+ const writable = await fileHandle.createWritable();
584
+ await writable.write(JSON.stringify(metadata, null, 2));
585
+ await writable.close();
586
+ }
587
+ /**
588
+ * Get noun metadata from storage
589
+ */
590
+ async getNounMetadata(id) {
591
+ await this.ensureInitialized();
592
+ const fileName = `${id}.json`;
593
+ try {
594
+ const fileHandle = await this.nounMetadataDir.getFileHandle(fileName);
595
+ const file = await safeGetFile(fileHandle);
596
+ const text = await file.text();
597
+ return JSON.parse(text);
598
+ }
599
+ catch (error) {
600
+ if (error.name !== 'NotFoundError') {
601
+ console.error(`Error reading noun metadata ${id}:`, error);
602
+ }
603
+ return null;
604
+ }
605
+ }
606
+ /**
607
+ * Clear all data from storage
608
+ */
609
+ async clear() {
610
+ await this.ensureInitialized();
611
+ // Helper function to remove all files in a directory
612
+ const removeDirectoryContents = async (dirHandle) => {
613
+ try {
614
+ for await (const [name, handle] of dirHandle.entries()) {
615
+ // Use recursive option to handle directories that may contain files
616
+ await dirHandle.removeEntry(name, { recursive: true });
617
+ }
618
+ }
619
+ catch (error) {
620
+ console.error(`Error removing directory contents:`, error);
621
+ throw error;
622
+ }
623
+ };
624
+ try {
625
+ // Remove all files in the nouns directory
626
+ await removeDirectoryContents(this.nounsDir);
627
+ // Remove all files in the verbs directory
628
+ await removeDirectoryContents(this.verbsDir);
629
+ // Remove all files in the metadata directory
630
+ await removeDirectoryContents(this.metadataDir);
631
+ // Remove all files in the noun metadata directory
632
+ await removeDirectoryContents(this.nounMetadataDir);
633
+ // Remove all files in the verb metadata directory
634
+ await removeDirectoryContents(this.verbMetadataDir);
635
+ // Remove all files in the index directory
636
+ await removeDirectoryContents(this.indexDir);
637
+ // Clear the statistics cache
638
+ this.statisticsCache = null;
639
+ this.statisticsModified = false;
640
+ }
641
+ catch (error) {
642
+ console.error('Error clearing storage:', error);
643
+ throw error;
644
+ }
645
+ }
646
+ /**
647
+ * Get information about storage usage and capacity
648
+ */
649
+ async getStorageStatus() {
650
+ await this.ensureInitialized();
651
+ try {
652
+ // Calculate the total size of all files in the storage directories
653
+ let totalSize = 0;
654
+ // Helper function to calculate directory size
655
+ const calculateDirSize = async (dirHandle) => {
656
+ let size = 0;
657
+ try {
658
+ for await (const [name, handle] of dirHandle.entries()) {
659
+ if (handle.kind === 'file') {
660
+ const file = await handle.getFile();
661
+ size += file.size;
662
+ }
663
+ else if (handle.kind === 'directory') {
664
+ size += await calculateDirSize(handle);
665
+ }
666
+ }
667
+ }
668
+ catch (error) {
669
+ console.warn(`Error calculating size for directory:`, error);
670
+ }
671
+ return size;
672
+ };
673
+ // Helper function to count files in a directory
674
+ const countFilesInDirectory = async (dirHandle) => {
675
+ let count = 0;
676
+ try {
677
+ for await (const [name, handle] of dirHandle.entries()) {
678
+ if (handle.kind === 'file') {
679
+ count++;
680
+ }
681
+ }
682
+ }
683
+ catch (error) {
684
+ console.warn(`Error counting files in directory:`, error);
685
+ }
686
+ return count;
687
+ };
688
+ // Calculate size for each directory
689
+ if (this.nounsDir) {
690
+ totalSize += await calculateDirSize(this.nounsDir);
691
+ }
692
+ if (this.verbsDir) {
693
+ totalSize += await calculateDirSize(this.verbsDir);
694
+ }
695
+ if (this.metadataDir) {
696
+ totalSize += await calculateDirSize(this.metadataDir);
697
+ }
698
+ if (this.indexDir) {
699
+ totalSize += await calculateDirSize(this.indexDir);
700
+ }
701
+ // Get storage quota information using the Storage API
702
+ let quota = null;
703
+ let details = {
704
+ isPersistent: await this.isPersistent(),
705
+ nounTypes: {}
706
+ };
707
+ try {
708
+ if (navigator.storage && navigator.storage.estimate) {
709
+ const estimate = await navigator.storage.estimate();
710
+ quota = estimate.quota || null;
711
+ details = {
712
+ ...details,
713
+ usage: estimate.usage,
714
+ quota: estimate.quota,
715
+ freePercentage: estimate.quota
716
+ ? ((estimate.quota - (estimate.usage || 0)) / estimate.quota) *
717
+ 100
718
+ : null
719
+ };
720
+ }
721
+ }
722
+ catch (error) {
723
+ console.warn('Unable to get storage estimate:', error);
724
+ }
725
+ // Count files in each directory
726
+ if (this.nounsDir) {
727
+ details.nounsCount = await countFilesInDirectory(this.nounsDir);
728
+ }
729
+ if (this.verbsDir) {
730
+ details.verbsCount = await countFilesInDirectory(this.verbsDir);
731
+ }
732
+ if (this.metadataDir) {
733
+ details.metadataCount = await countFilesInDirectory(this.metadataDir);
734
+ }
735
+ // Count nouns by type using metadata
736
+ const nounTypeCounts = {};
737
+ if (this.metadataDir) {
738
+ for await (const [name, handle] of this.metadataDir.entries()) {
739
+ if (handle.kind === 'file') {
740
+ try {
741
+ const file = await safeGetFile(handle);
742
+ const text = await file.text();
743
+ const metadata = JSON.parse(text);
744
+ if (metadata.noun) {
745
+ nounTypeCounts[metadata.noun] =
746
+ (nounTypeCounts[metadata.noun] || 0) + 1;
747
+ }
748
+ }
749
+ catch (error) {
750
+ console.error(`Error reading metadata file ${name}:`, error);
751
+ }
752
+ }
753
+ }
754
+ }
755
+ details.nounTypes = nounTypeCounts;
756
+ return {
757
+ type: 'opfs',
758
+ used: totalSize,
759
+ quota,
760
+ details
761
+ };
762
+ }
763
+ catch (error) {
764
+ console.error('Failed to get storage status:', error);
765
+ return {
766
+ type: 'opfs',
767
+ used: 0,
768
+ quota: null,
769
+ details: { error: String(error) }
770
+ };
771
+ }
772
+ }
773
+ /**
774
+ * Get the statistics key for a specific date
775
+ * @param date The date to get the key for
776
+ * @returns The statistics key for the specified date
777
+ */
778
+ getStatisticsKeyForDate(date) {
779
+ const year = date.getUTCFullYear();
780
+ const month = String(date.getUTCMonth() + 1).padStart(2, '0');
781
+ const day = String(date.getUTCDate()).padStart(2, '0');
782
+ return `statistics_${year}${month}${day}.json`;
783
+ }
784
+ /**
785
+ * Get the current statistics key
786
+ * @returns The current statistics key
787
+ */
788
+ getCurrentStatisticsKey() {
789
+ return this.getStatisticsKeyForDate(new Date());
790
+ }
791
+ /**
792
+ * Get the legacy statistics key (for backward compatibility)
793
+ * @returns The legacy statistics key
794
+ */
795
+ getLegacyStatisticsKey() {
796
+ return 'statistics.json';
797
+ }
798
+ /**
799
+ * Acquire a browser-based lock for coordinating operations across multiple tabs
800
+ * @param lockKey The key to lock on
801
+ * @param ttl Time to live for the lock in milliseconds (default: 30 seconds)
802
+ * @returns Promise that resolves to true if lock was acquired, false otherwise
803
+ */
804
+ async acquireLock(lockKey, ttl = 30000) {
805
+ if (typeof localStorage === 'undefined') {
806
+ console.warn('localStorage not available, proceeding without lock');
807
+ return false;
808
+ }
809
+ const lockStorageKey = `${this.lockPrefix}${lockKey}`;
810
+ const lockValue = `${Date.now()}_${Math.random()}_${window.location.href}`;
811
+ const expiresAt = Date.now() + ttl;
812
+ try {
813
+ // Check if lock already exists and is still valid
814
+ const existingLock = localStorage.getItem(lockStorageKey);
815
+ if (existingLock) {
816
+ try {
817
+ const lockInfo = JSON.parse(existingLock);
818
+ if (lockInfo.expiresAt > Date.now()) {
819
+ // Lock exists and is still valid
820
+ return false;
821
+ }
822
+ }
823
+ catch (error) {
824
+ // Invalid lock data, we can proceed to create a new lock
825
+ console.warn(`Invalid lock data for ${lockStorageKey}:`, error);
826
+ }
827
+ }
828
+ // Try to create the lock
829
+ const lockInfo = {
830
+ lockValue,
831
+ expiresAt,
832
+ tabId: window.location.href,
833
+ timestamp: Date.now()
834
+ };
835
+ localStorage.setItem(lockStorageKey, JSON.stringify(lockInfo));
836
+ // Add to active locks for cleanup
837
+ this.activeLocks.add(lockKey);
838
+ // Schedule automatic cleanup when lock expires
839
+ setTimeout(() => {
840
+ this.releaseLock(lockKey, lockValue).catch((error) => {
841
+ console.warn(`Failed to auto-release expired lock ${lockKey}:`, error);
842
+ });
843
+ }, ttl);
844
+ return true;
845
+ }
846
+ catch (error) {
847
+ console.warn(`Failed to acquire lock ${lockKey}:`, error);
848
+ return false;
849
+ }
850
+ }
851
+ /**
852
+ * Release a browser-based lock
853
+ * @param lockKey The key to unlock
854
+ * @param lockValue The value used when acquiring the lock (for verification)
855
+ * @returns Promise that resolves when lock is released
856
+ */
857
+ async releaseLock(lockKey, lockValue) {
858
+ if (typeof localStorage === 'undefined') {
859
+ return;
860
+ }
861
+ const lockStorageKey = `${this.lockPrefix}${lockKey}`;
862
+ try {
863
+ // If lockValue is provided, verify it matches before releasing
864
+ if (lockValue) {
865
+ const existingLock = localStorage.getItem(lockStorageKey);
866
+ if (existingLock) {
867
+ try {
868
+ const lockInfo = JSON.parse(existingLock);
869
+ if (lockInfo.lockValue !== lockValue) {
870
+ // Lock was acquired by someone else, don't release it
871
+ return;
872
+ }
873
+ }
874
+ catch (error) {
875
+ // Invalid lock data, remove it
876
+ localStorage.removeItem(lockStorageKey);
877
+ this.activeLocks.delete(lockKey);
878
+ return;
879
+ }
880
+ }
881
+ }
882
+ // Remove the lock
883
+ localStorage.removeItem(lockStorageKey);
884
+ // Remove from active locks
885
+ this.activeLocks.delete(lockKey);
886
+ }
887
+ catch (error) {
888
+ console.warn(`Failed to release lock ${lockKey}:`, error);
889
+ }
890
+ }
891
+ /**
892
+ * Clean up expired locks from localStorage
893
+ */
894
+ async cleanupExpiredLocks() {
895
+ if (typeof localStorage === 'undefined') {
896
+ return;
897
+ }
898
+ try {
899
+ const now = Date.now();
900
+ const keysToRemove = [];
901
+ // Iterate through localStorage to find expired locks
902
+ for (let i = 0; i < localStorage.length; i++) {
903
+ const key = localStorage.key(i);
904
+ if (key && key.startsWith(this.lockPrefix)) {
905
+ try {
906
+ const lockData = localStorage.getItem(key);
907
+ if (lockData) {
908
+ const lockInfo = JSON.parse(lockData);
909
+ if (lockInfo.expiresAt <= now) {
910
+ keysToRemove.push(key);
911
+ const lockKey = key.replace(this.lockPrefix, '');
912
+ this.activeLocks.delete(lockKey);
913
+ }
914
+ }
915
+ }
916
+ catch (error) {
917
+ // Invalid lock data, mark for removal
918
+ keysToRemove.push(key);
919
+ }
920
+ }
921
+ }
922
+ // Remove expired locks
923
+ keysToRemove.forEach((key) => {
924
+ localStorage.removeItem(key);
925
+ });
926
+ if (keysToRemove.length > 0) {
927
+ console.log(`Cleaned up ${keysToRemove.length} expired locks`);
928
+ }
929
+ }
930
+ catch (error) {
931
+ console.warn('Failed to cleanup expired locks:', error);
932
+ }
933
+ }
934
+ /**
935
+ * Save statistics data to storage with browser-based locking
936
+ * @param statistics The statistics data to save
937
+ */
938
+ async saveStatisticsData(statistics) {
939
+ const lockKey = 'statistics';
940
+ const lockAcquired = await this.acquireLock(lockKey, 10000); // 10 second timeout
941
+ if (!lockAcquired) {
942
+ console.warn('Failed to acquire lock for statistics update, proceeding without lock');
943
+ }
944
+ try {
945
+ // Get existing statistics to merge with new data
946
+ const existingStats = await this.getStatisticsData();
947
+ let mergedStats;
948
+ if (existingStats) {
949
+ // Merge statistics data
950
+ mergedStats = {
951
+ nounCount: {
952
+ ...existingStats.nounCount,
953
+ ...statistics.nounCount
954
+ },
955
+ verbCount: {
956
+ ...existingStats.verbCount,
957
+ ...statistics.verbCount
958
+ },
959
+ metadataCount: {
960
+ ...existingStats.metadataCount,
961
+ ...statistics.metadataCount
962
+ },
963
+ hnswIndexSize: Math.max(statistics.hnswIndexSize || 0, existingStats.hnswIndexSize || 0),
964
+ lastUpdated: new Date().toISOString()
965
+ };
966
+ }
967
+ else {
968
+ // No existing statistics, use new ones
969
+ mergedStats = {
970
+ ...statistics,
971
+ lastUpdated: new Date().toISOString()
972
+ };
973
+ }
974
+ // Create a deep copy to avoid reference issues
975
+ this.statistics = {
976
+ nounCount: { ...mergedStats.nounCount },
977
+ verbCount: { ...mergedStats.verbCount },
978
+ metadataCount: { ...mergedStats.metadataCount },
979
+ hnswIndexSize: mergedStats.hnswIndexSize,
980
+ lastUpdated: mergedStats.lastUpdated
981
+ };
982
+ // Ensure the root directory is initialized
983
+ await this.ensureInitialized();
984
+ // Get or create the index directory
985
+ if (!this.indexDir) {
986
+ throw new Error('Index directory not initialized');
987
+ }
988
+ // Get the current statistics key
989
+ const currentKey = this.getCurrentStatisticsKey();
990
+ // Create a file for the statistics data
991
+ const fileHandle = await this.indexDir.getFileHandle(currentKey, {
992
+ create: true
993
+ });
994
+ // Create a writable stream
995
+ const writable = await fileHandle.createWritable();
996
+ // Write the statistics data to the file
997
+ await writable.write(JSON.stringify(this.statistics, null, 2));
998
+ // Close the stream
999
+ await writable.close();
1000
+ // Also update the legacy key for backward compatibility, but less frequently
1001
+ if (Math.random() < 0.1) {
1002
+ const legacyKey = this.getLegacyStatisticsKey();
1003
+ const legacyFileHandle = await this.indexDir.getFileHandle(legacyKey, {
1004
+ create: true
1005
+ });
1006
+ const legacyWritable = await legacyFileHandle.createWritable();
1007
+ await legacyWritable.write(JSON.stringify(this.statistics, null, 2));
1008
+ await legacyWritable.close();
1009
+ }
1010
+ }
1011
+ catch (error) {
1012
+ console.error('Failed to save statistics data:', error);
1013
+ throw new Error(`Failed to save statistics data: ${error}`);
1014
+ }
1015
+ finally {
1016
+ if (lockAcquired) {
1017
+ await this.releaseLock(lockKey);
1018
+ }
1019
+ }
1020
+ }
1021
+ /**
1022
+ * Get statistics data from storage
1023
+ * @returns Promise that resolves to the statistics data or null if not found
1024
+ */
1025
+ async getStatisticsData() {
1026
+ // If we have cached statistics, return a deep copy
1027
+ if (this.statistics) {
1028
+ return {
1029
+ nounCount: { ...this.statistics.nounCount },
1030
+ verbCount: { ...this.statistics.verbCount },
1031
+ metadataCount: { ...this.statistics.metadataCount },
1032
+ hnswIndexSize: this.statistics.hnswIndexSize,
1033
+ lastUpdated: this.statistics.lastUpdated
1034
+ };
1035
+ }
1036
+ try {
1037
+ // Ensure the root directory is initialized
1038
+ await this.ensureInitialized();
1039
+ if (!this.indexDir) {
1040
+ throw new Error('Index directory not initialized');
1041
+ }
1042
+ // First try to get statistics from today's file
1043
+ const currentKey = this.getCurrentStatisticsKey();
1044
+ try {
1045
+ const fileHandle = await this.indexDir.getFileHandle(currentKey, {
1046
+ create: false
1047
+ });
1048
+ const file = await fileHandle.getFile();
1049
+ const text = await file.text();
1050
+ this.statistics = JSON.parse(text);
1051
+ if (this.statistics) {
1052
+ return {
1053
+ nounCount: { ...this.statistics.nounCount },
1054
+ verbCount: { ...this.statistics.verbCount },
1055
+ metadataCount: { ...this.statistics.metadataCount },
1056
+ hnswIndexSize: this.statistics.hnswIndexSize,
1057
+ lastUpdated: this.statistics.lastUpdated
1058
+ };
1059
+ }
1060
+ }
1061
+ catch (error) {
1062
+ // If today's file doesn't exist, try yesterday's file
1063
+ const yesterday = new Date();
1064
+ yesterday.setDate(yesterday.getDate() - 1);
1065
+ const yesterdayKey = this.getStatisticsKeyForDate(yesterday);
1066
+ try {
1067
+ const fileHandle = await this.indexDir.getFileHandle(yesterdayKey, {
1068
+ create: false
1069
+ });
1070
+ const file = await fileHandle.getFile();
1071
+ const text = await file.text();
1072
+ this.statistics = JSON.parse(text);
1073
+ if (this.statistics) {
1074
+ return {
1075
+ nounCount: { ...this.statistics.nounCount },
1076
+ verbCount: { ...this.statistics.verbCount },
1077
+ metadataCount: { ...this.statistics.metadataCount },
1078
+ hnswIndexSize: this.statistics.hnswIndexSize,
1079
+ lastUpdated: this.statistics.lastUpdated
1080
+ };
1081
+ }
1082
+ }
1083
+ catch (error) {
1084
+ // If yesterday's file doesn't exist, try the legacy file
1085
+ const legacyKey = this.getLegacyStatisticsKey();
1086
+ try {
1087
+ const fileHandle = await this.indexDir.getFileHandle(legacyKey, {
1088
+ create: false
1089
+ });
1090
+ const file = await fileHandle.getFile();
1091
+ const text = await file.text();
1092
+ this.statistics = JSON.parse(text);
1093
+ if (this.statistics) {
1094
+ return {
1095
+ nounCount: { ...this.statistics.nounCount },
1096
+ verbCount: { ...this.statistics.verbCount },
1097
+ metadataCount: { ...this.statistics.metadataCount },
1098
+ hnswIndexSize: this.statistics.hnswIndexSize,
1099
+ lastUpdated: this.statistics.lastUpdated
1100
+ };
1101
+ }
1102
+ }
1103
+ catch (error) {
1104
+ // If the legacy file doesn't exist either, return null
1105
+ return null;
1106
+ }
1107
+ }
1108
+ }
1109
+ // If we get here and statistics is null, return default statistics
1110
+ return this.statistics ? this.statistics : null;
1111
+ }
1112
+ catch (error) {
1113
+ console.error('Failed to get statistics data:', error);
1114
+ throw new Error(`Failed to get statistics data: ${error}`);
1115
+ }
1116
+ }
1117
+ }
1118
+ //# sourceMappingURL=opfsStorage.js.map