@soulcraft/brainy 4.10.2 → 4.10.3
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.
|
@@ -53,6 +53,7 @@ export declare class FileSystemStorage extends BaseStorage {
|
|
|
53
53
|
private ensureDirectoryExists;
|
|
54
54
|
/**
|
|
55
55
|
* Save a node to storage
|
|
56
|
+
* CRITICAL FIX (v4.10.3): Added atomic write pattern to prevent file corruption during concurrent imports
|
|
56
57
|
*/
|
|
57
58
|
protected saveNode(node: HNSWNode): Promise<void>;
|
|
58
59
|
/**
|
|
@@ -78,6 +79,7 @@ export declare class FileSystemStorage extends BaseStorage {
|
|
|
78
79
|
protected deleteNode(id: string): Promise<void>;
|
|
79
80
|
/**
|
|
80
81
|
* Save an edge to storage
|
|
82
|
+
* CRITICAL FIX (v4.10.3): Added atomic write pattern to prevent file corruption during concurrent imports
|
|
81
83
|
*/
|
|
82
84
|
protected saveEdge(edge: Edge): Promise<void>;
|
|
83
85
|
/**
|
|
@@ -110,6 +112,7 @@ export declare class FileSystemStorage extends BaseStorage {
|
|
|
110
112
|
* Primitive operation: Write object to path
|
|
111
113
|
* All metadata operations use this internally via base class routing
|
|
112
114
|
* v4.0.0: Supports gzip compression for 60-80% disk savings
|
|
115
|
+
* CRITICAL FIX (v4.10.3): Added atomic write pattern to prevent file corruption during concurrent imports
|
|
113
116
|
*/
|
|
114
117
|
protected writeObjectToPath(pathStr: string, data: any): Promise<void>;
|
|
115
118
|
/**
|
|
@@ -180,6 +180,7 @@ export class FileSystemStorage extends BaseStorage {
|
|
|
180
180
|
}
|
|
181
181
|
/**
|
|
182
182
|
* Save a node to storage
|
|
183
|
+
* CRITICAL FIX (v4.10.3): Added atomic write pattern to prevent file corruption during concurrent imports
|
|
183
184
|
*/
|
|
184
185
|
async saveNode(node) {
|
|
185
186
|
await this.ensureInitialized();
|
|
@@ -194,8 +195,25 @@ export class FileSystemStorage extends BaseStorage {
|
|
|
194
195
|
// NO metadata field - saved separately for scalability
|
|
195
196
|
};
|
|
196
197
|
const filePath = this.getNodePath(node.id);
|
|
197
|
-
|
|
198
|
-
|
|
198
|
+
const tempPath = `${filePath}.tmp.${Date.now()}.${Math.random().toString(36).substring(2)}`;
|
|
199
|
+
try {
|
|
200
|
+
// ATOMIC WRITE SEQUENCE (v4.10.3):
|
|
201
|
+
// 1. Write to temp file
|
|
202
|
+
await this.ensureDirectoryExists(path.dirname(tempPath));
|
|
203
|
+
await fs.promises.writeFile(tempPath, JSON.stringify(serializableNode, null, 2));
|
|
204
|
+
// 2. Atomic rename temp → final (crash-safe, prevents truncation during concurrent writes)
|
|
205
|
+
await fs.promises.rename(tempPath, filePath);
|
|
206
|
+
}
|
|
207
|
+
catch (error) {
|
|
208
|
+
// Clean up temp file on any error
|
|
209
|
+
try {
|
|
210
|
+
await fs.promises.unlink(tempPath);
|
|
211
|
+
}
|
|
212
|
+
catch (cleanupError) {
|
|
213
|
+
// Ignore cleanup errors
|
|
214
|
+
}
|
|
215
|
+
throw error;
|
|
216
|
+
}
|
|
199
217
|
// Count tracking happens in baseStorage.saveNounMetadata_internal (v4.1.2)
|
|
200
218
|
// This fixes the race condition where metadata didn't exist yet
|
|
201
219
|
}
|
|
@@ -344,6 +362,7 @@ export class FileSystemStorage extends BaseStorage {
|
|
|
344
362
|
}
|
|
345
363
|
/**
|
|
346
364
|
* Save an edge to storage
|
|
365
|
+
* CRITICAL FIX (v4.10.3): Added atomic write pattern to prevent file corruption during concurrent imports
|
|
347
366
|
*/
|
|
348
367
|
async saveEdge(edge) {
|
|
349
368
|
await this.ensureInitialized();
|
|
@@ -362,8 +381,25 @@ export class FileSystemStorage extends BaseStorage {
|
|
|
362
381
|
// metadata field is saved separately via saveVerbMetadata()
|
|
363
382
|
};
|
|
364
383
|
const filePath = this.getVerbPath(edge.id);
|
|
365
|
-
|
|
366
|
-
|
|
384
|
+
const tempPath = `${filePath}.tmp.${Date.now()}.${Math.random().toString(36).substring(2)}`;
|
|
385
|
+
try {
|
|
386
|
+
// ATOMIC WRITE SEQUENCE (v4.10.3):
|
|
387
|
+
// 1. Write to temp file
|
|
388
|
+
await this.ensureDirectoryExists(path.dirname(tempPath));
|
|
389
|
+
await fs.promises.writeFile(tempPath, JSON.stringify(serializableEdge, null, 2));
|
|
390
|
+
// 2. Atomic rename temp → final (crash-safe, prevents truncation during concurrent writes)
|
|
391
|
+
await fs.promises.rename(tempPath, filePath);
|
|
392
|
+
}
|
|
393
|
+
catch (error) {
|
|
394
|
+
// Clean up temp file on any error
|
|
395
|
+
try {
|
|
396
|
+
await fs.promises.unlink(tempPath);
|
|
397
|
+
}
|
|
398
|
+
catch (cleanupError) {
|
|
399
|
+
// Ignore cleanup errors
|
|
400
|
+
}
|
|
401
|
+
throw error;
|
|
402
|
+
}
|
|
367
403
|
// Count tracking happens in baseStorage.saveVerbMetadata_internal (v4.1.2)
|
|
368
404
|
// This fixes the race condition where metadata didn't exist yet
|
|
369
405
|
}
|
|
@@ -507,24 +543,42 @@ export class FileSystemStorage extends BaseStorage {
|
|
|
507
543
|
* Primitive operation: Write object to path
|
|
508
544
|
* All metadata operations use this internally via base class routing
|
|
509
545
|
* v4.0.0: Supports gzip compression for 60-80% disk savings
|
|
546
|
+
* CRITICAL FIX (v4.10.3): Added atomic write pattern to prevent file corruption during concurrent imports
|
|
510
547
|
*/
|
|
511
548
|
async writeObjectToPath(pathStr, data) {
|
|
512
549
|
await this.ensureInitialized();
|
|
513
550
|
const fullPath = path.join(this.rootDir, pathStr);
|
|
514
551
|
await this.ensureDirectoryExists(path.dirname(fullPath));
|
|
515
552
|
if (this.compressionEnabled) {
|
|
516
|
-
// Write compressed data with .gz extension
|
|
553
|
+
// Write compressed data with .gz extension using atomic pattern
|
|
517
554
|
const compressedPath = `${fullPath}.gz`;
|
|
518
|
-
const
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
555
|
+
const tempPath = `${compressedPath}.tmp.${Date.now()}.${Math.random().toString(36).substring(2)}`;
|
|
556
|
+
try {
|
|
557
|
+
// ATOMIC WRITE SEQUENCE (v4.10.3):
|
|
558
|
+
// 1. Compress and write to temp file
|
|
559
|
+
const jsonString = JSON.stringify(data, null, 2);
|
|
560
|
+
const compressed = await new Promise((resolve, reject) => {
|
|
561
|
+
zlib.gzip(Buffer.from(jsonString, 'utf-8'), { level: this.compressionLevel }, (err, result) => {
|
|
562
|
+
if (err)
|
|
563
|
+
reject(err);
|
|
564
|
+
else
|
|
565
|
+
resolve(result);
|
|
566
|
+
});
|
|
525
567
|
});
|
|
526
|
-
|
|
527
|
-
|
|
568
|
+
await fs.promises.writeFile(tempPath, compressed);
|
|
569
|
+
// 2. Atomic rename temp → final (crash-safe, prevents truncation during concurrent writes)
|
|
570
|
+
await fs.promises.rename(tempPath, compressedPath);
|
|
571
|
+
}
|
|
572
|
+
catch (error) {
|
|
573
|
+
// Clean up temp file on any error
|
|
574
|
+
try {
|
|
575
|
+
await fs.promises.unlink(tempPath);
|
|
576
|
+
}
|
|
577
|
+
catch (cleanupError) {
|
|
578
|
+
// Ignore cleanup errors
|
|
579
|
+
}
|
|
580
|
+
throw error;
|
|
581
|
+
}
|
|
528
582
|
// Clean up uncompressed file if it exists (migration from uncompressed)
|
|
529
583
|
try {
|
|
530
584
|
await fs.promises.unlink(fullPath);
|
|
@@ -537,8 +591,25 @@ export class FileSystemStorage extends BaseStorage {
|
|
|
537
591
|
}
|
|
538
592
|
}
|
|
539
593
|
else {
|
|
540
|
-
// Write uncompressed data
|
|
541
|
-
|
|
594
|
+
// Write uncompressed data using atomic pattern
|
|
595
|
+
const tempPath = `${fullPath}.tmp.${Date.now()}.${Math.random().toString(36).substring(2)}`;
|
|
596
|
+
try {
|
|
597
|
+
// ATOMIC WRITE SEQUENCE (v4.10.3):
|
|
598
|
+
// 1. Write to temp file
|
|
599
|
+
await fs.promises.writeFile(tempPath, JSON.stringify(data, null, 2));
|
|
600
|
+
// 2. Atomic rename temp → final (crash-safe, prevents truncation during concurrent writes)
|
|
601
|
+
await fs.promises.rename(tempPath, fullPath);
|
|
602
|
+
}
|
|
603
|
+
catch (error) {
|
|
604
|
+
// Clean up temp file on any error
|
|
605
|
+
try {
|
|
606
|
+
await fs.promises.unlink(tempPath);
|
|
607
|
+
}
|
|
608
|
+
catch (cleanupError) {
|
|
609
|
+
// Ignore cleanup errors
|
|
610
|
+
}
|
|
611
|
+
throw error;
|
|
612
|
+
}
|
|
542
613
|
}
|
|
543
614
|
}
|
|
544
615
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@soulcraft/brainy",
|
|
3
|
-
"version": "4.10.
|
|
3
|
+
"version": "4.10.3",
|
|
4
4
|
"description": "Universal Knowledge Protocol™ - World's first Triple Intelligence database unifying vector, graph, and document search in one API. 31 nouns × 40 verbs for infinite expressiveness.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.js",
|