@soulcraft/brainy 3.36.1 → 3.37.1
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/CHANGELOG.md +12 -0
- package/dist/coreTypes.d.ts +1 -0
- package/dist/storage/adapters/fileSystemStorage.d.ts +4 -2
- package/dist/storage/adapters/fileSystemStorage.js +47 -10
- package/dist/storage/adapters/gcsStorage.d.ts +9 -0
- package/dist/storage/adapters/gcsStorage.js +82 -7
- package/dist/storage/adapters/memoryStorage.d.ts +4 -2
- package/dist/storage/adapters/memoryStorage.js +26 -19
- package/dist/storage/adapters/opfsStorage.d.ts +3 -1
- package/dist/storage/adapters/opfsStorage.js +35 -8
- package/dist/storage/adapters/s3CompatibleStorage.d.ts +2 -0
- package/dist/storage/adapters/s3CompatibleStorage.js +55 -6
- package/dist/storage/baseStorage.d.ts +5 -0
- package/dist/storage/baseStorage.js +55 -6
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
|
4
4
|
|
|
5
|
+
### [3.37.1](https://github.com/soulcraftlabs/brainy/compare/v3.37.0...v3.37.1) (2025-10-10)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### 🐛 Bug Fixes
|
|
9
|
+
|
|
10
|
+
* combine vector and metadata in getNoun/getVerb internal methods ([cb1e37c](https://github.com/soulcraftlabs/brainy/commit/cb1e37c0e8132f53be0f359feaef5dcf342462d2))
|
|
11
|
+
|
|
12
|
+
### [3.37.0](https://github.com/soulcraftlabs/brainy/compare/v3.36.1...v3.37.0) (2025-10-10)
|
|
13
|
+
|
|
14
|
+
- fix: implement 2-file storage architecture for GCS scalability (59da5f6)
|
|
15
|
+
|
|
16
|
+
|
|
5
17
|
### [3.36.1](https://github.com/soulcraftlabs/brainy/compare/v3.36.0...v3.36.1) (2025-10-10)
|
|
6
18
|
|
|
7
19
|
- fix: resolve critical GCS storage bugs preventing production use (3cd0b9a)
|
package/dist/coreTypes.d.ts
CHANGED
|
@@ -161,7 +161,8 @@ export declare class FileSystemStorage extends BaseStorage {
|
|
|
161
161
|
*/
|
|
162
162
|
protected saveNoun_internal(noun: HNSWNoun): Promise<void>;
|
|
163
163
|
/**
|
|
164
|
-
* Get a noun from storage
|
|
164
|
+
* Get a noun from storage (internal implementation)
|
|
165
|
+
* Combines vector data from getNode() with metadata from getNounMetadata()
|
|
165
166
|
*/
|
|
166
167
|
protected getNoun_internal(id: string): Promise<HNSWNoun | null>;
|
|
167
168
|
/**
|
|
@@ -177,7 +178,8 @@ export declare class FileSystemStorage extends BaseStorage {
|
|
|
177
178
|
*/
|
|
178
179
|
protected saveVerb_internal(verb: HNSWVerb): Promise<void>;
|
|
179
180
|
/**
|
|
180
|
-
* Get a verb from storage
|
|
181
|
+
* Get a verb from storage (internal implementation)
|
|
182
|
+
* Combines vector data from getEdge() with metadata from getVerbMetadata()
|
|
181
183
|
*/
|
|
182
184
|
protected getVerb_internal(id: string): Promise<HNSWVerb | null>;
|
|
183
185
|
/**
|
|
@@ -168,9 +168,14 @@ export class FileSystemStorage extends BaseStorage {
|
|
|
168
168
|
// Check if this is a new node to update counts
|
|
169
169
|
const isNew = !(await this.fileExists(this.getNodePath(node.id)));
|
|
170
170
|
// Convert connections Map to a serializable format
|
|
171
|
+
// CRITICAL: Only save lightweight vector data (no metadata)
|
|
172
|
+
// Metadata is saved separately via saveNounMetadata() (2-file system)
|
|
171
173
|
const serializableNode = {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
+
id: node.id,
|
|
175
|
+
vector: node.vector,
|
|
176
|
+
connections: this.mapToObject(node.connections, (set) => Array.from(set)),
|
|
177
|
+
level: node.level || 0
|
|
178
|
+
// NO metadata field - saved separately for scalability
|
|
174
179
|
};
|
|
175
180
|
const filePath = this.getNodePath(node.id);
|
|
176
181
|
await this.ensureDirectoryExists(path.dirname(filePath));
|
|
@@ -200,12 +205,14 @@ export class FileSystemStorage extends BaseStorage {
|
|
|
200
205
|
for (const [level, nodeIds] of Object.entries(parsedNode.connections)) {
|
|
201
206
|
connections.set(Number(level), new Set(nodeIds));
|
|
202
207
|
}
|
|
208
|
+
// CRITICAL: Only return lightweight vector data (no metadata)
|
|
209
|
+
// Metadata is retrieved separately via getNounMetadata() (2-file system)
|
|
203
210
|
return {
|
|
204
211
|
id: parsedNode.id,
|
|
205
212
|
vector: parsedNode.vector,
|
|
206
213
|
connections,
|
|
207
|
-
level: parsedNode.level || 0
|
|
208
|
-
metadata
|
|
214
|
+
level: parsedNode.level || 0
|
|
215
|
+
// NO metadata field - retrieved separately for scalability
|
|
209
216
|
};
|
|
210
217
|
}
|
|
211
218
|
catch (error) {
|
|
@@ -329,9 +336,13 @@ export class FileSystemStorage extends BaseStorage {
|
|
|
329
336
|
// Check if this is a new edge to update counts
|
|
330
337
|
const isNew = !(await this.fileExists(this.getVerbPath(edge.id)));
|
|
331
338
|
// Convert connections Map to a serializable format
|
|
339
|
+
// CRITICAL: Only save lightweight vector data (no metadata)
|
|
340
|
+
// Metadata is saved separately via saveVerbMetadata() (2-file system)
|
|
332
341
|
const serializableEdge = {
|
|
333
|
-
|
|
342
|
+
id: edge.id,
|
|
343
|
+
vector: edge.vector,
|
|
334
344
|
connections: this.mapToObject(edge.connections, (set) => Array.from(set))
|
|
345
|
+
// NO metadata field - saved separately for scalability
|
|
335
346
|
};
|
|
336
347
|
const filePath = this.getVerbPath(edge.id);
|
|
337
348
|
await this.ensureDirectoryExists(path.dirname(filePath));
|
|
@@ -558,7 +569,9 @@ export class FileSystemStorage extends BaseStorage {
|
|
|
558
569
|
const batch = ids.slice(i, i + batchSize);
|
|
559
570
|
const batchPromises = batch.map(async (id) => {
|
|
560
571
|
try {
|
|
561
|
-
|
|
572
|
+
// CRITICAL: Use getNounMetadata() instead of deprecated getMetadata()
|
|
573
|
+
// This ensures we fetch from the correct noun metadata store (2-file system)
|
|
574
|
+
const metadata = await this.getNounMetadata(id);
|
|
562
575
|
return { id, metadata };
|
|
563
576
|
}
|
|
564
577
|
catch (error) {
|
|
@@ -850,10 +863,22 @@ export class FileSystemStorage extends BaseStorage {
|
|
|
850
863
|
return this.saveNode(noun);
|
|
851
864
|
}
|
|
852
865
|
/**
|
|
853
|
-
* Get a noun from storage
|
|
866
|
+
* Get a noun from storage (internal implementation)
|
|
867
|
+
* Combines vector data from getNode() with metadata from getNounMetadata()
|
|
854
868
|
*/
|
|
855
869
|
async getNoun_internal(id) {
|
|
856
|
-
|
|
870
|
+
// Get vector data (lightweight)
|
|
871
|
+
const node = await this.getNode(id);
|
|
872
|
+
if (!node) {
|
|
873
|
+
return null;
|
|
874
|
+
}
|
|
875
|
+
// Get metadata (entity data in 2-file system)
|
|
876
|
+
const metadata = await this.getNounMetadata(id);
|
|
877
|
+
// Combine into complete noun object
|
|
878
|
+
return {
|
|
879
|
+
...node,
|
|
880
|
+
metadata: metadata || {}
|
|
881
|
+
};
|
|
857
882
|
}
|
|
858
883
|
/**
|
|
859
884
|
* Get nouns by noun type
|
|
@@ -874,10 +899,22 @@ export class FileSystemStorage extends BaseStorage {
|
|
|
874
899
|
return this.saveEdge(verb);
|
|
875
900
|
}
|
|
876
901
|
/**
|
|
877
|
-
* Get a verb from storage
|
|
902
|
+
* Get a verb from storage (internal implementation)
|
|
903
|
+
* Combines vector data from getEdge() with metadata from getVerbMetadata()
|
|
878
904
|
*/
|
|
879
905
|
async getVerb_internal(id) {
|
|
880
|
-
|
|
906
|
+
// Get vector data (lightweight)
|
|
907
|
+
const edge = await this.getEdge(id);
|
|
908
|
+
if (!edge) {
|
|
909
|
+
return null;
|
|
910
|
+
}
|
|
911
|
+
// Get metadata (relationship data in 2-file system)
|
|
912
|
+
const metadata = await this.getVerbMetadata(id);
|
|
913
|
+
// Combine into complete verb object
|
|
914
|
+
return {
|
|
915
|
+
...edge,
|
|
916
|
+
metadata: metadata || {}
|
|
917
|
+
};
|
|
881
918
|
}
|
|
882
919
|
/**
|
|
883
920
|
* Get verbs by source
|
|
@@ -148,6 +148,7 @@ export declare class GcsStorage extends BaseStorage {
|
|
|
148
148
|
private saveNodeDirect;
|
|
149
149
|
/**
|
|
150
150
|
* Get a noun from storage (internal implementation)
|
|
151
|
+
* Combines vector data from getNode() with metadata from getNounMetadata()
|
|
151
152
|
*/
|
|
152
153
|
protected getNoun_internal(id: string): Promise<HNSWNoun | null>;
|
|
153
154
|
/**
|
|
@@ -196,6 +197,7 @@ export declare class GcsStorage extends BaseStorage {
|
|
|
196
197
|
private saveEdgeDirect;
|
|
197
198
|
/**
|
|
198
199
|
* Get a verb from storage (internal implementation)
|
|
200
|
+
* Combines vector data from getEdge() with metadata from getVerbMetadata()
|
|
199
201
|
*/
|
|
200
202
|
protected getVerb_internal(id: string): Promise<HNSWVerb | null>;
|
|
201
203
|
/**
|
|
@@ -306,6 +308,13 @@ export declare class GcsStorage extends BaseStorage {
|
|
|
306
308
|
hasMore: boolean;
|
|
307
309
|
nextCursor?: string;
|
|
308
310
|
}>;
|
|
311
|
+
/**
|
|
312
|
+
* Batch fetch metadata for multiple noun IDs (efficient for large queries)
|
|
313
|
+
* Uses smaller batches to prevent GCS socket exhaustion
|
|
314
|
+
* @param ids Array of noun IDs to fetch metadata for
|
|
315
|
+
* @returns Map of ID to metadata
|
|
316
|
+
*/
|
|
317
|
+
getMetadataBatch(ids: string[]): Promise<Map<string, any>>;
|
|
309
318
|
/**
|
|
310
319
|
* Clear all data from storage
|
|
311
320
|
*/
|
|
@@ -316,12 +316,17 @@ export class GcsStorage extends BaseStorage {
|
|
|
316
316
|
try {
|
|
317
317
|
this.logger.trace(`Saving node ${node.id}`);
|
|
318
318
|
// Convert connections Map to a serializable format
|
|
319
|
+
// CRITICAL: Only save lightweight vector data (no metadata)
|
|
320
|
+
// Metadata is saved separately via saveNounMetadata() (2-file system)
|
|
319
321
|
const serializableNode = {
|
|
320
|
-
|
|
322
|
+
id: node.id,
|
|
323
|
+
vector: node.vector,
|
|
321
324
|
connections: Object.fromEntries(Array.from(node.connections.entries()).map(([level, nounIds]) => [
|
|
322
325
|
level,
|
|
323
326
|
Array.from(nounIds)
|
|
324
|
-
]))
|
|
327
|
+
])),
|
|
328
|
+
level: node.level || 0
|
|
329
|
+
// NO metadata field - saved separately for scalability
|
|
325
330
|
};
|
|
326
331
|
// Get the GCS key with UUID-based sharding
|
|
327
332
|
const key = this.getNounKey(node.id);
|
|
@@ -354,9 +359,21 @@ export class GcsStorage extends BaseStorage {
|
|
|
354
359
|
}
|
|
355
360
|
/**
|
|
356
361
|
* Get a noun from storage (internal implementation)
|
|
362
|
+
* Combines vector data from getNode() with metadata from getNounMetadata()
|
|
357
363
|
*/
|
|
358
364
|
async getNoun_internal(id) {
|
|
359
|
-
|
|
365
|
+
// Get vector data (lightweight)
|
|
366
|
+
const node = await this.getNode(id);
|
|
367
|
+
if (!node) {
|
|
368
|
+
return null;
|
|
369
|
+
}
|
|
370
|
+
// Get metadata (entity data in 2-file system)
|
|
371
|
+
const metadata = await this.getNounMetadata(id);
|
|
372
|
+
// Combine into complete noun object
|
|
373
|
+
return {
|
|
374
|
+
...node,
|
|
375
|
+
metadata: metadata || {}
|
|
376
|
+
};
|
|
360
377
|
}
|
|
361
378
|
/**
|
|
362
379
|
* Get a node from storage
|
|
@@ -385,12 +402,14 @@ export class GcsStorage extends BaseStorage {
|
|
|
385
402
|
for (const [level, nounIds] of Object.entries(data.connections || {})) {
|
|
386
403
|
connections.set(Number(level), new Set(nounIds));
|
|
387
404
|
}
|
|
405
|
+
// CRITICAL: Only return lightweight vector data (no metadata)
|
|
406
|
+
// Metadata is retrieved separately via getNounMetadata() (2-file system)
|
|
388
407
|
const node = {
|
|
389
408
|
id: data.id,
|
|
390
409
|
vector: data.vector,
|
|
391
410
|
connections,
|
|
392
|
-
level: data.level || 0
|
|
393
|
-
|
|
411
|
+
level: data.level || 0
|
|
412
|
+
// NO metadata field - retrieved separately for scalability
|
|
394
413
|
};
|
|
395
414
|
// Update cache
|
|
396
415
|
this.nounCacheManager.set(id, node);
|
|
@@ -571,12 +590,16 @@ export class GcsStorage extends BaseStorage {
|
|
|
571
590
|
try {
|
|
572
591
|
this.logger.trace(`Saving edge ${edge.id}`);
|
|
573
592
|
// Convert connections Map to serializable format
|
|
593
|
+
// CRITICAL: Only save lightweight vector data (no metadata)
|
|
594
|
+
// Metadata is saved separately via saveVerbMetadata() (2-file system)
|
|
574
595
|
const serializableEdge = {
|
|
575
|
-
|
|
596
|
+
id: edge.id,
|
|
597
|
+
vector: edge.vector,
|
|
576
598
|
connections: Object.fromEntries(Array.from(edge.connections.entries()).map(([level, verbIds]) => [
|
|
577
599
|
level,
|
|
578
600
|
Array.from(verbIds)
|
|
579
601
|
]))
|
|
602
|
+
// NO metadata field - saved separately for scalability
|
|
580
603
|
};
|
|
581
604
|
// Get the GCS key with UUID-based sharding
|
|
582
605
|
const key = this.getVerbKey(edge.id);
|
|
@@ -608,9 +631,21 @@ export class GcsStorage extends BaseStorage {
|
|
|
608
631
|
}
|
|
609
632
|
/**
|
|
610
633
|
* Get a verb from storage (internal implementation)
|
|
634
|
+
* Combines vector data from getEdge() with metadata from getVerbMetadata()
|
|
611
635
|
*/
|
|
612
636
|
async getVerb_internal(id) {
|
|
613
|
-
|
|
637
|
+
// Get vector data (lightweight)
|
|
638
|
+
const edge = await this.getEdge(id);
|
|
639
|
+
if (!edge) {
|
|
640
|
+
return null;
|
|
641
|
+
}
|
|
642
|
+
// Get metadata (relationship data in 2-file system)
|
|
643
|
+
const metadata = await this.getVerbMetadata(id);
|
|
644
|
+
// Combine into complete verb object
|
|
645
|
+
return {
|
|
646
|
+
...edge,
|
|
647
|
+
metadata: metadata || {}
|
|
648
|
+
};
|
|
614
649
|
}
|
|
615
650
|
/**
|
|
616
651
|
* Get an edge from storage
|
|
@@ -1001,6 +1036,46 @@ export class GcsStorage extends BaseStorage {
|
|
|
1001
1036
|
filter: options?.filter
|
|
1002
1037
|
});
|
|
1003
1038
|
}
|
|
1039
|
+
/**
|
|
1040
|
+
* Batch fetch metadata for multiple noun IDs (efficient for large queries)
|
|
1041
|
+
* Uses smaller batches to prevent GCS socket exhaustion
|
|
1042
|
+
* @param ids Array of noun IDs to fetch metadata for
|
|
1043
|
+
* @returns Map of ID to metadata
|
|
1044
|
+
*/
|
|
1045
|
+
async getMetadataBatch(ids) {
|
|
1046
|
+
await this.ensureInitialized();
|
|
1047
|
+
const results = new Map();
|
|
1048
|
+
const batchSize = 10; // Smaller batches for metadata to prevent socket exhaustion
|
|
1049
|
+
// Process in smaller batches
|
|
1050
|
+
for (let i = 0; i < ids.length; i += batchSize) {
|
|
1051
|
+
const batch = ids.slice(i, i + batchSize);
|
|
1052
|
+
const batchPromises = batch.map(async (id) => {
|
|
1053
|
+
try {
|
|
1054
|
+
// CRITICAL: Use getNounMetadata() instead of deprecated getMetadata()
|
|
1055
|
+
// This ensures we fetch from the correct noun metadata store (2-file system)
|
|
1056
|
+
const metadata = await this.getNounMetadata(id);
|
|
1057
|
+
return { id, metadata };
|
|
1058
|
+
}
|
|
1059
|
+
catch (error) {
|
|
1060
|
+
// Handle GCS-specific errors
|
|
1061
|
+
if (this.isThrottlingError(error)) {
|
|
1062
|
+
await this.handleThrottling(error);
|
|
1063
|
+
}
|
|
1064
|
+
this.logger.debug(`Failed to read metadata for ${id}:`, error);
|
|
1065
|
+
return { id, metadata: null };
|
|
1066
|
+
}
|
|
1067
|
+
});
|
|
1068
|
+
const batchResults = await Promise.all(batchPromises);
|
|
1069
|
+
for (const { id, metadata } of batchResults) {
|
|
1070
|
+
if (metadata !== null) {
|
|
1071
|
+
results.set(id, metadata);
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
// Small yield between batches to prevent overwhelming GCS
|
|
1075
|
+
await new Promise(resolve => setImmediate(resolve));
|
|
1076
|
+
}
|
|
1077
|
+
return results;
|
|
1078
|
+
}
|
|
1004
1079
|
/**
|
|
1005
1080
|
* Clear all data from storage
|
|
1006
1081
|
*/
|
|
@@ -28,7 +28,8 @@ export declare class MemoryStorage extends BaseStorage {
|
|
|
28
28
|
*/
|
|
29
29
|
protected saveNoun_internal(noun: HNSWNoun): Promise<void>;
|
|
30
30
|
/**
|
|
31
|
-
* Get a noun from storage
|
|
31
|
+
* Get a noun from storage (internal implementation)
|
|
32
|
+
* Combines vector data from nouns map with metadata from getNounMetadata()
|
|
32
33
|
*/
|
|
33
34
|
protected getNoun_internal(id: string): Promise<HNSWNoun | null>;
|
|
34
35
|
/**
|
|
@@ -77,7 +78,8 @@ export declare class MemoryStorage extends BaseStorage {
|
|
|
77
78
|
*/
|
|
78
79
|
protected saveVerb_internal(verb: HNSWVerb): Promise<void>;
|
|
79
80
|
/**
|
|
80
|
-
* Get a verb from storage
|
|
81
|
+
* Get a verb from storage (internal implementation)
|
|
82
|
+
* Combines vector data from verbs map with metadata from getVerbMetadata()
|
|
81
83
|
*/
|
|
82
84
|
protected getVerb_internal(id: string): Promise<HNSWVerb | null>;
|
|
83
85
|
/**
|
|
@@ -41,12 +41,14 @@ export class MemoryStorage extends BaseStorage {
|
|
|
41
41
|
async saveNoun_internal(noun) {
|
|
42
42
|
const isNew = !this.nouns.has(noun.id);
|
|
43
43
|
// Create a deep copy to avoid reference issues
|
|
44
|
+
// CRITICAL: Only save lightweight vector data (no metadata)
|
|
45
|
+
// Metadata is saved separately via saveNounMetadata() (2-file system)
|
|
44
46
|
const nounCopy = {
|
|
45
47
|
id: noun.id,
|
|
46
48
|
vector: [...noun.vector],
|
|
47
49
|
connections: new Map(),
|
|
48
|
-
level: noun.level || 0
|
|
49
|
-
metadata
|
|
50
|
+
level: noun.level || 0
|
|
51
|
+
// NO metadata field - saved separately for scalability
|
|
50
52
|
};
|
|
51
53
|
// Copy connections
|
|
52
54
|
for (const [level, connections] of noun.connections.entries()) {
|
|
@@ -61,7 +63,8 @@ export class MemoryStorage extends BaseStorage {
|
|
|
61
63
|
}
|
|
62
64
|
}
|
|
63
65
|
/**
|
|
64
|
-
* Get a noun from storage
|
|
66
|
+
* Get a noun from storage (internal implementation)
|
|
67
|
+
* Combines vector data from nouns map with metadata from getNounMetadata()
|
|
65
68
|
*/
|
|
66
69
|
async getNoun_internal(id) {
|
|
67
70
|
// Get the noun directly from the nouns map
|
|
@@ -75,14 +78,19 @@ export class MemoryStorage extends BaseStorage {
|
|
|
75
78
|
id: noun.id,
|
|
76
79
|
vector: [...noun.vector],
|
|
77
80
|
connections: new Map(),
|
|
78
|
-
level: noun.level || 0
|
|
79
|
-
metadata: noun.metadata
|
|
81
|
+
level: noun.level || 0
|
|
80
82
|
};
|
|
81
83
|
// Copy connections
|
|
82
84
|
for (const [level, connections] of noun.connections.entries()) {
|
|
83
85
|
nounCopy.connections.set(level, new Set(connections));
|
|
84
86
|
}
|
|
85
|
-
|
|
87
|
+
// Get metadata (entity data in 2-file system)
|
|
88
|
+
const metadata = await this.getNounMetadata(id);
|
|
89
|
+
// Combine into complete noun object
|
|
90
|
+
return {
|
|
91
|
+
...nounCopy,
|
|
92
|
+
metadata: metadata || {}
|
|
93
|
+
};
|
|
86
94
|
}
|
|
87
95
|
/**
|
|
88
96
|
* Get nouns with pagination and filtering
|
|
@@ -233,7 +241,8 @@ export class MemoryStorage extends BaseStorage {
|
|
|
233
241
|
// since HNSWVerb doesn't contain type information
|
|
234
242
|
}
|
|
235
243
|
/**
|
|
236
|
-
* Get a verb from storage
|
|
244
|
+
* Get a verb from storage (internal implementation)
|
|
245
|
+
* Combines vector data from verbs map with metadata from getVerbMetadata()
|
|
237
246
|
*/
|
|
238
247
|
async getVerb_internal(id) {
|
|
239
248
|
// Get the verb directly from the verbs map
|
|
@@ -242,16 +251,6 @@ export class MemoryStorage extends BaseStorage {
|
|
|
242
251
|
if (!verb) {
|
|
243
252
|
return null;
|
|
244
253
|
}
|
|
245
|
-
// Create default timestamp if not present
|
|
246
|
-
const defaultTimestamp = {
|
|
247
|
-
seconds: Math.floor(Date.now() / 1000),
|
|
248
|
-
nanoseconds: (Date.now() % 1000) * 1000000
|
|
249
|
-
};
|
|
250
|
-
// Create default createdBy if not present
|
|
251
|
-
const defaultCreatedBy = {
|
|
252
|
-
augmentation: 'unknown',
|
|
253
|
-
version: '1.0'
|
|
254
|
-
};
|
|
255
254
|
// Return a deep copy of the HNSWVerb
|
|
256
255
|
const verbCopy = {
|
|
257
256
|
id: verb.id,
|
|
@@ -262,7 +261,13 @@ export class MemoryStorage extends BaseStorage {
|
|
|
262
261
|
for (const [level, connections] of verb.connections.entries()) {
|
|
263
262
|
verbCopy.connections.set(level, new Set(connections));
|
|
264
263
|
}
|
|
265
|
-
|
|
264
|
+
// Get metadata (relationship data in 2-file system)
|
|
265
|
+
const metadata = await this.getVerbMetadata(id);
|
|
266
|
+
// Combine into complete verb object
|
|
267
|
+
return {
|
|
268
|
+
...verbCopy,
|
|
269
|
+
metadata: metadata || {}
|
|
270
|
+
};
|
|
266
271
|
}
|
|
267
272
|
/**
|
|
268
273
|
* Get verbs with pagination and filtering
|
|
@@ -475,7 +480,9 @@ export class MemoryStorage extends BaseStorage {
|
|
|
475
480
|
const results = new Map();
|
|
476
481
|
// Memory storage can handle all IDs at once since it's in-memory
|
|
477
482
|
for (const id of ids) {
|
|
478
|
-
|
|
483
|
+
// CRITICAL: Use getNounMetadata() instead of deprecated getMetadata()
|
|
484
|
+
// This ensures we fetch from the correct noun metadata store (2-file system)
|
|
485
|
+
const metadata = await this.getNounMetadata(id);
|
|
479
486
|
if (metadata) {
|
|
480
487
|
results.set(id, metadata);
|
|
481
488
|
}
|
|
@@ -53,7 +53,8 @@ export declare class OPFSStorage extends BaseStorage {
|
|
|
53
53
|
*/
|
|
54
54
|
protected saveNoun_internal(noun: HNSWNoun_internal): Promise<void>;
|
|
55
55
|
/**
|
|
56
|
-
* Get a noun from storage
|
|
56
|
+
* Get a noun from storage (internal implementation)
|
|
57
|
+
* Combines vector data from file with metadata from getNounMetadata()
|
|
57
58
|
*/
|
|
58
59
|
protected getNoun_internal(id: string): Promise<HNSWNoun_internal | null>;
|
|
59
60
|
/**
|
|
@@ -86,6 +87,7 @@ export declare class OPFSStorage extends BaseStorage {
|
|
|
86
87
|
protected saveEdge(edge: Edge): Promise<void>;
|
|
87
88
|
/**
|
|
88
89
|
* Get a verb from storage (internal implementation)
|
|
90
|
+
* Combines vector data from getEdge() with metadata from getVerbMetadata()
|
|
89
91
|
*/
|
|
90
92
|
protected getVerb_internal(id: string): Promise<HNSWVerb | null>;
|
|
91
93
|
/**
|
|
@@ -143,10 +143,14 @@ export class OPFSStorage extends BaseStorage {
|
|
|
143
143
|
async saveNoun_internal(noun) {
|
|
144
144
|
await this.ensureInitialized();
|
|
145
145
|
try {
|
|
146
|
-
//
|
|
146
|
+
// CRITICAL: Only save lightweight vector data (no metadata)
|
|
147
|
+
// Metadata is saved separately via saveNounMetadata() (2-file system)
|
|
147
148
|
const serializableNoun = {
|
|
148
|
-
|
|
149
|
-
|
|
149
|
+
id: noun.id,
|
|
150
|
+
vector: noun.vector,
|
|
151
|
+
connections: this.mapToObject(noun.connections, (set) => Array.from(set)),
|
|
152
|
+
level: noun.level || 0
|
|
153
|
+
// NO metadata field - saved separately for scalability
|
|
150
154
|
};
|
|
151
155
|
// Use UUID-based sharding for nouns
|
|
152
156
|
const shardId = getShardIdFromUuid(noun.id);
|
|
@@ -169,7 +173,8 @@ export class OPFSStorage extends BaseStorage {
|
|
|
169
173
|
}
|
|
170
174
|
}
|
|
171
175
|
/**
|
|
172
|
-
* Get a noun from storage
|
|
176
|
+
* Get a noun from storage (internal implementation)
|
|
177
|
+
* Combines vector data from file with metadata from getNounMetadata()
|
|
173
178
|
*/
|
|
174
179
|
async getNoun_internal(id) {
|
|
175
180
|
await this.ensureInitialized();
|
|
@@ -189,12 +194,19 @@ export class OPFSStorage extends BaseStorage {
|
|
|
189
194
|
for (const [level, nounIds] of Object.entries(data.connections)) {
|
|
190
195
|
connections.set(Number(level), new Set(nounIds));
|
|
191
196
|
}
|
|
192
|
-
|
|
197
|
+
const node = {
|
|
193
198
|
id: data.id,
|
|
194
199
|
vector: data.vector,
|
|
195
200
|
connections,
|
|
196
201
|
level: data.level || 0
|
|
197
202
|
};
|
|
203
|
+
// Get metadata (entity data in 2-file system)
|
|
204
|
+
const metadata = await this.getNounMetadata(id);
|
|
205
|
+
// Combine into complete noun object
|
|
206
|
+
return {
|
|
207
|
+
...node,
|
|
208
|
+
metadata: metadata || {}
|
|
209
|
+
};
|
|
198
210
|
}
|
|
199
211
|
catch (error) {
|
|
200
212
|
// Noun not found or other error
|
|
@@ -299,10 +311,13 @@ export class OPFSStorage extends BaseStorage {
|
|
|
299
311
|
async saveEdge(edge) {
|
|
300
312
|
await this.ensureInitialized();
|
|
301
313
|
try {
|
|
302
|
-
//
|
|
314
|
+
// CRITICAL: Only save lightweight vector data (no metadata)
|
|
315
|
+
// Metadata is saved separately via saveVerbMetadata() (2-file system)
|
|
303
316
|
const serializableEdge = {
|
|
304
|
-
|
|
317
|
+
id: edge.id,
|
|
318
|
+
vector: edge.vector,
|
|
305
319
|
connections: this.mapToObject(edge.connections, (set) => Array.from(set))
|
|
320
|
+
// NO metadata field - saved separately for scalability
|
|
306
321
|
};
|
|
307
322
|
// Use UUID-based sharding for verbs
|
|
308
323
|
const shardId = getShardIdFromUuid(edge.id);
|
|
@@ -326,9 +341,21 @@ export class OPFSStorage extends BaseStorage {
|
|
|
326
341
|
}
|
|
327
342
|
/**
|
|
328
343
|
* Get a verb from storage (internal implementation)
|
|
344
|
+
* Combines vector data from getEdge() with metadata from getVerbMetadata()
|
|
329
345
|
*/
|
|
330
346
|
async getVerb_internal(id) {
|
|
331
|
-
|
|
347
|
+
// Get vector data (lightweight)
|
|
348
|
+
const edge = await this.getEdge(id);
|
|
349
|
+
if (!edge) {
|
|
350
|
+
return null;
|
|
351
|
+
}
|
|
352
|
+
// Get metadata (relationship data in 2-file system)
|
|
353
|
+
const metadata = await this.getVerbMetadata(id);
|
|
354
|
+
// Combine into complete verb object
|
|
355
|
+
return {
|
|
356
|
+
...edge,
|
|
357
|
+
metadata: metadata || {}
|
|
358
|
+
};
|
|
332
359
|
}
|
|
333
360
|
/**
|
|
334
361
|
* Get an edge from storage
|
|
@@ -222,6 +222,7 @@ export declare class S3CompatibleStorage extends BaseStorage {
|
|
|
222
222
|
protected saveNode(node: HNSWNode): Promise<void>;
|
|
223
223
|
/**
|
|
224
224
|
* Get a noun from storage (internal implementation)
|
|
225
|
+
* Combines vector data from getNode() with metadata from getNounMetadata()
|
|
225
226
|
*/
|
|
226
227
|
protected getNoun_internal(id: string): Promise<HNSWNoun | null>;
|
|
227
228
|
/**
|
|
@@ -297,6 +298,7 @@ export declare class S3CompatibleStorage extends BaseStorage {
|
|
|
297
298
|
protected saveEdge(edge: Edge): Promise<void>;
|
|
298
299
|
/**
|
|
299
300
|
* Get a verb from storage (internal implementation)
|
|
301
|
+
* Combines vector data from getEdge() with metadata from getVerbMetadata()
|
|
300
302
|
*/
|
|
301
303
|
protected getVerb_internal(id: string): Promise<HNSWVerb | null>;
|
|
302
304
|
/**
|
|
@@ -717,9 +717,14 @@ export class S3CompatibleStorage extends BaseStorage {
|
|
|
717
717
|
try {
|
|
718
718
|
this.logger.trace(`Saving node ${node.id}`);
|
|
719
719
|
// Convert connections Map to a serializable format
|
|
720
|
+
// CRITICAL: Only save lightweight vector data (no metadata)
|
|
721
|
+
// Metadata is saved separately via saveNounMetadata() (2-file system)
|
|
720
722
|
const serializableNode = {
|
|
721
|
-
|
|
722
|
-
|
|
723
|
+
id: node.id,
|
|
724
|
+
vector: node.vector,
|
|
725
|
+
connections: this.mapToObject(node.connections, (set) => Array.from(set)),
|
|
726
|
+
level: node.level || 0
|
|
727
|
+
// NO metadata field - saved separately for scalability
|
|
723
728
|
};
|
|
724
729
|
// Import the PutObjectCommand only when needed
|
|
725
730
|
const { PutObjectCommand } = await import('@aws-sdk/client-s3');
|
|
@@ -763,6 +768,13 @@ export class S3CompatibleStorage extends BaseStorage {
|
|
|
763
768
|
catch (verifyError) {
|
|
764
769
|
this.logger.warn(`Failed to verify node ${node.id} was saved correctly:`, verifyError);
|
|
765
770
|
}
|
|
771
|
+
// Increment noun count - always increment total, and increment by type if metadata exists
|
|
772
|
+
this.totalNounCount++;
|
|
773
|
+
const metadata = await this.getNounMetadata(node.id);
|
|
774
|
+
if (metadata && metadata.type) {
|
|
775
|
+
const currentCount = this.entityCounts.get(metadata.type) || 0;
|
|
776
|
+
this.entityCounts.set(metadata.type, currentCount + 1);
|
|
777
|
+
}
|
|
766
778
|
// Release backpressure on success
|
|
767
779
|
this.releaseBackpressure(true, requestId);
|
|
768
780
|
}
|
|
@@ -775,9 +787,21 @@ export class S3CompatibleStorage extends BaseStorage {
|
|
|
775
787
|
}
|
|
776
788
|
/**
|
|
777
789
|
* Get a noun from storage (internal implementation)
|
|
790
|
+
* Combines vector data from getNode() with metadata from getNounMetadata()
|
|
778
791
|
*/
|
|
779
792
|
async getNoun_internal(id) {
|
|
780
|
-
|
|
793
|
+
// Get vector data (lightweight)
|
|
794
|
+
const node = await this.getNode(id);
|
|
795
|
+
if (!node) {
|
|
796
|
+
return null;
|
|
797
|
+
}
|
|
798
|
+
// Get metadata (entity data in 2-file system)
|
|
799
|
+
const metadata = await this.getNounMetadata(id);
|
|
800
|
+
// Combine into complete noun object
|
|
801
|
+
return {
|
|
802
|
+
...node,
|
|
803
|
+
metadata: metadata || {}
|
|
804
|
+
};
|
|
781
805
|
}
|
|
782
806
|
/**
|
|
783
807
|
* Get a node from storage
|
|
@@ -1112,9 +1136,13 @@ export class S3CompatibleStorage extends BaseStorage {
|
|
|
1112
1136
|
const requestId = await this.applyBackpressure();
|
|
1113
1137
|
try {
|
|
1114
1138
|
// Convert connections Map to a serializable format
|
|
1139
|
+
// CRITICAL: Only save lightweight vector data (no metadata)
|
|
1140
|
+
// Metadata is saved separately via saveVerbMetadata() (2-file system)
|
|
1115
1141
|
const serializableEdge = {
|
|
1116
|
-
|
|
1142
|
+
id: edge.id,
|
|
1143
|
+
vector: edge.vector,
|
|
1117
1144
|
connections: this.mapToObject(edge.connections, (set) => Array.from(set))
|
|
1145
|
+
// NO metadata field - saved separately for scalability
|
|
1118
1146
|
};
|
|
1119
1147
|
// Import the PutObjectCommand only when needed
|
|
1120
1148
|
const { PutObjectCommand } = await import('@aws-sdk/client-s3');
|
|
@@ -1135,6 +1163,13 @@ export class S3CompatibleStorage extends BaseStorage {
|
|
|
1135
1163
|
vector: edge.vector
|
|
1136
1164
|
}
|
|
1137
1165
|
});
|
|
1166
|
+
// Increment verb count - always increment total, and increment by type if metadata exists
|
|
1167
|
+
this.totalVerbCount++;
|
|
1168
|
+
const metadata = await this.getVerbMetadata(edge.id);
|
|
1169
|
+
if (metadata && metadata.type) {
|
|
1170
|
+
const currentCount = this.verbCounts.get(metadata.type) || 0;
|
|
1171
|
+
this.verbCounts.set(metadata.type, currentCount + 1);
|
|
1172
|
+
}
|
|
1138
1173
|
// Release backpressure on success
|
|
1139
1174
|
this.releaseBackpressure(true, requestId);
|
|
1140
1175
|
}
|
|
@@ -1147,9 +1182,21 @@ export class S3CompatibleStorage extends BaseStorage {
|
|
|
1147
1182
|
}
|
|
1148
1183
|
/**
|
|
1149
1184
|
* Get a verb from storage (internal implementation)
|
|
1185
|
+
* Combines vector data from getEdge() with metadata from getVerbMetadata()
|
|
1150
1186
|
*/
|
|
1151
1187
|
async getVerb_internal(id) {
|
|
1152
|
-
|
|
1188
|
+
// Get vector data (lightweight)
|
|
1189
|
+
const edge = await this.getEdge(id);
|
|
1190
|
+
if (!edge) {
|
|
1191
|
+
return null;
|
|
1192
|
+
}
|
|
1193
|
+
// Get metadata (relationship data in 2-file system)
|
|
1194
|
+
const metadata = await this.getVerbMetadata(id);
|
|
1195
|
+
// Combine into complete verb object
|
|
1196
|
+
return {
|
|
1197
|
+
...edge,
|
|
1198
|
+
metadata: metadata || {}
|
|
1199
|
+
};
|
|
1153
1200
|
}
|
|
1154
1201
|
/**
|
|
1155
1202
|
* Get an edge from storage
|
|
@@ -1643,8 +1690,10 @@ export class S3CompatibleStorage extends BaseStorage {
|
|
|
1643
1690
|
const batchPromises = batch.map(async (id) => {
|
|
1644
1691
|
try {
|
|
1645
1692
|
// Add timeout wrapper for individual metadata reads
|
|
1693
|
+
// CRITICAL: Use getNounMetadata() instead of deprecated getMetadata()
|
|
1694
|
+
// This ensures we fetch from the correct noun metadata store (2-file system)
|
|
1646
1695
|
const metadata = await Promise.race([
|
|
1647
|
-
this.
|
|
1696
|
+
this.getNounMetadata(id),
|
|
1648
1697
|
new Promise((_, reject) => setTimeout(() => reject(new Error('Metadata read timeout')), 5000) // 5 second timeout
|
|
1649
1698
|
)
|
|
1650
1699
|
]);
|
|
@@ -229,6 +229,11 @@ export declare abstract class BaseStorage extends BaseStorageAdapter {
|
|
|
229
229
|
* Uses routing logic to handle both UUIDs (sharded) and system keys (unsharded)
|
|
230
230
|
*/
|
|
231
231
|
getNounMetadata(id: string): Promise<any | null>;
|
|
232
|
+
/**
|
|
233
|
+
* Delete noun metadata from storage
|
|
234
|
+
* Uses routing logic to handle both UUIDs (sharded) and system keys (unsharded)
|
|
235
|
+
*/
|
|
236
|
+
deleteNounMetadata(id: string): Promise<void>;
|
|
232
237
|
/**
|
|
233
238
|
* Save verb metadata to storage
|
|
234
239
|
* Routes to correct sharded location based on UUID
|
|
@@ -141,11 +141,33 @@ export class BaseStorage extends BaseStorageAdapter {
|
|
|
141
141
|
async saveNoun(noun) {
|
|
142
142
|
await this.ensureInitialized();
|
|
143
143
|
// Validate noun type before saving - storage boundary protection
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
144
|
+
if (noun.metadata?.noun) {
|
|
145
|
+
validateNounType(noun.metadata.noun);
|
|
146
|
+
}
|
|
147
|
+
// Save both the HNSWNoun vector data and metadata separately (2-file system)
|
|
148
|
+
try {
|
|
149
|
+
// Save the lightweight HNSWNoun vector file first
|
|
150
|
+
await this.saveNoun_internal(noun);
|
|
151
|
+
// Then save the metadata to separate file (if present)
|
|
152
|
+
if (noun.metadata) {
|
|
153
|
+
await this.saveNounMetadata(noun.id, noun.metadata);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
console.error(`[ERROR] Failed to save noun ${noun.id}:`, error);
|
|
158
|
+
// Attempt cleanup - remove noun file if metadata failed
|
|
159
|
+
try {
|
|
160
|
+
const nounExists = await this.getNoun_internal(noun.id);
|
|
161
|
+
if (nounExists) {
|
|
162
|
+
console.log(`[CLEANUP] Attempting to remove orphaned noun file ${noun.id}`);
|
|
163
|
+
await this.deleteNoun_internal(noun.id);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
catch (cleanupError) {
|
|
167
|
+
console.error(`[ERROR] Failed to cleanup orphaned noun ${noun.id}:`, cleanupError);
|
|
168
|
+
}
|
|
169
|
+
throw new Error(`Failed to save noun ${noun.id}: ${error instanceof Error ? error.message : String(error)}`);
|
|
147
170
|
}
|
|
148
|
-
return this.saveNoun_internal(noun);
|
|
149
171
|
}
|
|
150
172
|
/**
|
|
151
173
|
* Get a noun from storage
|
|
@@ -168,7 +190,16 @@ export class BaseStorage extends BaseStorageAdapter {
|
|
|
168
190
|
*/
|
|
169
191
|
async deleteNoun(id) {
|
|
170
192
|
await this.ensureInitialized();
|
|
171
|
-
|
|
193
|
+
// Delete both the vector file and metadata file (2-file system)
|
|
194
|
+
await this.deleteNoun_internal(id);
|
|
195
|
+
// Delete metadata file (if it exists)
|
|
196
|
+
try {
|
|
197
|
+
await this.deleteNounMetadata(id);
|
|
198
|
+
}
|
|
199
|
+
catch (error) {
|
|
200
|
+
// Ignore if metadata file doesn't exist
|
|
201
|
+
console.debug(`No metadata file to delete for noun ${id}`);
|
|
202
|
+
}
|
|
172
203
|
}
|
|
173
204
|
/**
|
|
174
205
|
* Save a verb to storage
|
|
@@ -618,7 +649,16 @@ export class BaseStorage extends BaseStorageAdapter {
|
|
|
618
649
|
*/
|
|
619
650
|
async deleteVerb(id) {
|
|
620
651
|
await this.ensureInitialized();
|
|
621
|
-
|
|
652
|
+
// Delete both the vector file and metadata file (2-file system)
|
|
653
|
+
await this.deleteVerb_internal(id);
|
|
654
|
+
// Delete metadata file (if it exists)
|
|
655
|
+
try {
|
|
656
|
+
await this.deleteVerbMetadata(id);
|
|
657
|
+
}
|
|
658
|
+
catch (error) {
|
|
659
|
+
// Ignore if metadata file doesn't exist
|
|
660
|
+
console.debug(`No metadata file to delete for verb ${id}`);
|
|
661
|
+
}
|
|
622
662
|
}
|
|
623
663
|
/**
|
|
624
664
|
* Get graph index (lazy initialization)
|
|
@@ -684,6 +724,15 @@ export class BaseStorage extends BaseStorageAdapter {
|
|
|
684
724
|
const keyInfo = this.analyzeKey(id, 'noun-metadata');
|
|
685
725
|
return this.readObjectFromPath(keyInfo.fullPath);
|
|
686
726
|
}
|
|
727
|
+
/**
|
|
728
|
+
* Delete noun metadata from storage
|
|
729
|
+
* Uses routing logic to handle both UUIDs (sharded) and system keys (unsharded)
|
|
730
|
+
*/
|
|
731
|
+
async deleteNounMetadata(id) {
|
|
732
|
+
await this.ensureInitialized();
|
|
733
|
+
const keyInfo = this.analyzeKey(id, 'noun-metadata');
|
|
734
|
+
return this.deleteObjectFromPath(keyInfo.fullPath);
|
|
735
|
+
}
|
|
687
736
|
/**
|
|
688
737
|
* Save verb metadata to storage
|
|
689
738
|
* Routes to correct sharded location based on UUID
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@soulcraft/brainy",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.37.1",
|
|
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",
|