@soulcraft/cortex 1.4.0 → 1.4.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 +100 -60
- package/dist/hnsw/NativeHNSWWrapper.d.ts +115 -0
- package/dist/hnsw/NativeHNSWWrapper.js +515 -0
- package/dist/plugin.d.ts +10 -8
- package/dist/plugin.js +28 -13
- package/dist/utils/NativeMetadataIndex.js +1 -1
- package/dist/utils/NativeUnifiedCache.d.ts +81 -0
- package/dist/utils/NativeUnifiedCache.js +268 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,97 +1,121 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Cortex
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
<p align="center">
|
|
4
|
+
<img src="https://raw.githubusercontent.com/soulcraftlabs/brainy/main/brainy.png" alt="Brainy Logo" width="200">
|
|
5
|
+
</p>
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
<p align="center">
|
|
8
|
+
<strong>Native Rust acceleration for <a href="https://github.com/soulcraftlabs/brainy">Brainy</a></strong>
|
|
9
|
+
</p>
|
|
6
10
|
|
|
7
|
-
|
|
11
|
+
<p align="center">
|
|
12
|
+
<a href="https://www.npmjs.com/package/@soulcraft/cortex"><img src="https://badge.fury.io/js/%40soulcraft%2Fcortex.svg" alt="npm version"></a>
|
|
13
|
+
<a href="https://soulcraft.com/product"><img src="https://img.shields.io/badge/info-soulcraft.com-blue.svg" alt="Product Info"></a>
|
|
14
|
+
</p>
|
|
8
15
|
|
|
9
|
-
|
|
10
|
-
|------|-------|-----|
|
|
11
|
-
| **Standard** | $199/year | Individuals and small teams |
|
|
12
|
-
| **Enterprise** | $999/year | Companies, priority support |
|
|
16
|
+
---
|
|
13
17
|
|
|
14
|
-
|
|
18
|
+
## What is Cortex?
|
|
15
19
|
|
|
16
|
-
|
|
20
|
+
Cortex replaces Brainy's JavaScript internals with native Rust implementations. Same API, same data, dramatically faster.
|
|
17
21
|
|
|
18
|
-
|
|
22
|
+
Install it, and Brainy automatically uses Rust for distance calculations, embeddings, metadata queries, graph operations, and more. No code changes needed.
|
|
19
23
|
|
|
20
24
|
```bash
|
|
21
25
|
npm install @soulcraft/cortex
|
|
22
26
|
```
|
|
23
27
|
|
|
24
|
-
|
|
28
|
+
```typescript
|
|
29
|
+
import Brainy from '@soulcraft/brainy'
|
|
25
30
|
|
|
26
|
-
|
|
31
|
+
const brain = new Brainy()
|
|
32
|
+
await brain.init()
|
|
33
|
+
// Cortex is auto-detected. That's it.
|
|
27
34
|
|
|
28
|
-
|
|
35
|
+
// Verify what's accelerated
|
|
36
|
+
const diag = brain.diagnostics()
|
|
37
|
+
console.log(diag.providers)
|
|
38
|
+
// { distance: { source: 'plugin' }, embeddings: { source: 'plugin' }, ... }
|
|
39
|
+
```
|
|
29
40
|
|
|
30
|
-
|
|
31
|
-
# Option 1: Environment variable
|
|
32
|
-
export SOULCRAFT_LICENSE=sc_cortex_<your-key>
|
|
41
|
+
## Why Cortex?
|
|
33
42
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
43
|
+
Brainy works great on its own with pure JavaScript and WASM. Cortex is for when you need more:
|
|
44
|
+
|
|
45
|
+
- **SIMD distance calculations** — every vector comparison gets faster
|
|
46
|
+
- **Native ML inference** — Candle engine replaces WASM embeddings, with CPU, CUDA, and Metal support
|
|
47
|
+
- **Rust metadata engine** — queries and mutations run in Rust with CRoaring bitmaps
|
|
48
|
+
- **Native graph index** — 4 LSM-trees with verb tracking, all in Rust
|
|
49
|
+
- **Batch embeddings** — single Rust forward pass for bulk operations
|
|
50
|
+
- **Zero-copy storage** — mmap-backed SSTables for disk-heavy workloads
|
|
37
51
|
|
|
38
|
-
|
|
52
|
+
Brainy's plugin system means cortex acceleration flows through every operation — `add()`, `find()`, `update()`, `delete()`, `similar()`, VFS path resolution, neural APIs, and everything in between.
|
|
39
53
|
|
|
40
54
|
## What Gets Accelerated
|
|
41
55
|
|
|
42
|
-
| Subsystem |
|
|
43
|
-
|
|
44
|
-
| Distance
|
|
45
|
-
|
|
|
46
|
-
|
|
|
47
|
-
|
|
|
48
|
-
|
|
|
49
|
-
|
|
|
50
|
-
|
|
|
51
|
-
|
|
|
52
|
-
|
|
|
53
|
-
|
|
|
56
|
+
| Subsystem | What Cortex Provides | Impact |
|
|
57
|
+
|-----------|---------------------|--------|
|
|
58
|
+
| **Distance** | SIMD cosine, euclidean, manhattan, dot product | Every vector comparison |
|
|
59
|
+
| **Embeddings** | Candle ML engine (CPU/CUDA/Metal) | No WASM compilation, GPU support |
|
|
60
|
+
| **Batch embeddings** | Single forward pass for multiple texts | Bulk import and reindex |
|
|
61
|
+
| **Metadata index** | Rust query/mutation engine with CRoaring | Faster filtering and type queries |
|
|
62
|
+
| **Graph adjacency** | 4 LSM-trees with verb tracking | Faster relationship operations |
|
|
63
|
+
| **Entity ID mapper** | Rust O(1) HashMap for UUID-integer mapping | Bitmap index operations |
|
|
64
|
+
| **Vector quantization** | SIMD SQ8/SQ4 distance on compressed vectors | 4-8x memory reduction |
|
|
65
|
+
| **Product quantization** | Native PQ codebook training and ADC | 16-32x compression |
|
|
66
|
+
| **Graph compression** | Delta-varint encoded connections | 2-3x smaller graph storage |
|
|
67
|
+
| **Roaring bitmaps** | CRoaring bindings | Faster set operations |
|
|
68
|
+
| **Msgpack** | Native encode/decode | Faster serialization |
|
|
69
|
+
| **Storage** | MmapFileSystemStorage with zero-copy SSTables | Faster disk I/O |
|
|
70
|
+
|
|
71
|
+
## Getting Started
|
|
72
|
+
|
|
73
|
+
### 1. Install
|
|
54
74
|
|
|
55
|
-
|
|
75
|
+
```bash
|
|
76
|
+
npm install @soulcraft/brainy @soulcraft/cortex
|
|
77
|
+
```
|
|
56
78
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
| macOS | x64 (Intel) | Supported |
|
|
63
|
-
| Windows | x64 | Supported |
|
|
79
|
+
### 2. Set up your license
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
# Environment variable
|
|
83
|
+
export SOULCRAFT_LICENSE=sc_cortex_<your-key>
|
|
64
84
|
|
|
65
|
-
|
|
85
|
+
# Or license file
|
|
86
|
+
echo "sc_cortex_<your-key>" > ~/.soulcraft/license
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
License validation is offline — no network calls, no telemetry.
|
|
90
|
+
|
|
91
|
+
### 3. Use Brainy as normal
|
|
66
92
|
|
|
67
93
|
```typescript
|
|
68
94
|
import Brainy from '@soulcraft/brainy'
|
|
69
95
|
|
|
70
|
-
// cortex is auto-detected — no import or config needed
|
|
71
96
|
const brain = new Brainy({ storage: { type: 'filesystem', rootDirectory: './data' } })
|
|
72
97
|
await brain.init()
|
|
73
|
-
|
|
74
|
-
//
|
|
75
|
-
console.log(brain.getActivePlugins())
|
|
76
|
-
// → ['@soulcraft/cortex']
|
|
98
|
+
// [brainy] Plugin activated: @soulcraft/cortex
|
|
99
|
+
// [brainy] Providers: 10/10 native (@soulcraft/cortex)
|
|
77
100
|
```
|
|
78
101
|
|
|
79
|
-
###
|
|
102
|
+
### 4. Verify in production
|
|
80
103
|
|
|
81
|
-
|
|
104
|
+
Use `requireProviders()` to fail fast if cortex isn't providing expected acceleration:
|
|
82
105
|
|
|
83
106
|
```typescript
|
|
84
|
-
|
|
85
|
-
|
|
107
|
+
brain.requireProviders(['distance', 'embeddings', 'metadataIndex', 'graphIndex'])
|
|
108
|
+
```
|
|
86
109
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
110
|
+
Or inspect the full picture with diagnostics:
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
console.log(brain.diagnostics())
|
|
90
114
|
```
|
|
91
115
|
|
|
92
|
-
|
|
116
|
+
## MmapFileSystemStorage
|
|
93
117
|
|
|
94
|
-
|
|
118
|
+
Cortex includes a storage adapter with mmap-backed binary blob support:
|
|
95
119
|
|
|
96
120
|
```typescript
|
|
97
121
|
const brain = new Brainy({
|
|
@@ -99,17 +123,26 @@ const brain = new Brainy({
|
|
|
99
123
|
})
|
|
100
124
|
```
|
|
101
125
|
|
|
102
|
-
|
|
126
|
+
Zero-copy access to LSM-tree SSTables — useful for large graph datasets where disk I/O is the bottleneck.
|
|
127
|
+
|
|
128
|
+
## Platform Support
|
|
129
|
+
|
|
130
|
+
| Platform | Architecture | Status |
|
|
131
|
+
|----------|-------------|--------|
|
|
132
|
+
| Linux | x64 (glibc) | Supported |
|
|
133
|
+
| Linux | arm64 (glibc) | Supported |
|
|
134
|
+
| macOS | arm64 (Apple Silicon) | Supported |
|
|
135
|
+
| macOS | x64 (Intel) | Supported |
|
|
136
|
+
| Windows | x64 | Supported |
|
|
103
137
|
|
|
104
138
|
## Requirements
|
|
105
139
|
|
|
106
140
|
- `@soulcraft/brainy` >= 7.0.0
|
|
107
141
|
- Node.js >= 22 or Bun >= 1.3.0
|
|
108
|
-
- Platform: Linux/macOS/Windows (see table above)
|
|
109
142
|
|
|
110
|
-
## Direct Access
|
|
143
|
+
## Direct Native Access
|
|
111
144
|
|
|
112
|
-
For advanced use cases,
|
|
145
|
+
For advanced use cases, access the Rust bindings directly:
|
|
113
146
|
|
|
114
147
|
```typescript
|
|
115
148
|
import { loadNativeModule, isNativeAvailable } from '@soulcraft/cortex'
|
|
@@ -120,6 +153,13 @@ if (isNativeAvailable()) {
|
|
|
120
153
|
}
|
|
121
154
|
```
|
|
122
155
|
|
|
156
|
+
## Learn More
|
|
157
|
+
|
|
158
|
+
- **[Product info and licensing](https://soulcraft.com/product)**
|
|
159
|
+
- **[Brainy documentation](https://soulcraft.com/docs)**
|
|
160
|
+
- **[Brainy on GitHub](https://github.com/soulcraftlabs/brainy)**
|
|
161
|
+
- **[Plugin development guide](https://github.com/soulcraftlabs/brainy/blob/main/docs/PLUGINS.md)**
|
|
162
|
+
|
|
123
163
|
## License
|
|
124
164
|
|
|
125
165
|
Commercial. See [LICENSE](./LICENSE) for details.
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NativeHNSWWrapper — Thin TS wrapper around the Rust HNSW graph engine
|
|
3
|
+
*
|
|
4
|
+
* Bridges brainy's HNSWIndex API to the native Rust NativeHNSWIndex.
|
|
5
|
+
* The Rust engine handles all graph operations (insert, search, remove) in-memory
|
|
6
|
+
* while this wrapper handles async storage I/O for persistence.
|
|
7
|
+
*
|
|
8
|
+
* Key design:
|
|
9
|
+
* - Uses addItemFull() for single-FFI-call inserts (returns node + neighbors + system)
|
|
10
|
+
* - Persistence modes: 'immediate' (safe) or 'deferred' (30-50x faster for cloud)
|
|
11
|
+
* - COW via native fork() (Arc-based copy-on-write in Rust)
|
|
12
|
+
* - rebuild() restores pre-computed graph from storage (O(N) vs O(N log N) rebuild)
|
|
13
|
+
*/
|
|
14
|
+
import type { HNSWConfig, HNSWNoun, Vector, VectorDocument, DistanceFunction, StorageAdapter } from '@soulcraft/brainy';
|
|
15
|
+
export declare class NativeHNSWWrapper {
|
|
16
|
+
private native;
|
|
17
|
+
private config;
|
|
18
|
+
private distanceFunction;
|
|
19
|
+
private storage;
|
|
20
|
+
private persistMode;
|
|
21
|
+
private dirtyNodes;
|
|
22
|
+
private dirtySystem;
|
|
23
|
+
private useParallelization;
|
|
24
|
+
private unifiedCache;
|
|
25
|
+
private cowEnabled;
|
|
26
|
+
constructor(config: (Partial<HNSWConfig> & {
|
|
27
|
+
distanceFunction?: DistanceFunction;
|
|
28
|
+
}) | undefined, distanceFunction: DistanceFunction, options?: {
|
|
29
|
+
storage?: StorageAdapter | null;
|
|
30
|
+
persistMode?: 'immediate' | 'deferred';
|
|
31
|
+
});
|
|
32
|
+
addItem(item: VectorDocument): Promise<string>;
|
|
33
|
+
removeItem(id: string): Promise<boolean>;
|
|
34
|
+
search(queryVector: Vector, k?: number, filter?: (id: string) => Promise<boolean>, options?: {
|
|
35
|
+
rerank?: {
|
|
36
|
+
multiplier: number;
|
|
37
|
+
};
|
|
38
|
+
candidateIds?: string[];
|
|
39
|
+
}): Promise<Array<[string, number]>>;
|
|
40
|
+
flush(): Promise<number>;
|
|
41
|
+
getDirtyNodeCount(): number;
|
|
42
|
+
getPersistMode(): 'immediate' | 'deferred';
|
|
43
|
+
rebuild(options?: {
|
|
44
|
+
lazy?: boolean;
|
|
45
|
+
batchSize?: number;
|
|
46
|
+
onProgress?: (loaded: number, total: number) => void;
|
|
47
|
+
}): Promise<void>;
|
|
48
|
+
enableCOW(parent: NativeHNSWWrapper): void;
|
|
49
|
+
setUseParallelization(useParallelization: boolean): void;
|
|
50
|
+
getUseParallelization(): boolean;
|
|
51
|
+
size(): number;
|
|
52
|
+
clear(): void;
|
|
53
|
+
getEntryPointId(): string | null;
|
|
54
|
+
getMaxLevel(): number;
|
|
55
|
+
getDimension(): number | null;
|
|
56
|
+
getConfig(): HNSWConfig;
|
|
57
|
+
getDistanceFunction(): DistanceFunction;
|
|
58
|
+
/**
|
|
59
|
+
* Get all nouns — builds HNSWNoun objects from native data.
|
|
60
|
+
* @deprecated Use getNounsPaginated() instead
|
|
61
|
+
*/
|
|
62
|
+
getNouns(): Map<string, HNSWNoun>;
|
|
63
|
+
getNounsPaginated(options?: {
|
|
64
|
+
offset?: number;
|
|
65
|
+
limit?: number;
|
|
66
|
+
filter?: (noun: HNSWNoun) => boolean;
|
|
67
|
+
}): {
|
|
68
|
+
items: Map<string, HNSWNoun>;
|
|
69
|
+
totalCount: number;
|
|
70
|
+
hasMore: boolean;
|
|
71
|
+
};
|
|
72
|
+
getNodesAtLevel(level: number): HNSWNoun[];
|
|
73
|
+
getLevelStats(): Array<{
|
|
74
|
+
level: number;
|
|
75
|
+
nodeCount: number;
|
|
76
|
+
avgConnections: number;
|
|
77
|
+
}>;
|
|
78
|
+
getIndexHealth(): {
|
|
79
|
+
averageConnections: number;
|
|
80
|
+
layerDistribution: number[];
|
|
81
|
+
maxLayer: number;
|
|
82
|
+
totalNodes: number;
|
|
83
|
+
};
|
|
84
|
+
getCacheStats(): {
|
|
85
|
+
cachingStrategy: 'preloaded' | 'on-demand';
|
|
86
|
+
autoDetection: {
|
|
87
|
+
entityCount: number;
|
|
88
|
+
estimatedVectorMemoryMB: number;
|
|
89
|
+
availableCacheMB: number;
|
|
90
|
+
threshold: number;
|
|
91
|
+
rationale: string;
|
|
92
|
+
};
|
|
93
|
+
unifiedCache: {
|
|
94
|
+
totalSize: number;
|
|
95
|
+
maxSize: number;
|
|
96
|
+
utilizationPercent: number;
|
|
97
|
+
itemCount: number;
|
|
98
|
+
hitRatePercent: number;
|
|
99
|
+
totalAccessCount: number;
|
|
100
|
+
};
|
|
101
|
+
hnswCache: {
|
|
102
|
+
vectorsInCache: number;
|
|
103
|
+
cacheKeyPrefix: string;
|
|
104
|
+
estimatedMemoryMB: number;
|
|
105
|
+
};
|
|
106
|
+
fairness: {
|
|
107
|
+
hnswAccessCount: number;
|
|
108
|
+
hnswAccessPercent: number;
|
|
109
|
+
totalAccessCount: number;
|
|
110
|
+
fairnessViolation: boolean;
|
|
111
|
+
};
|
|
112
|
+
recommendations: string[];
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=NativeHNSWWrapper.d.ts.map
|
|
@@ -0,0 +1,515 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NativeHNSWWrapper — Thin TS wrapper around the Rust HNSW graph engine
|
|
3
|
+
*
|
|
4
|
+
* Bridges brainy's HNSWIndex API to the native Rust NativeHNSWIndex.
|
|
5
|
+
* The Rust engine handles all graph operations (insert, search, remove) in-memory
|
|
6
|
+
* while this wrapper handles async storage I/O for persistence.
|
|
7
|
+
*
|
|
8
|
+
* Key design:
|
|
9
|
+
* - Uses addItemFull() for single-FFI-call inserts (returns node + neighbors + system)
|
|
10
|
+
* - Persistence modes: 'immediate' (safe) or 'deferred' (30-50x faster for cloud)
|
|
11
|
+
* - COW via native fork() (Arc-based copy-on-write in Rust)
|
|
12
|
+
* - rebuild() restores pre-computed graph from storage (O(N) vs O(N log N) rebuild)
|
|
13
|
+
*/
|
|
14
|
+
import { loadNativeModule } from '../native/index.js';
|
|
15
|
+
import { getGlobalCache, prodLog } from '@soulcraft/brainy/internals';
|
|
16
|
+
const DEFAULT_CONFIG = {
|
|
17
|
+
M: 16,
|
|
18
|
+
efConstruction: 200,
|
|
19
|
+
efSearch: 50,
|
|
20
|
+
ml: 16,
|
|
21
|
+
};
|
|
22
|
+
export class NativeHNSWWrapper {
|
|
23
|
+
native;
|
|
24
|
+
config;
|
|
25
|
+
distanceFunction;
|
|
26
|
+
storage;
|
|
27
|
+
persistMode;
|
|
28
|
+
dirtyNodes = new Set();
|
|
29
|
+
dirtySystem = false;
|
|
30
|
+
useParallelization = true;
|
|
31
|
+
unifiedCache;
|
|
32
|
+
// COW support
|
|
33
|
+
cowEnabled = false;
|
|
34
|
+
constructor(config = {}, distanceFunction, options = {}) {
|
|
35
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
36
|
+
this.distanceFunction = distanceFunction;
|
|
37
|
+
this.storage = options.storage || null;
|
|
38
|
+
this.persistMode = options.persistMode || 'immediate';
|
|
39
|
+
this.unifiedCache = getGlobalCache();
|
|
40
|
+
const bindings = loadNativeModule();
|
|
41
|
+
const nativeConfig = {
|
|
42
|
+
m: this.config.M,
|
|
43
|
+
efConstruction: this.config.efConstruction,
|
|
44
|
+
efSearch: this.config.efSearch,
|
|
45
|
+
ml: this.config.ml,
|
|
46
|
+
};
|
|
47
|
+
this.native = new bindings.NativeHNSWIndex(nativeConfig);
|
|
48
|
+
prodLog.info(`NativeHNSWWrapper initialized (M=${this.config.M}, ef=${this.config.efSearch}, ` +
|
|
49
|
+
`persist=${this.persistMode})`);
|
|
50
|
+
}
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
// Core CRUD
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
async addItem(item) {
|
|
55
|
+
if (!item)
|
|
56
|
+
throw new Error('Item is undefined or null');
|
|
57
|
+
if (!item.vector)
|
|
58
|
+
throw new Error('Vector is undefined or null');
|
|
59
|
+
const { id, vector } = item;
|
|
60
|
+
// Use addItemFull — single FFI call returns all persistence data
|
|
61
|
+
const result = this.native.addItemFull(id, vector);
|
|
62
|
+
if (this.persistMode === 'immediate' && this.storage) {
|
|
63
|
+
// Persist new node
|
|
64
|
+
await this.storage.saveHNSWData(result.id, result.nodeData).catch((e) => {
|
|
65
|
+
console.error(`[HNSW native] Failed to persist node ${result.id}:`, e);
|
|
66
|
+
});
|
|
67
|
+
// Persist modified neighbors
|
|
68
|
+
const neighborPromises = result.modifiedNeighbors.map(neighbor => this.storage.saveHNSWData(neighbor.id, {
|
|
69
|
+
level: neighbor.level,
|
|
70
|
+
connections: neighbor.connections,
|
|
71
|
+
}).catch((e) => {
|
|
72
|
+
console.error(`[HNSW native] Failed to persist neighbor ${neighbor.id}:`, e);
|
|
73
|
+
}));
|
|
74
|
+
await Promise.allSettled(neighborPromises);
|
|
75
|
+
// Persist system data
|
|
76
|
+
await this.storage.saveHNSWSystem(result.systemData).catch((e) => {
|
|
77
|
+
console.error('[HNSW native] Failed to persist system data:', e);
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
else if (this.persistMode === 'deferred') {
|
|
81
|
+
this.dirtyNodes.add(result.id);
|
|
82
|
+
for (const neighbor of result.modifiedNeighbors) {
|
|
83
|
+
this.dirtyNodes.add(neighbor.id);
|
|
84
|
+
}
|
|
85
|
+
this.dirtySystem = true;
|
|
86
|
+
}
|
|
87
|
+
return result.id;
|
|
88
|
+
}
|
|
89
|
+
async removeItem(id) {
|
|
90
|
+
if (!this.native.hasNode(id))
|
|
91
|
+
return false;
|
|
92
|
+
// Get neighbors before removal (they'll be modified)
|
|
93
|
+
const nodeData = this.native.getNodeData(id);
|
|
94
|
+
const neighborIds = new Set();
|
|
95
|
+
if (nodeData) {
|
|
96
|
+
for (const ids of Object.values(nodeData.connections)) {
|
|
97
|
+
for (const nid of ids) {
|
|
98
|
+
neighborIds.add(nid);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
const removed = this.native.removeItem(id);
|
|
103
|
+
if (!removed)
|
|
104
|
+
return false;
|
|
105
|
+
if (this.persistMode === 'immediate' && this.storage) {
|
|
106
|
+
// Persist updated neighbors (their connections changed)
|
|
107
|
+
const promises = Array.from(neighborIds).map(async (nid) => {
|
|
108
|
+
const nData = this.native.getNodeData(nid);
|
|
109
|
+
if (nData) {
|
|
110
|
+
await this.storage.saveHNSWData(nid, nData).catch((e) => {
|
|
111
|
+
console.error(`[HNSW native] Failed to persist neighbor ${nid} after removal:`, e);
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
await Promise.allSettled(promises);
|
|
116
|
+
// Persist system data (entry point may have changed)
|
|
117
|
+
const sysData = this.native.getSystemData();
|
|
118
|
+
await this.storage.saveHNSWSystem(sysData).catch((e) => {
|
|
119
|
+
console.error('[HNSW native] Failed to persist system data after removal:', e);
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
else if (this.persistMode === 'deferred') {
|
|
123
|
+
for (const nid of neighborIds) {
|
|
124
|
+
this.dirtyNodes.add(nid);
|
|
125
|
+
}
|
|
126
|
+
// Remove the deleted node from dirty set (no need to persist it)
|
|
127
|
+
this.dirtyNodes.delete(id);
|
|
128
|
+
this.dirtySystem = true;
|
|
129
|
+
}
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
async search(queryVector, k = 10, filter, options) {
|
|
133
|
+
if (this.native.size() === 0)
|
|
134
|
+
return [];
|
|
135
|
+
if (!queryVector)
|
|
136
|
+
throw new Error('Query vector is undefined or null');
|
|
137
|
+
let results;
|
|
138
|
+
if (options?.candidateIds && options.candidateIds.length > 0) {
|
|
139
|
+
// Pre-filtered search: use native searchWithCandidates for optimal performance
|
|
140
|
+
// The Rust engine only traverses candidate nodes, avoiding unnecessary distance calculations
|
|
141
|
+
results = this.native.searchWithCandidates(queryVector, k, this.config.efSearch, options.candidateIds);
|
|
142
|
+
}
|
|
143
|
+
else if (filter) {
|
|
144
|
+
// Fallback: search with larger k and post-filter asynchronously
|
|
145
|
+
const overRetrieve = k * 5;
|
|
146
|
+
const rawResults = this.native.search(queryVector, overRetrieve, this.config.efSearch);
|
|
147
|
+
const filtered = [];
|
|
148
|
+
for (const r of rawResults) {
|
|
149
|
+
if (await filter(r.id)) {
|
|
150
|
+
filtered.push(r);
|
|
151
|
+
if (filtered.length >= k)
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
results = filtered;
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
results = this.native.search(queryVector, k, this.config.efSearch);
|
|
159
|
+
}
|
|
160
|
+
// Convert NativeSearchResult[] to [string, number][] tuples
|
|
161
|
+
return results.map(r => [r.id, r.distance]);
|
|
162
|
+
}
|
|
163
|
+
// ---------------------------------------------------------------------------
|
|
164
|
+
// Persistence
|
|
165
|
+
// ---------------------------------------------------------------------------
|
|
166
|
+
async flush() {
|
|
167
|
+
if (!this.storage)
|
|
168
|
+
return 0;
|
|
169
|
+
if (this.dirtyNodes.size === 0 && !this.dirtySystem)
|
|
170
|
+
return 0;
|
|
171
|
+
const startTime = Date.now();
|
|
172
|
+
const nodeCount = this.dirtyNodes.size;
|
|
173
|
+
// Batch persist dirty nodes
|
|
174
|
+
if (this.dirtyNodes.size > 0) {
|
|
175
|
+
const batchSize = 50;
|
|
176
|
+
const nodeIds = Array.from(this.dirtyNodes);
|
|
177
|
+
for (let i = 0; i < nodeIds.length; i += batchSize) {
|
|
178
|
+
const batch = nodeIds.slice(i, i + batchSize);
|
|
179
|
+
const promises = batch.map(nodeId => {
|
|
180
|
+
const nodeData = this.native.getNodeData(nodeId);
|
|
181
|
+
if (!nodeData)
|
|
182
|
+
return Promise.resolve(); // Node was deleted
|
|
183
|
+
return this.storage.saveHNSWData(nodeId, nodeData).catch((e) => {
|
|
184
|
+
console.error(`[HNSW native flush] Failed to persist node ${nodeId}:`, e);
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
await Promise.allSettled(promises);
|
|
188
|
+
}
|
|
189
|
+
this.dirtyNodes.clear();
|
|
190
|
+
}
|
|
191
|
+
// Persist system data
|
|
192
|
+
if (this.dirtySystem) {
|
|
193
|
+
const sysData = this.native.getSystemData();
|
|
194
|
+
await this.storage.saveHNSWSystem(sysData).catch((e) => {
|
|
195
|
+
console.error('[HNSW native flush] Failed to persist system data:', e);
|
|
196
|
+
});
|
|
197
|
+
this.dirtySystem = false;
|
|
198
|
+
}
|
|
199
|
+
const duration = Date.now() - startTime;
|
|
200
|
+
if (nodeCount > 0) {
|
|
201
|
+
prodLog.info(`[HNSW native] Flushed ${nodeCount} dirty nodes in ${duration}ms`);
|
|
202
|
+
}
|
|
203
|
+
return nodeCount;
|
|
204
|
+
}
|
|
205
|
+
getDirtyNodeCount() {
|
|
206
|
+
return this.dirtyNodes.size;
|
|
207
|
+
}
|
|
208
|
+
getPersistMode() {
|
|
209
|
+
return this.persistMode;
|
|
210
|
+
}
|
|
211
|
+
// ---------------------------------------------------------------------------
|
|
212
|
+
// Rebuild from storage
|
|
213
|
+
// ---------------------------------------------------------------------------
|
|
214
|
+
async rebuild(options = {}) {
|
|
215
|
+
if (!this.storage) {
|
|
216
|
+
prodLog.warn('NativeHNSWWrapper rebuild skipped: no storage adapter configured');
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
const batchSize = options.batchSize || 1000;
|
|
220
|
+
try {
|
|
221
|
+
// Step 1: Clear native index
|
|
222
|
+
this.native.clear();
|
|
223
|
+
// Step 2: Load system data
|
|
224
|
+
const systemData = await this.storage.getHNSWSystem();
|
|
225
|
+
if (systemData && systemData.entryPointId) {
|
|
226
|
+
this.native.setSystemData(systemData.entryPointId, systemData.maxLevel);
|
|
227
|
+
}
|
|
228
|
+
// Step 3: Load nouns from storage and restore into native engine
|
|
229
|
+
const storageType = this.storage?.constructor.name || '';
|
|
230
|
+
const isLocalStorage = storageType === 'FileSystemStorage' ||
|
|
231
|
+
storageType === 'MemoryStorage' ||
|
|
232
|
+
storageType === 'OPFSStorage' ||
|
|
233
|
+
storageType === 'MmapFileSystemStorage';
|
|
234
|
+
let loadedCount = 0;
|
|
235
|
+
let totalCount;
|
|
236
|
+
if (isLocalStorage) {
|
|
237
|
+
// Local: load all at once
|
|
238
|
+
const result = await this.storage.getNounsWithPagination({
|
|
239
|
+
limit: 10000000
|
|
240
|
+
});
|
|
241
|
+
totalCount = result.totalCount || result.items.length;
|
|
242
|
+
for (const nounData of result.items) {
|
|
243
|
+
try {
|
|
244
|
+
const hnswData = await this.storage.getHNSWData(nounData.id);
|
|
245
|
+
if (!hnswData)
|
|
246
|
+
continue;
|
|
247
|
+
this.native.restoreNode(nounData.id, nounData.vector, hnswData.level, hnswData.connections);
|
|
248
|
+
loadedCount++;
|
|
249
|
+
}
|
|
250
|
+
catch (error) {
|
|
251
|
+
console.error(`Failed to rebuild HNSW data for ${nounData.id}:`, error);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
if (options.onProgress && totalCount !== undefined) {
|
|
255
|
+
options.onProgress(loadedCount, totalCount);
|
|
256
|
+
}
|
|
257
|
+
prodLog.info(`NativeHNSW: Loaded ${loadedCount.toLocaleString()} nodes at once (local storage)`);
|
|
258
|
+
}
|
|
259
|
+
else {
|
|
260
|
+
// Cloud: paginated loading
|
|
261
|
+
let hasMore = true;
|
|
262
|
+
let offset = 0;
|
|
263
|
+
while (hasMore) {
|
|
264
|
+
const result = await this.storage.getNounsWithPagination({
|
|
265
|
+
limit: batchSize,
|
|
266
|
+
offset,
|
|
267
|
+
});
|
|
268
|
+
if (totalCount === undefined && result.totalCount !== undefined) {
|
|
269
|
+
totalCount = result.totalCount;
|
|
270
|
+
}
|
|
271
|
+
for (const nounData of result.items) {
|
|
272
|
+
try {
|
|
273
|
+
const hnswData = await this.storage.getHNSWData(nounData.id);
|
|
274
|
+
if (!hnswData)
|
|
275
|
+
continue;
|
|
276
|
+
this.native.restoreNode(nounData.id, nounData.vector, hnswData.level, hnswData.connections);
|
|
277
|
+
loadedCount++;
|
|
278
|
+
}
|
|
279
|
+
catch (error) {
|
|
280
|
+
console.error(`Failed to rebuild HNSW data for ${nounData.id}:`, error);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
if (options.onProgress && totalCount !== undefined) {
|
|
284
|
+
options.onProgress(loadedCount, totalCount);
|
|
285
|
+
}
|
|
286
|
+
hasMore = result.hasMore;
|
|
287
|
+
offset += batchSize;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
// Step 4: Recover entry point if needed
|
|
291
|
+
if (this.native.size() > 0 && !this.native.getEntryPointId()) {
|
|
292
|
+
// Find highest level node from native
|
|
293
|
+
const allIds = this.native.getAllIds();
|
|
294
|
+
let bestId = null;
|
|
295
|
+
let bestLevel = -1;
|
|
296
|
+
for (const nid of allIds) {
|
|
297
|
+
const nd = this.native.getNodeData(nid);
|
|
298
|
+
if (nd && nd.level > bestLevel) {
|
|
299
|
+
bestLevel = nd.level;
|
|
300
|
+
bestId = nid;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
if (bestId) {
|
|
304
|
+
this.native.setSystemData(bestId, bestLevel);
|
|
305
|
+
prodLog.info(`NativeHNSW entry point recovered: ${bestId} at level ${bestLevel}`);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
prodLog.info(`NativeHNSW index rebuilt: ${loadedCount.toLocaleString()} entities, ` +
|
|
309
|
+
`${this.native.getMaxLevel() + 1} levels, ` +
|
|
310
|
+
`entry point: ${this.native.getEntryPointId() || 'none'}`);
|
|
311
|
+
}
|
|
312
|
+
catch (error) {
|
|
313
|
+
prodLog.error('NativeHNSW rebuild failed:', error);
|
|
314
|
+
throw new Error(`Failed to rebuild NativeHNSW index: ${error}`);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
// ---------------------------------------------------------------------------
|
|
318
|
+
// COW
|
|
319
|
+
// ---------------------------------------------------------------------------
|
|
320
|
+
enableCOW(parent) {
|
|
321
|
+
this.cowEnabled = true;
|
|
322
|
+
this.native = parent.native.fork();
|
|
323
|
+
this.config = parent.config;
|
|
324
|
+
this.distanceFunction = parent.distanceFunction;
|
|
325
|
+
this.useParallelization = parent.useParallelization;
|
|
326
|
+
this.unifiedCache = parent.unifiedCache;
|
|
327
|
+
prodLog.info(`NativeHNSW COW enabled: ${this.native.size()} nodes (Arc-based fork)`);
|
|
328
|
+
}
|
|
329
|
+
setUseParallelization(useParallelization) {
|
|
330
|
+
this.useParallelization = useParallelization;
|
|
331
|
+
}
|
|
332
|
+
getUseParallelization() {
|
|
333
|
+
return this.useParallelization;
|
|
334
|
+
}
|
|
335
|
+
// ---------------------------------------------------------------------------
|
|
336
|
+
// Info / Introspection
|
|
337
|
+
// ---------------------------------------------------------------------------
|
|
338
|
+
size() {
|
|
339
|
+
return this.native.size();
|
|
340
|
+
}
|
|
341
|
+
clear() {
|
|
342
|
+
this.native.clear();
|
|
343
|
+
this.dirtyNodes.clear();
|
|
344
|
+
this.dirtySystem = false;
|
|
345
|
+
}
|
|
346
|
+
getEntryPointId() {
|
|
347
|
+
return this.native.getEntryPointId() || null;
|
|
348
|
+
}
|
|
349
|
+
getMaxLevel() {
|
|
350
|
+
return this.native.getMaxLevel();
|
|
351
|
+
}
|
|
352
|
+
getDimension() {
|
|
353
|
+
return this.native.getDimension() ?? null;
|
|
354
|
+
}
|
|
355
|
+
getConfig() {
|
|
356
|
+
return { ...this.config };
|
|
357
|
+
}
|
|
358
|
+
getDistanceFunction() {
|
|
359
|
+
return this.distanceFunction;
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Get all nouns — builds HNSWNoun objects from native data.
|
|
363
|
+
* @deprecated Use getNounsPaginated() instead
|
|
364
|
+
*/
|
|
365
|
+
getNouns() {
|
|
366
|
+
const result = new Map();
|
|
367
|
+
const allIds = this.native.getAllIds();
|
|
368
|
+
for (const id of allIds) {
|
|
369
|
+
const nodeData = this.native.getNodeData(id);
|
|
370
|
+
const vector = this.native.getVector(id);
|
|
371
|
+
if (!nodeData || !vector)
|
|
372
|
+
continue;
|
|
373
|
+
const connections = new Map();
|
|
374
|
+
for (const [levelStr, ids] of Object.entries(nodeData.connections)) {
|
|
375
|
+
connections.set(parseInt(levelStr, 10), new Set(ids));
|
|
376
|
+
}
|
|
377
|
+
result.set(id, {
|
|
378
|
+
id,
|
|
379
|
+
vector,
|
|
380
|
+
connections,
|
|
381
|
+
level: nodeData.level,
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
return result;
|
|
385
|
+
}
|
|
386
|
+
getNounsPaginated(options = {}) {
|
|
387
|
+
const offset = options.offset || 0;
|
|
388
|
+
const limit = options.limit || 100;
|
|
389
|
+
const allIds = this.native.getAllIds();
|
|
390
|
+
const totalCount = allIds.length;
|
|
391
|
+
// Build noun objects for the requested page
|
|
392
|
+
const items = new Map();
|
|
393
|
+
let added = 0;
|
|
394
|
+
let skipped = 0;
|
|
395
|
+
for (const id of allIds) {
|
|
396
|
+
const nodeData = this.native.getNodeData(id);
|
|
397
|
+
const vector = this.native.getVector(id);
|
|
398
|
+
if (!nodeData || !vector)
|
|
399
|
+
continue;
|
|
400
|
+
const connections = new Map();
|
|
401
|
+
for (const [levelStr, ids] of Object.entries(nodeData.connections)) {
|
|
402
|
+
connections.set(parseInt(levelStr, 10), new Set(ids));
|
|
403
|
+
}
|
|
404
|
+
const noun = { id, vector, connections, level: nodeData.level };
|
|
405
|
+
if (options.filter && !options.filter(noun))
|
|
406
|
+
continue;
|
|
407
|
+
if (skipped < offset) {
|
|
408
|
+
skipped++;
|
|
409
|
+
continue;
|
|
410
|
+
}
|
|
411
|
+
items.set(id, noun);
|
|
412
|
+
added++;
|
|
413
|
+
if (added >= limit)
|
|
414
|
+
break;
|
|
415
|
+
}
|
|
416
|
+
return {
|
|
417
|
+
items,
|
|
418
|
+
totalCount,
|
|
419
|
+
hasMore: offset + limit < totalCount,
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
getNodesAtLevel(level) {
|
|
423
|
+
const ids = this.native.getNodesAtLevel(level);
|
|
424
|
+
const nodes = [];
|
|
425
|
+
for (const id of ids) {
|
|
426
|
+
const nodeData = this.native.getNodeData(id);
|
|
427
|
+
const vector = this.native.getVector(id);
|
|
428
|
+
if (!nodeData || !vector)
|
|
429
|
+
continue;
|
|
430
|
+
const connections = new Map();
|
|
431
|
+
for (const [levelStr, nids] of Object.entries(nodeData.connections)) {
|
|
432
|
+
connections.set(parseInt(levelStr, 10), new Set(nids));
|
|
433
|
+
}
|
|
434
|
+
nodes.push({ id, vector, connections, level: nodeData.level });
|
|
435
|
+
}
|
|
436
|
+
return nodes;
|
|
437
|
+
}
|
|
438
|
+
getLevelStats() {
|
|
439
|
+
const nativeStats = this.native.getLevelStats();
|
|
440
|
+
return nativeStats.map(s => ({
|
|
441
|
+
level: s.level,
|
|
442
|
+
nodeCount: s.nodeCount,
|
|
443
|
+
avgConnections: s.avgConnections,
|
|
444
|
+
}));
|
|
445
|
+
}
|
|
446
|
+
getIndexHealth() {
|
|
447
|
+
const health = this.native.getIndexHealth();
|
|
448
|
+
return {
|
|
449
|
+
averageConnections: health.averageConnections,
|
|
450
|
+
layerDistribution: health.layerDistribution,
|
|
451
|
+
maxLayer: health.maxLayer,
|
|
452
|
+
totalNodes: health.totalNodes,
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
getCacheStats() {
|
|
456
|
+
const cacheStats = this.unifiedCache.getStats();
|
|
457
|
+
const entityCount = this.native.size();
|
|
458
|
+
const vectorDimension = this.native.getDimension() || 384;
|
|
459
|
+
const bytesPerVector = vectorDimension * 4;
|
|
460
|
+
const estimatedVectorMemoryMB = (entityCount * bytesPerVector) / (1024 * 1024);
|
|
461
|
+
const availableCacheMB = (cacheStats.maxSize * 0.8) / (1024 * 1024);
|
|
462
|
+
const vectorsInCache = cacheStats.typeCounts.hnsw || 0;
|
|
463
|
+
const hnswMemoryBytes = cacheStats.typeSizes.hnsw || 0;
|
|
464
|
+
const hnswAccessCount = cacheStats.typeAccessCounts.hnsw || 0;
|
|
465
|
+
const totalAccessCount = cacheStats.totalAccessCount;
|
|
466
|
+
const hnswAccessPercent = totalAccessCount > 0 ? (hnswAccessCount / totalAccessCount) * 100 : 0;
|
|
467
|
+
const hnswCachePercent = cacheStats.maxSize > 0 ? (hnswMemoryBytes / cacheStats.maxSize) * 100 : 0;
|
|
468
|
+
const fairnessViolation = hnswCachePercent > 90 && hnswAccessPercent < 10;
|
|
469
|
+
const hitRatePercent = (cacheStats.hitRate * 100) || 0;
|
|
470
|
+
const cachingStrategy = estimatedVectorMemoryMB < availableCacheMB ? 'preloaded' : 'on-demand';
|
|
471
|
+
const recommendations = [];
|
|
472
|
+
if (cachingStrategy === 'on-demand' && hitRatePercent < 50) {
|
|
473
|
+
recommendations.push(`Low cache hit rate (${hitRatePercent.toFixed(1)}%). Consider increasing UnifiedCache size`);
|
|
474
|
+
}
|
|
475
|
+
if (fairnessViolation) {
|
|
476
|
+
recommendations.push(`Fairness violation: HNSW using ${hnswCachePercent.toFixed(1)}% cache with only ${hnswAccessPercent.toFixed(1)}% access`);
|
|
477
|
+
}
|
|
478
|
+
if (recommendations.length === 0) {
|
|
479
|
+
recommendations.push('All metrics healthy - no action needed');
|
|
480
|
+
}
|
|
481
|
+
return {
|
|
482
|
+
cachingStrategy,
|
|
483
|
+
autoDetection: {
|
|
484
|
+
entityCount,
|
|
485
|
+
estimatedVectorMemoryMB: parseFloat(estimatedVectorMemoryMB.toFixed(2)),
|
|
486
|
+
availableCacheMB: parseFloat(availableCacheMB.toFixed(2)),
|
|
487
|
+
threshold: 0.8,
|
|
488
|
+
rationale: cachingStrategy === 'preloaded'
|
|
489
|
+
? `Vectors in native Rust memory (${estimatedVectorMemoryMB.toFixed(1)}MB)`
|
|
490
|
+
: `Adaptive on-demand loading (${estimatedVectorMemoryMB.toFixed(1)}MB > ${availableCacheMB.toFixed(1)}MB threshold)`
|
|
491
|
+
},
|
|
492
|
+
unifiedCache: {
|
|
493
|
+
totalSize: cacheStats.totalSize,
|
|
494
|
+
maxSize: cacheStats.maxSize,
|
|
495
|
+
utilizationPercent: parseFloat((cacheStats.utilization * 100).toFixed(2)),
|
|
496
|
+
itemCount: cacheStats.itemCount,
|
|
497
|
+
hitRatePercent: parseFloat(hitRatePercent.toFixed(2)),
|
|
498
|
+
totalAccessCount: cacheStats.totalAccessCount,
|
|
499
|
+
},
|
|
500
|
+
hnswCache: {
|
|
501
|
+
vectorsInCache,
|
|
502
|
+
cacheKeyPrefix: 'hnsw:vector:',
|
|
503
|
+
estimatedMemoryMB: parseFloat((hnswMemoryBytes / (1024 * 1024)).toFixed(2)),
|
|
504
|
+
},
|
|
505
|
+
fairness: {
|
|
506
|
+
hnswAccessCount,
|
|
507
|
+
hnswAccessPercent: parseFloat(hnswAccessPercent.toFixed(2)),
|
|
508
|
+
totalAccessCount,
|
|
509
|
+
fairnessViolation,
|
|
510
|
+
},
|
|
511
|
+
recommendations,
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
//# sourceMappingURL=NativeHNSWWrapper.js.map
|
package/dist/plugin.d.ts
CHANGED
|
@@ -8,15 +8,17 @@
|
|
|
8
8
|
* can always access their data. Compute acceleration requires a valid license.
|
|
9
9
|
*
|
|
10
10
|
* Provider registration order:
|
|
11
|
-
* 1. storage:
|
|
11
|
+
* 1. storage:filesystem — mmap-enhanced FileSystemStorage (ALWAYS registered)
|
|
12
12
|
* 2. distance — SIMD-accelerated cosine (licensed)
|
|
13
|
-
* 3.
|
|
14
|
-
* 4.
|
|
15
|
-
* 5.
|
|
16
|
-
* 6.
|
|
17
|
-
* 7.
|
|
18
|
-
* 8.
|
|
19
|
-
* 9.
|
|
13
|
+
* 3. hnsw — Native Rust HNSW graph engine (licensed)
|
|
14
|
+
* 4. cache — Native Rust eviction engine with JS data storage (licensed)
|
|
15
|
+
* 5. metadataIndex — Native Rust query/mutation engine (licensed)
|
|
16
|
+
* 6. graphIndex — Native 4 LSM-trees with verb tracking (licensed)
|
|
17
|
+
* 7. embeddings — Candle ML native engine (CPU/CUDA/Metal) (licensed)
|
|
18
|
+
* 8. embedBatch — Native batch embedding (single forward pass) (licensed)
|
|
19
|
+
* 9. entityIdMapper — Native UUID ↔ integer mapping (licensed)
|
|
20
|
+
* 10. roaring — CRoaring bitmap bindings (licensed)
|
|
21
|
+
* 11. msgpack — Native encode/decode (licensed)
|
|
20
22
|
*/
|
|
21
23
|
import type { BrainyPlugin } from '@soulcraft/brainy/plugin';
|
|
22
24
|
declare const cortexPlugin: BrainyPlugin;
|
package/dist/plugin.js
CHANGED
|
@@ -8,27 +8,33 @@
|
|
|
8
8
|
* can always access their data. Compute acceleration requires a valid license.
|
|
9
9
|
*
|
|
10
10
|
* Provider registration order:
|
|
11
|
-
* 1. storage:
|
|
11
|
+
* 1. storage:filesystem — mmap-enhanced FileSystemStorage (ALWAYS registered)
|
|
12
12
|
* 2. distance — SIMD-accelerated cosine (licensed)
|
|
13
|
-
* 3.
|
|
14
|
-
* 4.
|
|
15
|
-
* 5.
|
|
16
|
-
* 6.
|
|
17
|
-
* 7.
|
|
18
|
-
* 8.
|
|
19
|
-
* 9.
|
|
13
|
+
* 3. hnsw — Native Rust HNSW graph engine (licensed)
|
|
14
|
+
* 4. cache — Native Rust eviction engine with JS data storage (licensed)
|
|
15
|
+
* 5. metadataIndex — Native Rust query/mutation engine (licensed)
|
|
16
|
+
* 6. graphIndex — Native 4 LSM-trees with verb tracking (licensed)
|
|
17
|
+
* 7. embeddings — Candle ML native engine (CPU/CUDA/Metal) (licensed)
|
|
18
|
+
* 8. embedBatch — Native batch embedding (single forward pass) (licensed)
|
|
19
|
+
* 9. entityIdMapper — Native UUID ↔ integer mapping (licensed)
|
|
20
|
+
* 10. roaring — CRoaring bitmap bindings (licensed)
|
|
21
|
+
* 11. msgpack — Native encode/decode (licensed)
|
|
20
22
|
*/
|
|
21
23
|
import { loadNativeModule, isNativeAvailable } from './native/index.js';
|
|
22
24
|
import { validateLicense } from './license.js';
|
|
23
25
|
const cortexPlugin = {
|
|
24
26
|
name: '@soulcraft/cortex',
|
|
25
27
|
async activate(context) {
|
|
26
|
-
// Storage
|
|
27
|
-
//
|
|
28
|
-
//
|
|
28
|
+
// Storage: Override built-in FileSystemStorage with mmap-enhanced version.
|
|
29
|
+
// MmapFileSystemStorage extends FileSystemStorage, adding binary blob methods
|
|
30
|
+
// (saveBinaryBlob/loadBinaryBlob/getBinaryBlobPath) that enable Rust native
|
|
31
|
+
// indexes to mmap SSTable files directly. Zero-config — users with
|
|
32
|
+
// type: 'filesystem' automatically get the enhanced version.
|
|
33
|
+
//
|
|
34
|
+
// ALWAYS registered (even without a license) so users can always access data.
|
|
29
35
|
const { MmapFileSystemStorage } = await import('./storage/mmapFileSystemStorage.js');
|
|
30
|
-
context.registerProvider('storage:
|
|
31
|
-
name: '
|
|
36
|
+
context.registerProvider('storage:filesystem', {
|
|
37
|
+
name: 'filesystem',
|
|
32
38
|
create: (config) => new MmapFileSystemStorage(config.rootDirectory, {
|
|
33
39
|
compression: config.compression,
|
|
34
40
|
compressionLevel: config.compressionLevel
|
|
@@ -72,6 +78,15 @@ const cortexPlugin = {
|
|
|
72
78
|
});
|
|
73
79
|
// Product Quantization: 16-32x compression for large datasets
|
|
74
80
|
context.registerProvider('quantization:pq', native.NativePqCodebook);
|
|
81
|
+
// HNSW: Native Rust graph engine with SIMD distance and Arc-based COW
|
|
82
|
+
const { NativeHNSWWrapper } = await import('./hnsw/NativeHNSWWrapper.js');
|
|
83
|
+
context.registerProvider('hnsw', (config, distanceFn, options) => {
|
|
84
|
+
return new NativeHNSWWrapper(config, distanceFn, options);
|
|
85
|
+
});
|
|
86
|
+
// Cache: Native Rust eviction engine with JS data storage
|
|
87
|
+
// Registered before indexes so they use native cache for memory management
|
|
88
|
+
const { NativeUnifiedCacheWrapper } = await import('./utils/NativeUnifiedCache.js');
|
|
89
|
+
context.registerProvider('cache', new NativeUnifiedCacheWrapper());
|
|
75
90
|
// Metadata index: Native Rust query/mutation engine
|
|
76
91
|
const { MetadataIndexManager: NativeMetadataIndex } = await import('./utils/NativeMetadataIndex.js');
|
|
77
92
|
context.registerProvider('metadataIndex', (storage) => new NativeMetadataIndex(storage));
|
|
@@ -879,7 +879,7 @@ export class MetadataIndexManager {
|
|
|
879
879
|
catch { }
|
|
880
880
|
// Adaptive rebuild strategy
|
|
881
881
|
const storageType = this.storage.constructor.name;
|
|
882
|
-
const isLocalStorage = storageType === 'FileSystemStorage' || storageType === 'MemoryStorage';
|
|
882
|
+
const isLocalStorage = storageType === 'FileSystemStorage' || storageType === 'MmapFileSystemStorage' || storageType === 'MemoryStorage';
|
|
883
883
|
let totalNounsProcessed = 0;
|
|
884
884
|
if (isLocalStorage) {
|
|
885
885
|
const result = await this.storage.getNouns({
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NativeUnifiedCacheWrapper — Native Rust eviction engine with JS data storage
|
|
3
|
+
*
|
|
4
|
+
* The Rust NativeUnifiedCache manages eviction metadata (key, type, size, cost,
|
|
5
|
+
* access counts) but does NOT store actual JS data. This wrapper:
|
|
6
|
+
* - Keeps a Map<string, any> for actual cached data
|
|
7
|
+
* - Delegates eviction decisions to the native engine
|
|
8
|
+
* - Implements request coalescing in JS (same as brainy's UnifiedCache)
|
|
9
|
+
* - Drop-in replacement for brainy's UnifiedCache
|
|
10
|
+
*/
|
|
11
|
+
import type { UnifiedCacheConfig } from '@soulcraft/brainy/internals';
|
|
12
|
+
export declare class NativeUnifiedCacheWrapper {
|
|
13
|
+
private data;
|
|
14
|
+
private native;
|
|
15
|
+
private loadingPromises;
|
|
16
|
+
private readonly maxSize;
|
|
17
|
+
private readonly config;
|
|
18
|
+
private readonly memoryInfo;
|
|
19
|
+
private readonly allocationStrategy;
|
|
20
|
+
private memoryPressureCheckTimer;
|
|
21
|
+
private lastMemoryWarning;
|
|
22
|
+
constructor(config?: UnifiedCacheConfig);
|
|
23
|
+
get(key: string, loadFn?: () => Promise<any>): Promise<any>;
|
|
24
|
+
getSync(key: string): any | undefined;
|
|
25
|
+
set(key: string, data: any, type: 'hnsw' | 'metadata' | 'embedding' | 'other', size: number, rebuildCost?: number): void;
|
|
26
|
+
delete(key: string): boolean;
|
|
27
|
+
deleteByPrefix(prefix: string): number;
|
|
28
|
+
clear(type?: 'hnsw' | 'metadata' | 'embedding' | 'other'): void;
|
|
29
|
+
evictForSize(bytesNeeded: number): boolean;
|
|
30
|
+
private startFairnessMonitor;
|
|
31
|
+
private checkFairness;
|
|
32
|
+
private startMemoryPressureMonitor;
|
|
33
|
+
private checkMemoryPressure;
|
|
34
|
+
getStats(): {
|
|
35
|
+
totalSize: number;
|
|
36
|
+
maxSize: number;
|
|
37
|
+
utilization: number;
|
|
38
|
+
itemCount: number;
|
|
39
|
+
typeSizes: Record<string, number>;
|
|
40
|
+
typeCounts: Record<string, number>;
|
|
41
|
+
typeAccessCounts: Record<string, number>;
|
|
42
|
+
totalAccessCount: number;
|
|
43
|
+
hitRate: number;
|
|
44
|
+
memory: {
|
|
45
|
+
available: number;
|
|
46
|
+
source: "cgroup-v2" | "cgroup-v1" | "system" | "fallback";
|
|
47
|
+
isContainer: boolean;
|
|
48
|
+
systemTotal: number;
|
|
49
|
+
allocationRatio: number;
|
|
50
|
+
environment: "production" | "development" | "container" | "unknown";
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
getMemoryInfo(): {
|
|
54
|
+
memoryInfo: {
|
|
55
|
+
available: number;
|
|
56
|
+
source: "cgroup-v2" | "cgroup-v1" | "system" | "fallback";
|
|
57
|
+
isContainer: boolean;
|
|
58
|
+
systemTotal: number;
|
|
59
|
+
free: number;
|
|
60
|
+
warnings: string[];
|
|
61
|
+
};
|
|
62
|
+
allocationStrategy: {
|
|
63
|
+
cacheSize: number;
|
|
64
|
+
ratio: number;
|
|
65
|
+
minSize: number;
|
|
66
|
+
maxSize: number | null;
|
|
67
|
+
environment: "production" | "development" | "container" | "unknown";
|
|
68
|
+
modelMemory: number;
|
|
69
|
+
modelPrecision: "q8" | "fp32";
|
|
70
|
+
availableForCache: number;
|
|
71
|
+
reasoning: string;
|
|
72
|
+
};
|
|
73
|
+
currentPressure: {
|
|
74
|
+
pressure: "none" | "moderate" | "high" | "critical";
|
|
75
|
+
warnings: string[];
|
|
76
|
+
};
|
|
77
|
+
};
|
|
78
|
+
saveAccessPatterns(): Promise<any>;
|
|
79
|
+
loadAccessPatterns(patterns: any): Promise<void>;
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=NativeUnifiedCache.d.ts.map
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NativeUnifiedCacheWrapper — Native Rust eviction engine with JS data storage
|
|
3
|
+
*
|
|
4
|
+
* The Rust NativeUnifiedCache manages eviction metadata (key, type, size, cost,
|
|
5
|
+
* access counts) but does NOT store actual JS data. This wrapper:
|
|
6
|
+
* - Keeps a Map<string, any> for actual cached data
|
|
7
|
+
* - Delegates eviction decisions to the native engine
|
|
8
|
+
* - Implements request coalescing in JS (same as brainy's UnifiedCache)
|
|
9
|
+
* - Drop-in replacement for brainy's UnifiedCache
|
|
10
|
+
*/
|
|
11
|
+
import { loadNativeModule } from '../native/index.js';
|
|
12
|
+
import { prodLog } from '@soulcraft/brainy/internals';
|
|
13
|
+
import { getRecommendedCacheConfig, formatBytes, checkMemoryPressure, } from '@soulcraft/brainy/internals';
|
|
14
|
+
// Cache type enum matching Rust (0-3)
|
|
15
|
+
const CACHE_TYPE_MAP = {
|
|
16
|
+
hnsw: 0,
|
|
17
|
+
metadata: 1,
|
|
18
|
+
embedding: 2,
|
|
19
|
+
other: 3,
|
|
20
|
+
};
|
|
21
|
+
export class NativeUnifiedCacheWrapper {
|
|
22
|
+
data = new Map();
|
|
23
|
+
native;
|
|
24
|
+
loadingPromises = new Map();
|
|
25
|
+
maxSize;
|
|
26
|
+
config;
|
|
27
|
+
memoryInfo;
|
|
28
|
+
allocationStrategy;
|
|
29
|
+
memoryPressureCheckTimer = null;
|
|
30
|
+
lastMemoryWarning = 0;
|
|
31
|
+
constructor(config = {}) {
|
|
32
|
+
const recommendation = getRecommendedCacheConfig({
|
|
33
|
+
manualSize: config.maxSize,
|
|
34
|
+
minSize: config.minSize,
|
|
35
|
+
developmentMode: config.developmentMode,
|
|
36
|
+
});
|
|
37
|
+
this.memoryInfo = recommendation.memoryInfo;
|
|
38
|
+
this.allocationStrategy = recommendation.allocation;
|
|
39
|
+
this.maxSize = recommendation.allocation.cacheSize;
|
|
40
|
+
prodLog.info(`NativeUnifiedCache initialized: ${formatBytes(this.maxSize)} ` +
|
|
41
|
+
`(${this.allocationStrategy.environment} mode, ` +
|
|
42
|
+
`${(this.allocationStrategy.ratio * 100).toFixed(0)}% of ${formatBytes(this.allocationStrategy.availableForCache)} ` +
|
|
43
|
+
`after ${formatBytes(this.allocationStrategy.modelMemory)} ${this.allocationStrategy.modelPrecision.toUpperCase()} model)`);
|
|
44
|
+
for (const warning of recommendation.warnings) {
|
|
45
|
+
prodLog.warn(`NativeUnifiedCache: ${warning}`);
|
|
46
|
+
}
|
|
47
|
+
this.config = {
|
|
48
|
+
enableRequestCoalescing: true,
|
|
49
|
+
enableFairnessCheck: true,
|
|
50
|
+
fairnessCheckInterval: 30000,
|
|
51
|
+
persistPatterns: true,
|
|
52
|
+
enableMemoryMonitoring: true,
|
|
53
|
+
memoryCheckInterval: 30000,
|
|
54
|
+
...config,
|
|
55
|
+
};
|
|
56
|
+
const bindings = loadNativeModule();
|
|
57
|
+
this.native = new bindings.NativeUnifiedCache(this.maxSize);
|
|
58
|
+
if (this.config.enableFairnessCheck) {
|
|
59
|
+
this.startFairnessMonitor();
|
|
60
|
+
}
|
|
61
|
+
if (this.config.enableMemoryMonitoring) {
|
|
62
|
+
this.startMemoryPressureMonitor();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
// Core API
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
async get(key, loadFn) {
|
|
69
|
+
// Check JS data map
|
|
70
|
+
const cached = this.data.get(key);
|
|
71
|
+
if (cached !== undefined) {
|
|
72
|
+
this.native.recordAccess(key);
|
|
73
|
+
return cached;
|
|
74
|
+
}
|
|
75
|
+
if (!loadFn)
|
|
76
|
+
return undefined;
|
|
77
|
+
// Request coalescing
|
|
78
|
+
if (this.config.enableRequestCoalescing && this.loadingPromises.has(key)) {
|
|
79
|
+
return this.loadingPromises.get(key);
|
|
80
|
+
}
|
|
81
|
+
const loadPromise = loadFn();
|
|
82
|
+
if (this.config.enableRequestCoalescing) {
|
|
83
|
+
this.loadingPromises.set(key, loadPromise);
|
|
84
|
+
}
|
|
85
|
+
try {
|
|
86
|
+
const result = await loadPromise;
|
|
87
|
+
return result;
|
|
88
|
+
}
|
|
89
|
+
finally {
|
|
90
|
+
if (this.config.enableRequestCoalescing) {
|
|
91
|
+
this.loadingPromises.delete(key);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
getSync(key) {
|
|
96
|
+
const cached = this.data.get(key);
|
|
97
|
+
if (cached !== undefined) {
|
|
98
|
+
this.native.recordAccess(key);
|
|
99
|
+
return cached;
|
|
100
|
+
}
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
set(key, data, type, size, rebuildCost = 1) {
|
|
104
|
+
const cacheType = CACHE_TYPE_MAP[type] ?? 3;
|
|
105
|
+
// Remove existing entry if present
|
|
106
|
+
if (this.data.has(key)) {
|
|
107
|
+
this.native.remove(key);
|
|
108
|
+
}
|
|
109
|
+
// Insert into native (may trigger eviction)
|
|
110
|
+
const evictionResult = this.native.insert(key, cacheType, size, rebuildCost);
|
|
111
|
+
// Remove evicted keys from JS data
|
|
112
|
+
for (const evictedKey of evictionResult.evictedKeys) {
|
|
113
|
+
this.data.delete(evictedKey);
|
|
114
|
+
}
|
|
115
|
+
// Store actual data in JS
|
|
116
|
+
this.data.set(key, data);
|
|
117
|
+
}
|
|
118
|
+
delete(key) {
|
|
119
|
+
const existed = this.data.has(key);
|
|
120
|
+
if (existed) {
|
|
121
|
+
this.native.remove(key);
|
|
122
|
+
this.data.delete(key);
|
|
123
|
+
}
|
|
124
|
+
return existed;
|
|
125
|
+
}
|
|
126
|
+
deleteByPrefix(prefix) {
|
|
127
|
+
const removedKeys = this.native.removeByPrefix(prefix);
|
|
128
|
+
for (const key of removedKeys) {
|
|
129
|
+
this.data.delete(key);
|
|
130
|
+
}
|
|
131
|
+
return removedKeys.length;
|
|
132
|
+
}
|
|
133
|
+
clear(type) {
|
|
134
|
+
if (!type) {
|
|
135
|
+
const clearedKeys = this.native.clear();
|
|
136
|
+
for (const key of clearedKeys) {
|
|
137
|
+
this.data.delete(key);
|
|
138
|
+
}
|
|
139
|
+
// Also clear any remaining data entries
|
|
140
|
+
this.data.clear();
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
const cacheType = CACHE_TYPE_MAP[type] ?? 3;
|
|
144
|
+
const clearedKeys = this.native.clear(cacheType);
|
|
145
|
+
for (const key of clearedKeys) {
|
|
146
|
+
this.data.delete(key);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
// ---------------------------------------------------------------------------
|
|
151
|
+
// Eviction
|
|
152
|
+
// ---------------------------------------------------------------------------
|
|
153
|
+
evictForSize(bytesNeeded) {
|
|
154
|
+
const result = this.native.evictForSize(bytesNeeded);
|
|
155
|
+
for (const key of result.evictedKeys) {
|
|
156
|
+
this.data.delete(key);
|
|
157
|
+
}
|
|
158
|
+
return result.bytesFreed >= bytesNeeded;
|
|
159
|
+
}
|
|
160
|
+
// ---------------------------------------------------------------------------
|
|
161
|
+
// Fairness
|
|
162
|
+
// ---------------------------------------------------------------------------
|
|
163
|
+
startFairnessMonitor() {
|
|
164
|
+
const timer = setInterval(() => {
|
|
165
|
+
this.checkFairness();
|
|
166
|
+
}, this.config.fairnessCheckInterval);
|
|
167
|
+
if (timer.unref)
|
|
168
|
+
timer.unref();
|
|
169
|
+
}
|
|
170
|
+
checkFairness() {
|
|
171
|
+
const result = this.native.checkFairness();
|
|
172
|
+
for (const key of result.evictedKeys) {
|
|
173
|
+
this.data.delete(key);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
// ---------------------------------------------------------------------------
|
|
177
|
+
// Memory pressure
|
|
178
|
+
// ---------------------------------------------------------------------------
|
|
179
|
+
startMemoryPressureMonitor() {
|
|
180
|
+
const checkInterval = this.config.memoryCheckInterval || 30000;
|
|
181
|
+
this.memoryPressureCheckTimer = setInterval(() => {
|
|
182
|
+
this.checkMemoryPressure();
|
|
183
|
+
}, checkInterval);
|
|
184
|
+
if (this.memoryPressureCheckTimer.unref) {
|
|
185
|
+
this.memoryPressureCheckTimer.unref();
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
checkMemoryPressure() {
|
|
189
|
+
const stats = this.native.getStats();
|
|
190
|
+
const pressure = checkMemoryPressure(stats.totalSize, this.memoryInfo);
|
|
191
|
+
const now = Date.now();
|
|
192
|
+
const fiveMinutes = 5 * 60 * 1000;
|
|
193
|
+
if (pressure.warnings.length > 0 && now - this.lastMemoryWarning > fiveMinutes) {
|
|
194
|
+
for (const warning of pressure.warnings) {
|
|
195
|
+
prodLog.warn(`NativeUnifiedCache: ${warning}`);
|
|
196
|
+
}
|
|
197
|
+
this.lastMemoryWarning = now;
|
|
198
|
+
}
|
|
199
|
+
if (pressure.pressure === 'critical') {
|
|
200
|
+
const targetSize = Math.floor(this.maxSize * 0.7);
|
|
201
|
+
const bytesToFree = stats.totalSize - targetSize;
|
|
202
|
+
if (bytesToFree > 0) {
|
|
203
|
+
prodLog.warn(`NativeUnifiedCache: Critical memory pressure - forcing eviction of ${formatBytes(bytesToFree)}`);
|
|
204
|
+
this.evictForSize(bytesToFree);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
// ---------------------------------------------------------------------------
|
|
209
|
+
// Statistics
|
|
210
|
+
// ---------------------------------------------------------------------------
|
|
211
|
+
getStats() {
|
|
212
|
+
const nativeStats = this.native.getStats();
|
|
213
|
+
const typeNames = ['hnsw', 'metadata', 'embedding', 'other'];
|
|
214
|
+
const typeSizes = {};
|
|
215
|
+
const typeCounts = {};
|
|
216
|
+
const typeAccessCounts = {};
|
|
217
|
+
for (let i = 0; i < typeNames.length; i++) {
|
|
218
|
+
typeSizes[typeNames[i]] = nativeStats.typeSizes[i] || 0;
|
|
219
|
+
typeCounts[typeNames[i]] = nativeStats.typeCounts[i] || 0;
|
|
220
|
+
typeAccessCounts[typeNames[i]] = nativeStats.typeAccessCounts[i] || 0;
|
|
221
|
+
}
|
|
222
|
+
const hitRate = nativeStats.totalAccessCount > 0
|
|
223
|
+
? nativeStats.itemCount / nativeStats.totalAccessCount
|
|
224
|
+
: 0;
|
|
225
|
+
return {
|
|
226
|
+
totalSize: nativeStats.totalSize,
|
|
227
|
+
maxSize: nativeStats.maxSize,
|
|
228
|
+
utilization: nativeStats.maxSize > 0 ? nativeStats.totalSize / nativeStats.maxSize : 0,
|
|
229
|
+
itemCount: nativeStats.itemCount,
|
|
230
|
+
typeSizes,
|
|
231
|
+
typeCounts,
|
|
232
|
+
typeAccessCounts,
|
|
233
|
+
totalAccessCount: nativeStats.totalAccessCount,
|
|
234
|
+
hitRate,
|
|
235
|
+
memory: {
|
|
236
|
+
available: this.memoryInfo.available,
|
|
237
|
+
source: this.memoryInfo.source,
|
|
238
|
+
isContainer: this.memoryInfo.isContainer,
|
|
239
|
+
systemTotal: this.memoryInfo.systemTotal,
|
|
240
|
+
allocationRatio: this.allocationStrategy.ratio,
|
|
241
|
+
environment: this.allocationStrategy.environment,
|
|
242
|
+
},
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
getMemoryInfo() {
|
|
246
|
+
const stats = this.native.getStats();
|
|
247
|
+
return {
|
|
248
|
+
memoryInfo: { ...this.memoryInfo },
|
|
249
|
+
allocationStrategy: { ...this.allocationStrategy },
|
|
250
|
+
currentPressure: checkMemoryPressure(stats.totalSize, this.memoryInfo),
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
// ---------------------------------------------------------------------------
|
|
254
|
+
// Access pattern persistence
|
|
255
|
+
// ---------------------------------------------------------------------------
|
|
256
|
+
async saveAccessPatterns() {
|
|
257
|
+
if (!this.config.persistPatterns)
|
|
258
|
+
return;
|
|
259
|
+
const json = this.native.saveAccessPatterns();
|
|
260
|
+
return JSON.parse(json);
|
|
261
|
+
}
|
|
262
|
+
async loadAccessPatterns(patterns) {
|
|
263
|
+
if (!patterns)
|
|
264
|
+
return;
|
|
265
|
+
this.native.loadAccessPatterns(JSON.stringify(patterns));
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
//# sourceMappingURL=NativeUnifiedCache.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@soulcraft/cortex",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.1",
|
|
4
4
|
"description": "Native Rust acceleration for Brainy — SIMD distance, vector quantization, zero-copy mmap, native embeddings. Commercial license required (14-day free trial).",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|