offheap 0.1.0 → 0.2.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.
Files changed (30) hide show
  1. package/.github/workflows/CI.yml +22 -1
  2. package/Cargo.toml +2 -2
  3. package/artifacts/bindings-aarch64-apple-darwin/index.d.ts +37 -25
  4. package/artifacts/bindings-aarch64-apple-darwin/index.js +165 -295
  5. package/artifacts/bindings-aarch64-unknown-linux-musl/index.d.ts +42 -0
  6. package/artifacts/bindings-aarch64-unknown-linux-musl/index.js +186 -0
  7. package/artifacts/bindings-x86_64-apple-darwin/index.d.ts +37 -25
  8. package/artifacts/bindings-x86_64-apple-darwin/index.js +165 -295
  9. package/artifacts/bindings-x86_64-pc-windows-msvc/index.d.ts +42 -30
  10. package/artifacts/bindings-x86_64-pc-windows-msvc/index.js +186 -316
  11. package/artifacts/bindings-x86_64-unknown-linux-gnu/index.d.ts +37 -25
  12. package/artifacts/bindings-x86_64-unknown-linux-gnu/index.js +165 -295
  13. package/artifacts/bindings-x86_64-unknown-linux-musl/index.d.ts +42 -0
  14. package/artifacts/bindings-x86_64-unknown-linux-musl/index.js +186 -0
  15. package/benchmarks/crossover_bench.js +139 -0
  16. package/docs/.vitepress/config.js +35 -0
  17. package/docs/guide/api.md +216 -0
  18. package/docs/guide/architecture.md +58 -0
  19. package/docs/guide/benchmarks.md +48 -0
  20. package/docs/guide/getting-started.md +74 -0
  21. package/docs/index.md +23 -0
  22. package/index.d.ts +37 -25
  23. package/index.js +165 -295
  24. package/package.json +21 -12
  25. package/src/algorithms/arc.rs +123 -25
  26. package/src/algorithms/lru.rs +94 -12
  27. package/src/algorithms/mod.rs +4 -0
  28. package/src/algorithms/tinylfu.rs +98 -35
  29. package/src/cache.rs +289 -94
  30. package/tests/index.test.js +134 -7
@@ -18,6 +18,12 @@ jobs:
18
18
  - host: ubuntu-latest
19
19
  target: x86_64-unknown-linux-gnu
20
20
  architecture: x64
21
+ - host: ubuntu-latest
22
+ target: x86_64-unknown-linux-musl
23
+ architecture: x64
24
+ - host: ubuntu-latest
25
+ target: aarch64-unknown-linux-musl
26
+ architecture: arm64
21
27
  - host: windows-latest
22
28
  target: x86_64-pc-windows-msvc
23
29
  architecture: x64
@@ -41,6 +47,16 @@ jobs:
41
47
  node-version: 20
42
48
  cache: npm
43
49
 
50
+ - name: Install musl-tools
51
+ if: matrix.settings.target == 'x86_64-unknown-linux-musl'
52
+ run: sudo apt-get update && sudo apt-get install -y musl-tools
53
+
54
+ - name: Install Zig
55
+ if: matrix.settings.target == 'aarch64-unknown-linux-musl'
56
+ uses: goto-bus-stop/setup-zig@v2
57
+ with:
58
+ version: 0.11.0
59
+
44
60
  - name: Install Rust
45
61
  uses: dtolnay/rust-toolchain@stable
46
62
  with:
@@ -61,7 +77,12 @@ jobs:
61
77
  run: npm ci
62
78
 
63
79
  - name: Build
64
- run: npm run build -- --target ${{ matrix.settings.target }}
80
+ run: |
81
+ if [[ "${{ matrix.settings.target }}" == "aarch64-unknown-linux-musl" ]]; then
82
+ npm run build -- --target ${{ matrix.settings.target }} --zig
83
+ else
84
+ npm run build -- --target ${{ matrix.settings.target }}
85
+ fi
65
86
  shell: bash
66
87
 
67
88
  - name: Upload artifact
package/Cargo.toml CHANGED
@@ -1,8 +1,8 @@
1
1
  [package]
2
2
  name = "offheap"
3
- version = "0.1.0"
3
+ version = "0.2.0"
4
4
  edition = "2021"
5
- authors = ["Ryan Gustav"]
5
+ authors = ["Ryan Gustavo"]
6
6
  license = "MIT OR Apache-2.0"
7
7
 
8
8
  [lib]
@@ -1,30 +1,42 @@
1
- /* tslint:disable */
2
- /* eslint-disable */
3
-
4
- /* auto-generated by NAPI-RS */
5
-
6
1
  export interface CacheConfig {
7
- policy: string
8
- capacity: number
2
+ policy: 'lru' | 'arc' | 'tinylfu';
3
+ capacity: number;
4
+ shards?: number;
5
+ maxBytes?: number;
9
6
  }
10
- export interface CacheStatsJs {
11
- hits: number
12
- misses: number
13
- capacity: number
14
- size: number
7
+
8
+ export interface CacheStats {
9
+ hits: number;
10
+ misses: number;
11
+ capacity: number;
12
+ size: number;
13
+ bytesUsed: number;
15
14
  }
16
- export declare class Cache {
17
- get(key: string): unknown
18
- set(key: string, value: unknown, ttlMs?: number | undefined | null): unknown
19
- delete(key: string): boolean
20
- clear(): void
21
- stats(): CacheStatsJs
22
- keys(): Array<string>
15
+
16
+ export class Cache {
17
+ get<T = any>(key: string): T | undefined;
18
+ peek<T = any>(key: string): T | undefined;
19
+ has(key: string): boolean;
20
+ set<T = any>(key: string, value: T, ttlMs?: number): T | undefined;
21
+ touch(key: string, ttlMs?: number): boolean;
22
+ delete(key: string): boolean;
23
+ clear(): void;
24
+ stats(): CacheStats;
25
+ keys(): string[];
26
+ increment(key: string, delta?: number, ttlMs?: number): number;
27
+ decrement(key: string, delta?: number, ttlMs?: number): number;
28
+ mget(keys: string[]): Record<string, any>;
29
+ mset(entries: Record<string, any>, ttlMs?: number): void;
30
+ mdelete(keys: string[]): number;
31
+ dispose(): void;
32
+ getOrSet<T = any>(key: string, factory: () => T | Promise<T>, ttlMs?: number): T | Promise<T>;
23
33
  }
24
- export declare class CacheManager {
25
- constructor()
26
- createCache(name: string, config: CacheConfig): Cache
27
- getCache(name: string): Cache | null
28
- deleteCache(name: string): boolean
29
- clear(): void
34
+
35
+ export class CacheManager {
36
+ constructor();
37
+ createCache(name: string, config: CacheConfig): Cache;
38
+ getCache(name: string): Cache | null;
39
+ deleteCache(name: string): boolean;
40
+ clear(): void;
41
+ dispose(): void;
30
42
  }
@@ -1,316 +1,186 @@
1
- /* tslint:disable */
2
- /* eslint-disable */
3
- /* prettier-ignore */
1
+ const { Cache: NativeCache, CacheManager: NativeCacheManager } = require('./binding');
4
2
 
5
- /* auto-generated by NAPI-RS */
3
+ const activePromises = new Map();
4
+ const finalizer = new FinalizationRegistry((nativeCache) => {
5
+ try {
6
+ nativeCache.dispose();
7
+ } catch (e) {
8
+ // Ignore errors during GC finalization
9
+ }
10
+ });
6
11
 
7
- const { existsSync, readFileSync } = require('fs')
8
- const { join } = require('path')
12
+ class Cache {
13
+ constructor(nativeCache) {
14
+ this._native = nativeCache;
15
+ this._id = Math.random().toString(36).substring(2);
16
+ finalizer.register(this, nativeCache, this);
17
+ }
9
18
 
10
- const { platform, arch } = process
19
+ get(key) {
20
+ return this._native.get(key);
21
+ }
11
22
 
12
- let nativeBinding = null
13
- let localFileExisted = false
14
- let loadError = null
23
+ peek(key) {
24
+ return this._native.peek(key);
25
+ }
15
26
 
16
- function isMusl() {
17
- // For Node 10
18
- if (!process.report || typeof process.report.getReport !== 'function') {
19
- try {
20
- const lddPath = require('child_process').execSync('which ldd').toString().trim()
21
- return readFileSync(lddPath, 'utf8').includes('musl')
22
- } catch (e) {
23
- return true
24
- }
25
- } else {
26
- const { glibcVersionRuntime } = process.report.getReport().header
27
- return !glibcVersionRuntime
27
+ has(key) {
28
+ return this._native.has(key);
28
29
  }
29
- }
30
30
 
31
- switch (platform) {
32
- case 'android':
33
- switch (arch) {
34
- case 'arm64':
35
- localFileExisted = existsSync(join(__dirname, 'offheap.android-arm64.node'))
36
- try {
37
- if (localFileExisted) {
38
- nativeBinding = require('./offheap.android-arm64.node')
39
- } else {
40
- nativeBinding = require('offheap-android-arm64')
41
- }
42
- } catch (e) {
43
- loadError = e
44
- }
45
- break
46
- case 'arm':
47
- localFileExisted = existsSync(join(__dirname, 'offheap.android-arm-eabi.node'))
48
- try {
49
- if (localFileExisted) {
50
- nativeBinding = require('./offheap.android-arm-eabi.node')
51
- } else {
52
- nativeBinding = require('offheap-android-arm-eabi')
53
- }
54
- } catch (e) {
55
- loadError = e
56
- }
57
- break
58
- default:
59
- throw new Error(`Unsupported architecture on Android ${arch}`)
31
+ set(key, value, ttlMs) {
32
+ return this._native.set(key, value, ttlMs);
33
+ }
34
+
35
+ touch(key, ttlMs) {
36
+ return this._native.touch(key, ttlMs);
37
+ }
38
+
39
+ delete(key) {
40
+ return this._native.delete(key);
41
+ }
42
+
43
+ clear() {
44
+ this._native.clear();
45
+ }
46
+
47
+ stats() {
48
+ const rawStats = this._native.stats();
49
+ return {
50
+ hits: rawStats.hits,
51
+ misses: rawStats.misses,
52
+ capacity: rawStats.capacity,
53
+ size: rawStats.size,
54
+ bytesUsed: rawStats.bytesUsed,
55
+ };
56
+ }
57
+
58
+ keys() {
59
+ return this._native.keys();
60
+ }
61
+
62
+ increment(key, delta = 1, ttlMs) {
63
+ return this._native.increment(key, delta, ttlMs);
64
+ }
65
+
66
+ decrement(key, delta = 1, ttlMs) {
67
+ return this._native.decrement(key, delta, ttlMs);
68
+ }
69
+
70
+ mget(keys) {
71
+ return this._native.mget(keys);
72
+ }
73
+
74
+ mset(entries, ttlMs) {
75
+ if (typeof entries !== 'object' || entries === null) {
76
+ throw new Error('mset requires an object of key-value entries');
60
77
  }
61
- break
62
- case 'win32':
63
- switch (arch) {
64
- case 'x64':
65
- localFileExisted = existsSync(
66
- join(__dirname, 'offheap.win32-x64-msvc.node')
67
- )
68
- try {
69
- if (localFileExisted) {
70
- nativeBinding = require('./offheap.win32-x64-msvc.node')
71
- } else {
72
- nativeBinding = require('offheap-win32-x64-msvc')
73
- }
74
- } catch (e) {
75
- loadError = e
76
- }
77
- break
78
- case 'ia32':
79
- localFileExisted = existsSync(
80
- join(__dirname, 'offheap.win32-ia32-msvc.node')
81
- )
82
- try {
83
- if (localFileExisted) {
84
- nativeBinding = require('./offheap.win32-ia32-msvc.node')
85
- } else {
86
- nativeBinding = require('offheap-win32-ia32-msvc')
87
- }
88
- } catch (e) {
89
- loadError = e
90
- }
91
- break
92
- case 'arm64':
93
- localFileExisted = existsSync(
94
- join(__dirname, 'offheap.win32-arm64-msvc.node')
95
- )
96
- try {
97
- if (localFileExisted) {
98
- nativeBinding = require('./offheap.win32-arm64-msvc.node')
99
- } else {
100
- nativeBinding = require('offheap-win32-arm64-msvc')
101
- }
102
- } catch (e) {
103
- loadError = e
104
- }
105
- break
106
- default:
107
- throw new Error(`Unsupported architecture on Windows: ${arch}`)
78
+ this._native.mset(entries, ttlMs);
79
+ }
80
+
81
+ mdelete(keys) {
82
+ if (!Array.isArray(keys)) {
83
+ throw new Error('mdelete requires an array of keys');
108
84
  }
109
- break
110
- case 'darwin':
111
- localFileExisted = existsSync(join(__dirname, 'offheap.darwin-universal.node'))
112
- try {
113
- if (localFileExisted) {
114
- nativeBinding = require('./offheap.darwin-universal.node')
115
- } else {
116
- nativeBinding = require('offheap-darwin-universal')
117
- }
118
- break
119
- } catch {}
120
- switch (arch) {
121
- case 'x64':
122
- localFileExisted = existsSync(join(__dirname, 'offheap.darwin-x64.node'))
123
- try {
124
- if (localFileExisted) {
125
- nativeBinding = require('./offheap.darwin-x64.node')
126
- } else {
127
- nativeBinding = require('offheap-darwin-x64')
128
- }
129
- } catch (e) {
130
- loadError = e
131
- }
132
- break
133
- case 'arm64':
134
- localFileExisted = existsSync(
135
- join(__dirname, 'offheap.darwin-arm64.node')
136
- )
137
- try {
138
- if (localFileExisted) {
139
- nativeBinding = require('./offheap.darwin-arm64.node')
140
- } else {
141
- nativeBinding = require('offheap-darwin-arm64')
142
- }
143
- } catch (e) {
144
- loadError = e
145
- }
146
- break
147
- default:
148
- throw new Error(`Unsupported architecture on macOS: ${arch}`)
85
+ return this._native.mdelete(keys);
86
+ }
87
+
88
+ dispose() {
89
+ finalizer.unregister(this);
90
+ this._native.dispose();
91
+ }
92
+
93
+ getOrSet(key, factory, ttlMs) {
94
+ const val = this.get(key);
95
+ if (val !== undefined) {
96
+ return val;
149
97
  }
150
- break
151
- case 'freebsd':
152
- if (arch !== 'x64') {
153
- throw new Error(`Unsupported architecture on FreeBSD: ${arch}`)
98
+
99
+ const promiseKey = `${this._id}:${key}`;
100
+ if (activePromises.has(promiseKey)) {
101
+ return activePromises.get(promiseKey);
154
102
  }
155
- localFileExisted = existsSync(join(__dirname, 'offheap.freebsd-x64.node'))
103
+
156
104
  try {
157
- if (localFileExisted) {
158
- nativeBinding = require('./offheap.freebsd-x64.node')
159
- } else {
160
- nativeBinding = require('offheap-freebsd-x64')
161
- }
162
- } catch (e) {
163
- loadError = e
164
- }
165
- break
166
- case 'linux':
167
- switch (arch) {
168
- case 'x64':
169
- if (isMusl()) {
170
- localFileExisted = existsSync(
171
- join(__dirname, 'offheap.linux-x64-musl.node')
172
- )
173
- try {
174
- if (localFileExisted) {
175
- nativeBinding = require('./offheap.linux-x64-musl.node')
176
- } else {
177
- nativeBinding = require('offheap-linux-x64-musl')
178
- }
179
- } catch (e) {
180
- loadError = e
181
- }
182
- } else {
183
- localFileExisted = existsSync(
184
- join(__dirname, 'offheap.linux-x64-gnu.node')
185
- )
186
- try {
187
- if (localFileExisted) {
188
- nativeBinding = require('./offheap.linux-x64-gnu.node')
189
- } else {
190
- nativeBinding = require('offheap-linux-x64-gnu')
191
- }
192
- } catch (e) {
193
- loadError = e
194
- }
195
- }
196
- break
197
- case 'arm64':
198
- if (isMusl()) {
199
- localFileExisted = existsSync(
200
- join(__dirname, 'offheap.linux-arm64-musl.node')
201
- )
202
- try {
203
- if (localFileExisted) {
204
- nativeBinding = require('./offheap.linux-arm64-musl.node')
205
- } else {
206
- nativeBinding = require('offheap-linux-arm64-musl')
207
- }
208
- } catch (e) {
209
- loadError = e
210
- }
211
- } else {
212
- localFileExisted = existsSync(
213
- join(__dirname, 'offheap.linux-arm64-gnu.node')
214
- )
215
- try {
216
- if (localFileExisted) {
217
- nativeBinding = require('./offheap.linux-arm64-gnu.node')
218
- } else {
219
- nativeBinding = require('offheap-linux-arm64-gnu')
220
- }
221
- } catch (e) {
222
- loadError = e
223
- }
224
- }
225
- break
226
- case 'arm':
227
- if (isMusl()) {
228
- localFileExisted = existsSync(
229
- join(__dirname, 'offheap.linux-arm-musleabihf.node')
230
- )
231
- try {
232
- if (localFileExisted) {
233
- nativeBinding = require('./offheap.linux-arm-musleabihf.node')
234
- } else {
235
- nativeBinding = require('offheap-linux-arm-musleabihf')
236
- }
237
- } catch (e) {
238
- loadError = e
239
- }
240
- } else {
241
- localFileExisted = existsSync(
242
- join(__dirname, 'offheap.linux-arm-gnueabihf.node')
243
- )
244
- try {
245
- if (localFileExisted) {
246
- nativeBinding = require('./offheap.linux-arm-gnueabihf.node')
247
- } else {
248
- nativeBinding = require('offheap-linux-arm-gnueabihf')
249
- }
250
- } catch (e) {
251
- loadError = e
252
- }
253
- }
254
- break
255
- case 'riscv64':
256
- if (isMusl()) {
257
- localFileExisted = existsSync(
258
- join(__dirname, 'offheap.linux-riscv64-musl.node')
259
- )
260
- try {
261
- if (localFileExisted) {
262
- nativeBinding = require('./offheap.linux-riscv64-musl.node')
263
- } else {
264
- nativeBinding = require('offheap-linux-riscv64-musl')
265
- }
266
- } catch (e) {
267
- loadError = e
268
- }
269
- } else {
270
- localFileExisted = existsSync(
271
- join(__dirname, 'offheap.linux-riscv64-gnu.node')
272
- )
273
- try {
274
- if (localFileExisted) {
275
- nativeBinding = require('./offheap.linux-riscv64-gnu.node')
276
- } else {
277
- nativeBinding = require('offheap-linux-riscv64-gnu')
105
+ const res = factory();
106
+ if (res instanceof Promise) {
107
+ const p = res
108
+ .then((resolvedVal) => {
109
+ activePromises.delete(promiseKey);
110
+ if (resolvedVal !== undefined) {
111
+ this.set(key, resolvedVal, ttlMs);
278
112
  }
279
- } catch (e) {
280
- loadError = e
281
- }
282
- }
283
- break
284
- case 's390x':
285
- localFileExisted = existsSync(
286
- join(__dirname, 'offheap.linux-s390x-gnu.node')
287
- )
288
- try {
289
- if (localFileExisted) {
290
- nativeBinding = require('./offheap.linux-s390x-gnu.node')
291
- } else {
292
- nativeBinding = require('offheap-linux-s390x-gnu')
293
- }
294
- } catch (e) {
295
- loadError = e
113
+ return resolvedVal;
114
+ })
115
+ .catch((err) => {
116
+ activePromises.delete(promiseKey);
117
+ throw err;
118
+ });
119
+ activePromises.set(promiseKey, p);
120
+ return p;
121
+ } else {
122
+ if (res !== undefined) {
123
+ this.set(key, res, ttlMs);
296
124
  }
297
- break
298
- default:
299
- throw new Error(`Unsupported architecture on Linux: ${arch}`)
125
+ return res;
126
+ }
127
+ } catch (err) {
128
+ activePromises.delete(promiseKey);
129
+ throw err;
300
130
  }
301
- break
302
- default:
303
- throw new Error(`Unsupported OS: ${platform}, architecture: ${arch}`)
131
+ }
304
132
  }
305
133
 
306
- if (!nativeBinding) {
307
- if (loadError) {
308
- throw loadError
134
+ const activeManagers = new Set();
135
+
136
+ class CacheManager {
137
+ constructor() {
138
+ this._native = new NativeCacheManager();
139
+ activeManagers.add(this);
140
+ }
141
+
142
+ createCache(name, config) {
143
+ const rawConfig = {
144
+ policy: config.policy,
145
+ capacity: config.capacity,
146
+ shards: config.shards,
147
+ maxBytes: config.maxBytes,
148
+ };
149
+ const nativeCache = this._native.createCache(name, rawConfig);
150
+ return new Cache(nativeCache);
151
+ }
152
+
153
+ getCache(name) {
154
+ const nativeCache = this._native.getCache(name);
155
+ if (!nativeCache) return null;
156
+ return new Cache(nativeCache);
157
+ }
158
+
159
+ deleteCache(name) {
160
+ return this._native.deleteCache(name);
161
+ }
162
+
163
+ clear() {
164
+ this._native.clear();
165
+ }
166
+
167
+ dispose() {
168
+ activeManagers.delete(this);
169
+ this._native.clear();
309
170
  }
310
- throw new Error(`Failed to load native binding`)
311
171
  }
312
172
 
313
- const { Cache, CacheManager } = nativeBinding
173
+ process.on('exit', () => {
174
+ for (const manager of activeManagers) {
175
+ try {
176
+ manager.dispose();
177
+ } catch (e) {
178
+ // Ignore cleanup failures on process exit
179
+ }
180
+ }
181
+ });
314
182
 
315
- module.exports.Cache = Cache
316
- module.exports.CacheManager = CacheManager
183
+ module.exports = {
184
+ Cache,
185
+ CacheManager,
186
+ };
@@ -0,0 +1,42 @@
1
+ export interface CacheConfig {
2
+ policy: 'lru' | 'arc' | 'tinylfu';
3
+ capacity: number;
4
+ shards?: number;
5
+ maxBytes?: number;
6
+ }
7
+
8
+ export interface CacheStats {
9
+ hits: number;
10
+ misses: number;
11
+ capacity: number;
12
+ size: number;
13
+ bytesUsed: number;
14
+ }
15
+
16
+ export class Cache {
17
+ get<T = any>(key: string): T | undefined;
18
+ peek<T = any>(key: string): T | undefined;
19
+ has(key: string): boolean;
20
+ set<T = any>(key: string, value: T, ttlMs?: number): T | undefined;
21
+ touch(key: string, ttlMs?: number): boolean;
22
+ delete(key: string): boolean;
23
+ clear(): void;
24
+ stats(): CacheStats;
25
+ keys(): string[];
26
+ increment(key: string, delta?: number, ttlMs?: number): number;
27
+ decrement(key: string, delta?: number, ttlMs?: number): number;
28
+ mget(keys: string[]): Record<string, any>;
29
+ mset(entries: Record<string, any>, ttlMs?: number): void;
30
+ mdelete(keys: string[]): number;
31
+ dispose(): void;
32
+ getOrSet<T = any>(key: string, factory: () => T | Promise<T>, ttlMs?: number): T | Promise<T>;
33
+ }
34
+
35
+ export class CacheManager {
36
+ constructor();
37
+ createCache(name: string, config: CacheConfig): Cache;
38
+ getCache(name: string): Cache | null;
39
+ deleteCache(name: string): boolean;
40
+ clear(): void;
41
+ dispose(): void;
42
+ }