offheap 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/CI.yml +115 -0
- package/Cargo.toml +35 -0
- package/LICENSE-APACHE +201 -0
- package/LICENSE-MIT +21 -0
- package/README.md +161 -0
- package/artifacts/bindings-aarch64-apple-darwin/index.d.ts +30 -0
- package/artifacts/bindings-aarch64-apple-darwin/index.js +316 -0
- package/artifacts/bindings-x86_64-apple-darwin/index.d.ts +30 -0
- package/artifacts/bindings-x86_64-apple-darwin/index.js +316 -0
- package/artifacts/bindings-x86_64-pc-windows-msvc/index.d.ts +30 -0
- package/artifacts/bindings-x86_64-pc-windows-msvc/index.js +316 -0
- package/artifacts/bindings-x86_64-unknown-linux-gnu/index.d.ts +30 -0
- package/artifacts/bindings-x86_64-unknown-linux-gnu/index.js +316 -0
- package/benches/rust_bench.rs +96 -0
- package/benchmarks/js_bench.js +67 -0
- package/build.rs +3 -0
- package/index.d.ts +30 -0
- package/index.js +316 -0
- package/package.json +35 -0
- package/src/algorithms/arc.rs +355 -0
- package/src/algorithms/lru.rs +212 -0
- package/src/algorithms/mod.rs +42 -0
- package/src/algorithms/tinylfu.rs +499 -0
- package/src/cache.rs +172 -0
- package/src/lib.rs +6 -0
- package/src/manager.rs +50 -0
- package/tests/index.test.js +135 -0
package/src/manager.rs
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
use std::collections::HashMap;
|
|
2
|
+
use napi::{Result, Error, Status};
|
|
3
|
+
use super::cache::{Cache, CacheConfig};
|
|
4
|
+
|
|
5
|
+
#[napi]
|
|
6
|
+
pub struct CacheManager {
|
|
7
|
+
caches: parking_lot::RwLock<HashMap<String, Cache>>,
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
#[napi]
|
|
11
|
+
impl CacheManager {
|
|
12
|
+
#[napi(constructor)]
|
|
13
|
+
pub fn new() -> Self {
|
|
14
|
+
Self {
|
|
15
|
+
caches: parking_lot::RwLock::new(HashMap::new()),
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
#[napi]
|
|
20
|
+
pub fn create_cache(&self, name: String, config: CacheConfig) -> Result<Cache> {
|
|
21
|
+
let mut lock = self.caches.write();
|
|
22
|
+
if lock.contains_key(&name) {
|
|
23
|
+
return Err(Error::new(
|
|
24
|
+
Status::InvalidArg,
|
|
25
|
+
format!("Cache '{}' already exists", name),
|
|
26
|
+
));
|
|
27
|
+
}
|
|
28
|
+
let cache = Cache::from_config(config);
|
|
29
|
+
lock.insert(name, cache.clone());
|
|
30
|
+
Ok(cache)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
#[napi]
|
|
34
|
+
pub fn get_cache(&self, name: String) -> Option<Cache> {
|
|
35
|
+
let lock = self.caches.read();
|
|
36
|
+
lock.get(&name).cloned()
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
#[napi]
|
|
40
|
+
pub fn delete_cache(&self, name: String) -> bool {
|
|
41
|
+
let mut lock = self.caches.write();
|
|
42
|
+
lock.remove(&name).is_some()
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
#[napi]
|
|
46
|
+
pub fn clear(&self) {
|
|
47
|
+
let mut lock = self.caches.write();
|
|
48
|
+
lock.clear();
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
const test = require('node:test');
|
|
2
|
+
const assert = require('node:assert');
|
|
3
|
+
const { CacheManager } = require('../index.js'); // NAPI generated file
|
|
4
|
+
|
|
5
|
+
test('CacheManager - isolated caches', () => {
|
|
6
|
+
const manager = new CacheManager();
|
|
7
|
+
|
|
8
|
+
const cache1 = manager.createCache('lru-1', { policy: 'lru', capacity: 10 });
|
|
9
|
+
const cache2 = manager.createCache('lru-2', { policy: 'lru', capacity: 10 });
|
|
10
|
+
|
|
11
|
+
cache1.set('key', 'value-1');
|
|
12
|
+
cache2.set('key', 'value-2');
|
|
13
|
+
|
|
14
|
+
assert.strictEqual(cache1.get('key'), 'value-1');
|
|
15
|
+
assert.strictEqual(cache2.get('key'), 'value-2');
|
|
16
|
+
|
|
17
|
+
assert.deepStrictEqual(manager.getCache('lru-1').stats(), cache1.stats());
|
|
18
|
+
|
|
19
|
+
manager.deleteCache('lru-1');
|
|
20
|
+
assert.strictEqual(manager.getCache('lru-1'), null);
|
|
21
|
+
assert.ok(manager.getCache('lru-2') !== null);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test('Cache - DataType Preservation (String, Buffer, JSON Object)', () => {
|
|
25
|
+
const manager = new CacheManager();
|
|
26
|
+
const cache = manager.createCache('lru-types', { policy: 'lru', capacity: 10 });
|
|
27
|
+
|
|
28
|
+
// String
|
|
29
|
+
cache.set('str', 'hello world');
|
|
30
|
+
assert.strictEqual(cache.get('str'), 'hello world');
|
|
31
|
+
|
|
32
|
+
// Buffer
|
|
33
|
+
const buf = Buffer.from([1, 2, 3, 4]);
|
|
34
|
+
cache.set('buf', buf);
|
|
35
|
+
const retrievedBuf = cache.get('buf');
|
|
36
|
+
assert.ok(Buffer.isBuffer(retrievedBuf));
|
|
37
|
+
assert.deepStrictEqual(retrievedBuf, buf);
|
|
38
|
+
|
|
39
|
+
// JSON Object
|
|
40
|
+
const obj = { x: 42, y: 'test', z: [1, 2] };
|
|
41
|
+
cache.set('json', obj);
|
|
42
|
+
assert.deepStrictEqual(cache.get('json'), obj);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test('Cache Policies - LRU eviction', () => {
|
|
46
|
+
const manager = new CacheManager();
|
|
47
|
+
const cache = manager.createCache('lru-evict', { policy: 'lru', capacity: 3 });
|
|
48
|
+
|
|
49
|
+
cache.set('a', 1);
|
|
50
|
+
cache.set('b', 2);
|
|
51
|
+
cache.set('c', 3);
|
|
52
|
+
|
|
53
|
+
// Access a to make it most recently used, b becomes oldest
|
|
54
|
+
cache.get('a');
|
|
55
|
+
|
|
56
|
+
// Insert d, which should evict b
|
|
57
|
+
cache.set('d', 4);
|
|
58
|
+
|
|
59
|
+
assert.strictEqual(cache.get('b'), undefined);
|
|
60
|
+
assert.strictEqual(cache.get('a'), 1);
|
|
61
|
+
assert.strictEqual(cache.get('c'), 3);
|
|
62
|
+
assert.strictEqual(cache.get('d'), 4);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test('Cache Policies - ARC adaptation & eviction', () => {
|
|
66
|
+
const manager = new CacheManager();
|
|
67
|
+
const cache = manager.createCache('arc-evict', { policy: 'arc', capacity: 4 });
|
|
68
|
+
|
|
69
|
+
// Warm up ARC
|
|
70
|
+
cache.set('a', 1);
|
|
71
|
+
cache.set('b', 2);
|
|
72
|
+
cache.set('c', 3);
|
|
73
|
+
cache.set('d', 4);
|
|
74
|
+
|
|
75
|
+
// Retrieve some keys to establish frequency (moving them to T2)
|
|
76
|
+
cache.get('a');
|
|
77
|
+
cache.get('b');
|
|
78
|
+
|
|
79
|
+
// Now a and b are in T2 (frequent), c and d are in T1 (recent)
|
|
80
|
+
// Insert e, which triggers replace. Since |T1| > p, it evicts from T1 (evicting c or d)
|
|
81
|
+
cache.set('e', 5);
|
|
82
|
+
|
|
83
|
+
// Check stats
|
|
84
|
+
const stats = cache.stats();
|
|
85
|
+
assert.strictEqual(stats.size, 4);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test('Cache Policies - W-TinyLFU eviction competition', () => {
|
|
89
|
+
const manager = new CacheManager();
|
|
90
|
+
// W-TinyLFU capacity 10
|
|
91
|
+
const cache = manager.createCache('tinylfu-evict', { policy: 'tinylfu', capacity: 10 });
|
|
92
|
+
|
|
93
|
+
// Fill cache
|
|
94
|
+
for (let i = 0; i < 12; i++) {
|
|
95
|
+
cache.set(`key-${i}`, i);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Check that size is limited to capacity
|
|
99
|
+
const stats = cache.stats();
|
|
100
|
+
assert.strictEqual(stats.capacity, 10);
|
|
101
|
+
assert.ok(stats.size <= 10);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test('Cache - TTL (Time To Live)', async () => {
|
|
105
|
+
const manager = new CacheManager();
|
|
106
|
+
const cache = manager.createCache('lru-ttl', { policy: 'lru', capacity: 10 });
|
|
107
|
+
|
|
108
|
+
cache.set('short-lived', 'expire-me', 10); // 10ms TTL
|
|
109
|
+
cache.set('long-lived', 'keep-me', 1000); // 1000ms TTL
|
|
110
|
+
|
|
111
|
+
assert.strictEqual(cache.get('short-lived'), 'expire-me');
|
|
112
|
+
assert.strictEqual(cache.get('long-lived'), 'keep-me');
|
|
113
|
+
|
|
114
|
+
// Wait 30ms for short-lived key to expire
|
|
115
|
+
await new Promise((resolve) => setTimeout(resolve, 30));
|
|
116
|
+
|
|
117
|
+
assert.strictEqual(cache.get('short-lived'), undefined);
|
|
118
|
+
assert.strictEqual(cache.get('long-lived'), 'keep-me');
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test('Cache - keys() and delete()', () => {
|
|
122
|
+
const manager = new CacheManager();
|
|
123
|
+
const cache = manager.createCache('lru-ops', { policy: 'lru', capacity: 5 });
|
|
124
|
+
|
|
125
|
+
cache.set('a', 1);
|
|
126
|
+
cache.set('b', 2);
|
|
127
|
+
cache.set('c', 3);
|
|
128
|
+
|
|
129
|
+
assert.deepStrictEqual(cache.keys().sort(), ['a', 'b', 'c']);
|
|
130
|
+
|
|
131
|
+
assert.strictEqual(cache.delete('b'), true);
|
|
132
|
+
assert.strictEqual(cache.delete('non-existent'), false);
|
|
133
|
+
|
|
134
|
+
assert.deepStrictEqual(cache.keys().sort(), ['a', 'c']);
|
|
135
|
+
});
|