@soulcraft/brainy 3.8.3 → 3.9.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/README.md +26 -28
- package/dist/brainy.d.ts +27 -0
- package/dist/brainy.js +231 -10
- package/dist/coreTypes.d.ts +10 -0
- package/dist/hnsw/hnswIndex.d.ts +2 -0
- package/dist/hnsw/hnswIndex.js +10 -0
- package/dist/neural/improvedNeuralAPI.d.ts +14 -1
- package/dist/neural/improvedNeuralAPI.js +59 -20
- package/dist/neural/naturalLanguageProcessorStatic.d.ts +1 -0
- package/dist/neural/naturalLanguageProcessorStatic.js +3 -2
- package/dist/storage/adapters/baseStorageAdapter.d.ts +59 -0
- package/dist/storage/adapters/baseStorageAdapter.js +137 -0
- package/dist/storage/adapters/fileSystemStorage.d.ts +41 -0
- package/dist/storage/adapters/fileSystemStorage.js +227 -19
- package/dist/storage/adapters/memoryStorage.d.ts +8 -0
- package/dist/storage/adapters/memoryStorage.js +48 -1
- package/dist/storage/adapters/opfsStorage.d.ts +12 -0
- package/dist/storage/adapters/opfsStorage.js +68 -0
- package/dist/storage/adapters/s3CompatibleStorage.d.ts +34 -0
- package/dist/storage/adapters/s3CompatibleStorage.js +129 -3
- package/dist/storage/baseStorage.js +4 -3
- package/dist/storage/readOnlyOptimizations.d.ts +0 -9
- package/dist/storage/readOnlyOptimizations.js +6 -28
- package/dist/types/brainy.types.d.ts +15 -0
- package/dist/utils/metadataIndex.d.ts +5 -0
- package/dist/utils/metadataIndex.js +24 -0
- package/dist/utils/mutex.d.ts +53 -0
- package/dist/utils/mutex.js +221 -0
- package/dist/utils/paramValidation.js +20 -4
- package/package.json +1 -1
|
@@ -37,8 +37,13 @@ export class FileSystemStorage extends BaseStorage {
|
|
|
37
37
|
*/
|
|
38
38
|
constructor(rootDirectory) {
|
|
39
39
|
super();
|
|
40
|
+
// Intelligent sharding configuration
|
|
41
|
+
this.shardingDepth = 2; // 0=flat, 1=ab/, 2=ab/cd/
|
|
42
|
+
this.SHARDING_THRESHOLD = 1000; // Enable deep sharding at 1k files
|
|
40
43
|
this.useDualWrite = true; // Write to both locations during migration
|
|
41
44
|
this.activeLocks = new Set();
|
|
45
|
+
this.lockTimers = new Map(); // Track timers for cleanup
|
|
46
|
+
this.allTimers = new Set(); // Track all timers for cleanup
|
|
42
47
|
this.rootDir = rootDirectory;
|
|
43
48
|
// Defer path operations until init() when path module is guaranteed to be loaded
|
|
44
49
|
}
|
|
@@ -92,6 +97,14 @@ export class FileSystemStorage extends BaseStorage {
|
|
|
92
97
|
}
|
|
93
98
|
// Create the locks directory if it doesn't exist
|
|
94
99
|
await this.ensureDirectoryExists(this.lockDir);
|
|
100
|
+
// Initialize count management
|
|
101
|
+
this.countsFilePath = path.join(this.systemDir, 'counts.json');
|
|
102
|
+
await this.initializeCounts();
|
|
103
|
+
// Cache sharding depth for consistency during this session
|
|
104
|
+
this.cachedShardingDepth = this.getOptimalShardingDepth();
|
|
105
|
+
// Log sharding strategy for transparency
|
|
106
|
+
const strategy = this.cachedShardingDepth === 0 ? 'flat' : this.cachedShardingDepth === 1 ? 'single-level' : 'deep';
|
|
107
|
+
console.log(`📁 Using ${strategy} sharding for optimal performance (${this.totalNounCount} items)`);
|
|
95
108
|
this.isInitialized = true;
|
|
96
109
|
}
|
|
97
110
|
catch (error) {
|
|
@@ -130,20 +143,33 @@ export class FileSystemStorage extends BaseStorage {
|
|
|
130
143
|
*/
|
|
131
144
|
async saveNode(node) {
|
|
132
145
|
await this.ensureInitialized();
|
|
146
|
+
// Check if this is a new node to update counts
|
|
147
|
+
const isNew = !(await this.fileExists(this.getNodePath(node.id)));
|
|
133
148
|
// Convert connections Map to a serializable format
|
|
134
149
|
const serializableNode = {
|
|
135
150
|
...node,
|
|
136
151
|
connections: this.mapToObject(node.connections, (set) => Array.from(set))
|
|
137
152
|
};
|
|
138
|
-
const filePath =
|
|
153
|
+
const filePath = this.getNodePath(node.id);
|
|
154
|
+
await this.ensureDirectoryExists(path.dirname(filePath));
|
|
139
155
|
await fs.promises.writeFile(filePath, JSON.stringify(serializableNode, null, 2));
|
|
156
|
+
// Update counts for new nodes (intelligent type detection)
|
|
157
|
+
if (isNew) {
|
|
158
|
+
const type = node.metadata?.type || node.metadata?.nounType || 'default';
|
|
159
|
+
this.incrementEntityCount(type);
|
|
160
|
+
// Persist counts periodically (every 10 operations for efficiency)
|
|
161
|
+
if (this.totalNounCount % 10 === 0) {
|
|
162
|
+
await this.persistCounts();
|
|
163
|
+
}
|
|
164
|
+
}
|
|
140
165
|
}
|
|
141
166
|
/**
|
|
142
167
|
* Get a node from storage
|
|
143
168
|
*/
|
|
144
169
|
async getNode(id) {
|
|
145
170
|
await this.ensureInitialized();
|
|
146
|
-
|
|
171
|
+
// Clean, predictable path - no backward compatibility needed
|
|
172
|
+
const filePath = this.getNodePath(id);
|
|
147
173
|
try {
|
|
148
174
|
const data = await fs.promises.readFile(filePath, 'utf-8');
|
|
149
175
|
const parsedNode = JSON.parse(data);
|
|
@@ -246,9 +272,24 @@ export class FileSystemStorage extends BaseStorage {
|
|
|
246
272
|
*/
|
|
247
273
|
async deleteNode(id) {
|
|
248
274
|
await this.ensureInitialized();
|
|
249
|
-
const filePath =
|
|
275
|
+
const filePath = this.getNodePath(id);
|
|
276
|
+
// Load node to get type for count update
|
|
277
|
+
try {
|
|
278
|
+
const node = await this.getNode(id);
|
|
279
|
+
if (node) {
|
|
280
|
+
const type = node.metadata?.type || node.metadata?.nounType || 'default';
|
|
281
|
+
this.decrementEntityCount(type);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
catch {
|
|
285
|
+
// Node might not exist, that's ok
|
|
286
|
+
}
|
|
250
287
|
try {
|
|
251
288
|
await fs.promises.unlink(filePath);
|
|
289
|
+
// Persist counts periodically
|
|
290
|
+
if (this.totalNounCount % 10 === 0) {
|
|
291
|
+
await this.persistCounts();
|
|
292
|
+
}
|
|
252
293
|
}
|
|
253
294
|
catch (error) {
|
|
254
295
|
if (error.code !== 'ENOENT') {
|
|
@@ -267,7 +308,8 @@ export class FileSystemStorage extends BaseStorage {
|
|
|
267
308
|
...edge,
|
|
268
309
|
connections: this.mapToObject(edge.connections, (set) => Array.from(set))
|
|
269
310
|
};
|
|
270
|
-
const filePath =
|
|
311
|
+
const filePath = this.getVerbPath(edge.id);
|
|
312
|
+
await this.ensureDirectoryExists(path.dirname(filePath));
|
|
271
313
|
await fs.promises.writeFile(filePath, JSON.stringify(serializableEdge, null, 2));
|
|
272
314
|
}
|
|
273
315
|
/**
|
|
@@ -275,7 +317,7 @@ export class FileSystemStorage extends BaseStorage {
|
|
|
275
317
|
*/
|
|
276
318
|
async getEdge(id) {
|
|
277
319
|
await this.ensureInitialized();
|
|
278
|
-
const filePath =
|
|
320
|
+
const filePath = this.getVerbPath(id);
|
|
279
321
|
try {
|
|
280
322
|
const data = await fs.promises.readFile(filePath, 'utf-8');
|
|
281
323
|
const parsedEdge = JSON.parse(data);
|
|
@@ -494,9 +536,15 @@ export class FileSystemStorage extends BaseStorage {
|
|
|
494
536
|
const nounFiles = files.filter((f) => f.endsWith('.json'));
|
|
495
537
|
// Sort for consistent pagination
|
|
496
538
|
nounFiles.sort();
|
|
497
|
-
// Find starting position
|
|
539
|
+
// Find starting position - prioritize offset for O(1) operation
|
|
498
540
|
let startIndex = 0;
|
|
499
|
-
|
|
541
|
+
const offset = options.offset; // Cast to any since offset might not be in type
|
|
542
|
+
if (offset !== undefined) {
|
|
543
|
+
// Direct offset - O(1) operation
|
|
544
|
+
startIndex = offset;
|
|
545
|
+
}
|
|
546
|
+
else if (cursor) {
|
|
547
|
+
// Cursor-based pagination
|
|
500
548
|
startIndex = nounFiles.findIndex((f) => f.replace('.json', '') > cursor);
|
|
501
549
|
if (startIndex === -1)
|
|
502
550
|
startIndex = nounFiles.length;
|
|
@@ -507,18 +555,10 @@ export class FileSystemStorage extends BaseStorage {
|
|
|
507
555
|
const items = [];
|
|
508
556
|
let successfullyLoaded = 0;
|
|
509
557
|
let totalValidFiles = 0;
|
|
510
|
-
//
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
// Just check if file exists and is readable
|
|
515
|
-
await fs.promises.access(path.join(this.nounsDir, file), fs.constants.R_OK);
|
|
516
|
-
totalValidFiles++;
|
|
517
|
-
}
|
|
518
|
-
catch {
|
|
519
|
-
// File not readable, skip
|
|
520
|
-
}
|
|
521
|
-
}
|
|
558
|
+
// Use persisted counts - O(1) operation!
|
|
559
|
+
totalValidFiles = this.totalNounCount;
|
|
560
|
+
// No need to count files anymore - we maintain accurate counts
|
|
561
|
+
// This eliminates the O(n) operation completely
|
|
522
562
|
// Second pass: load the current page
|
|
523
563
|
for (const file of pageFiles) {
|
|
524
564
|
try {
|
|
@@ -1253,5 +1293,173 @@ export class FileSystemStorage extends BaseStorage {
|
|
|
1253
1293
|
lastUpdated: new Date().toISOString()
|
|
1254
1294
|
};
|
|
1255
1295
|
}
|
|
1296
|
+
// =============================================
|
|
1297
|
+
// Count Management for O(1) Scalability
|
|
1298
|
+
// =============================================
|
|
1299
|
+
/**
|
|
1300
|
+
* Initialize counts from filesystem storage
|
|
1301
|
+
*/
|
|
1302
|
+
async initializeCounts() {
|
|
1303
|
+
if (!this.countsFilePath)
|
|
1304
|
+
return;
|
|
1305
|
+
try {
|
|
1306
|
+
if (await this.fileExists(this.countsFilePath)) {
|
|
1307
|
+
const data = await fs.promises.readFile(this.countsFilePath, 'utf-8');
|
|
1308
|
+
const counts = JSON.parse(data);
|
|
1309
|
+
// Restore entity counts
|
|
1310
|
+
this.entityCounts = new Map(Object.entries(counts.entityCounts || {}));
|
|
1311
|
+
this.verbCounts = new Map(Object.entries(counts.verbCounts || {}));
|
|
1312
|
+
this.totalNounCount = counts.totalNounCount || 0;
|
|
1313
|
+
this.totalVerbCount = counts.totalVerbCount || 0;
|
|
1314
|
+
// Also populate the cache for backward compatibility
|
|
1315
|
+
this.countCache.set('nouns_count', {
|
|
1316
|
+
count: this.totalNounCount,
|
|
1317
|
+
timestamp: Date.now()
|
|
1318
|
+
});
|
|
1319
|
+
this.countCache.set('verbs_count', {
|
|
1320
|
+
count: this.totalVerbCount,
|
|
1321
|
+
timestamp: Date.now()
|
|
1322
|
+
});
|
|
1323
|
+
}
|
|
1324
|
+
else {
|
|
1325
|
+
// If no counts file exists, do one initial count
|
|
1326
|
+
await this.initializeCountsFromDisk();
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
catch (error) {
|
|
1330
|
+
console.warn('Could not load persisted counts, will initialize from disk:', error);
|
|
1331
|
+
await this.initializeCountsFromDisk();
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
/**
|
|
1335
|
+
* Initialize counts by scanning disk (only done once)
|
|
1336
|
+
*/
|
|
1337
|
+
async initializeCountsFromDisk() {
|
|
1338
|
+
try {
|
|
1339
|
+
// Count nouns
|
|
1340
|
+
const nounFiles = await fs.promises.readdir(this.nounsDir);
|
|
1341
|
+
const validNounFiles = nounFiles.filter((f) => f.endsWith('.json'));
|
|
1342
|
+
this.totalNounCount = validNounFiles.length;
|
|
1343
|
+
// Count verbs
|
|
1344
|
+
const verbFiles = await fs.promises.readdir(this.verbsDir);
|
|
1345
|
+
const validVerbFiles = verbFiles.filter((f) => f.endsWith('.json'));
|
|
1346
|
+
this.totalVerbCount = validVerbFiles.length;
|
|
1347
|
+
// Sample some files to get type distribution (don't read all)
|
|
1348
|
+
const sampleSize = Math.min(100, validNounFiles.length);
|
|
1349
|
+
for (let i = 0; i < sampleSize; i++) {
|
|
1350
|
+
try {
|
|
1351
|
+
const file = validNounFiles[i];
|
|
1352
|
+
const data = await fs.promises.readFile(path.join(this.nounsDir, file), 'utf-8');
|
|
1353
|
+
const noun = JSON.parse(data);
|
|
1354
|
+
const type = noun.metadata?.type || noun.metadata?.nounType || 'default';
|
|
1355
|
+
this.entityCounts.set(type, (this.entityCounts.get(type) || 0) + 1);
|
|
1356
|
+
}
|
|
1357
|
+
catch {
|
|
1358
|
+
// Skip invalid files
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
// Extrapolate counts if we sampled
|
|
1362
|
+
if (sampleSize < this.totalNounCount && sampleSize > 0) {
|
|
1363
|
+
const multiplier = this.totalNounCount / sampleSize;
|
|
1364
|
+
for (const [type, count] of this.entityCounts.entries()) {
|
|
1365
|
+
this.entityCounts.set(type, Math.round(count * multiplier));
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
await this.persistCounts();
|
|
1369
|
+
}
|
|
1370
|
+
catch (error) {
|
|
1371
|
+
console.error('Error initializing counts from disk:', error);
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
/**
|
|
1375
|
+
* Persist counts to filesystem storage
|
|
1376
|
+
*/
|
|
1377
|
+
async persistCounts() {
|
|
1378
|
+
if (!this.countsFilePath)
|
|
1379
|
+
return;
|
|
1380
|
+
try {
|
|
1381
|
+
const counts = {
|
|
1382
|
+
entityCounts: Object.fromEntries(this.entityCounts),
|
|
1383
|
+
verbCounts: Object.fromEntries(this.verbCounts),
|
|
1384
|
+
totalNounCount: this.totalNounCount,
|
|
1385
|
+
totalVerbCount: this.totalVerbCount,
|
|
1386
|
+
lastUpdated: new Date().toISOString()
|
|
1387
|
+
};
|
|
1388
|
+
await fs.promises.writeFile(this.countsFilePath, JSON.stringify(counts, null, 2));
|
|
1389
|
+
}
|
|
1390
|
+
catch (error) {
|
|
1391
|
+
console.error('Error persisting counts:', error);
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
// =============================================
|
|
1395
|
+
// Intelligent Directory Sharding
|
|
1396
|
+
// =============================================
|
|
1397
|
+
/**
|
|
1398
|
+
* Determine optimal sharding depth based on dataset size
|
|
1399
|
+
* This is called once during initialization for consistent behavior
|
|
1400
|
+
*/
|
|
1401
|
+
getOptimalShardingDepth() {
|
|
1402
|
+
// For new installations, use intelligent defaults
|
|
1403
|
+
if (this.totalNounCount === 0 && this.totalVerbCount === 0) {
|
|
1404
|
+
return 1; // Default to single-level sharding for new installs
|
|
1405
|
+
}
|
|
1406
|
+
const maxCount = Math.max(this.totalNounCount, this.totalVerbCount);
|
|
1407
|
+
if (maxCount >= this.SHARDING_THRESHOLD) {
|
|
1408
|
+
return 2; // Deep sharding for large datasets
|
|
1409
|
+
}
|
|
1410
|
+
else if (maxCount >= 100) {
|
|
1411
|
+
return 1; // Single-level sharding for medium datasets
|
|
1412
|
+
}
|
|
1413
|
+
else {
|
|
1414
|
+
return 1; // Always use at least single-level sharding for consistency
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
/**
|
|
1418
|
+
* Get the path for a node with consistent sharding strategy
|
|
1419
|
+
* Clean, predictable path generation
|
|
1420
|
+
*/
|
|
1421
|
+
getNodePath(id) {
|
|
1422
|
+
return this.getShardedPath(this.nounsDir, id);
|
|
1423
|
+
}
|
|
1424
|
+
/**
|
|
1425
|
+
* Get the path for a verb with consistent sharding strategy
|
|
1426
|
+
*/
|
|
1427
|
+
getVerbPath(id) {
|
|
1428
|
+
return this.getShardedPath(this.verbsDir, id);
|
|
1429
|
+
}
|
|
1430
|
+
/**
|
|
1431
|
+
* Universal sharded path generator
|
|
1432
|
+
* Consistent across all entity types
|
|
1433
|
+
*/
|
|
1434
|
+
getShardedPath(baseDir, id) {
|
|
1435
|
+
const depth = this.cachedShardingDepth ?? this.getOptimalShardingDepth();
|
|
1436
|
+
switch (depth) {
|
|
1437
|
+
case 0:
|
|
1438
|
+
// Flat structure: /nouns/uuid.json
|
|
1439
|
+
return path.join(baseDir, `${id}.json`);
|
|
1440
|
+
case 1:
|
|
1441
|
+
// Single-level sharding: /nouns/ab/uuid.json
|
|
1442
|
+
const shard1 = id.substring(0, 2);
|
|
1443
|
+
return path.join(baseDir, shard1, `${id}.json`);
|
|
1444
|
+
case 2:
|
|
1445
|
+
default:
|
|
1446
|
+
// Deep sharding: /nouns/ab/cd/uuid.json
|
|
1447
|
+
const shard1Deep = id.substring(0, 2);
|
|
1448
|
+
const shard2Deep = id.substring(2, 4);
|
|
1449
|
+
return path.join(baseDir, shard1Deep, shard2Deep, `${id}.json`);
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
/**
|
|
1453
|
+
* Check if a file exists (handles both sharded and non-sharded)
|
|
1454
|
+
*/
|
|
1455
|
+
async fileExists(filePath) {
|
|
1456
|
+
try {
|
|
1457
|
+
await fs.promises.access(filePath, fs.constants.F_OK);
|
|
1458
|
+
return true;
|
|
1459
|
+
}
|
|
1460
|
+
catch {
|
|
1461
|
+
return false;
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1256
1464
|
}
|
|
1257
1465
|
//# sourceMappingURL=fileSystemStorage.js.map
|
|
@@ -169,4 +169,12 @@ export declare class MemoryStorage extends BaseStorage {
|
|
|
169
169
|
* @returns Promise that resolves to the statistics data or null if not found
|
|
170
170
|
*/
|
|
171
171
|
protected getStatisticsData(): Promise<StatisticsData | null>;
|
|
172
|
+
/**
|
|
173
|
+
* Initialize counts from in-memory storage - O(1) operation
|
|
174
|
+
*/
|
|
175
|
+
protected initializeCounts(): Promise<void>;
|
|
176
|
+
/**
|
|
177
|
+
* Persist counts to storage - no-op for memory storage
|
|
178
|
+
*/
|
|
179
|
+
protected persistCounts(): Promise<void>;
|
|
172
180
|
}
|
|
@@ -30,6 +30,7 @@ export class MemoryStorage extends BaseStorage {
|
|
|
30
30
|
* Save a noun to storage
|
|
31
31
|
*/
|
|
32
32
|
async saveNoun_internal(noun) {
|
|
33
|
+
const isNew = !this.nouns.has(noun.id);
|
|
33
34
|
// Create a deep copy to avoid reference issues
|
|
34
35
|
const nounCopy = {
|
|
35
36
|
id: noun.id,
|
|
@@ -44,6 +45,11 @@ export class MemoryStorage extends BaseStorage {
|
|
|
44
45
|
}
|
|
45
46
|
// Save the noun directly in the nouns map
|
|
46
47
|
this.nouns.set(noun.id, nounCopy);
|
|
48
|
+
// Update counts for new entities
|
|
49
|
+
if (isNew) {
|
|
50
|
+
const type = noun.metadata?.type || noun.metadata?.nounType || 'default';
|
|
51
|
+
this.incrementEntityCount(type);
|
|
52
|
+
}
|
|
47
53
|
}
|
|
48
54
|
/**
|
|
49
55
|
* Get a noun from storage
|
|
@@ -190,12 +196,18 @@ export class MemoryStorage extends BaseStorage {
|
|
|
190
196
|
* Delete a noun from storage
|
|
191
197
|
*/
|
|
192
198
|
async deleteNoun_internal(id) {
|
|
199
|
+
const noun = this.nouns.get(id);
|
|
200
|
+
if (noun) {
|
|
201
|
+
const type = noun.metadata?.type || noun.metadata?.nounType || 'default';
|
|
202
|
+
this.decrementEntityCount(type);
|
|
203
|
+
}
|
|
193
204
|
this.nouns.delete(id);
|
|
194
205
|
}
|
|
195
206
|
/**
|
|
196
207
|
* Save a verb to storage
|
|
197
208
|
*/
|
|
198
209
|
async saveVerb_internal(verb) {
|
|
210
|
+
const isNew = !this.verbs.has(verb.id);
|
|
199
211
|
// Create a deep copy to avoid reference issues
|
|
200
212
|
const verbCopy = {
|
|
201
213
|
id: verb.id,
|
|
@@ -208,6 +220,8 @@ export class MemoryStorage extends BaseStorage {
|
|
|
208
220
|
}
|
|
209
221
|
// Save the verb directly in the verbs map
|
|
210
222
|
this.verbs.set(verb.id, verbCopy);
|
|
223
|
+
// Count tracking will be handled in saveVerbMetadata_internal
|
|
224
|
+
// since HNSWVerb doesn't contain type information
|
|
211
225
|
}
|
|
212
226
|
/**
|
|
213
227
|
* Get a verb from storage
|
|
@@ -393,7 +407,8 @@ export class MemoryStorage extends BaseStorage {
|
|
|
393
407
|
* Delete a verb from storage
|
|
394
408
|
*/
|
|
395
409
|
async deleteVerb_internal(id) {
|
|
396
|
-
//
|
|
410
|
+
// Count tracking will be handled when verb metadata is deleted
|
|
411
|
+
// since HNSWVerb doesn't contain type information
|
|
397
412
|
this.verbs.delete(id);
|
|
398
413
|
}
|
|
399
414
|
/**
|
|
@@ -448,7 +463,13 @@ export class MemoryStorage extends BaseStorage {
|
|
|
448
463
|
* Save verb metadata to storage (internal implementation)
|
|
449
464
|
*/
|
|
450
465
|
async saveVerbMetadata_internal(id, metadata) {
|
|
466
|
+
const isNew = !this.verbMetadata.has(id);
|
|
451
467
|
this.verbMetadata.set(id, JSON.parse(JSON.stringify(metadata)));
|
|
468
|
+
// Update counts for new verbs
|
|
469
|
+
if (isNew) {
|
|
470
|
+
const type = metadata?.verb || metadata?.type || 'default';
|
|
471
|
+
this.incrementVerbCount(type);
|
|
472
|
+
}
|
|
452
473
|
}
|
|
453
474
|
/**
|
|
454
475
|
* Get verb metadata from storage
|
|
@@ -549,5 +570,31 @@ export class MemoryStorage extends BaseStorage {
|
|
|
549
570
|
// Since this is in-memory, there's no need for fallback mechanisms
|
|
550
571
|
// to check multiple storage locations
|
|
551
572
|
}
|
|
573
|
+
/**
|
|
574
|
+
* Initialize counts from in-memory storage - O(1) operation
|
|
575
|
+
*/
|
|
576
|
+
async initializeCounts() {
|
|
577
|
+
// For memory storage, initialize counts from current in-memory state
|
|
578
|
+
this.totalNounCount = this.nouns.size;
|
|
579
|
+
this.totalVerbCount = this.verbMetadata.size;
|
|
580
|
+
// Initialize type-based counts by scanning current data
|
|
581
|
+
this.entityCounts.clear();
|
|
582
|
+
this.verbCounts.clear();
|
|
583
|
+
for (const noun of this.nouns.values()) {
|
|
584
|
+
const type = noun.metadata?.type || noun.metadata?.nounType || 'default';
|
|
585
|
+
this.entityCounts.set(type, (this.entityCounts.get(type) || 0) + 1);
|
|
586
|
+
}
|
|
587
|
+
for (const verbMetadata of this.verbMetadata.values()) {
|
|
588
|
+
const type = verbMetadata?.verb || verbMetadata?.type || 'default';
|
|
589
|
+
this.verbCounts.set(type, (this.verbCounts.get(type) || 0) + 1);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
/**
|
|
593
|
+
* Persist counts to storage - no-op for memory storage
|
|
594
|
+
*/
|
|
595
|
+
async persistCounts() {
|
|
596
|
+
// No persistence needed for in-memory storage
|
|
597
|
+
// Counts are always accurate from the live data structures
|
|
598
|
+
}
|
|
552
599
|
}
|
|
553
600
|
//# sourceMappingURL=memoryStorage.js.map
|
|
@@ -254,5 +254,17 @@ export declare class OPFSStorage extends BaseStorage {
|
|
|
254
254
|
hasMore: boolean;
|
|
255
255
|
nextCursor?: string;
|
|
256
256
|
}>;
|
|
257
|
+
/**
|
|
258
|
+
* Initialize counts from OPFS storage
|
|
259
|
+
*/
|
|
260
|
+
protected initializeCounts(): Promise<void>;
|
|
261
|
+
/**
|
|
262
|
+
* Initialize counts by scanning OPFS (fallback for missing counts file)
|
|
263
|
+
*/
|
|
264
|
+
private initializeCountsFromScan;
|
|
265
|
+
/**
|
|
266
|
+
* Persist counts to OPFS storage
|
|
267
|
+
*/
|
|
268
|
+
protected persistCounts(): Promise<void>;
|
|
257
269
|
}
|
|
258
270
|
export {};
|
|
@@ -1303,5 +1303,73 @@ export class OPFSStorage extends BaseStorage {
|
|
|
1303
1303
|
nextCursor
|
|
1304
1304
|
};
|
|
1305
1305
|
}
|
|
1306
|
+
/**
|
|
1307
|
+
* Initialize counts from OPFS storage
|
|
1308
|
+
*/
|
|
1309
|
+
async initializeCounts() {
|
|
1310
|
+
try {
|
|
1311
|
+
// Try to load existing counts from counts.json
|
|
1312
|
+
const systemDir = await this.rootDir.getDirectoryHandle('system', { create: true });
|
|
1313
|
+
const countsFile = await systemDir.getFileHandle('counts.json');
|
|
1314
|
+
const file = await countsFile.getFile();
|
|
1315
|
+
const data = await file.text();
|
|
1316
|
+
const counts = JSON.parse(data);
|
|
1317
|
+
// Restore counts from OPFS
|
|
1318
|
+
this.entityCounts = new Map(Object.entries(counts.entityCounts || {}));
|
|
1319
|
+
this.verbCounts = new Map(Object.entries(counts.verbCounts || {}));
|
|
1320
|
+
this.totalNounCount = counts.totalNounCount || 0;
|
|
1321
|
+
this.totalVerbCount = counts.totalVerbCount || 0;
|
|
1322
|
+
}
|
|
1323
|
+
catch (error) {
|
|
1324
|
+
// If counts don't exist, initialize by scanning (one-time operation)
|
|
1325
|
+
await this.initializeCountsFromScan();
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
/**
|
|
1329
|
+
* Initialize counts by scanning OPFS (fallback for missing counts file)
|
|
1330
|
+
*/
|
|
1331
|
+
async initializeCountsFromScan() {
|
|
1332
|
+
try {
|
|
1333
|
+
// Count nouns
|
|
1334
|
+
let nounCount = 0;
|
|
1335
|
+
for await (const [,] of this.nounsDir.entries()) {
|
|
1336
|
+
nounCount++;
|
|
1337
|
+
}
|
|
1338
|
+
this.totalNounCount = nounCount;
|
|
1339
|
+
// Count verbs
|
|
1340
|
+
let verbCount = 0;
|
|
1341
|
+
for await (const [,] of this.verbsDir.entries()) {
|
|
1342
|
+
verbCount++;
|
|
1343
|
+
}
|
|
1344
|
+
this.totalVerbCount = verbCount;
|
|
1345
|
+
// Save initial counts
|
|
1346
|
+
await this.persistCounts();
|
|
1347
|
+
}
|
|
1348
|
+
catch (error) {
|
|
1349
|
+
console.error('Error initializing counts from OPFS scan:', error);
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
/**
|
|
1353
|
+
* Persist counts to OPFS storage
|
|
1354
|
+
*/
|
|
1355
|
+
async persistCounts() {
|
|
1356
|
+
try {
|
|
1357
|
+
const systemDir = await this.rootDir.getDirectoryHandle('system', { create: true });
|
|
1358
|
+
const countsFile = await systemDir.getFileHandle('counts.json', { create: true });
|
|
1359
|
+
const writable = await countsFile.createWritable();
|
|
1360
|
+
const counts = {
|
|
1361
|
+
entityCounts: Object.fromEntries(this.entityCounts),
|
|
1362
|
+
verbCounts: Object.fromEntries(this.verbCounts),
|
|
1363
|
+
totalNounCount: this.totalNounCount,
|
|
1364
|
+
totalVerbCount: this.totalVerbCount,
|
|
1365
|
+
lastUpdated: new Date().toISOString()
|
|
1366
|
+
};
|
|
1367
|
+
await writable.write(JSON.stringify(counts));
|
|
1368
|
+
await writable.close();
|
|
1369
|
+
}
|
|
1370
|
+
catch (error) {
|
|
1371
|
+
console.error('Error persisting counts to OPFS:', error);
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1306
1374
|
}
|
|
1307
1375
|
//# sourceMappingURL=opfsStorage.js.map
|
|
@@ -72,6 +72,10 @@ export declare class S3CompatibleStorage extends BaseStorage {
|
|
|
72
72
|
private backpressure;
|
|
73
73
|
private nounWriteBuffer;
|
|
74
74
|
private verbWriteBuffer;
|
|
75
|
+
private coordinator?;
|
|
76
|
+
private shardManager?;
|
|
77
|
+
private cacheSync?;
|
|
78
|
+
private readWriteSeparation?;
|
|
75
79
|
private requestCoalescer;
|
|
76
80
|
private highVolumeMode;
|
|
77
81
|
private lastVolumeCheck;
|
|
@@ -106,6 +110,24 @@ export declare class S3CompatibleStorage extends BaseStorage {
|
|
|
106
110
|
* Initialize the storage adapter
|
|
107
111
|
*/
|
|
108
112
|
init(): Promise<void>;
|
|
113
|
+
/**
|
|
114
|
+
* Set distributed components for multi-node coordination
|
|
115
|
+
* Zero-config: Automatically optimizes based on components provided
|
|
116
|
+
*/
|
|
117
|
+
setDistributedComponents(components: {
|
|
118
|
+
coordinator?: any;
|
|
119
|
+
shardManager?: any;
|
|
120
|
+
cacheSync?: any;
|
|
121
|
+
readWriteSeparation?: any;
|
|
122
|
+
}): void;
|
|
123
|
+
/**
|
|
124
|
+
* Get the S3 key for a noun, using sharding if available
|
|
125
|
+
*/
|
|
126
|
+
private getNounKey;
|
|
127
|
+
/**
|
|
128
|
+
* Get the S3 key for a verb, using sharding if available
|
|
129
|
+
*/
|
|
130
|
+
private getVerbKey;
|
|
109
131
|
/**
|
|
110
132
|
* Override base class method to detect S3-specific throttling errors
|
|
111
133
|
*/
|
|
@@ -495,4 +517,16 @@ export declare class S3CompatibleStorage extends BaseStorage {
|
|
|
495
517
|
hasMore: boolean;
|
|
496
518
|
nextCursor?: string;
|
|
497
519
|
}>;
|
|
520
|
+
/**
|
|
521
|
+
* Initialize counts from S3 storage
|
|
522
|
+
*/
|
|
523
|
+
protected initializeCounts(): Promise<void>;
|
|
524
|
+
/**
|
|
525
|
+
* Initialize counts by scanning S3 (fallback for missing counts file)
|
|
526
|
+
*/
|
|
527
|
+
private initializeCountsFromScan;
|
|
528
|
+
/**
|
|
529
|
+
* Persist counts to S3 storage
|
|
530
|
+
*/
|
|
531
|
+
protected persistCounts(): Promise<void>;
|
|
498
532
|
}
|