@soulcraft/cortex 1.3.1 → 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.
@@ -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.3.1",
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",