@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
- await this.ensureDirectoryExists(path.dirname(filePath));
198
- await fs.promises.writeFile(filePath, JSON.stringify(serializableNode, null, 2));
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
- await this.ensureDirectoryExists(path.dirname(filePath));
366
- await fs.promises.writeFile(filePath, JSON.stringify(serializableEdge, null, 2));
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 jsonString = JSON.stringify(data, null, 2);
519
- const compressed = await new Promise((resolve, reject) => {
520
- zlib.gzip(Buffer.from(jsonString, 'utf-8'), { level: this.compressionLevel }, (err, result) => {
521
- if (err)
522
- reject(err);
523
- else
524
- resolve(result);
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
- await fs.promises.writeFile(compressedPath, compressed);
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
- await fs.promises.writeFile(fullPath, JSON.stringify(data, null, 2));
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.2",
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",