@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.
- package/README.md +605 -194
- package/dist/augmentationFactory.d.ts.map +1 -0
- package/dist/augmentationFactory.js +342 -0
- package/dist/augmentationFactory.js.map +1 -0
- package/dist/augmentationPipeline.d.ts.map +1 -0
- package/dist/augmentationPipeline.js +472 -0
- package/dist/augmentationPipeline.js.map +1 -0
- package/dist/augmentationRegistry.d.ts.map +1 -0
- package/dist/augmentationRegistry.js +105 -0
- package/dist/augmentationRegistry.js.map +1 -0
- package/dist/augmentationRegistryLoader.d.ts.map +1 -0
- package/dist/augmentationRegistryLoader.js +213 -0
- package/dist/augmentationRegistryLoader.js.map +1 -0
- package/dist/augmentations/conduitAugmentations.js +1158 -0
- package/dist/augmentations/conduitAugmentations.js.map +1 -0
- package/dist/augmentations/memoryAugmentations.d.ts +2 -0
- package/dist/augmentations/memoryAugmentations.d.ts.map +1 -1
- package/dist/augmentations/memoryAugmentations.js +270 -0
- package/dist/augmentations/memoryAugmentations.js.map +1 -0
- package/dist/augmentations/serverSearchAugmentations.js +531 -0
- package/dist/augmentations/serverSearchAugmentations.js.map +1 -0
- package/dist/brainyData.d.ts.map +1 -0
- package/dist/brainyData.js +3999 -0
- package/dist/brainyData.js.map +1 -0
- package/dist/browserFramework.d.ts +15 -0
- package/dist/browserFramework.d.ts.map +1 -0
- package/dist/browserFramework.js +31 -0
- package/dist/browserFramework.js.map +1 -0
- package/dist/coreTypes.d.ts.map +1 -0
- package/dist/coreTypes.js +5 -0
- package/dist/coreTypes.js.map +1 -0
- package/dist/demo.d.ts +106 -0
- package/dist/demo.d.ts.map +1 -0
- package/dist/demo.js +201 -0
- package/dist/demo.js.map +1 -0
- package/dist/distributed/configManager.d.ts.map +1 -0
- package/dist/distributed/configManager.js +322 -0
- package/dist/distributed/configManager.js.map +1 -0
- package/dist/distributed/domainDetector.d.ts.map +1 -0
- package/dist/distributed/domainDetector.js +307 -0
- package/dist/distributed/domainDetector.js.map +1 -0
- package/dist/distributed/hashPartitioner.d.ts.map +1 -0
- package/dist/distributed/hashPartitioner.js +146 -0
- package/dist/distributed/hashPartitioner.js.map +1 -0
- package/dist/distributed/healthMonitor.d.ts.map +1 -0
- package/dist/distributed/healthMonitor.js +244 -0
- package/dist/distributed/healthMonitor.js.map +1 -0
- package/dist/distributed/index.d.ts.map +1 -0
- package/dist/distributed/index.js +9 -0
- package/dist/distributed/index.js.map +1 -0
- package/dist/distributed/operationalModes.d.ts.map +1 -0
- package/dist/distributed/operationalModes.js +201 -0
- package/dist/distributed/operationalModes.js.map +1 -0
- package/dist/errors/brainyError.d.ts.map +1 -0
- package/dist/errors/brainyError.js +113 -0
- package/dist/errors/brainyError.js.map +1 -0
- package/dist/examples/basicUsage.js +118 -0
- package/dist/examples/basicUsage.js.map +1 -0
- package/dist/hnsw/distributedSearch.js +452 -0
- package/dist/hnsw/distributedSearch.js.map +1 -0
- package/dist/hnsw/hnswIndex.js +602 -0
- package/dist/hnsw/hnswIndex.js.map +1 -0
- package/dist/hnsw/hnswIndexOptimized.js +471 -0
- package/dist/hnsw/hnswIndexOptimized.js.map +1 -0
- package/dist/hnsw/optimizedHNSWIndex.js +313 -0
- package/dist/hnsw/optimizedHNSWIndex.js.map +1 -0
- package/dist/hnsw/partitionedHNSWIndex.js +304 -0
- package/dist/hnsw/partitionedHNSWIndex.js.map +1 -0
- package/dist/hnsw/scaledHNSWSystem.js +559 -0
- package/dist/hnsw/scaledHNSWSystem.js.map +1 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +81 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/brainyMCPAdapter.js +142 -0
- package/dist/mcp/brainyMCPAdapter.js.map +1 -0
- package/dist/mcp/brainyMCPService.js +248 -0
- package/dist/mcp/brainyMCPService.js.map +1 -0
- package/dist/mcp/index.js +17 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/mcpAugmentationToolset.js +180 -0
- package/dist/mcp/mcpAugmentationToolset.js.map +1 -0
- package/dist/pipeline.d.ts.map +1 -0
- package/dist/pipeline.js +590 -0
- package/dist/pipeline.js.map +1 -0
- package/dist/sequentialPipeline.d.ts.map +1 -0
- package/dist/sequentialPipeline.js +417 -0
- package/dist/sequentialPipeline.js.map +1 -0
- package/dist/setup.d.ts.map +1 -0
- package/dist/setup.js +46 -0
- package/dist/setup.js.map +1 -0
- package/dist/storage/adapters/baseStorageAdapter.js +349 -0
- package/dist/storage/adapters/baseStorageAdapter.js.map +1 -0
- package/dist/storage/adapters/batchS3Operations.js +287 -0
- package/dist/storage/adapters/batchS3Operations.js.map +1 -0
- package/dist/storage/adapters/fileSystemStorage.js +846 -0
- package/dist/storage/adapters/fileSystemStorage.js.map +1 -0
- package/dist/storage/adapters/memoryStorage.js +532 -0
- package/dist/storage/adapters/memoryStorage.js.map +1 -0
- package/dist/storage/adapters/opfsStorage.d.ts.map +1 -1
- package/dist/storage/adapters/opfsStorage.js +1118 -0
- package/dist/storage/adapters/opfsStorage.js.map +1 -0
- package/dist/storage/adapters/optimizedS3Search.js +248 -0
- package/dist/storage/adapters/optimizedS3Search.js.map +1 -0
- package/dist/storage/adapters/s3CompatibleStorage.js +2026 -0
- package/dist/storage/adapters/s3CompatibleStorage.js.map +1 -0
- package/dist/storage/baseStorage.js +603 -0
- package/dist/storage/baseStorage.js.map +1 -0
- package/dist/storage/cacheManager.js +1306 -0
- package/dist/storage/cacheManager.js.map +1 -0
- package/dist/storage/enhancedCacheManager.js +520 -0
- package/dist/storage/enhancedCacheManager.js.map +1 -0
- package/dist/storage/readOnlyOptimizations.js +425 -0
- package/dist/storage/readOnlyOptimizations.js.map +1 -0
- package/dist/storage/storageFactory.d.ts +0 -1
- package/dist/storage/storageFactory.d.ts.map +1 -1
- package/dist/storage/storageFactory.js +227 -0
- package/dist/storage/storageFactory.js.map +1 -0
- package/dist/types/augmentations.js +16 -0
- package/dist/types/augmentations.js.map +1 -0
- package/dist/types/brainyDataInterface.js +8 -0
- package/dist/types/brainyDataInterface.js.map +1 -0
- package/dist/types/distributedTypes.js +6 -0
- package/dist/types/distributedTypes.js.map +1 -0
- package/dist/types/fileSystemTypes.js +8 -0
- package/dist/types/fileSystemTypes.js.map +1 -0
- package/dist/types/graphTypes.js +247 -0
- package/dist/types/graphTypes.js.map +1 -0
- package/dist/types/mcpTypes.js +22 -0
- package/dist/types/mcpTypes.js.map +1 -0
- package/dist/types/paginationTypes.js +5 -0
- package/dist/types/paginationTypes.js.map +1 -0
- package/dist/types/pipelineTypes.js +7 -0
- package/dist/types/pipelineTypes.js.map +1 -0
- package/dist/types/tensorflowTypes.js +6 -0
- package/dist/types/tensorflowTypes.js.map +1 -0
- package/dist/unified.d.ts.map +1 -0
- package/dist/unified.js +52 -128251
- package/dist/unified.js.map +1 -0
- package/dist/utils/autoConfiguration.js +341 -0
- package/dist/utils/autoConfiguration.js.map +1 -0
- package/dist/utils/cacheAutoConfig.js +261 -0
- package/dist/utils/cacheAutoConfig.js.map +1 -0
- package/dist/utils/crypto.js +45 -0
- package/dist/utils/crypto.js.map +1 -0
- package/dist/utils/distance.js +239 -0
- package/dist/utils/distance.js.map +1 -0
- package/dist/utils/embedding.d.ts.map +1 -1
- package/dist/utils/embedding.js +702 -0
- package/dist/utils/embedding.js.map +1 -0
- package/dist/utils/environment.js +75 -0
- package/dist/utils/environment.js.map +1 -0
- package/dist/utils/fieldNameTracking.js +90 -0
- package/dist/utils/fieldNameTracking.js.map +1 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +8 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/jsonProcessing.js +179 -0
- package/dist/utils/jsonProcessing.js.map +1 -0
- package/dist/utils/logger.js +129 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/operationUtils.js +126 -0
- package/dist/utils/operationUtils.js.map +1 -0
- package/dist/utils/robustModelLoader.d.ts +14 -0
- package/dist/utils/robustModelLoader.d.ts.map +1 -1
- package/dist/utils/robustModelLoader.js +537 -0
- package/dist/utils/robustModelLoader.js.map +1 -0
- package/dist/utils/searchCache.js +248 -0
- package/dist/utils/searchCache.js.map +1 -0
- package/dist/utils/statistics.js +25 -0
- package/dist/utils/statistics.js.map +1 -0
- package/dist/utils/statisticsCollector.js +224 -0
- package/dist/utils/statisticsCollector.js.map +1 -0
- package/dist/utils/textEncoding.js +309 -0
- package/dist/utils/textEncoding.js.map +1 -0
- package/dist/utils/typeUtils.js +40 -0
- package/dist/utils/typeUtils.js.map +1 -0
- package/dist/utils/version.d.ts +15 -3
- package/dist/utils/version.d.ts.map +1 -1
- package/dist/utils/version.js +24 -0
- package/dist/utils/version.js.map +1 -0
- package/dist/utils/workerUtils.js +458 -0
- package/dist/utils/workerUtils.js.map +1 -0
- package/dist/worker.d.ts.map +1 -0
- package/dist/worker.js +54 -0
- package/dist/worker.js.map +1 -0
- package/package.json +30 -29
- package/dist/brainy.js +0 -90220
- package/dist/brainy.min.js +0 -12511
- package/dist/patched-platform-node.d.ts +0 -17
- package/dist/statistics/statisticsManager.d.ts +0 -121
- package/dist/storage/fileSystemStorage.d.ts +0 -73
- package/dist/storage/fileSystemStorage.d.ts.map +0 -1
- package/dist/storage/opfsStorage.d.ts +0 -236
- package/dist/storage/opfsStorage.d.ts.map +0 -1
- package/dist/storage/s3CompatibleStorage.d.ts +0 -157
- package/dist/storage/s3CompatibleStorage.d.ts.map +0 -1
- package/dist/testing/prettyReporter.d.ts +0 -23
- package/dist/testing/prettySummaryReporter.d.ts +0 -22
- package/dist/unified.min.js +0 -16153
- package/dist/utils/environmentDetection.d.ts +0 -47
- package/dist/utils/environmentDetection.d.ts.map +0 -1
- package/dist/utils/tensorflowUtils.d.ts +0 -17
- package/dist/utils/tensorflowUtils.d.ts.map +0 -1
|
@@ -0,0 +1,846 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File System Storage Adapter
|
|
3
|
+
* File system storage adapter for Node.js environments
|
|
4
|
+
*/
|
|
5
|
+
import { BaseStorage, NOUNS_DIR, VERBS_DIR, METADATA_DIR, NOUN_METADATA_DIR, VERB_METADATA_DIR, INDEX_DIR, STATISTICS_KEY } from '../baseStorage.js';
|
|
6
|
+
// Node.js modules - dynamically imported to avoid issues in browser environments
|
|
7
|
+
let fs;
|
|
8
|
+
let path;
|
|
9
|
+
let moduleLoadingPromise = null;
|
|
10
|
+
// Try to load Node.js modules
|
|
11
|
+
try {
|
|
12
|
+
// Using dynamic imports to avoid issues in browser environments
|
|
13
|
+
const fsPromise = import('fs');
|
|
14
|
+
const pathPromise = import('path');
|
|
15
|
+
moduleLoadingPromise = Promise.all([fsPromise, pathPromise])
|
|
16
|
+
.then(([fsModule, pathModule]) => {
|
|
17
|
+
fs = fsModule;
|
|
18
|
+
path = pathModule.default;
|
|
19
|
+
})
|
|
20
|
+
.catch((error) => {
|
|
21
|
+
console.error('Failed to load Node.js modules:', error);
|
|
22
|
+
throw error;
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
console.error('FileSystemStorage: Failed to load Node.js modules. This adapter is not supported in this environment.', error);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* File system storage adapter for Node.js environments
|
|
30
|
+
* Uses the file system to store data in the specified directory structure
|
|
31
|
+
*/
|
|
32
|
+
export class FileSystemStorage extends BaseStorage {
|
|
33
|
+
/**
|
|
34
|
+
* Initialize the storage adapter
|
|
35
|
+
* @param rootDirectory The root directory for storage
|
|
36
|
+
*/
|
|
37
|
+
constructor(rootDirectory) {
|
|
38
|
+
super();
|
|
39
|
+
this.activeLocks = new Set();
|
|
40
|
+
this.rootDir = rootDirectory;
|
|
41
|
+
// Defer path operations until init() when path module is guaranteed to be loaded
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Initialize the storage adapter
|
|
45
|
+
*/
|
|
46
|
+
async init() {
|
|
47
|
+
if (this.isInitialized) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
// Wait for module loading to complete
|
|
51
|
+
if (moduleLoadingPromise) {
|
|
52
|
+
try {
|
|
53
|
+
await moduleLoadingPromise;
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
throw new Error('FileSystemStorage requires a Node.js environment, but `fs` and `path` modules could not be loaded.');
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// Check if Node.js modules are available
|
|
60
|
+
if (!fs || !path) {
|
|
61
|
+
throw new Error('FileSystemStorage requires a Node.js environment, but `fs` and `path` modules could not be loaded.');
|
|
62
|
+
}
|
|
63
|
+
try {
|
|
64
|
+
// Initialize directory paths now that path module is loaded
|
|
65
|
+
this.nounsDir = path.join(this.rootDir, NOUNS_DIR);
|
|
66
|
+
this.verbsDir = path.join(this.rootDir, VERBS_DIR);
|
|
67
|
+
this.metadataDir = path.join(this.rootDir, METADATA_DIR);
|
|
68
|
+
this.nounMetadataDir = path.join(this.rootDir, NOUN_METADATA_DIR);
|
|
69
|
+
this.verbMetadataDir = path.join(this.rootDir, VERB_METADATA_DIR);
|
|
70
|
+
this.indexDir = path.join(this.rootDir, INDEX_DIR);
|
|
71
|
+
this.lockDir = path.join(this.rootDir, 'locks');
|
|
72
|
+
// Create the root directory if it doesn't exist
|
|
73
|
+
await this.ensureDirectoryExists(this.rootDir);
|
|
74
|
+
// Create the nouns directory if it doesn't exist
|
|
75
|
+
await this.ensureDirectoryExists(this.nounsDir);
|
|
76
|
+
// Create the verbs directory if it doesn't exist
|
|
77
|
+
await this.ensureDirectoryExists(this.verbsDir);
|
|
78
|
+
// Create the metadata directory if it doesn't exist
|
|
79
|
+
await this.ensureDirectoryExists(this.metadataDir);
|
|
80
|
+
// Create the noun metadata directory if it doesn't exist
|
|
81
|
+
await this.ensureDirectoryExists(this.nounMetadataDir);
|
|
82
|
+
// Create the verb metadata directory if it doesn't exist
|
|
83
|
+
await this.ensureDirectoryExists(this.verbMetadataDir);
|
|
84
|
+
// Create the index directory if it doesn't exist
|
|
85
|
+
await this.ensureDirectoryExists(this.indexDir);
|
|
86
|
+
// Create the locks directory if it doesn't exist
|
|
87
|
+
await this.ensureDirectoryExists(this.lockDir);
|
|
88
|
+
this.isInitialized = true;
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
console.error('Error initializing FileSystemStorage:', error);
|
|
92
|
+
throw error;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Ensure a directory exists, creating it if necessary
|
|
97
|
+
*/
|
|
98
|
+
async ensureDirectoryExists(dirPath) {
|
|
99
|
+
try {
|
|
100
|
+
await fs.promises.mkdir(dirPath, { recursive: true });
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
// Ignore EEXIST error, which means the directory already exists
|
|
104
|
+
if (error.code !== 'EEXIST') {
|
|
105
|
+
throw error;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Save a node to storage
|
|
111
|
+
*/
|
|
112
|
+
async saveNode(node) {
|
|
113
|
+
await this.ensureInitialized();
|
|
114
|
+
// Convert connections Map to a serializable format
|
|
115
|
+
const serializableNode = {
|
|
116
|
+
...node,
|
|
117
|
+
connections: this.mapToObject(node.connections, (set) => Array.from(set))
|
|
118
|
+
};
|
|
119
|
+
const filePath = path.join(this.nounsDir, `${node.id}.json`);
|
|
120
|
+
await fs.promises.writeFile(filePath, JSON.stringify(serializableNode, null, 2));
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Get a node from storage
|
|
124
|
+
*/
|
|
125
|
+
async getNode(id) {
|
|
126
|
+
await this.ensureInitialized();
|
|
127
|
+
const filePath = path.join(this.nounsDir, `${id}.json`);
|
|
128
|
+
try {
|
|
129
|
+
const data = await fs.promises.readFile(filePath, 'utf-8');
|
|
130
|
+
const parsedNode = JSON.parse(data);
|
|
131
|
+
// Convert serialized connections back to Map<number, Set<string>>
|
|
132
|
+
const connections = new Map();
|
|
133
|
+
for (const [level, nodeIds] of Object.entries(parsedNode.connections)) {
|
|
134
|
+
connections.set(Number(level), new Set(nodeIds));
|
|
135
|
+
}
|
|
136
|
+
return {
|
|
137
|
+
id: parsedNode.id,
|
|
138
|
+
vector: parsedNode.vector,
|
|
139
|
+
connections,
|
|
140
|
+
level: parsedNode.level || 0
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
if (error.code !== 'ENOENT') {
|
|
145
|
+
console.error(`Error reading node ${id}:`, error);
|
|
146
|
+
}
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Get all nodes from storage
|
|
152
|
+
*/
|
|
153
|
+
async getAllNodes() {
|
|
154
|
+
await this.ensureInitialized();
|
|
155
|
+
const allNodes = [];
|
|
156
|
+
try {
|
|
157
|
+
const files = await fs.promises.readdir(this.nounsDir);
|
|
158
|
+
for (const file of files) {
|
|
159
|
+
if (file.endsWith('.json')) {
|
|
160
|
+
const filePath = path.join(this.nounsDir, file);
|
|
161
|
+
const data = await fs.promises.readFile(filePath, 'utf-8');
|
|
162
|
+
const parsedNode = JSON.parse(data);
|
|
163
|
+
// Convert serialized connections back to Map<number, Set<string>>
|
|
164
|
+
const connections = new Map();
|
|
165
|
+
for (const [level, nodeIds] of Object.entries(parsedNode.connections)) {
|
|
166
|
+
connections.set(Number(level), new Set(nodeIds));
|
|
167
|
+
}
|
|
168
|
+
allNodes.push({
|
|
169
|
+
id: parsedNode.id,
|
|
170
|
+
vector: parsedNode.vector,
|
|
171
|
+
connections,
|
|
172
|
+
level: parsedNode.level || 0
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
if (error.code !== 'ENOENT') {
|
|
179
|
+
console.error(`Error reading directory ${this.nounsDir}:`, error);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return allNodes;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Get nodes by noun type
|
|
186
|
+
* @param nounType The noun type to filter by
|
|
187
|
+
* @returns Promise that resolves to an array of nodes of the specified noun type
|
|
188
|
+
*/
|
|
189
|
+
async getNodesByNounType(nounType) {
|
|
190
|
+
await this.ensureInitialized();
|
|
191
|
+
const nouns = [];
|
|
192
|
+
try {
|
|
193
|
+
const files = await fs.promises.readdir(this.nounsDir);
|
|
194
|
+
for (const file of files) {
|
|
195
|
+
if (file.endsWith('.json')) {
|
|
196
|
+
const filePath = path.join(this.nounsDir, file);
|
|
197
|
+
const data = await fs.promises.readFile(filePath, 'utf-8');
|
|
198
|
+
const parsedNode = JSON.parse(data);
|
|
199
|
+
// Filter by noun type using metadata
|
|
200
|
+
const nodeId = parsedNode.id;
|
|
201
|
+
const metadata = await this.getMetadata(nodeId);
|
|
202
|
+
if (metadata && metadata.noun === nounType) {
|
|
203
|
+
// Convert serialized connections back to Map<number, Set<string>>
|
|
204
|
+
const connections = new Map();
|
|
205
|
+
for (const [level, nodeIds] of Object.entries(parsedNode.connections)) {
|
|
206
|
+
connections.set(Number(level), new Set(nodeIds));
|
|
207
|
+
}
|
|
208
|
+
nouns.push({
|
|
209
|
+
id: parsedNode.id,
|
|
210
|
+
vector: parsedNode.vector,
|
|
211
|
+
connections,
|
|
212
|
+
level: parsedNode.level || 0
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
catch (error) {
|
|
219
|
+
if (error.code !== 'ENOENT') {
|
|
220
|
+
console.error(`Error reading directory ${this.nounsDir}:`, error);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return nouns;
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Delete a node from storage
|
|
227
|
+
*/
|
|
228
|
+
async deleteNode(id) {
|
|
229
|
+
await this.ensureInitialized();
|
|
230
|
+
const filePath = path.join(this.nounsDir, `${id}.json`);
|
|
231
|
+
try {
|
|
232
|
+
await fs.promises.unlink(filePath);
|
|
233
|
+
}
|
|
234
|
+
catch (error) {
|
|
235
|
+
if (error.code !== 'ENOENT') {
|
|
236
|
+
console.error(`Error deleting node file ${filePath}:`, error);
|
|
237
|
+
throw error;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Save an edge to storage
|
|
243
|
+
*/
|
|
244
|
+
async saveEdge(edge) {
|
|
245
|
+
await this.ensureInitialized();
|
|
246
|
+
// Convert connections Map to a serializable format
|
|
247
|
+
const serializableEdge = {
|
|
248
|
+
...edge,
|
|
249
|
+
connections: this.mapToObject(edge.connections, (set) => Array.from(set))
|
|
250
|
+
};
|
|
251
|
+
const filePath = path.join(this.verbsDir, `${edge.id}.json`);
|
|
252
|
+
await fs.promises.writeFile(filePath, JSON.stringify(serializableEdge, null, 2));
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Get an edge from storage
|
|
256
|
+
*/
|
|
257
|
+
async getEdge(id) {
|
|
258
|
+
await this.ensureInitialized();
|
|
259
|
+
const filePath = path.join(this.verbsDir, `${id}.json`);
|
|
260
|
+
try {
|
|
261
|
+
const data = await fs.promises.readFile(filePath, 'utf-8');
|
|
262
|
+
const parsedEdge = JSON.parse(data);
|
|
263
|
+
// Convert serialized connections back to Map<number, Set<string>>
|
|
264
|
+
const connections = new Map();
|
|
265
|
+
for (const [level, nodeIds] of Object.entries(parsedEdge.connections)) {
|
|
266
|
+
connections.set(Number(level), new Set(nodeIds));
|
|
267
|
+
}
|
|
268
|
+
return {
|
|
269
|
+
id: parsedEdge.id,
|
|
270
|
+
vector: parsedEdge.vector,
|
|
271
|
+
connections
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
catch (error) {
|
|
275
|
+
if (error.code !== 'ENOENT') {
|
|
276
|
+
console.error(`Error reading edge ${id}:`, error);
|
|
277
|
+
}
|
|
278
|
+
return null;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Get all edges from storage
|
|
283
|
+
*/
|
|
284
|
+
async getAllEdges() {
|
|
285
|
+
await this.ensureInitialized();
|
|
286
|
+
const allEdges = [];
|
|
287
|
+
try {
|
|
288
|
+
const files = await fs.promises.readdir(this.verbsDir);
|
|
289
|
+
for (const file of files) {
|
|
290
|
+
if (file.endsWith('.json')) {
|
|
291
|
+
const filePath = path.join(this.verbsDir, file);
|
|
292
|
+
const data = await fs.promises.readFile(filePath, 'utf-8');
|
|
293
|
+
const parsedEdge = JSON.parse(data);
|
|
294
|
+
// Convert serialized connections back to Map<number, Set<string>>
|
|
295
|
+
const connections = new Map();
|
|
296
|
+
for (const [level, nodeIds] of Object.entries(parsedEdge.connections)) {
|
|
297
|
+
connections.set(Number(level), new Set(nodeIds));
|
|
298
|
+
}
|
|
299
|
+
allEdges.push({
|
|
300
|
+
id: parsedEdge.id,
|
|
301
|
+
vector: parsedEdge.vector,
|
|
302
|
+
connections
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
catch (error) {
|
|
308
|
+
if (error.code !== 'ENOENT') {
|
|
309
|
+
console.error(`Error reading directory ${this.verbsDir}:`, error);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
return allEdges;
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Get edges by source
|
|
316
|
+
*/
|
|
317
|
+
async getEdgesBySource(sourceId) {
|
|
318
|
+
// This method is deprecated and would require loading metadata for each edge
|
|
319
|
+
// For now, return empty array since this is not efficiently implementable with new storage pattern
|
|
320
|
+
console.warn('getEdgesBySource is deprecated and not efficiently supported in new storage pattern');
|
|
321
|
+
return [];
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* Get edges by target
|
|
325
|
+
*/
|
|
326
|
+
async getEdgesByTarget(targetId) {
|
|
327
|
+
// This method is deprecated and would require loading metadata for each edge
|
|
328
|
+
// For now, return empty array since this is not efficiently implementable with new storage pattern
|
|
329
|
+
console.warn('getEdgesByTarget is deprecated and not efficiently supported in new storage pattern');
|
|
330
|
+
return [];
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Get edges by type
|
|
334
|
+
*/
|
|
335
|
+
async getEdgesByType(type) {
|
|
336
|
+
// This method is deprecated and would require loading metadata for each edge
|
|
337
|
+
// For now, return empty array since this is not efficiently implementable with new storage pattern
|
|
338
|
+
console.warn('getEdgesByType is deprecated and not efficiently supported in new storage pattern');
|
|
339
|
+
return [];
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* Delete an edge from storage
|
|
343
|
+
*/
|
|
344
|
+
async deleteEdge(id) {
|
|
345
|
+
await this.ensureInitialized();
|
|
346
|
+
const filePath = path.join(this.verbsDir, `${id}.json`);
|
|
347
|
+
try {
|
|
348
|
+
await fs.promises.unlink(filePath);
|
|
349
|
+
}
|
|
350
|
+
catch (error) {
|
|
351
|
+
if (error.code !== 'ENOENT') {
|
|
352
|
+
console.error(`Error deleting edge file ${filePath}:`, error);
|
|
353
|
+
throw error;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* Save metadata to storage
|
|
359
|
+
*/
|
|
360
|
+
async saveMetadata(id, metadata) {
|
|
361
|
+
await this.ensureInitialized();
|
|
362
|
+
const filePath = path.join(this.metadataDir, `${id}.json`);
|
|
363
|
+
await fs.promises.writeFile(filePath, JSON.stringify(metadata, null, 2));
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Get metadata from storage
|
|
367
|
+
*/
|
|
368
|
+
async getMetadata(id) {
|
|
369
|
+
await this.ensureInitialized();
|
|
370
|
+
const filePath = path.join(this.metadataDir, `${id}.json`);
|
|
371
|
+
try {
|
|
372
|
+
const data = await fs.promises.readFile(filePath, 'utf-8');
|
|
373
|
+
return JSON.parse(data);
|
|
374
|
+
}
|
|
375
|
+
catch (error) {
|
|
376
|
+
if (error.code !== 'ENOENT') {
|
|
377
|
+
console.error(`Error reading metadata ${id}:`, error);
|
|
378
|
+
}
|
|
379
|
+
return null;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* Save noun metadata to storage
|
|
384
|
+
*/
|
|
385
|
+
async saveNounMetadata(id, metadata) {
|
|
386
|
+
await this.ensureInitialized();
|
|
387
|
+
const filePath = path.join(this.nounMetadataDir, `${id}.json`);
|
|
388
|
+
await fs.promises.writeFile(filePath, JSON.stringify(metadata, null, 2));
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* Get noun metadata from storage
|
|
392
|
+
*/
|
|
393
|
+
async getNounMetadata(id) {
|
|
394
|
+
await this.ensureInitialized();
|
|
395
|
+
const filePath = path.join(this.nounMetadataDir, `${id}.json`);
|
|
396
|
+
try {
|
|
397
|
+
const data = await fs.promises.readFile(filePath, 'utf-8');
|
|
398
|
+
return JSON.parse(data);
|
|
399
|
+
}
|
|
400
|
+
catch (error) {
|
|
401
|
+
if (error.code !== 'ENOENT') {
|
|
402
|
+
console.error(`Error reading noun metadata ${id}:`, error);
|
|
403
|
+
}
|
|
404
|
+
return null;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
/**
|
|
408
|
+
* Save verb metadata to storage
|
|
409
|
+
*/
|
|
410
|
+
async saveVerbMetadata(id, metadata) {
|
|
411
|
+
await this.ensureInitialized();
|
|
412
|
+
const filePath = path.join(this.verbMetadataDir, `${id}.json`);
|
|
413
|
+
await fs.promises.writeFile(filePath, JSON.stringify(metadata, null, 2));
|
|
414
|
+
}
|
|
415
|
+
/**
|
|
416
|
+
* Get verb metadata from storage
|
|
417
|
+
*/
|
|
418
|
+
async getVerbMetadata(id) {
|
|
419
|
+
await this.ensureInitialized();
|
|
420
|
+
const filePath = path.join(this.verbMetadataDir, `${id}.json`);
|
|
421
|
+
try {
|
|
422
|
+
const data = await fs.promises.readFile(filePath, 'utf-8');
|
|
423
|
+
return JSON.parse(data);
|
|
424
|
+
}
|
|
425
|
+
catch (error) {
|
|
426
|
+
if (error.code !== 'ENOENT') {
|
|
427
|
+
console.error(`Error reading verb metadata ${id}:`, error);
|
|
428
|
+
}
|
|
429
|
+
return null;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* Clear all data from storage
|
|
434
|
+
*/
|
|
435
|
+
async clear() {
|
|
436
|
+
await this.ensureInitialized();
|
|
437
|
+
// Check if fs module is available
|
|
438
|
+
if (!fs || !fs.promises) {
|
|
439
|
+
console.warn('FileSystemStorage.clear: fs module not available, skipping clear operation');
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
// Helper function to remove all files in a directory
|
|
443
|
+
const removeDirectoryContents = async (dirPath) => {
|
|
444
|
+
try {
|
|
445
|
+
const files = await fs.promises.readdir(dirPath);
|
|
446
|
+
for (const file of files) {
|
|
447
|
+
const filePath = path.join(dirPath, file);
|
|
448
|
+
const stats = await fs.promises.stat(filePath);
|
|
449
|
+
if (stats.isDirectory()) {
|
|
450
|
+
await removeDirectoryContents(filePath);
|
|
451
|
+
await fs.promises.rmdir(filePath);
|
|
452
|
+
}
|
|
453
|
+
else {
|
|
454
|
+
await fs.promises.unlink(filePath);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
catch (error) {
|
|
459
|
+
if (error.code !== 'ENOENT') {
|
|
460
|
+
console.error(`Error removing directory contents ${dirPath}:`, error);
|
|
461
|
+
throw error;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
};
|
|
465
|
+
// Remove all files in the nouns directory
|
|
466
|
+
await removeDirectoryContents(this.nounsDir);
|
|
467
|
+
// Remove all files in the verbs directory
|
|
468
|
+
await removeDirectoryContents(this.verbsDir);
|
|
469
|
+
// Remove all files in the metadata directory
|
|
470
|
+
await removeDirectoryContents(this.metadataDir);
|
|
471
|
+
// Remove all files in the noun metadata directory
|
|
472
|
+
await removeDirectoryContents(this.nounMetadataDir);
|
|
473
|
+
// Remove all files in the verb metadata directory
|
|
474
|
+
await removeDirectoryContents(this.verbMetadataDir);
|
|
475
|
+
// Remove all files in the index directory
|
|
476
|
+
await removeDirectoryContents(this.indexDir);
|
|
477
|
+
// Clear the statistics cache
|
|
478
|
+
this.statisticsCache = null;
|
|
479
|
+
this.statisticsModified = false;
|
|
480
|
+
}
|
|
481
|
+
/**
|
|
482
|
+
* Get information about storage usage and capacity
|
|
483
|
+
*/
|
|
484
|
+
async getStorageStatus() {
|
|
485
|
+
await this.ensureInitialized();
|
|
486
|
+
// Check if fs module is available
|
|
487
|
+
if (!fs || !fs.promises) {
|
|
488
|
+
console.warn('FileSystemStorage.getStorageStatus: fs module not available, returning default values');
|
|
489
|
+
return {
|
|
490
|
+
type: 'filesystem',
|
|
491
|
+
used: 0,
|
|
492
|
+
quota: null,
|
|
493
|
+
details: {
|
|
494
|
+
nounsCount: 0,
|
|
495
|
+
verbsCount: 0,
|
|
496
|
+
metadataCount: 0,
|
|
497
|
+
directorySizes: {
|
|
498
|
+
nouns: 0,
|
|
499
|
+
verbs: 0,
|
|
500
|
+
metadata: 0,
|
|
501
|
+
index: 0
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
try {
|
|
507
|
+
// Calculate the total size of all files in the storage directories
|
|
508
|
+
let totalSize = 0;
|
|
509
|
+
// Helper function to calculate directory size
|
|
510
|
+
const calculateSize = async (dirPath) => {
|
|
511
|
+
let size = 0;
|
|
512
|
+
try {
|
|
513
|
+
const files = await fs.promises.readdir(dirPath);
|
|
514
|
+
for (const file of files) {
|
|
515
|
+
const filePath = path.join(dirPath, file);
|
|
516
|
+
const stats = await fs.promises.stat(filePath);
|
|
517
|
+
if (stats.isDirectory()) {
|
|
518
|
+
size += await calculateSize(filePath);
|
|
519
|
+
}
|
|
520
|
+
else {
|
|
521
|
+
size += stats.size;
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
catch (error) {
|
|
526
|
+
if (error.code !== 'ENOENT') {
|
|
527
|
+
console.error(`Error calculating size for directory ${dirPath}:`, error);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
return size;
|
|
531
|
+
};
|
|
532
|
+
// Calculate size for each directory
|
|
533
|
+
const nounsDirSize = await calculateSize(this.nounsDir);
|
|
534
|
+
const verbsDirSize = await calculateSize(this.verbsDir);
|
|
535
|
+
const metadataDirSize = await calculateSize(this.metadataDir);
|
|
536
|
+
const indexDirSize = await calculateSize(this.indexDir);
|
|
537
|
+
totalSize = nounsDirSize + verbsDirSize + metadataDirSize + indexDirSize;
|
|
538
|
+
// Count files in each directory
|
|
539
|
+
const nounsCount = (await fs.promises.readdir(this.nounsDir)).filter((file) => file.endsWith('.json')).length;
|
|
540
|
+
const verbsCount = (await fs.promises.readdir(this.verbsDir)).filter((file) => file.endsWith('.json')).length;
|
|
541
|
+
const metadataCount = (await fs.promises.readdir(this.metadataDir)).filter((file) => file.endsWith('.json')).length;
|
|
542
|
+
// Count nouns by type using metadata
|
|
543
|
+
const nounTypeCounts = {};
|
|
544
|
+
const metadataFiles = await fs.promises.readdir(this.metadataDir);
|
|
545
|
+
for (const file of metadataFiles) {
|
|
546
|
+
if (file.endsWith('.json')) {
|
|
547
|
+
try {
|
|
548
|
+
const filePath = path.join(this.metadataDir, file);
|
|
549
|
+
const data = await fs.promises.readFile(filePath, 'utf-8');
|
|
550
|
+
const metadata = JSON.parse(data);
|
|
551
|
+
if (metadata.noun) {
|
|
552
|
+
nounTypeCounts[metadata.noun] =
|
|
553
|
+
(nounTypeCounts[metadata.noun] || 0) + 1;
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
catch (error) {
|
|
557
|
+
console.error(`Error reading metadata file ${file}:`, error);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
return {
|
|
562
|
+
type: 'filesystem',
|
|
563
|
+
used: totalSize,
|
|
564
|
+
quota: null, // File system doesn't provide quota information
|
|
565
|
+
details: {
|
|
566
|
+
rootDirectory: this.rootDir,
|
|
567
|
+
nounsCount,
|
|
568
|
+
verbsCount,
|
|
569
|
+
metadataCount,
|
|
570
|
+
nounsDirSize,
|
|
571
|
+
verbsDirSize,
|
|
572
|
+
metadataDirSize,
|
|
573
|
+
indexDirSize,
|
|
574
|
+
nounTypes: nounTypeCounts
|
|
575
|
+
}
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
catch (error) {
|
|
579
|
+
console.error('Failed to get storage status:', error);
|
|
580
|
+
return {
|
|
581
|
+
type: 'filesystem',
|
|
582
|
+
used: 0,
|
|
583
|
+
quota: null,
|
|
584
|
+
details: { error: String(error) }
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
/**
|
|
589
|
+
* Implementation of abstract methods from BaseStorage
|
|
590
|
+
*/
|
|
591
|
+
/**
|
|
592
|
+
* Save a noun to storage
|
|
593
|
+
*/
|
|
594
|
+
async saveNoun_internal(noun) {
|
|
595
|
+
return this.saveNode(noun);
|
|
596
|
+
}
|
|
597
|
+
/**
|
|
598
|
+
* Get a noun from storage
|
|
599
|
+
*/
|
|
600
|
+
async getNoun_internal(id) {
|
|
601
|
+
return this.getNode(id);
|
|
602
|
+
}
|
|
603
|
+
/**
|
|
604
|
+
* Get all nouns from storage
|
|
605
|
+
*/
|
|
606
|
+
async getAllNouns_internal() {
|
|
607
|
+
return this.getAllNodes();
|
|
608
|
+
}
|
|
609
|
+
/**
|
|
610
|
+
* Get nouns by noun type
|
|
611
|
+
*/
|
|
612
|
+
async getNounsByNounType_internal(nounType) {
|
|
613
|
+
return this.getNodesByNounType(nounType);
|
|
614
|
+
}
|
|
615
|
+
/**
|
|
616
|
+
* Delete a noun from storage
|
|
617
|
+
*/
|
|
618
|
+
async deleteNoun_internal(id) {
|
|
619
|
+
return this.deleteNode(id);
|
|
620
|
+
}
|
|
621
|
+
/**
|
|
622
|
+
* Save a verb to storage
|
|
623
|
+
*/
|
|
624
|
+
async saveVerb_internal(verb) {
|
|
625
|
+
return this.saveEdge(verb);
|
|
626
|
+
}
|
|
627
|
+
/**
|
|
628
|
+
* Get a verb from storage
|
|
629
|
+
*/
|
|
630
|
+
async getVerb_internal(id) {
|
|
631
|
+
return this.getEdge(id);
|
|
632
|
+
}
|
|
633
|
+
/**
|
|
634
|
+
* Get all verbs from storage
|
|
635
|
+
*/
|
|
636
|
+
async getAllVerbs_internal() {
|
|
637
|
+
return this.getAllEdges();
|
|
638
|
+
}
|
|
639
|
+
/**
|
|
640
|
+
* Get verbs by source
|
|
641
|
+
*/
|
|
642
|
+
async getVerbsBySource_internal(sourceId) {
|
|
643
|
+
// This method is deprecated and would require loading metadata for each edge
|
|
644
|
+
// For now, return empty array since this is not efficiently implementable with new storage pattern
|
|
645
|
+
console.warn('getVerbsBySource_internal is deprecated and not efficiently supported in new storage pattern');
|
|
646
|
+
return [];
|
|
647
|
+
}
|
|
648
|
+
/**
|
|
649
|
+
* Get verbs by target
|
|
650
|
+
*/
|
|
651
|
+
async getVerbsByTarget_internal(targetId) {
|
|
652
|
+
// This method is deprecated and would require loading metadata for each edge
|
|
653
|
+
// For now, return empty array since this is not efficiently implementable with new storage pattern
|
|
654
|
+
console.warn('getVerbsByTarget_internal is deprecated and not efficiently supported in new storage pattern');
|
|
655
|
+
return [];
|
|
656
|
+
}
|
|
657
|
+
/**
|
|
658
|
+
* Get verbs by type
|
|
659
|
+
*/
|
|
660
|
+
async getVerbsByType_internal(type) {
|
|
661
|
+
// This method is deprecated and would require loading metadata for each edge
|
|
662
|
+
// For now, return empty array since this is not efficiently implementable with new storage pattern
|
|
663
|
+
console.warn('getVerbsByType_internal is deprecated and not efficiently supported in new storage pattern');
|
|
664
|
+
return [];
|
|
665
|
+
}
|
|
666
|
+
/**
|
|
667
|
+
* Delete a verb from storage
|
|
668
|
+
*/
|
|
669
|
+
async deleteVerb_internal(id) {
|
|
670
|
+
return this.deleteEdge(id);
|
|
671
|
+
}
|
|
672
|
+
/**
|
|
673
|
+
* Acquire a file-based lock for coordinating operations across multiple processes
|
|
674
|
+
* @param lockKey The key to lock on
|
|
675
|
+
* @param ttl Time to live for the lock in milliseconds (default: 30 seconds)
|
|
676
|
+
* @returns Promise that resolves to true if lock was acquired, false otherwise
|
|
677
|
+
*/
|
|
678
|
+
async acquireLock(lockKey, ttl = 30000) {
|
|
679
|
+
await this.ensureInitialized();
|
|
680
|
+
const lockFile = path.join(this.lockDir, `${lockKey}.lock`);
|
|
681
|
+
const lockValue = `${Date.now()}_${Math.random()}_${process.pid || 'unknown'}`;
|
|
682
|
+
const expiresAt = Date.now() + ttl;
|
|
683
|
+
try {
|
|
684
|
+
// Check if lock file already exists and is still valid
|
|
685
|
+
try {
|
|
686
|
+
const lockData = await fs.promises.readFile(lockFile, 'utf-8');
|
|
687
|
+
const lockInfo = JSON.parse(lockData);
|
|
688
|
+
if (lockInfo.expiresAt > Date.now()) {
|
|
689
|
+
// Lock exists and is still valid
|
|
690
|
+
return false;
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
catch (error) {
|
|
694
|
+
// If file doesn't exist or can't be read, we can proceed to create the lock
|
|
695
|
+
if (error.code !== 'ENOENT') {
|
|
696
|
+
console.warn(`Error reading lock file ${lockFile}:`, error);
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
// Try to create the lock file
|
|
700
|
+
const lockInfo = {
|
|
701
|
+
lockValue,
|
|
702
|
+
expiresAt,
|
|
703
|
+
pid: process.pid || 'unknown',
|
|
704
|
+
timestamp: Date.now()
|
|
705
|
+
};
|
|
706
|
+
await fs.promises.writeFile(lockFile, JSON.stringify(lockInfo, null, 2));
|
|
707
|
+
// Add to active locks for cleanup
|
|
708
|
+
this.activeLocks.add(lockKey);
|
|
709
|
+
// Schedule automatic cleanup when lock expires
|
|
710
|
+
setTimeout(() => {
|
|
711
|
+
this.releaseLock(lockKey, lockValue).catch((error) => {
|
|
712
|
+
console.warn(`Failed to auto-release expired lock ${lockKey}:`, error);
|
|
713
|
+
});
|
|
714
|
+
}, ttl);
|
|
715
|
+
return true;
|
|
716
|
+
}
|
|
717
|
+
catch (error) {
|
|
718
|
+
console.warn(`Failed to acquire lock ${lockKey}:`, error);
|
|
719
|
+
return false;
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
/**
|
|
723
|
+
* Release a file-based lock
|
|
724
|
+
* @param lockKey The key to unlock
|
|
725
|
+
* @param lockValue The value used when acquiring the lock (for verification)
|
|
726
|
+
* @returns Promise that resolves when lock is released
|
|
727
|
+
*/
|
|
728
|
+
async releaseLock(lockKey, lockValue) {
|
|
729
|
+
await this.ensureInitialized();
|
|
730
|
+
const lockFile = path.join(this.lockDir, `${lockKey}.lock`);
|
|
731
|
+
try {
|
|
732
|
+
// If lockValue is provided, verify it matches before releasing
|
|
733
|
+
if (lockValue) {
|
|
734
|
+
try {
|
|
735
|
+
const lockData = await fs.promises.readFile(lockFile, 'utf-8');
|
|
736
|
+
const lockInfo = JSON.parse(lockData);
|
|
737
|
+
if (lockInfo.lockValue !== lockValue) {
|
|
738
|
+
// Lock was acquired by someone else, don't release it
|
|
739
|
+
return;
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
catch (error) {
|
|
743
|
+
// If lock file doesn't exist, that's fine
|
|
744
|
+
if (error.code === 'ENOENT') {
|
|
745
|
+
return;
|
|
746
|
+
}
|
|
747
|
+
throw error;
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
// Delete the lock file
|
|
751
|
+
await fs.promises.unlink(lockFile);
|
|
752
|
+
// Remove from active locks
|
|
753
|
+
this.activeLocks.delete(lockKey);
|
|
754
|
+
}
|
|
755
|
+
catch (error) {
|
|
756
|
+
if (error.code !== 'ENOENT') {
|
|
757
|
+
console.warn(`Failed to release lock ${lockKey}:`, error);
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
/**
|
|
762
|
+
* Clean up expired lock files
|
|
763
|
+
*/
|
|
764
|
+
async cleanupExpiredLocks() {
|
|
765
|
+
await this.ensureInitialized();
|
|
766
|
+
try {
|
|
767
|
+
const lockFiles = await fs.promises.readdir(this.lockDir);
|
|
768
|
+
const now = Date.now();
|
|
769
|
+
for (const lockFile of lockFiles) {
|
|
770
|
+
if (!lockFile.endsWith('.lock'))
|
|
771
|
+
continue;
|
|
772
|
+
const lockPath = path.join(this.lockDir, lockFile);
|
|
773
|
+
try {
|
|
774
|
+
const lockData = await fs.promises.readFile(lockPath, 'utf-8');
|
|
775
|
+
const lockInfo = JSON.parse(lockData);
|
|
776
|
+
if (lockInfo.expiresAt <= now) {
|
|
777
|
+
await fs.promises.unlink(lockPath);
|
|
778
|
+
const lockKey = lockFile.replace('.lock', '');
|
|
779
|
+
this.activeLocks.delete(lockKey);
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
catch (error) {
|
|
783
|
+
// If we can't read or parse the lock file, remove it
|
|
784
|
+
try {
|
|
785
|
+
await fs.promises.unlink(lockPath);
|
|
786
|
+
}
|
|
787
|
+
catch (unlinkError) {
|
|
788
|
+
console.warn(`Failed to cleanup invalid lock file ${lockPath}:`, unlinkError);
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
catch (error) {
|
|
794
|
+
console.warn('Failed to cleanup expired locks:', error);
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
/**
|
|
798
|
+
* Save statistics data to storage with file-based locking
|
|
799
|
+
*/
|
|
800
|
+
async saveStatisticsData(statistics) {
|
|
801
|
+
const lockKey = 'statistics';
|
|
802
|
+
const lockAcquired = await this.acquireLock(lockKey, 10000); // 10 second timeout
|
|
803
|
+
if (!lockAcquired) {
|
|
804
|
+
console.warn('Failed to acquire lock for statistics update, proceeding without lock');
|
|
805
|
+
}
|
|
806
|
+
try {
|
|
807
|
+
// Get existing statistics to merge with new data
|
|
808
|
+
const existingStats = (await this.getMetadata(STATISTICS_KEY));
|
|
809
|
+
if (existingStats) {
|
|
810
|
+
// Merge statistics data
|
|
811
|
+
const mergedStats = {
|
|
812
|
+
totalNodes: Math.max(statistics.totalNodes || 0, existingStats.totalNodes || 0),
|
|
813
|
+
totalEdges: Math.max(statistics.totalEdges || 0, existingStats.totalEdges || 0),
|
|
814
|
+
totalMetadata: Math.max(statistics.totalMetadata || 0, existingStats.totalMetadata || 0),
|
|
815
|
+
// Preserve any additional fields from existing stats
|
|
816
|
+
...existingStats,
|
|
817
|
+
// Override with new values where provided
|
|
818
|
+
...statistics,
|
|
819
|
+
// Always update lastUpdated to current time
|
|
820
|
+
lastUpdated: new Date().toISOString()
|
|
821
|
+
};
|
|
822
|
+
await this.saveMetadata(STATISTICS_KEY, mergedStats);
|
|
823
|
+
}
|
|
824
|
+
else {
|
|
825
|
+
// No existing statistics, save new ones
|
|
826
|
+
const newStats = {
|
|
827
|
+
...statistics,
|
|
828
|
+
lastUpdated: new Date().toISOString()
|
|
829
|
+
};
|
|
830
|
+
await this.saveMetadata(STATISTICS_KEY, newStats);
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
finally {
|
|
834
|
+
if (lockAcquired) {
|
|
835
|
+
await this.releaseLock(lockKey);
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
/**
|
|
840
|
+
* Get statistics data from storage
|
|
841
|
+
*/
|
|
842
|
+
async getStatisticsData() {
|
|
843
|
+
return this.getMetadata(STATISTICS_KEY);
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
//# sourceMappingURL=fileSystemStorage.js.map
|