@soulcraft/brainy 6.2.8 → 6.3.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/dist/brainy.js +18 -7
- package/dist/graph/graphAdjacencyIndex.d.ts +7 -0
- package/dist/graph/graphAdjacencyIndex.js +42 -0
- package/dist/storage/baseStorage.d.ts +7 -0
- package/dist/storage/baseStorage.js +98 -44
- package/dist/utils/unifiedCache.d.ts +7 -0
- package/dist/utils/unifiedCache.js +17 -0
- package/dist/vfs/PathResolver.d.ts +8 -0
- package/dist/vfs/PathResolver.js +39 -7
- package/dist/vfs/semantic/SemanticPathResolver.d.ts +6 -0
- package/dist/vfs/semantic/SemanticPathResolver.js +11 -0
- package/package.json +1 -1
package/dist/brainy.js
CHANGED
|
@@ -18,7 +18,6 @@ import { TripleIntelligenceSystem } from './triple/TripleIntelligenceSystem.js';
|
|
|
18
18
|
import { VirtualFileSystem } from './vfs/VirtualFileSystem.js';
|
|
19
19
|
import { VersioningAPI } from './versioning/VersioningAPI.js';
|
|
20
20
|
import { MetadataIndexManager } from './utils/metadataIndex.js';
|
|
21
|
-
import { GraphAdjacencyIndex } from './graph/graphAdjacencyIndex.js';
|
|
22
21
|
import { CommitBuilder } from './storage/cow/CommitObject.js';
|
|
23
22
|
import { NULL_HASH } from './storage/cow/constants.js';
|
|
24
23
|
import { createPipeline } from './streaming/pipeline.js';
|
|
@@ -127,8 +126,11 @@ export class Brainy {
|
|
|
127
126
|
// Initialize core metadata index
|
|
128
127
|
this.metadataIndex = new MetadataIndexManager(this.storage);
|
|
129
128
|
await this.metadataIndex.init();
|
|
130
|
-
//
|
|
131
|
-
|
|
129
|
+
// v6.3.0: Get GraphAdjacencyIndex from storage (SINGLETON pattern)
|
|
130
|
+
// Storage owns the single instance, Brainy accesses it via getGraphIndex()
|
|
131
|
+
// This fixes the dual-ownership bug where Brainy and Storage had separate instances
|
|
132
|
+
// causing verbIdSet to be out of sync and VFS tree queries to fail
|
|
133
|
+
this.graphIndex = await this.storage.getGraphIndex();
|
|
132
134
|
// Rebuild indexes if needed for existing data
|
|
133
135
|
await this.rebuildIndexesIfNeeded();
|
|
134
136
|
// Initialize augmentations
|
|
@@ -2198,8 +2200,10 @@ export class Brainy {
|
|
|
2198
2200
|
// Fast rebuild for small indexes from COW storage (Metadata/Graph are fast)
|
|
2199
2201
|
clone.metadataIndex = new MetadataIndexManager(clone.storage);
|
|
2200
2202
|
await clone.metadataIndex.init();
|
|
2201
|
-
clone.graphIndex =
|
|
2202
|
-
|
|
2203
|
+
clone.storage.graphIndex = undefined;
|
|
2204
|
+
clone.storage.graphIndexPromise = undefined;
|
|
2205
|
+
clone.graphIndex = await clone.storage.getGraphIndex();
|
|
2206
|
+
// getGraphIndex() will rebuild automatically if data exists (via _initializeGraphIndex)
|
|
2203
2207
|
// Setup augmentations
|
|
2204
2208
|
clone.augmentationRegistry = this.setupAugmentations();
|
|
2205
2209
|
await clone.augmentationRegistry.initializeAll({
|
|
@@ -2289,14 +2293,21 @@ export class Brainy {
|
|
|
2289
2293
|
this.index = this.setupIndex();
|
|
2290
2294
|
this.metadataIndex = new MetadataIndexManager(this.storage);
|
|
2291
2295
|
await this.metadataIndex.init();
|
|
2292
|
-
this.
|
|
2296
|
+
this.storage.invalidateGraphIndex();
|
|
2297
|
+
this.graphIndex = await this.storage.getGraphIndex();
|
|
2293
2298
|
// v5.7.7: Reset lazy loading state when switching branches
|
|
2294
2299
|
// Indexes contain data from previous branch, must rebuild for new branch
|
|
2295
2300
|
this.lazyRebuildCompleted = false;
|
|
2296
2301
|
// Rebuild indexes from new branch data (force=true to override disableAutoRebuild)
|
|
2297
2302
|
await this.rebuildIndexesIfNeeded(true);
|
|
2298
|
-
//
|
|
2303
|
+
// v6.3.0: Clear VFS caches before recreating VFS for new branch
|
|
2304
|
+
// UnifiedCache is global, so old branch's VFS path cache entries would persist
|
|
2299
2305
|
if (this._vfs) {
|
|
2306
|
+
// Clear old PathResolver's caches including UnifiedCache entries
|
|
2307
|
+
if (this._vfs.pathResolver?.invalidateAllCaches) {
|
|
2308
|
+
this._vfs.pathResolver.invalidateAllCaches();
|
|
2309
|
+
}
|
|
2310
|
+
// Recreate VFS for new branch
|
|
2300
2311
|
this._vfs = new VirtualFileSystem(this);
|
|
2301
2312
|
await this._vfs.init();
|
|
2302
2313
|
}
|
|
@@ -51,8 +51,15 @@ export declare class GraphAdjacencyIndex {
|
|
|
51
51
|
constructor(storage: StorageAdapter, config?: GraphIndexConfig);
|
|
52
52
|
/**
|
|
53
53
|
* Initialize the graph index (lazy initialization)
|
|
54
|
+
* v6.3.0: Added defensive auto-rebuild check for verbIdSet consistency
|
|
54
55
|
*/
|
|
55
56
|
private ensureInitialized;
|
|
57
|
+
/**
|
|
58
|
+
* Populate verbIdSet from storage without full rebuild (v6.3.0)
|
|
59
|
+
* Lighter weight than full rebuild - only loads verb IDs, not all verb data
|
|
60
|
+
* @private
|
|
61
|
+
*/
|
|
62
|
+
private populateVerbIdSetFromStorage;
|
|
56
63
|
/**
|
|
57
64
|
* Core API - Neighbor lookup with LSM-tree storage
|
|
58
65
|
*
|
|
@@ -72,6 +72,7 @@ export class GraphAdjacencyIndex {
|
|
|
72
72
|
}
|
|
73
73
|
/**
|
|
74
74
|
* Initialize the graph index (lazy initialization)
|
|
75
|
+
* v6.3.0: Added defensive auto-rebuild check for verbIdSet consistency
|
|
75
76
|
*/
|
|
76
77
|
async ensureInitialized() {
|
|
77
78
|
if (this.initialized) {
|
|
@@ -81,10 +82,51 @@ export class GraphAdjacencyIndex {
|
|
|
81
82
|
await this.lsmTreeTarget.init();
|
|
82
83
|
await this.lsmTreeVerbsBySource.init();
|
|
83
84
|
await this.lsmTreeVerbsByTarget.init();
|
|
85
|
+
// v6.3.0: Defensive check - if LSM-trees have data but verbIdSet is empty,
|
|
86
|
+
// the index was created without proper rebuild (shouldn't happen with singleton
|
|
87
|
+
// pattern but protects against edge cases and future refactoring)
|
|
88
|
+
const lsmTreeSize = this.lsmTreeVerbsBySource.size();
|
|
89
|
+
if (lsmTreeSize > 0 && this.verbIdSet.size === 0) {
|
|
90
|
+
prodLog.warn(`GraphAdjacencyIndex: LSM-trees have ${lsmTreeSize} relationships but verbIdSet is empty. ` +
|
|
91
|
+
`Triggering auto-rebuild to restore consistency.`);
|
|
92
|
+
// Note: We don't await rebuild() here to avoid infinite loop
|
|
93
|
+
// (rebuild calls ensureInitialized). Instead, we'll populate verbIdSet
|
|
94
|
+
// by loading all verb IDs from storage.
|
|
95
|
+
await this.populateVerbIdSetFromStorage();
|
|
96
|
+
}
|
|
84
97
|
// Start auto-flush timer after initialization
|
|
85
98
|
this.startAutoFlush();
|
|
86
99
|
this.initialized = true;
|
|
87
100
|
}
|
|
101
|
+
/**
|
|
102
|
+
* Populate verbIdSet from storage without full rebuild (v6.3.0)
|
|
103
|
+
* Lighter weight than full rebuild - only loads verb IDs, not all verb data
|
|
104
|
+
* @private
|
|
105
|
+
*/
|
|
106
|
+
async populateVerbIdSetFromStorage() {
|
|
107
|
+
prodLog.info('GraphAdjacencyIndex: Populating verbIdSet from storage...');
|
|
108
|
+
const startTime = Date.now();
|
|
109
|
+
// Use pagination to load all verb IDs
|
|
110
|
+
let hasMore = true;
|
|
111
|
+
let cursor = undefined;
|
|
112
|
+
let count = 0;
|
|
113
|
+
while (hasMore) {
|
|
114
|
+
const result = await this.storage.getVerbs({
|
|
115
|
+
pagination: { limit: 10000, cursor }
|
|
116
|
+
});
|
|
117
|
+
for (const verb of result.items) {
|
|
118
|
+
this.verbIdSet.add(verb.id);
|
|
119
|
+
// Also update counts
|
|
120
|
+
const verbType = verb.verb || 'unknown';
|
|
121
|
+
this.relationshipCountsByType.set(verbType, (this.relationshipCountsByType.get(verbType) || 0) + 1);
|
|
122
|
+
count++;
|
|
123
|
+
}
|
|
124
|
+
hasMore = result.hasMore;
|
|
125
|
+
cursor = result.nextCursor;
|
|
126
|
+
}
|
|
127
|
+
const elapsed = Date.now() - startTime;
|
|
128
|
+
prodLog.info(`GraphAdjacencyIndex: Populated verbIdSet with ${count} verb IDs in ${elapsed}ms`);
|
|
129
|
+
}
|
|
88
130
|
/**
|
|
89
131
|
* Core API - Neighbor lookup with LSM-tree storage
|
|
90
132
|
*
|
|
@@ -82,6 +82,13 @@ export declare abstract class BaseStorage extends BaseStorageAdapter {
|
|
|
82
82
|
* @public
|
|
83
83
|
*/
|
|
84
84
|
rebuildGraphIndex(): Promise<void>;
|
|
85
|
+
/**
|
|
86
|
+
* Invalidate GraphAdjacencyIndex (v6.3.0)
|
|
87
|
+
* Call this when switching branches or clearing data to force re-creation
|
|
88
|
+
* The next getGraphIndex() call will create a fresh instance and rebuild
|
|
89
|
+
* @public
|
|
90
|
+
*/
|
|
91
|
+
invalidateGraphIndex(): void;
|
|
85
92
|
/**
|
|
86
93
|
* Ensure the storage adapter is initialized
|
|
87
94
|
*/
|
|
@@ -185,18 +185,12 @@ export class BaseStorage extends BaseStorageAdapter {
|
|
|
185
185
|
try {
|
|
186
186
|
// Load type statistics from storage (if they exist)
|
|
187
187
|
await this.loadTypeStatistics();
|
|
188
|
-
// v6.
|
|
189
|
-
//
|
|
190
|
-
//
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
prodLog.debug(`[BaseStorage] GraphAdjacencyIndex instantiated (lazy-loaded), graphIndex=${!!this.graphIndex}`);
|
|
195
|
-
}
|
|
196
|
-
catch (error) {
|
|
197
|
-
prodLog.error('[BaseStorage] Failed to create GraphAdjacencyIndex:', error);
|
|
198
|
-
throw error;
|
|
199
|
-
}
|
|
188
|
+
// v6.3.0: GraphAdjacencyIndex is now SINGLETON via getGraphIndex()
|
|
189
|
+
// - Removed direct creation here to fix dual-ownership bug
|
|
190
|
+
// - GraphAdjacencyIndex will be created lazily on first getGraphIndex() call
|
|
191
|
+
// - This ensures there's only ONE instance per storage adapter
|
|
192
|
+
// - See: https://github.com/soulcraftlabs/brainy/issues/vfs-corruption
|
|
193
|
+
prodLog.debug('[BaseStorage] init() complete - GraphAdjacencyIndex will be created via getGraphIndex()');
|
|
200
194
|
}
|
|
201
195
|
catch (error) {
|
|
202
196
|
// Reset flag on failure to allow retry
|
|
@@ -210,13 +204,28 @@ export class BaseStorage extends BaseStorageAdapter {
|
|
|
210
204
|
* @public
|
|
211
205
|
*/
|
|
212
206
|
async rebuildGraphIndex() {
|
|
213
|
-
|
|
214
|
-
throw new Error('GraphAdjacencyIndex not initialized');
|
|
215
|
-
}
|
|
207
|
+
const index = await this.getGraphIndex();
|
|
216
208
|
prodLog.info('[BaseStorage] Rebuilding graph index from existing data...');
|
|
217
|
-
await
|
|
209
|
+
await index.rebuild();
|
|
218
210
|
prodLog.info('[BaseStorage] Graph index rebuild complete');
|
|
219
211
|
}
|
|
212
|
+
/**
|
|
213
|
+
* Invalidate GraphAdjacencyIndex (v6.3.0)
|
|
214
|
+
* Call this when switching branches or clearing data to force re-creation
|
|
215
|
+
* The next getGraphIndex() call will create a fresh instance and rebuild
|
|
216
|
+
* @public
|
|
217
|
+
*/
|
|
218
|
+
invalidateGraphIndex() {
|
|
219
|
+
if (this.graphIndex) {
|
|
220
|
+
prodLog.info('[BaseStorage] Invalidating GraphAdjacencyIndex for branch switch/clear');
|
|
221
|
+
// Stop any pending operations
|
|
222
|
+
if (typeof this.graphIndex.stopAutoFlush === 'function') {
|
|
223
|
+
this.graphIndex.stopAutoFlush();
|
|
224
|
+
}
|
|
225
|
+
this.graphIndex = undefined;
|
|
226
|
+
this.graphIndexPromise = undefined;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
220
229
|
/**
|
|
221
230
|
* Ensure the storage adapter is initialized
|
|
222
231
|
*/
|
|
@@ -1276,6 +1285,42 @@ export class BaseStorage extends BaseStorageAdapter {
|
|
|
1276
1285
|
nextCursor
|
|
1277
1286
|
};
|
|
1278
1287
|
}
|
|
1288
|
+
// v6.2.9: Fast path for SINGLE sourceId + verbType combo (common VFS pattern)
|
|
1289
|
+
// This avoids the slow type-iteration fallback for VFS operations
|
|
1290
|
+
// NOTE: Only use fast path for single sourceId to avoid incomplete results
|
|
1291
|
+
const isSingleSourceId = options.filter.sourceId &&
|
|
1292
|
+
!Array.isArray(options.filter.sourceId);
|
|
1293
|
+
if (isSingleSourceId &&
|
|
1294
|
+
options.filter.verbType &&
|
|
1295
|
+
!options.filter.targetId &&
|
|
1296
|
+
!options.filter.service &&
|
|
1297
|
+
!options.filter.metadata) {
|
|
1298
|
+
const sourceId = options.filter.sourceId;
|
|
1299
|
+
const verbTypes = Array.isArray(options.filter.verbType)
|
|
1300
|
+
? options.filter.verbType
|
|
1301
|
+
: [options.filter.verbType];
|
|
1302
|
+
prodLog.debug(`[BaseStorage] getVerbs: Using fast path for sourceId=${sourceId}, verbTypes=${verbTypes.join(',')}`);
|
|
1303
|
+
// Get verbs by source (uses GraphAdjacencyIndex if available)
|
|
1304
|
+
const verbsBySource = await this.getVerbsBySource_internal(sourceId);
|
|
1305
|
+
// Filter by verbType in memory (fast - usually small number of verbs per source)
|
|
1306
|
+
const filtered = verbsBySource.filter(v => verbTypes.includes(v.verb));
|
|
1307
|
+
// Apply pagination
|
|
1308
|
+
const paginatedVerbs = filtered.slice(offset, offset + limit);
|
|
1309
|
+
const hasMore = offset + limit < filtered.length;
|
|
1310
|
+
// Set next cursor if there are more items
|
|
1311
|
+
let nextCursor = undefined;
|
|
1312
|
+
if (hasMore && paginatedVerbs.length > 0) {
|
|
1313
|
+
const lastItem = paginatedVerbs[paginatedVerbs.length - 1];
|
|
1314
|
+
nextCursor = lastItem.id;
|
|
1315
|
+
}
|
|
1316
|
+
prodLog.debug(`[BaseStorage] getVerbs: Fast path returned ${filtered.length} verbs (${paginatedVerbs.length} after pagination)`);
|
|
1317
|
+
return {
|
|
1318
|
+
items: paginatedVerbs,
|
|
1319
|
+
totalCount: filtered.length,
|
|
1320
|
+
hasMore,
|
|
1321
|
+
nextCursor
|
|
1322
|
+
};
|
|
1323
|
+
}
|
|
1279
1324
|
}
|
|
1280
1325
|
// For more complex filtering or no filtering, use a paginated approach
|
|
1281
1326
|
// that avoids loading all verbs into memory at once
|
|
@@ -1336,14 +1381,32 @@ export class BaseStorage extends BaseStorageAdapter {
|
|
|
1336
1381
|
// Only use type-skipping optimization if counts are non-zero (reliable)
|
|
1337
1382
|
const totalVerbCountFromArray = this.verbCountsByType.reduce((sum, c) => sum + c, 0);
|
|
1338
1383
|
const useOptimization = totalVerbCountFromArray > 0;
|
|
1384
|
+
// v6.2.9 BUG FIX: Pre-compute requested verb types to avoid skipping them
|
|
1385
|
+
// When a specific verbType filter is provided, we MUST check that type
|
|
1386
|
+
// even if verbCountsByType shows 0 (counts can be stale after restart)
|
|
1387
|
+
const requestedVerbTypes = options?.filter?.verbType;
|
|
1388
|
+
const requestedVerbTypesSet = requestedVerbTypes
|
|
1389
|
+
? new Set(Array.isArray(requestedVerbTypes) ? requestedVerbTypes : [requestedVerbTypes])
|
|
1390
|
+
: null;
|
|
1339
1391
|
// Iterate through all 127 verb types (Stage 3 CANONICAL) with early termination
|
|
1340
1392
|
// OPTIMIZATION: Skip types with zero count (only if counts are reliable)
|
|
1341
1393
|
for (let i = 0; i < VERB_TYPE_COUNT && collectedVerbs.length < targetCount; i++) {
|
|
1342
|
-
|
|
1343
|
-
|
|
1394
|
+
const type = TypeUtils.getVerbFromIndex(i);
|
|
1395
|
+
// v6.2.9 FIX: Never skip a type that's explicitly requested in the filter
|
|
1396
|
+
// This fixes VFS bug where Contains relationships were skipped after restart
|
|
1397
|
+
// when verbCountsByType[Contains] was 0 due to stale statistics
|
|
1398
|
+
const isRequestedType = requestedVerbTypesSet?.has(type) ?? false;
|
|
1399
|
+
const countIsZero = this.verbCountsByType[i] === 0;
|
|
1400
|
+
// Skip empty types for performance (but only if optimization is enabled AND not requested)
|
|
1401
|
+
if (useOptimization && countIsZero && !isRequestedType) {
|
|
1344
1402
|
continue;
|
|
1345
1403
|
}
|
|
1346
|
-
|
|
1404
|
+
// v6.2.9: Log when we DON'T skip a requested type that would have been skipped
|
|
1405
|
+
// This helps diagnose stale statistics issues in production
|
|
1406
|
+
if (useOptimization && countIsZero && isRequestedType) {
|
|
1407
|
+
prodLog.debug(`[BaseStorage] getVerbs: NOT skipping type=${type} despite count=0 (type was explicitly requested). ` +
|
|
1408
|
+
`Statistics may be stale - consider running rebuildTypeCounts().`);
|
|
1409
|
+
}
|
|
1347
1410
|
try {
|
|
1348
1411
|
const verbsOfType = await this.getVerbsByType_internal(type);
|
|
1349
1412
|
// Apply filtering inline (memory efficient)
|
|
@@ -2169,8 +2232,11 @@ export class BaseStorage extends BaseStorageAdapter {
|
|
|
2169
2232
|
this.nounCountsByType[typeIndex]++;
|
|
2170
2233
|
// COW-aware write (v5.0.1): Use COW helper for branch isolation
|
|
2171
2234
|
await this.writeObjectToBranch(path, noun);
|
|
2172
|
-
// Periodically save statistics
|
|
2173
|
-
|
|
2235
|
+
// Periodically save statistics
|
|
2236
|
+
// v6.2.9: Also save on first noun of each type to ensure low-count types are tracked
|
|
2237
|
+
const shouldSave = this.nounCountsByType[typeIndex] === 1 || // First noun of type
|
|
2238
|
+
this.nounCountsByType[typeIndex] % 100 === 0; // Every 100th
|
|
2239
|
+
if (shouldSave) {
|
|
2174
2240
|
await this.saveTypeStatistics();
|
|
2175
2241
|
}
|
|
2176
2242
|
}
|
|
@@ -2254,30 +2320,18 @@ export class BaseStorage extends BaseStorageAdapter {
|
|
|
2254
2320
|
this.verbCountsByType[typeIndex]++;
|
|
2255
2321
|
// COW-aware write (v5.0.1): Use COW helper for branch isolation
|
|
2256
2322
|
await this.writeObjectToBranch(path, verb);
|
|
2257
|
-
// v6.
|
|
2258
|
-
//
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
sourceId: verb.sourceId,
|
|
2264
|
-
targetId: verb.targetId,
|
|
2265
|
-
vector: verb.vector,
|
|
2266
|
-
source: verb.sourceId,
|
|
2267
|
-
target: verb.targetId,
|
|
2268
|
-
verb: verb.verb,
|
|
2269
|
-
type: verb.verb,
|
|
2270
|
-
createdAt: { seconds: Math.floor(Date.now() / 1000), nanoseconds: 0 },
|
|
2271
|
-
updatedAt: { seconds: Math.floor(Date.now() / 1000), nanoseconds: 0 },
|
|
2272
|
-
createdBy: { augmentation: 'storage', version: '6.0.0' }
|
|
2273
|
-
});
|
|
2274
|
-
prodLog.debug(`[BaseStorage] GraphAdjacencyIndex updated successfully`);
|
|
2275
|
-
}
|
|
2276
|
-
else {
|
|
2277
|
-
prodLog.warn(`[BaseStorage] graphIndex is null, cannot update index for verb ${verb.id}`);
|
|
2278
|
-
}
|
|
2323
|
+
// v6.3.0: GraphAdjacencyIndex updates are now handled EXCLUSIVELY by Brainy.relate()
|
|
2324
|
+
// via AddToGraphIndexOperation in the transaction system. This provides:
|
|
2325
|
+
// 1. Singleton pattern - only one graphIndex instance exists (via getGraphIndex())
|
|
2326
|
+
// 2. Transaction rollback - if relate() fails, index update is rolled back
|
|
2327
|
+
// 3. No double-counting - prevents duplicate addVerb() calls
|
|
2328
|
+
// REMOVED: Direct graphIndex.addVerb() call that caused dual-ownership bugs
|
|
2279
2329
|
// Periodically save statistics
|
|
2280
|
-
|
|
2330
|
+
// v6.2.9: Also save on first verb of each type to ensure low-count types are tracked
|
|
2331
|
+
// This prevents stale statistics after restart for types with < 100 verbs (common for VFS)
|
|
2332
|
+
const shouldSave = this.verbCountsByType[typeIndex] === 1 || // First verb of type
|
|
2333
|
+
this.verbCountsByType[typeIndex] % 100 === 0; // Every 100th
|
|
2334
|
+
if (shouldSave) {
|
|
2281
2335
|
await this.saveTypeStatistics();
|
|
2282
2336
|
}
|
|
2283
2337
|
}
|
|
@@ -92,6 +92,13 @@ export declare class UnifiedCache {
|
|
|
92
92
|
* Delete specific item from cache
|
|
93
93
|
*/
|
|
94
94
|
delete(key: string): boolean;
|
|
95
|
+
/**
|
|
96
|
+
* Delete all items with keys starting with the given prefix
|
|
97
|
+
* v6.2.9: Added for VFS cache invalidation (fixes stale parent ID bug)
|
|
98
|
+
* @param prefix - The key prefix to match
|
|
99
|
+
* @returns Number of items deleted
|
|
100
|
+
*/
|
|
101
|
+
deleteByPrefix(prefix: string): number;
|
|
95
102
|
/**
|
|
96
103
|
* Clear cache or specific type
|
|
97
104
|
*/
|
|
@@ -304,6 +304,23 @@ export class UnifiedCache {
|
|
|
304
304
|
}
|
|
305
305
|
return false;
|
|
306
306
|
}
|
|
307
|
+
/**
|
|
308
|
+
* Delete all items with keys starting with the given prefix
|
|
309
|
+
* v6.2.9: Added for VFS cache invalidation (fixes stale parent ID bug)
|
|
310
|
+
* @param prefix - The key prefix to match
|
|
311
|
+
* @returns Number of items deleted
|
|
312
|
+
*/
|
|
313
|
+
deleteByPrefix(prefix) {
|
|
314
|
+
let deleted = 0;
|
|
315
|
+
for (const [key, item] of this.cache) {
|
|
316
|
+
if (key.startsWith(prefix)) {
|
|
317
|
+
this.currentSize -= item.size;
|
|
318
|
+
this.cache.delete(key);
|
|
319
|
+
deleted++;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
return deleted;
|
|
323
|
+
}
|
|
307
324
|
/**
|
|
308
325
|
* Clear cache or specific type
|
|
309
326
|
*/
|
|
@@ -62,8 +62,16 @@ export declare class PathResolver {
|
|
|
62
62
|
* Create a new path entry (for mkdir/writeFile)
|
|
63
63
|
*/
|
|
64
64
|
createPath(path: string, entityId: string): Promise<void>;
|
|
65
|
+
/**
|
|
66
|
+
* Invalidate ALL caches (v6.3.0)
|
|
67
|
+
* Call this when switching branches (checkout), clearing data (clear), or forking
|
|
68
|
+
* This ensures no stale data from previous branch/state remains in cache
|
|
69
|
+
*/
|
|
70
|
+
invalidateAllCaches(): void;
|
|
65
71
|
/**
|
|
66
72
|
* Invalidate cache entries for a path and its children
|
|
73
|
+
* v6.2.9 FIX: Also invalidates UnifiedCache to prevent stale entity IDs
|
|
74
|
+
* This fixes the "Source entity not found" bug after delete+recreate operations
|
|
67
75
|
*/
|
|
68
76
|
invalidatePath(path: string, recursive?: boolean): void;
|
|
69
77
|
/**
|
package/dist/vfs/PathResolver.js
CHANGED
|
@@ -252,28 +252,60 @@ export class PathResolver {
|
|
|
252
252
|
this.parentCache.get(parentId).add(name);
|
|
253
253
|
}
|
|
254
254
|
}
|
|
255
|
+
/**
|
|
256
|
+
* Invalidate ALL caches (v6.3.0)
|
|
257
|
+
* Call this when switching branches (checkout), clearing data (clear), or forking
|
|
258
|
+
* This ensures no stale data from previous branch/state remains in cache
|
|
259
|
+
*/
|
|
260
|
+
invalidateAllCaches() {
|
|
261
|
+
// Clear all local caches
|
|
262
|
+
this.pathCache.clear();
|
|
263
|
+
this.parentCache.clear();
|
|
264
|
+
this.hotPaths.clear();
|
|
265
|
+
// Clear all VFS entries from UnifiedCache
|
|
266
|
+
getGlobalCache().deleteByPrefix('vfs:path:');
|
|
267
|
+
// Reset statistics (optional but helpful for debugging)
|
|
268
|
+
this.cacheHits = 0;
|
|
269
|
+
this.cacheMisses = 0;
|
|
270
|
+
this.metadataIndexHits = 0;
|
|
271
|
+
this.metadataIndexMisses = 0;
|
|
272
|
+
this.graphTraversalFallbacks = 0;
|
|
273
|
+
prodLog.info('[PathResolver] All caches invalidated');
|
|
274
|
+
}
|
|
255
275
|
/**
|
|
256
276
|
* Invalidate cache entries for a path and its children
|
|
277
|
+
* v6.2.9 FIX: Also invalidates UnifiedCache to prevent stale entity IDs
|
|
278
|
+
* This fixes the "Source entity not found" bug after delete+recreate operations
|
|
257
279
|
*/
|
|
258
280
|
invalidatePath(path, recursive = false) {
|
|
259
281
|
const normalizedPath = this.normalizePath(path);
|
|
260
|
-
//
|
|
282
|
+
// v6.2.9 FIX: Clear parent cache BEFORE deleting from pathCache
|
|
283
|
+
// (we need the entityId from the cache entry)
|
|
284
|
+
const cached = this.pathCache.get(normalizedPath);
|
|
285
|
+
if (cached) {
|
|
286
|
+
this.parentCache.delete(cached.entityId);
|
|
287
|
+
}
|
|
288
|
+
// Remove from local caches
|
|
261
289
|
this.pathCache.delete(normalizedPath);
|
|
262
290
|
this.hotPaths.delete(normalizedPath);
|
|
291
|
+
// v6.2.9 CRITICAL FIX: Also invalidate UnifiedCache (global LRU cache)
|
|
292
|
+
// This was missing before, causing stale entity IDs to be returned after delete
|
|
293
|
+
const cacheKey = `vfs:path:${normalizedPath}`;
|
|
294
|
+
getGlobalCache().delete(cacheKey);
|
|
263
295
|
if (recursive) {
|
|
264
296
|
// Remove all paths that start with this path
|
|
265
297
|
const prefix = normalizedPath.endsWith('/') ? normalizedPath : normalizedPath + '/';
|
|
266
|
-
for (const [cachedPath] of this.pathCache) {
|
|
298
|
+
for (const [cachedPath, entry] of this.pathCache) {
|
|
267
299
|
if (cachedPath.startsWith(prefix)) {
|
|
268
300
|
this.pathCache.delete(cachedPath);
|
|
269
301
|
this.hotPaths.delete(cachedPath);
|
|
302
|
+
// v6.2.9: Also clear parent cache for this entry
|
|
303
|
+
this.parentCache.delete(entry.entityId);
|
|
270
304
|
}
|
|
271
305
|
}
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
if (cached) {
|
|
276
|
-
this.parentCache.delete(cached.entityId);
|
|
306
|
+
// v6.2.9 CRITICAL FIX: Also invalidate UnifiedCache entries with this prefix
|
|
307
|
+
const globalCachePrefix = `vfs:path:${prefix}`;
|
|
308
|
+
getGlobalCache().deleteByPrefix(globalCachePrefix);
|
|
277
309
|
}
|
|
278
310
|
}
|
|
279
311
|
/**
|
|
@@ -92,6 +92,12 @@ export declare class SemanticPathResolver {
|
|
|
92
92
|
* Uses UnifiedCache's clear method
|
|
93
93
|
*/
|
|
94
94
|
invalidateSemanticCache(): void;
|
|
95
|
+
/**
|
|
96
|
+
* Invalidate ALL caches (v6.3.0)
|
|
97
|
+
* Clears both traditional path cache AND semantic cache
|
|
98
|
+
* Call this when switching branches, clearing data, or forking
|
|
99
|
+
*/
|
|
100
|
+
invalidateAllCaches(): void;
|
|
95
101
|
/**
|
|
96
102
|
* Cleanup resources
|
|
97
103
|
*/
|
|
@@ -231,6 +231,17 @@ export class SemanticPathResolver {
|
|
|
231
231
|
invalidateSemanticCache() {
|
|
232
232
|
this.cache.clear();
|
|
233
233
|
}
|
|
234
|
+
/**
|
|
235
|
+
* Invalidate ALL caches (v6.3.0)
|
|
236
|
+
* Clears both traditional path cache AND semantic cache
|
|
237
|
+
* Call this when switching branches, clearing data, or forking
|
|
238
|
+
*/
|
|
239
|
+
invalidateAllCaches() {
|
|
240
|
+
// Clear traditional PathResolver caches (including UnifiedCache VFS entries)
|
|
241
|
+
this.pathResolver.invalidateAllCaches();
|
|
242
|
+
// Clear semantic cache
|
|
243
|
+
this.cache.clear();
|
|
244
|
+
}
|
|
234
245
|
/**
|
|
235
246
|
* Cleanup resources
|
|
236
247
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@soulcraft/brainy",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.3.0",
|
|
4
4
|
"description": "Universal Knowledge Protocol™ - World's first Triple Intelligence database unifying vector, graph, and document search in one API. Stage 3 CANONICAL: 42 nouns × 127 verbs covering 96-97% of all human knowledge.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.js",
|