@soulcraft/cortex 1.3.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.
Files changed (35) hide show
  1. package/LICENSE +16 -0
  2. package/README.md +125 -0
  3. package/dist/graph/NativeGraphAdjacencyIndex.d.ts +92 -0
  4. package/dist/graph/NativeGraphAdjacencyIndex.js +671 -0
  5. package/dist/index.d.ts +22 -0
  6. package/dist/index.js +23 -0
  7. package/dist/license.d.ts +18 -0
  8. package/dist/license.js +172 -0
  9. package/dist/native/NativeEmbeddingEngine.d.ts +79 -0
  10. package/dist/native/NativeEmbeddingEngine.js +302 -0
  11. package/dist/native/NativeRoaringBitmap32.d.ts +114 -0
  12. package/dist/native/NativeRoaringBitmap32.js +221 -0
  13. package/dist/native/ffi.d.ts +20 -0
  14. package/dist/native/ffi.js +48 -0
  15. package/dist/native/index.d.ts +30 -0
  16. package/dist/native/index.js +58 -0
  17. package/dist/native/napi.d.ts +21 -0
  18. package/dist/native/napi.js +88 -0
  19. package/dist/native/types.d.ts +710 -0
  20. package/dist/native/types.js +16 -0
  21. package/dist/plugin.d.ts +22 -0
  22. package/dist/plugin.js +115 -0
  23. package/dist/storage/mmapFileSystemStorage.d.ts +24 -0
  24. package/dist/storage/mmapFileSystemStorage.js +73 -0
  25. package/dist/utils/NativeMetadataIndex.d.ts +185 -0
  26. package/dist/utils/NativeMetadataIndex.js +1274 -0
  27. package/dist/utils/nativeEntityIdMapper.d.ts +84 -0
  28. package/dist/utils/nativeEntityIdMapper.js +134 -0
  29. package/native/brainy-native.darwin-arm64.node +0 -0
  30. package/native/brainy-native.darwin-x64.node +0 -0
  31. package/native/brainy-native.linux-arm64-gnu.node +0 -0
  32. package/native/brainy-native.linux-x64-gnu.node +0 -0
  33. package/native/brainy-native.win32-x64-msvc.node +0 -0
  34. package/native/index.d.ts +1068 -0
  35. package/package.json +66 -0
@@ -0,0 +1,671 @@
1
+ /**
2
+ * NativeGraphAdjacencyIndex — Thin TS wrapper around the Rust graph engine
3
+ *
4
+ * Handles async storage I/O while the Rust engine handles all in-memory
5
+ * data operations (4 LSM-trees, verb tracking, relationship counts).
6
+ *
7
+ * Storage pattern: adapter-controlled I/O with 3-tier fallback.
8
+ * - Tier 1 (mmap): saveBinaryBlob + getBinaryBlobPath → zero-copy queries
9
+ * - Tier 2 (binary blob): saveBinaryBlob only → binary format in memory
10
+ * - Tier 3 (legacy): saveMetadata → base64 JSON
11
+ *
12
+ * UnifiedCache integration stays in TS (getVerbCached, getVerbsBatchCached)
13
+ * since it coordinates cross-subsystem caching and requires async storage access.
14
+ */
15
+ import { loadNativeModule } from '../native/index.js';
16
+ import { getGlobalCache, prodLog } from '@soulcraft/brainy/internals';
17
+ // Storage prefixes for each tree
18
+ const TREE_PREFIXES = {
19
+ 'source': 'graph-lsm-source',
20
+ 'target': 'graph-lsm-target',
21
+ 'verbs-source': 'graph-lsm-verbs-source',
22
+ 'verbs-target': 'graph-lsm-verbs-target',
23
+ };
24
+ export class GraphAdjacencyIndex {
25
+ storage;
26
+ native;
27
+ unifiedCache;
28
+ config;
29
+ initialized = false;
30
+ isRebuilding = false;
31
+ flushTimer;
32
+ rebuildStartTime = 0;
33
+ totalRelationshipsIndexed = 0;
34
+ isCompacting = {};
35
+ // Adapter capability detection (set in constructor)
36
+ hasBinaryBlobs;
37
+ hasMmapPaths;
38
+ get isInitialized() {
39
+ return this.initialized;
40
+ }
41
+ constructor(storage, config = {}) {
42
+ this.storage = storage;
43
+ this.config = {
44
+ maxIndexSize: config.maxIndexSize ?? 100000,
45
+ rebuildThreshold: config.rebuildThreshold ?? 0.1,
46
+ autoOptimize: config.autoOptimize ?? true,
47
+ flushInterval: config.flushInterval ?? 30000,
48
+ memTableThreshold: config.memTableThreshold ?? 100000,
49
+ maxSSTablesPerLevel: config.maxSSTablesPerLevel ?? 10,
50
+ };
51
+ const bindings = loadNativeModule();
52
+ this.native = new bindings.NativeGraphAdjacencyIndex({
53
+ memTableThreshold: this.config.memTableThreshold,
54
+ maxSstablesPerLevel: this.config.maxSSTablesPerLevel,
55
+ });
56
+ this.unifiedCache = getGlobalCache();
57
+ // Detect adapter capabilities via optional method presence
58
+ this.hasBinaryBlobs = typeof storage.saveBinaryBlob === 'function';
59
+ this.hasMmapPaths = typeof storage.getBinaryBlobPath === 'function';
60
+ if (this.hasMmapPaths) {
61
+ prodLog.info('GraphAdjacencyIndex initialized with native Rust engine (4 embedded LSM-trees, mmap SSTable I/O)');
62
+ }
63
+ else if (this.hasBinaryBlobs) {
64
+ prodLog.info('GraphAdjacencyIndex initialized with native Rust engine (4 embedded LSM-trees, binary blob I/O)');
65
+ }
66
+ else {
67
+ prodLog.info('GraphAdjacencyIndex initialized with native Rust engine (4 embedded LSM-trees)');
68
+ }
69
+ }
70
+ // ---------------------------------------------------------------------------
71
+ // Initialization
72
+ // ---------------------------------------------------------------------------
73
+ async ensureInitialized() {
74
+ if (this.initialized) {
75
+ return;
76
+ }
77
+ // Load manifests and SSTables for all 4 trees
78
+ const treeNames = this.native.treeNames();
79
+ for (const treeName of treeNames) {
80
+ await this.loadTreeFromStorage(treeName);
81
+ }
82
+ // Defensive check: if LSM-trees have data but verbIdSet is empty, populate
83
+ const sourceSize = this.native.size();
84
+ if (sourceSize > 0 && this.native.verbIdCount() === 0) {
85
+ prodLog.warn(`GraphAdjacencyIndex: LSM-trees have ${sourceSize} relationships but verbIdSet is empty. ` +
86
+ `Triggering auto-rebuild to restore consistency.`);
87
+ await this.populateVerbIdSetFromStorage();
88
+ }
89
+ this.startAutoFlush();
90
+ this.initialized = true;
91
+ }
92
+ async populateVerbIdSetFromStorage() {
93
+ prodLog.info('GraphAdjacencyIndex: Populating verbIdSet from storage...');
94
+ const startTime = Date.now();
95
+ let hasMore = true;
96
+ let cursor = undefined;
97
+ let count = 0;
98
+ while (hasMore) {
99
+ const result = await this.storage.getVerbs({
100
+ pagination: { limit: 10000, cursor }
101
+ });
102
+ for (const verb of result.items) {
103
+ const verbType = verb.verb || 'unknown';
104
+ this.native.trackVerbId(verb.id, verbType);
105
+ count++;
106
+ }
107
+ hasMore = result.hasMore;
108
+ cursor = result.nextCursor;
109
+ }
110
+ const elapsed = Date.now() - startTime;
111
+ prodLog.info(`GraphAdjacencyIndex: Populated verbIdSet with ${count} verb IDs in ${elapsed}ms`);
112
+ }
113
+ // ---------------------------------------------------------------------------
114
+ // Core graph operations
115
+ // ---------------------------------------------------------------------------
116
+ async getNeighbors(id, optionsOrDirection) {
117
+ await this.ensureInitialized();
118
+ const options = typeof optionsOrDirection === 'string'
119
+ ? { direction: optionsOrDirection }
120
+ : (optionsOrDirection || {});
121
+ const startTime = performance.now();
122
+ const direction = options.direction || 'both';
123
+ const limit = options.limit ?? null;
124
+ const offset = options.offset ?? null;
125
+ const result = this.native.getNeighbors(id, direction, limit, offset);
126
+ const elapsed = performance.now() - startTime;
127
+ if (elapsed > 5.0) {
128
+ prodLog.warn(`GraphAdjacencyIndex: Slow neighbor lookup for ${id}: ${elapsed.toFixed(2)}ms`);
129
+ }
130
+ return result;
131
+ }
132
+ async getVerbIdsBySource(sourceId, options) {
133
+ await this.ensureInitialized();
134
+ const startTime = performance.now();
135
+ const limit = options?.limit ?? null;
136
+ const offset = options?.offset ?? null;
137
+ const result = this.native.getVerbIdsBySource(sourceId, limit, offset);
138
+ const elapsed = performance.now() - startTime;
139
+ if (elapsed > 5.0) {
140
+ prodLog.warn(`GraphAdjacencyIndex: Slow getVerbIdsBySource for ${sourceId}: ${elapsed.toFixed(2)}ms`);
141
+ }
142
+ return result;
143
+ }
144
+ async getVerbIdsByTarget(targetId, options) {
145
+ await this.ensureInitialized();
146
+ const startTime = performance.now();
147
+ const limit = options?.limit ?? null;
148
+ const offset = options?.offset ?? null;
149
+ const result = this.native.getVerbIdsByTarget(targetId, limit, offset);
150
+ const elapsed = performance.now() - startTime;
151
+ if (elapsed > 5.0) {
152
+ prodLog.warn(`GraphAdjacencyIndex: Slow getVerbIdsByTarget for ${targetId}: ${elapsed.toFixed(2)}ms`);
153
+ }
154
+ return result;
155
+ }
156
+ async getVerbCached(verbId) {
157
+ const cacheKey = `graph:verb:${verbId}`;
158
+ const verb = await this.unifiedCache.get(cacheKey, async () => {
159
+ const loadedVerb = await this.storage.getVerb(verbId);
160
+ if (loadedVerb) {
161
+ this.unifiedCache.set(cacheKey, loadedVerb, 'other', 128, 50);
162
+ }
163
+ return loadedVerb;
164
+ });
165
+ return verb;
166
+ }
167
+ async getVerbsBatchCached(verbIds) {
168
+ const results = new Map();
169
+ const uncached = [];
170
+ for (const verbId of verbIds) {
171
+ const cacheKey = `graph:verb:${verbId}`;
172
+ const cached = this.unifiedCache.getSync(cacheKey);
173
+ if (cached) {
174
+ results.set(verbId, cached);
175
+ }
176
+ else {
177
+ uncached.push(verbId);
178
+ }
179
+ }
180
+ if (uncached.length > 0 && this.storage.getVerbsBatch) {
181
+ const loadedVerbs = await this.storage.getVerbsBatch(uncached);
182
+ for (const [verbId, verb] of loadedVerbs.entries()) {
183
+ const cacheKey = `graph:verb:${verbId}`;
184
+ this.unifiedCache.set(cacheKey, verb, 'other', 128, 50);
185
+ results.set(verbId, verb);
186
+ }
187
+ }
188
+ return results;
189
+ }
190
+ // ---------------------------------------------------------------------------
191
+ // Mutation operations
192
+ // ---------------------------------------------------------------------------
193
+ async addVerb(verb) {
194
+ await this.ensureInitialized();
195
+ const startTime = performance.now();
196
+ const verbType = verb.verb || 'unknown';
197
+ const flushNeeded = this.native.addVerb(verb.id, verb.source, verb.target, verbType);
198
+ // Flush any trees that hit their threshold
199
+ const flushPromises = [];
200
+ if (flushNeeded.needsFlushSource) {
201
+ flushPromises.push(this.flushTree('source'));
202
+ }
203
+ if (flushNeeded.needsFlushTarget) {
204
+ flushPromises.push(this.flushTree('target'));
205
+ }
206
+ if (flushNeeded.needsFlushVerbsSource) {
207
+ flushPromises.push(this.flushTree('verbs-source'));
208
+ }
209
+ if (flushNeeded.needsFlushVerbsTarget) {
210
+ flushPromises.push(this.flushTree('verbs-target'));
211
+ }
212
+ if (flushPromises.length > 0) {
213
+ await Promise.all(flushPromises);
214
+ }
215
+ const elapsed = performance.now() - startTime;
216
+ this.totalRelationshipsIndexed++;
217
+ if (elapsed > 10.0) {
218
+ prodLog.warn(`GraphAdjacencyIndex: Slow addVerb for ${verb.id}: ${elapsed.toFixed(2)}ms`);
219
+ }
220
+ }
221
+ async removeVerb(verbId) {
222
+ await this.ensureInitialized();
223
+ // Load verb to get type info
224
+ const verb = await this.getVerbCached(verbId);
225
+ if (!verb)
226
+ return;
227
+ const startTime = performance.now();
228
+ const verbType = verb.verb || 'unknown';
229
+ this.native.removeVerb(verbId, verbType);
230
+ const elapsed = performance.now() - startTime;
231
+ if (elapsed > 5.0) {
232
+ prodLog.warn(`GraphAdjacencyIndex: Slow removeVerb for ${verbId}: ${elapsed.toFixed(2)}ms`);
233
+ }
234
+ }
235
+ // ---------------------------------------------------------------------------
236
+ // Size, counts, stats
237
+ // ---------------------------------------------------------------------------
238
+ size() {
239
+ return this.native.size();
240
+ }
241
+ getRelationshipCountByType(type) {
242
+ return this.native.getRelationshipCountByType(type);
243
+ }
244
+ getTotalRelationshipCount() {
245
+ return this.native.verbIdCount();
246
+ }
247
+ getAllRelationshipCounts() {
248
+ const json = this.native.getAllRelationshipCountsJson();
249
+ const obj = JSON.parse(json);
250
+ return new Map(Object.entries(obj));
251
+ }
252
+ getRelationshipStats() {
253
+ const json = this.native.getRelationshipStatsJson();
254
+ return JSON.parse(json);
255
+ }
256
+ getStats() {
257
+ const stats = this.native.getStats();
258
+ return {
259
+ totalRelationships: stats.totalRelationships,
260
+ sourceNodes: stats.sourceSstableCount,
261
+ targetNodes: stats.targetSstableCount,
262
+ memoryUsage: stats.sourceMemTableMemory + stats.targetMemTableMemory + (stats.verbIdCount * 8),
263
+ lastRebuild: this.rebuildStartTime,
264
+ rebuildTime: this.isRebuilding ? Date.now() - this.rebuildStartTime : 0,
265
+ };
266
+ }
267
+ isHealthy() {
268
+ if (!this.initialized) {
269
+ return false;
270
+ }
271
+ return !this.isRebuilding && this.native.isHealthy();
272
+ }
273
+ // ---------------------------------------------------------------------------
274
+ // Rebuild
275
+ // ---------------------------------------------------------------------------
276
+ async rebuild() {
277
+ await this.ensureInitialized();
278
+ if (this.isRebuilding) {
279
+ prodLog.warn('GraphAdjacencyIndex: Rebuild already in progress');
280
+ return;
281
+ }
282
+ this.isRebuilding = true;
283
+ this.rebuildStartTime = Date.now();
284
+ try {
285
+ prodLog.info('GraphAdjacencyIndex: Starting rebuild...');
286
+ // Clear verb tracking (LSM-trees rebuild from storage via addVerb)
287
+ this.native.clearVerbTracking();
288
+ this.totalRelationshipsIndexed = 0;
289
+ const storageType = this.storage?.constructor.name || '';
290
+ const isLocalStorage = storageType === 'FileSystemStorage' ||
291
+ storageType === 'MmapFileSystemStorage' ||
292
+ storageType === 'MemoryStorage';
293
+ let totalVerbs = 0;
294
+ if (isLocalStorage) {
295
+ prodLog.info(`GraphAdjacencyIndex: Using optimized strategy (${storageType})`);
296
+ const result = await this.storage.getVerbs({
297
+ pagination: { limit: 10000000 }
298
+ });
299
+ for (const verb of result.items) {
300
+ const v = verb;
301
+ const graphVerb = {
302
+ id: v.id,
303
+ source: v.source || v.sourceId,
304
+ target: v.target || v.targetId,
305
+ verb: v.verb,
306
+ createdAt: typeof v.createdAt === 'number'
307
+ ? { seconds: Math.floor(v.createdAt / 1000), nanoseconds: (v.createdAt % 1000) * 1000000 }
308
+ : v.createdAt || { seconds: 0, nanoseconds: 0 },
309
+ updatedAt: typeof v.updatedAt === 'number'
310
+ ? { seconds: Math.floor(v.updatedAt / 1000), nanoseconds: (v.updatedAt % 1000) * 1000000 }
311
+ : v.updatedAt || { seconds: 0, nanoseconds: 0 },
312
+ createdBy: v.createdBy || { augmentation: 'unknown', version: '0.0.0' },
313
+ service: v.service,
314
+ data: v.data,
315
+ embedding: v.vector || v.embedding,
316
+ confidence: v.confidence,
317
+ weight: v.weight
318
+ };
319
+ await this.addVerb(graphVerb);
320
+ totalVerbs++;
321
+ }
322
+ prodLog.info(`GraphAdjacencyIndex: Loaded ${totalVerbs.toLocaleString()} verbs (local storage)`);
323
+ }
324
+ else {
325
+ prodLog.info(`GraphAdjacencyIndex: Using cloud pagination strategy (${storageType})`);
326
+ let hasMore = true;
327
+ let cursor = undefined;
328
+ const batchSize = 1000;
329
+ while (hasMore) {
330
+ const result = await this.storage.getVerbs({
331
+ pagination: { limit: batchSize, cursor }
332
+ });
333
+ for (const verb of result.items) {
334
+ const v = verb;
335
+ const graphVerb = {
336
+ id: v.id,
337
+ source: v.source || v.sourceId,
338
+ target: v.target || v.targetId,
339
+ verb: v.verb,
340
+ createdAt: typeof v.createdAt === 'number'
341
+ ? { seconds: Math.floor(v.createdAt / 1000), nanoseconds: (v.createdAt % 1000) * 1000000 }
342
+ : v.createdAt || { seconds: 0, nanoseconds: 0 },
343
+ updatedAt: typeof v.updatedAt === 'number'
344
+ ? { seconds: Math.floor(v.updatedAt / 1000), nanoseconds: (v.updatedAt % 1000) * 1000000 }
345
+ : v.updatedAt || { seconds: 0, nanoseconds: 0 },
346
+ createdBy: v.createdBy || { augmentation: 'unknown', version: '0.0.0' },
347
+ service: v.service,
348
+ data: v.data,
349
+ embedding: v.vector || v.embedding,
350
+ confidence: v.confidence,
351
+ weight: v.weight
352
+ };
353
+ await this.addVerb(graphVerb);
354
+ totalVerbs++;
355
+ }
356
+ hasMore = result.hasMore;
357
+ cursor = result.nextCursor;
358
+ if (totalVerbs % 10000 === 0) {
359
+ prodLog.info(`GraphAdjacencyIndex: Indexed ${totalVerbs} verbs...`);
360
+ }
361
+ }
362
+ prodLog.info(`GraphAdjacencyIndex: Loaded ${totalVerbs.toLocaleString()} verbs (cloud storage)`);
363
+ }
364
+ const rebuildTime = Date.now() - this.rebuildStartTime;
365
+ const memoryUsage = this.calculateMemoryUsage();
366
+ prodLog.info(`GraphAdjacencyIndex: Rebuild complete in ${rebuildTime}ms`);
367
+ prodLog.info(` - Total relationships: ${totalVerbs}`);
368
+ prodLog.info(` - Memory usage: ${(memoryUsage / 1024 / 1024).toFixed(1)}MB`);
369
+ prodLog.info(` - Stats:`, this.native.getStats());
370
+ }
371
+ finally {
372
+ this.isRebuilding = false;
373
+ }
374
+ }
375
+ // ---------------------------------------------------------------------------
376
+ // Flush and close
377
+ // ---------------------------------------------------------------------------
378
+ async flush() {
379
+ if (!this.initialized) {
380
+ return;
381
+ }
382
+ const startTime = Date.now();
383
+ const treeNames = this.native.treeNames();
384
+ const flushPromises = treeNames
385
+ .filter(name => !this.native.memTableIsEmpty(name))
386
+ .map(name => this.flushTree(name));
387
+ if (flushPromises.length > 0) {
388
+ await Promise.all(flushPromises);
389
+ }
390
+ const elapsed = Date.now() - startTime;
391
+ prodLog.debug(`GraphAdjacencyIndex: Flush completed in ${elapsed}ms`);
392
+ }
393
+ async close() {
394
+ if (this.flushTimer) {
395
+ clearInterval(this.flushTimer);
396
+ this.flushTimer = undefined;
397
+ }
398
+ if (this.initialized) {
399
+ // Flush all trees that have pending data
400
+ await this.flush();
401
+ }
402
+ prodLog.info('GraphAdjacencyIndex: Shutdown complete');
403
+ }
404
+ // ---------------------------------------------------------------------------
405
+ // Private: Per-tree storage I/O (adapter-controlled)
406
+ // ---------------------------------------------------------------------------
407
+ /** Blob key for an SSTable: "graph-lsm/{treeName}/{sstableId}" */
408
+ blobKey(treeName, sstableId) {
409
+ return `graph-lsm/${treeName}/${sstableId}`;
410
+ }
411
+ async loadTreeFromStorage(treeName) {
412
+ const prefix = TREE_PREFIXES[treeName];
413
+ if (!prefix)
414
+ return;
415
+ try {
416
+ const metadata = await this.storage.getMetadata(`${prefix}-manifest`);
417
+ if (metadata && metadata.data) {
418
+ const data = metadata.data;
419
+ const manifestJson = JSON.stringify({
420
+ sstables: data.sstables || {},
421
+ lastCompaction: data.lastCompaction || Date.now(),
422
+ totalRelationships: data.totalRelationships || 0,
423
+ });
424
+ this.native.loadManifestJson(treeName, manifestJson);
425
+ await this.loadTreeSSTables(treeName, prefix);
426
+ }
427
+ }
428
+ catch {
429
+ prodLog.debug(`GraphAdjacencyIndex: No existing manifest for ${treeName}, starting fresh`);
430
+ }
431
+ }
432
+ async loadTreeSSTables(treeName, prefix) {
433
+ const ids = this.native.getManifestSstableIds(treeName);
434
+ let mmapLoaded = 0;
435
+ let binaryLoaded = 0;
436
+ let legacyLoaded = 0;
437
+ const loadPromises = ids.map(async (sstableId) => {
438
+ try {
439
+ const level = this.native.getManifestSstableLevel(treeName, sstableId);
440
+ if (level === null || level === undefined) {
441
+ return;
442
+ }
443
+ const key = this.blobKey(treeName, sstableId);
444
+ // Tier 1: Try mmap path (getBinaryBlobPath → loadSstableFromFile)
445
+ if (this.hasMmapPaths) {
446
+ const filePath = this.storage.getBinaryBlobPath(key);
447
+ if (filePath) {
448
+ try {
449
+ this.native.loadSstableFromFile(treeName, sstableId, level, filePath);
450
+ mmapLoaded++;
451
+ return;
452
+ }
453
+ catch {
454
+ // File may not exist yet (first run), fall through
455
+ }
456
+ }
457
+ }
458
+ // Tier 2: Try binary blob (loadBinaryBlob → loadSstableFromBinary)
459
+ if (this.hasBinaryBlobs) {
460
+ const blobData = await this.storage.loadBinaryBlob(key);
461
+ if (blobData) {
462
+ this.native.loadSstableFromBinary(treeName, sstableId, level, blobData);
463
+ binaryLoaded++;
464
+ return;
465
+ }
466
+ }
467
+ // Tier 3: Legacy (base64 in JSON metadata)
468
+ const storageKey = `${prefix}-${sstableId}`;
469
+ const metadata = await this.storage.getMetadata(storageKey);
470
+ if (metadata && metadata.data) {
471
+ const data = metadata.data;
472
+ if (data.type === 'native-lsm-sstable' && data.data) {
473
+ const buffer = Buffer.from(data.data, 'base64');
474
+ this.native.loadSstable(treeName, sstableId, level, buffer);
475
+ legacyLoaded++;
476
+ }
477
+ else if (data.type === 'lsm-sstable' && data.data) {
478
+ prodLog.warn(`GraphAdjacencyIndex: Skipping legacy SSTable ${sstableId} (old TS format)`);
479
+ }
480
+ }
481
+ }
482
+ catch (error) {
483
+ prodLog.warn(`GraphAdjacencyIndex: Failed to load SSTable ${sstableId} for ${treeName}`, error);
484
+ }
485
+ });
486
+ await Promise.all(loadPromises);
487
+ const parts = [];
488
+ if (mmapLoaded > 0)
489
+ parts.push(`${mmapLoaded} mmap`);
490
+ if (binaryLoaded > 0)
491
+ parts.push(`${binaryLoaded} binary`);
492
+ if (legacyLoaded > 0)
493
+ parts.push(`${legacyLoaded} legacy`);
494
+ const detail = parts.length > 0 ? ` (${parts.join(', ')})` : '';
495
+ prodLog.info(`GraphAdjacencyIndex: Loaded ${ids.length} SSTables for ${treeName}${detail}`);
496
+ }
497
+ async flushTree(treeName) {
498
+ if (this.native.memTableIsEmpty(treeName)) {
499
+ return;
500
+ }
501
+ const prefix = TREE_PREFIXES[treeName];
502
+ if (!prefix)
503
+ return;
504
+ const startTime = Date.now();
505
+ try {
506
+ // Binary blob flush (adapter-controlled): Rust serializes → adapter writes
507
+ if (this.hasBinaryBlobs) {
508
+ const result = this.native.flushTreeToBinary(treeName);
509
+ if (!result)
510
+ return;
511
+ const key = this.blobKey(treeName, result.sstableId);
512
+ await this.storage.saveBinaryBlob(key, Buffer.from(result.data));
513
+ // If adapter supports mmap, upgrade InMemory → Mapped
514
+ if (this.hasMmapPaths) {
515
+ const filePath = this.storage.getBinaryBlobPath(key);
516
+ if (filePath) {
517
+ try {
518
+ this.native.registerMmapSstable(treeName, result.sstableId, result.level, filePath);
519
+ }
520
+ catch {
521
+ // Non-fatal: InMemory variant still works
522
+ }
523
+ }
524
+ }
525
+ await this.saveTreeManifest(treeName, prefix);
526
+ const elapsed = Date.now() - startTime;
527
+ prodLog.info(`GraphAdjacencyIndex: Flushed ${treeName} in ${elapsed}ms (${result.entryCount} entries, ${result.relationshipCount} relationships, binary blob)`);
528
+ if (this.native.needsCompaction(treeName, 0)) {
529
+ setImmediate(() => this.compactTree(treeName, 0));
530
+ }
531
+ return;
532
+ }
533
+ // Legacy flush (base64 in JSON)
534
+ const result = this.native.flushTree(treeName);
535
+ if (!result) {
536
+ return;
537
+ }
538
+ // Persist SSTable via storage adapter (base64 in JSON)
539
+ const storageKey = `${prefix}-${result.sstableId}`;
540
+ await this.storage.saveMetadata(storageKey, {
541
+ noun: 'thing',
542
+ data: {
543
+ type: 'native-lsm-sstable',
544
+ data: Buffer.from(result.data).toString('base64'),
545
+ },
546
+ });
547
+ // Save manifest
548
+ await this.saveTreeManifest(treeName, prefix);
549
+ const elapsed = Date.now() - startTime;
550
+ prodLog.info(`GraphAdjacencyIndex: Flushed ${treeName} in ${elapsed}ms (${result.entryCount} entries, ${result.relationshipCount} relationships)`);
551
+ // Check if compaction needed
552
+ if (this.native.needsCompaction(treeName, 0)) {
553
+ setImmediate(() => this.compactTree(treeName, 0));
554
+ }
555
+ }
556
+ catch (error) {
557
+ prodLog.error(`GraphAdjacencyIndex: Failed to flush ${treeName}`, error);
558
+ throw error;
559
+ }
560
+ }
561
+ async compactTree(treeName, level) {
562
+ if (this.isCompacting[treeName]) {
563
+ return;
564
+ }
565
+ this.isCompacting[treeName] = true;
566
+ const prefix = TREE_PREFIXES[treeName];
567
+ if (!prefix)
568
+ return;
569
+ const startTime = Date.now();
570
+ try {
571
+ // Binary blob compaction (adapter-controlled)
572
+ if (this.hasBinaryBlobs) {
573
+ const result = this.native.compactTreeToBinary(treeName, level);
574
+ if (!result)
575
+ return;
576
+ prodLog.info(`GraphAdjacencyIndex: Compacting ${treeName} L${level} → L${result.newLevel} (binary blob)`);
577
+ // Save new merged SSTable via adapter
578
+ const newKey = this.blobKey(treeName, result.newSstableId);
579
+ await this.storage.saveBinaryBlob(newKey, Buffer.from(result.data));
580
+ // If adapter supports mmap, upgrade InMemory → Mapped
581
+ if (this.hasMmapPaths) {
582
+ const filePath = this.storage.getBinaryBlobPath(newKey);
583
+ if (filePath) {
584
+ try {
585
+ this.native.registerMmapSstable(treeName, result.newSstableId, result.newLevel, filePath);
586
+ }
587
+ catch {
588
+ // Non-fatal: InMemory variant still works
589
+ }
590
+ }
591
+ }
592
+ // Delete old blobs (best-effort)
593
+ for (const oldId of result.oldIds) {
594
+ const oldBlobKey = this.blobKey(treeName, oldId);
595
+ this.storage.deleteBinaryBlob(oldBlobKey).catch(() => { });
596
+ }
597
+ await this.saveTreeManifest(treeName, prefix);
598
+ const elapsed = Date.now() - startTime;
599
+ prodLog.info(`GraphAdjacencyIndex: Compaction ${treeName} L${level} → L${result.newLevel} complete in ${elapsed}ms`);
600
+ if (result.newLevel < 6 && this.native.needsCompaction(treeName, result.newLevel)) {
601
+ setImmediate(() => this.compactTree(treeName, result.newLevel));
602
+ }
603
+ return;
604
+ }
605
+ // Legacy compaction (base64 in JSON)
606
+ const result = this.native.compactTree(treeName, level);
607
+ if (!result) {
608
+ return;
609
+ }
610
+ prodLog.info(`GraphAdjacencyIndex: Compacting ${treeName} L${level} → L${result.newLevel}`);
611
+ // Save new merged SSTable
612
+ const storageKey = `${prefix}-${result.newSstableId}`;
613
+ await this.storage.saveMetadata(storageKey, {
614
+ noun: 'thing',
615
+ data: {
616
+ type: 'native-lsm-sstable',
617
+ data: Buffer.from(result.newData).toString('base64'),
618
+ },
619
+ });
620
+ // Best-effort cleanup of old SSTables
621
+ for (const oldId of result.oldIds) {
622
+ const oldKey = `${prefix}-${oldId}`;
623
+ this.storage.saveMetadata(oldKey, {
624
+ noun: 'thing',
625
+ data: { type: 'deleted' },
626
+ }).catch(() => { });
627
+ }
628
+ await this.saveTreeManifest(treeName, prefix);
629
+ const elapsed = Date.now() - startTime;
630
+ prodLog.info(`GraphAdjacencyIndex: Compaction ${treeName} L${level} → L${result.newLevel} complete in ${elapsed}ms`);
631
+ // Check next level
632
+ if (result.newLevel < 6 && this.native.needsCompaction(treeName, result.newLevel)) {
633
+ setImmediate(() => this.compactTree(treeName, result.newLevel));
634
+ }
635
+ }
636
+ catch (error) {
637
+ prodLog.error(`GraphAdjacencyIndex: Compaction failed for ${treeName} L${level}`, error);
638
+ }
639
+ finally {
640
+ this.isCompacting[treeName] = false;
641
+ }
642
+ }
643
+ async saveTreeManifest(treeName, prefix) {
644
+ try {
645
+ const manifestJson = this.native.getManifestJson(treeName);
646
+ const manifestData = JSON.parse(manifestJson);
647
+ await this.storage.saveMetadata(`${prefix}-manifest`, {
648
+ noun: 'thing',
649
+ data: manifestData,
650
+ });
651
+ }
652
+ catch (error) {
653
+ prodLog.error(`GraphAdjacencyIndex: Failed to save manifest for ${treeName}`, error);
654
+ throw error;
655
+ }
656
+ }
657
+ startAutoFlush() {
658
+ this.flushTimer = setInterval(async () => {
659
+ await this.flush();
660
+ }, this.config.flushInterval);
661
+ }
662
+ calculateMemoryUsage() {
663
+ const stats = this.native.getStats();
664
+ let bytes = 0;
665
+ bytes += stats.sourceMemTableMemory;
666
+ bytes += stats.targetMemTableMemory;
667
+ bytes += stats.verbIdCount * 8;
668
+ return bytes;
669
+ }
670
+ }
671
+ //# sourceMappingURL=NativeGraphAdjacencyIndex.js.map
@@ -0,0 +1,22 @@
1
+ /**
2
+ * @soulcraft/cortex — Native Rust acceleration for Brainy
3
+ *
4
+ * When installed alongside @soulcraft/brainy, this package automatically
5
+ * provides native Rust implementations for:
6
+ * - SIMD distance calculations (cosine, euclidean, manhattan, dot product)
7
+ * - Metadata index (full query/mutation engine in Rust)
8
+ * - Graph adjacency index (4 LSM-trees + verb tracking)
9
+ * - Entity ID mapper (UUID ↔ integer bidirectional mapping)
10
+ * - Roaring bitmaps (CRoaring bindings)
11
+ * - Embedding engine (Candle ML — CPU, CUDA, Metal)
12
+ * - Msgpack encoding/decoding
13
+ * - MmapFileSystemStorage adapter (zero-copy SSTables)
14
+ *
15
+ * Usage: `npm install @soulcraft/cortex` — auto-detected, zero config.
16
+ */
17
+ export { default } from './plugin.js';
18
+ export { loadNativeModule, isNativeAvailable } from './native/index.js';
19
+ export type { NativeBindings } from './native/types.js';
20
+ export { NativeEmbeddingEngine, nativeEmbeddingEngine, cosineSimilarity } from './native/NativeEmbeddingEngine.js';
21
+ export { RoaringBitmap32, RoaringBitmap32Iterator, roaringLibraryInitialize, roaringLibraryIsReady, SerializationFormat, DeserializationFormat } from './native/NativeRoaringBitmap32.js';
22
+ //# sourceMappingURL=index.d.ts.map