rust-node-cache 0.2.0 → 0.3.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/README.md +145 -48
- package/binding.d.ts +35 -27
- package/dist/express.d.mts +14 -10
- package/dist/express.d.ts +14 -10
- package/dist/express.js +13 -3
- package/dist/express.js.map +1 -1
- package/dist/express.mjs +13 -3
- package/dist/express.mjs.map +1 -1
- package/dist/fastify.d.mts +9 -9
- package/dist/fastify.d.ts +9 -9
- package/dist/fastify.js +5 -11
- package/dist/fastify.js.map +1 -1
- package/dist/fastify.mjs +5 -11
- package/dist/fastify.mjs.map +1 -1
- package/dist/index.d.mts +73 -44
- package/dist/index.d.ts +73 -44
- package/dist/index.js +128 -15
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +128 -15
- package/dist/index.mjs.map +1 -1
- package/dist/nestjs.d.mts +11 -11
- package/dist/nestjs.d.ts +11 -11
- package/dist/nestjs.js +11 -3
- package/dist/nestjs.js.map +1 -1
- package/dist/nestjs.mjs +11 -3
- package/dist/nestjs.mjs.map +1 -1
- package/package.json +7 -4
- package/rust-node-cache.darwin-arm64.node +0 -0
- package/rust-node-cache.darwin-x64.node +0 -0
- package/rust-node-cache.linux-arm64-gnu.node +0 -0
- package/rust-node-cache.linux-arm64-musl.node +0 -0
- package/rust-node-cache.linux-x64-gnu.node +0 -0
- package/rust-node-cache.linux-x64-musl.node +0 -0
- package/rust-node-cache.win32-x64-msvc.node +0 -0
package/README.md
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
# rust-node-cache
|
|
2
2
|
|
|
3
|
-
**
|
|
3
|
+
**In-memory cache for Node.js that keeps your data off the V8 heap.**
|
|
4
4
|
|
|
5
|
-
`rust-node-cache`
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
`rust-node-cache` stores entries in Rust memory instead of on the JavaScript
|
|
6
|
+
heap. A large cache adds nothing for the garbage collector to trace, so it does
|
|
7
|
+
not lengthen GC pauses or count against `--max-old-space-size`. Eviction is
|
|
8
|
+
exact LRU in O(1), with an optional TTL per entry and a background sweeper.
|
|
9
|
+
|
|
10
|
+
Values are kept as JSON. If you want the fastest possible cache of live object
|
|
11
|
+
references, use [`lru-cache`](https://www.npmjs.com/package/lru-cache) — see
|
|
12
|
+
[When to use it](#when-to-use-it).
|
|
9
13
|
|
|
10
14
|
It ships as a prebuilt native addon (via [napi-rs](https://napi.rs/)), so there
|
|
11
15
|
is **no compiler required at install time** on supported platforms.
|
|
@@ -21,12 +25,13 @@ is **no compiler required at install time** on supported platforms.
|
|
|
21
25
|
|
|
22
26
|
## Features
|
|
23
27
|
|
|
24
|
-
- 🦀 **
|
|
25
|
-
-
|
|
26
|
-
- ⏱️ **TTL support** per entry (lazy expiration + active cleanup)
|
|
28
|
+
- 🦀 **Off-heap storage** — entries live in Rust memory; the V8 heap and GC do not see them
|
|
29
|
+
- 🧠 **Exact LRU eviction in O(1)** — bound the cache by `maxSize` with `evictionPolicy: "lru"`
|
|
30
|
+
- ⏱️ **TTL support** per entry, with millisecond precision (lazy expiration + active cleanup)
|
|
27
31
|
- 🧹 **Background expiration** — optional sweeper thread (`cleanupIntervalSeconds`)
|
|
28
|
-
-
|
|
29
|
-
|
|
32
|
+
- 🛡️ **What you store is what you get** — values that JSON cannot represent are rejected with
|
|
33
|
+
a clear error instead of coming back changed
|
|
34
|
+
- 📦 **Raw JSON access** — `setJson`/`getJson` store and serve pre-serialized responses
|
|
30
35
|
- 📊 Built-in **statistics** (hits, misses, sets, deletes, expired, evicted, size)
|
|
31
36
|
- 🧩 **TypeScript** support with generics out of the box
|
|
32
37
|
- 🌐 **Framework agnostic** — works with Express, Fastify, NestJS, Hono,
|
|
@@ -44,6 +49,25 @@ Works with both **ESM** and **CommonJS**, and requires **Node.js 18+**.
|
|
|
44
49
|
|
|
45
50
|
---
|
|
46
51
|
|
|
52
|
+
## Supported platforms
|
|
53
|
+
|
|
54
|
+
Prebuilt binaries are published for:
|
|
55
|
+
|
|
56
|
+
| OS | Architecture | Triple |
|
|
57
|
+
| ------- | --------------------- | ---------------------------- |
|
|
58
|
+
| macOS | arm64 | `aarch64-apple-darwin` |
|
|
59
|
+
| macOS | x64 | `x86_64-apple-darwin` |
|
|
60
|
+
| Linux | x64 (glibc) | `x86_64-unknown-linux-gnu` |
|
|
61
|
+
| Linux | arm64 (glibc) | `aarch64-unknown-linux-gnu` |
|
|
62
|
+
| Linux | x64 (musl, Alpine) | `x86_64-unknown-linux-musl` |
|
|
63
|
+
| Linux | arm64 (musl, Alpine) | `aarch64-unknown-linux-musl` |
|
|
64
|
+
| Windows | x64 | `x86_64-pc-windows-msvc` |
|
|
65
|
+
|
|
66
|
+
Every release is installed from the packed tarball and exercised on each of these platforms
|
|
67
|
+
in CI before it is published.
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
47
71
|
## Quick Start
|
|
48
72
|
|
|
49
73
|
```ts
|
|
@@ -73,7 +97,8 @@ console.log(cache.get("greeting")); // "hello"
|
|
|
73
97
|
### `set(key, value, options?)`
|
|
74
98
|
|
|
75
99
|
Stores (or overwrites) a value. Returns `true` on success, or `false` when a
|
|
76
|
-
`maxSize` limit is set, the cache is full, and the key is new.
|
|
100
|
+
`maxSize` limit is set, the cache is full, and the key is new. Throws a
|
|
101
|
+
`TypeError` if the value cannot be stored as JSON (see [Values](#values)).
|
|
77
102
|
|
|
78
103
|
```ts
|
|
79
104
|
cache.set("user:1", { id: 1, name: "Roberto" });
|
|
@@ -82,12 +107,25 @@ cache.set("session:123", session, { ttlSeconds: 60 });
|
|
|
82
107
|
|
|
83
108
|
### `get(key)`
|
|
84
109
|
|
|
85
|
-
Returns the stored value, or `null` if the key is missing or expired.
|
|
110
|
+
Returns the stored value, or `null` if the key is missing or expired. Every call
|
|
111
|
+
returns a new copy, so changing it does not change the cache.
|
|
86
112
|
|
|
87
113
|
```ts
|
|
88
114
|
const user = cache.get<User>("user:1"); // User | null
|
|
89
115
|
```
|
|
90
116
|
|
|
117
|
+
### `setJson(key, json, options?)` / `getJson(key)`
|
|
118
|
+
|
|
119
|
+
Store and read text that is **already JSON**, without parsing or re-encoding it —
|
|
120
|
+
for example an HTTP response body. `setJson` does not validate the text; storing
|
|
121
|
+
something that is not JSON makes a later `get()` throw. The framework adapters
|
|
122
|
+
below use this pair, so a cache HIT sends the stored text as is.
|
|
123
|
+
|
|
124
|
+
```ts
|
|
125
|
+
cache.setJson("GET:/users/1", '{"id":1,"name":"Roberto"}', { ttlSeconds: 60 });
|
|
126
|
+
res.type("application/json").send(cache.getJson("GET:/users/1"));
|
|
127
|
+
```
|
|
128
|
+
|
|
91
129
|
### `delete(key)`
|
|
92
130
|
|
|
93
131
|
Removes a key. Returns `true` if it existed, `false` otherwise.
|
|
@@ -98,7 +136,8 @@ cache.delete("user:1"); // true
|
|
|
98
136
|
|
|
99
137
|
### `exists(key)`
|
|
100
138
|
|
|
101
|
-
Returns `true` if the key exists and is still valid (not expired).
|
|
139
|
+
Returns `true` if the key exists and is still valid (not expired). It does not
|
|
140
|
+
count as a use for LRU eviction.
|
|
102
141
|
|
|
103
142
|
```ts
|
|
104
143
|
cache.exists("user:1"); // boolean
|
|
@@ -122,14 +161,39 @@ cache.size(); // 42
|
|
|
122
161
|
|
|
123
162
|
---
|
|
124
163
|
|
|
164
|
+
## Values
|
|
165
|
+
|
|
166
|
+
Values are stored as JSON, so the cache accepts what JSON represents exactly:
|
|
167
|
+
plain objects, arrays, strings, finite numbers, booleans and `null`. Anything
|
|
168
|
+
else would come back different from what you stored — `JSON.stringify` turns a
|
|
169
|
+
`Date` into a string, a `Map` into `{}` and `NaN` into `null` — so `set` throws a
|
|
170
|
+
`TypeError` that names where the problem is:
|
|
171
|
+
|
|
172
|
+
```ts
|
|
173
|
+
cache.set("user:1", { profile: { createdAt: new Date() } });
|
|
174
|
+
// TypeError: rust-node-cache: cannot store value.profile.createdAt (a Date). Values are
|
|
175
|
+
// kept as JSON, so only plain objects, arrays, strings, finite numbers, booleans and null
|
|
176
|
+
// are accepted. Store date.toISOString() or date.getTime() instead.
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Rejected: `Date`, `Map`, `Set`, `BigInt`, class instances, functions, symbols,
|
|
180
|
+
`NaN`, `Infinity`, `undefined` as the value or as an array item, and circular
|
|
181
|
+
references. Object properties set to `undefined` are left out, as JSON does.
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
125
185
|
## TTL
|
|
126
186
|
|
|
127
|
-
Set an entry with an expiration in seconds
|
|
187
|
+
Set an entry with an expiration in seconds. Fractions are allowed down to one
|
|
188
|
+
millisecond (`ttlSeconds: 0.5` is 500 ms):
|
|
128
189
|
|
|
129
190
|
```ts
|
|
130
191
|
cache.set("session:123", session, { ttlSeconds: 60 });
|
|
131
192
|
```
|
|
132
193
|
|
|
194
|
+
`ttlSeconds` must be a positive number: `0`, negative values, `NaN` and
|
|
195
|
+
`Infinity` throw instead of expiring at once or never.
|
|
196
|
+
|
|
133
197
|
After the TTL elapses, reads return `null`:
|
|
134
198
|
|
|
135
199
|
```ts
|
|
@@ -170,8 +234,11 @@ const b = new Cache({ maxSize: 1000, evictionPolicy: "lru" });
|
|
|
170
234
|
b.evictionPolicy; // "lru"
|
|
171
235
|
```
|
|
172
236
|
|
|
173
|
-
|
|
174
|
-
|
|
237
|
+
Writes and `get` hits make an entry the most recently used; `exists` does not.
|
|
238
|
+
Eviction is O(1) however large the cache is, and the evicted count is surfaced in
|
|
239
|
+
`stats().evicted` (a victim that had already expired counts in `expired`
|
|
240
|
+
instead). Overwriting an existing key never evicts. `maxSize` must be a positive
|
|
241
|
+
integer.
|
|
175
242
|
|
|
176
243
|
---
|
|
177
244
|
|
|
@@ -200,20 +267,46 @@ the storage but does **not** reset the historical counters.
|
|
|
200
267
|
|
|
201
268
|
---
|
|
202
269
|
|
|
203
|
-
##
|
|
270
|
+
## When to use it
|
|
271
|
+
|
|
272
|
+
Each call crosses from JavaScript into Rust and values go through JSON, so this
|
|
273
|
+
cache is **slower than a pure-JavaScript cache** that just keeps references to
|
|
274
|
+
your objects. What it buys you is a cache the garbage collector does not have to
|
|
275
|
+
trace.
|
|
204
276
|
|
|
205
|
-
|
|
277
|
+
**Good fit:**
|
|
206
278
|
|
|
207
|
-
-
|
|
208
|
-
|
|
209
|
-
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
- **
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
279
|
+
- large caches (hundreds of thousands of entries or more) in latency-sensitive
|
|
280
|
+
services, where heap growth means longer GC pauses;
|
|
281
|
+
- processes close to their heap limit (`--max-old-space-size`);
|
|
282
|
+
- caching HTTP responses that are JSON anyway (`setJson`/`getJson`, or the
|
|
283
|
+
Express/Fastify/NestJS adapters).
|
|
284
|
+
|
|
285
|
+
**Use `lru-cache` instead** when the cache is small, when you need to store live
|
|
286
|
+
objects (class instances, `Date`, `Map`) or when raw operations per second is
|
|
287
|
+
what matters most.
|
|
288
|
+
|
|
289
|
+
---
|
|
290
|
+
|
|
291
|
+
## Benchmarks
|
|
292
|
+
|
|
293
|
+
200,000 entries, Apple M2, Node.js 24 (operations per second, higher is better):
|
|
294
|
+
|
|
295
|
+
| Operation | rust-node-cache 0.3 | rust-node-cache 0.2 | lru-cache 11 |
|
|
296
|
+
| --------------------------------- | ------------------- | ------------------- | ------------ |
|
|
297
|
+
| `set` (5-field object) | 1.6 M | 0.4 M | 12 M |
|
|
298
|
+
| `get` (5-field object) | 1.9 M | 0.5 M | 13 M |
|
|
299
|
+
| `set` (string) | 3.4 M | 2.2 M | 11 M |
|
|
300
|
+
| `get` (string) | 3.9 M | 3.0 M | 13 M |
|
|
301
|
+
| `set` that evicts, `maxSize` 100k | 2.0 M | 0.002 M | — |
|
|
302
|
+
|
|
303
|
+
Memory with 500,000 of those objects stored:
|
|
304
|
+
|
|
305
|
+
| | rust-node-cache | lru-cache |
|
|
306
|
+
| ------------------------ | --------------- | --------- |
|
|
307
|
+
| V8 heap growth | 0 MB | 102 MB |
|
|
308
|
+
| Full GC (`global.gc()`) | 1 ms | 26 ms |
|
|
309
|
+
| Process RSS growth | 150 MB | 174 MB |
|
|
217
310
|
|
|
218
311
|
---
|
|
219
312
|
|
|
@@ -222,17 +315,19 @@ The cache core is implemented entirely in Rust and tuned for throughput:
|
|
|
222
315
|
```txt
|
|
223
316
|
Node.js
|
|
224
317
|
↓
|
|
225
|
-
|
|
318
|
+
TypeScript layer (validates values, JSON.stringify / JSON.parse in V8)
|
|
319
|
+
↓
|
|
320
|
+
napi-rs (the Rust ⇆ V8 bridge: #[napi] generates the glue code)
|
|
226
321
|
↓
|
|
227
|
-
Rust
|
|
322
|
+
Rust cache engine (set / get / ttl / stats / cleanup)
|
|
228
323
|
↓
|
|
229
|
-
|
|
324
|
+
LruCache behind a Mutex (hash map + recency list: O(1) lookups and evictions)
|
|
230
325
|
```
|
|
231
326
|
|
|
232
|
-
Values cross the boundary as
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
327
|
+
Values cross the boundary as JSON **text**: V8 encodes and decodes JSON far
|
|
328
|
+
faster than an object graph can be converted field by field through N-API. The
|
|
329
|
+
lock is uncontended in practice — JavaScript calls come from one thread, and the
|
|
330
|
+
only other thread is the optional sweeper. Statistics counters are atomics.
|
|
236
331
|
|
|
237
332
|
---
|
|
238
333
|
|
|
@@ -244,6 +339,8 @@ Bincode) later without touching the cache logic.
|
|
|
244
339
|
| `set(key, value, options?)` | `boolean` | Store/overwrite a value. `options.ttlSeconds` sets expiration. |
|
|
245
340
|
| `evictionPolicy` (getter) | `string` | The active policy: `"reject"` or `"lru"`. |
|
|
246
341
|
| `get<T>(key)` | `T \| null` | Read a value (lazy-expires stale entries). |
|
|
342
|
+
| `setJson(key, json, options?)` | `boolean` | Store text that is already JSON, as is. |
|
|
343
|
+
| `getJson(key)` | `string \| null` | Read the stored JSON text without parsing it. |
|
|
247
344
|
| `delete(key)` | `boolean` | Remove a key. `true` if it existed. |
|
|
248
345
|
| `exists(key)` | `boolean` | Whether the key exists and is valid. |
|
|
249
346
|
| `clear()` | `void` | Remove all entries. |
|
|
@@ -255,13 +352,13 @@ Bincode) later without touching the cache logic.
|
|
|
255
352
|
|
|
256
353
|
```ts
|
|
257
354
|
interface CacheOptions {
|
|
258
|
-
maxSize?: number;
|
|
355
|
+
maxSize?: number; // positive integer
|
|
259
356
|
evictionPolicy?: "reject" | "lru"; // default "reject"
|
|
260
|
-
cleanupIntervalSeconds?: number; // enables the background sweeper
|
|
357
|
+
cleanupIntervalSeconds?: number; // enables the background sweeper; fractions allowed
|
|
261
358
|
}
|
|
262
359
|
|
|
263
360
|
interface SetOptions {
|
|
264
|
-
ttlSeconds?: number;
|
|
361
|
+
ttlSeconds?: number; // > 0, fractions allowed (min 0.001)
|
|
265
362
|
}
|
|
266
363
|
|
|
267
364
|
interface CacheStats {
|
|
@@ -305,6 +402,10 @@ app.get("/manual/:id", async (req, res) => {
|
|
|
305
402
|
});
|
|
306
403
|
```
|
|
307
404
|
|
|
405
|
+
The middleware stores the JSON the client received: a `Date` in the body is
|
|
406
|
+
cached as its ISO string, exactly as `res.json` sent it, so a HIT and a MISS
|
|
407
|
+
return the same response.
|
|
408
|
+
|
|
308
409
|
---
|
|
309
410
|
|
|
310
411
|
## Fastify Example
|
|
@@ -342,18 +443,13 @@ app.useGlobalInterceptors(new CacheInterceptor({ cache, ttlSeconds: 60 }));
|
|
|
342
443
|
|
|
343
444
|
---
|
|
344
445
|
|
|
345
|
-
## Benchmarks
|
|
346
|
-
|
|
347
|
-
Coming soon.
|
|
348
|
-
|
|
349
|
-
---
|
|
350
|
-
|
|
351
446
|
## Limitations
|
|
352
447
|
|
|
353
448
|
- Local process only
|
|
354
449
|
- Not distributed
|
|
355
450
|
- Data is lost on restart
|
|
356
451
|
- Multiple workers (cluster / PM2) have separate, independent caches
|
|
452
|
+
- Values must be JSON-representable (see [Values](#values))
|
|
357
453
|
|
|
358
454
|
---
|
|
359
455
|
|
|
@@ -363,10 +459,11 @@ Coming soon.
|
|
|
363
459
|
| ------- | ------------------------ | ------ |
|
|
364
460
|
| v0.1 | Basic cache | ✅ |
|
|
365
461
|
| v0.2 | TTL cleanup thread + LRU eviction | ✅ |
|
|
366
|
-
| v0.3 |
|
|
367
|
-
| v0.4 |
|
|
368
|
-
| v0.5 |
|
|
369
|
-
| v0.6 |
|
|
462
|
+
| v0.3 | O(1) LRU, JSON fast path, strict values, `setJson`/`getJson` | ✅ |
|
|
463
|
+
| v0.4 | One cache shared by `worker_threads` + `maxBytes` memory limit | 🔜 |
|
|
464
|
+
| v0.5 | LFU cache | 🔜 |
|
|
465
|
+
| v0.6 | Prometheus metrics | 🔜 |
|
|
466
|
+
| later | Redis synchronization, ImmutableLog integration | 🔜 |
|
|
370
467
|
|
|
371
468
|
### Future: ImmutableLog Integration
|
|
372
469
|
|
package/binding.d.ts
CHANGED
|
@@ -4,32 +4,36 @@
|
|
|
4
4
|
/* auto-generated by NAPI-RS */
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
7
|
+
* Constructor options:
|
|
8
8
|
* `new Cache({ maxSize, evictionPolicy, cleanupIntervalSeconds })`.
|
|
9
9
|
*/
|
|
10
10
|
export interface CacheOptions {
|
|
11
11
|
/**
|
|
12
|
-
*
|
|
13
|
-
* `evictionPolicy`.
|
|
12
|
+
* Optional limit on the number of keys. What happens when it is reached
|
|
13
|
+
* depends on `evictionPolicy`.
|
|
14
14
|
*/
|
|
15
15
|
maxSize?: number
|
|
16
16
|
/**
|
|
17
|
-
* `"reject"` (
|
|
18
|
-
* `"lru"` —
|
|
17
|
+
* `"reject"` (default) — `set` of a new key returns `false` when full;
|
|
18
|
+
* `"lru"` — removes the least recently used entry and goes on.
|
|
19
19
|
*/
|
|
20
20
|
evictionPolicy?: string
|
|
21
21
|
/**
|
|
22
|
-
*
|
|
23
|
-
*
|
|
22
|
+
* When set (> 0), starts a thread that sweeps expired entries every N
|
|
23
|
+
* seconds (background expiration, on top of the lazy one). Fractions are
|
|
24
|
+
* allowed; `0` leaves the sweeper off.
|
|
24
25
|
*/
|
|
25
26
|
cleanupIntervalSeconds?: number
|
|
26
27
|
}
|
|
27
|
-
/**
|
|
28
|
+
/** Per-write options: `cache.set(key, value, { ttlSeconds })`. */
|
|
28
29
|
export interface SetOptions {
|
|
29
|
-
/**
|
|
30
|
+
/**
|
|
31
|
+
* Time to live in seconds (fractions allowed, at least 0.001). Absent =>
|
|
32
|
+
* the entry does not expire by time.
|
|
33
|
+
*/
|
|
30
34
|
ttlSeconds?: number
|
|
31
35
|
}
|
|
32
|
-
/**
|
|
36
|
+
/** Object returned by `cache.stats()`. `i64` fields become `number` in JS. */
|
|
33
37
|
export interface CacheStatsObject {
|
|
34
38
|
hits: number
|
|
35
39
|
misses: number
|
|
@@ -40,34 +44,38 @@ export interface CacheStatsObject {
|
|
|
40
44
|
size: number
|
|
41
45
|
}
|
|
42
46
|
/**
|
|
43
|
-
*
|
|
47
|
+
* In-memory cache exposed to Node as the `Cache` class.
|
|
44
48
|
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
49
|
+
* It delegates everything to `RustCache` (which holds the entries and the
|
|
50
|
+
* atomic counters). Every method takes `&self`: the state is synchronized
|
|
51
|
+
* inside, so it is safe to share with the background sweeper thread.
|
|
48
52
|
*/
|
|
49
53
|
export declare class Cache {
|
|
50
|
-
/** `new Cache()`
|
|
54
|
+
/** `new Cache()` or `new Cache({ maxSize, evictionPolicy, cleanupIntervalSeconds })`. */
|
|
51
55
|
constructor(options?: CacheOptions | undefined | null)
|
|
52
|
-
/**
|
|
56
|
+
/** The active eviction policy: `"reject"` or `"lru"`. */
|
|
53
57
|
get evictionPolicy(): string
|
|
54
58
|
/**
|
|
55
|
-
*
|
|
56
|
-
*
|
|
59
|
+
* Inserts or updates a key. Returns `true` on success, `false` if the cache
|
|
60
|
+
* is full (`maxSize`) and the key is new. `value` is the JSON text of the
|
|
61
|
+
* value (encoded by the TypeScript layer).
|
|
62
|
+
*/
|
|
63
|
+
set(key: string, value: string, options?: SetOptions | undefined | null): boolean
|
|
64
|
+
/**
|
|
65
|
+
* Reads a key. Returns the value's JSON text, or `null` if missing or
|
|
66
|
+
* expired.
|
|
57
67
|
*/
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
get(key: string): any | null
|
|
61
|
-
/** Remove uma chave. Retorna `true` se existia, `false` caso contrário. */
|
|
68
|
+
get(key: string): string | null
|
|
69
|
+
/** Removes a key. Returns `true` if it existed, `false` otherwise. */
|
|
62
70
|
delete(key: string): boolean
|
|
63
|
-
/**
|
|
71
|
+
/** Whether the key exists and is still valid (not expired). */
|
|
64
72
|
exists(key: string): boolean
|
|
65
|
-
/**
|
|
73
|
+
/** Removes every entry from the cache. */
|
|
66
74
|
clear(): void
|
|
67
|
-
/**
|
|
75
|
+
/** Number of keys currently stored. */
|
|
68
76
|
size(): number
|
|
69
|
-
/**
|
|
77
|
+
/** Sweeps and removes every expired entry. Returns how many it removed. */
|
|
70
78
|
cleanupExpired(): number
|
|
71
|
-
/**
|
|
79
|
+
/** Accumulated statistics + the current size. */
|
|
72
80
|
stats(): CacheStatsObject
|
|
73
81
|
}
|
package/dist/express.d.mts
CHANGED
|
@@ -2,32 +2,36 @@ import { Request, Response, NextFunction } from 'express';
|
|
|
2
2
|
import { Cache } from './index.mjs';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
5
|
+
* Express integration: caching of JSON responses.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
7
|
+
* Usage:
|
|
8
8
|
* import { Cache } from "rust-node-cache";
|
|
9
9
|
* import { cacheMiddleware } from "rust-node-cache/express";
|
|
10
10
|
*
|
|
11
11
|
* const cache = new Cache();
|
|
12
12
|
* app.get("/users/:id", cacheMiddleware({ cache, ttlSeconds: 60 }), handler);
|
|
13
13
|
*
|
|
14
|
-
*
|
|
15
|
-
* MISS
|
|
14
|
+
* The middleware intercepts `res.json`: on a HIT it responds straight from the
|
|
15
|
+
* cache; on a MISS it lets the handler run and stores the response body (2xx only).
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
interface CacheMiddlewareOptions {
|
|
19
|
-
/**
|
|
19
|
+
/** Cache instance to use. */
|
|
20
20
|
cache: Cache;
|
|
21
|
-
/** TTL
|
|
21
|
+
/** TTL applied to stored responses (in seconds). */
|
|
22
22
|
ttlSeconds?: number;
|
|
23
|
-
/**
|
|
23
|
+
/** HTTP methods eligible for caching. Default: `["GET"]`. */
|
|
24
24
|
methods?: string[];
|
|
25
|
-
/**
|
|
25
|
+
/** Builds the cache key from the request. Default: `METHOD:url`. */
|
|
26
26
|
keyGenerator?: (req: Request) => string;
|
|
27
27
|
}
|
|
28
28
|
/**
|
|
29
|
-
*
|
|
30
|
-
* `X-Cache: HIT|MISS`
|
|
29
|
+
* Creates an Express middleware that caches JSON responses. Adds an
|
|
30
|
+
* `X-Cache: HIT|MISS` header for observability.
|
|
31
|
+
*
|
|
32
|
+
* What is stored is the JSON the client received (a `Date` in the body is
|
|
33
|
+
* stored as its ISO string, as `res.json` sends it), and a HIT sends that text
|
|
34
|
+
* as is, without parsing or re-serializing it.
|
|
31
35
|
*/
|
|
32
36
|
declare function cacheMiddleware(options: CacheMiddlewareOptions): (req: Request, res: Response, next: NextFunction) => void;
|
|
33
37
|
|
package/dist/express.d.ts
CHANGED
|
@@ -2,32 +2,36 @@ import { Request, Response, NextFunction } from 'express';
|
|
|
2
2
|
import { Cache } from './index.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
5
|
+
* Express integration: caching of JSON responses.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
7
|
+
* Usage:
|
|
8
8
|
* import { Cache } from "rust-node-cache";
|
|
9
9
|
* import { cacheMiddleware } from "rust-node-cache/express";
|
|
10
10
|
*
|
|
11
11
|
* const cache = new Cache();
|
|
12
12
|
* app.get("/users/:id", cacheMiddleware({ cache, ttlSeconds: 60 }), handler);
|
|
13
13
|
*
|
|
14
|
-
*
|
|
15
|
-
* MISS
|
|
14
|
+
* The middleware intercepts `res.json`: on a HIT it responds straight from the
|
|
15
|
+
* cache; on a MISS it lets the handler run and stores the response body (2xx only).
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
interface CacheMiddlewareOptions {
|
|
19
|
-
/**
|
|
19
|
+
/** Cache instance to use. */
|
|
20
20
|
cache: Cache;
|
|
21
|
-
/** TTL
|
|
21
|
+
/** TTL applied to stored responses (in seconds). */
|
|
22
22
|
ttlSeconds?: number;
|
|
23
|
-
/**
|
|
23
|
+
/** HTTP methods eligible for caching. Default: `["GET"]`. */
|
|
24
24
|
methods?: string[];
|
|
25
|
-
/**
|
|
25
|
+
/** Builds the cache key from the request. Default: `METHOD:url`. */
|
|
26
26
|
keyGenerator?: (req: Request) => string;
|
|
27
27
|
}
|
|
28
28
|
/**
|
|
29
|
-
*
|
|
30
|
-
* `X-Cache: HIT|MISS`
|
|
29
|
+
* Creates an Express middleware that caches JSON responses. Adds an
|
|
30
|
+
* `X-Cache: HIT|MISS` header for observability.
|
|
31
|
+
*
|
|
32
|
+
* What is stored is the JSON the client received (a `Date` in the body is
|
|
33
|
+
* stored as its ISO string, as `res.json` sends it), and a HIT sends that text
|
|
34
|
+
* as is, without parsing or re-serializing it.
|
|
31
35
|
*/
|
|
32
36
|
declare function cacheMiddleware(options: CacheMiddlewareOptions): (req: Request, res: Response, next: NextFunction) => void;
|
|
33
37
|
|
package/dist/express.js
CHANGED
|
@@ -34,23 +34,33 @@ function cacheMiddleware(options) {
|
|
|
34
34
|
return;
|
|
35
35
|
}
|
|
36
36
|
const key = keyGenerator(req);
|
|
37
|
-
const cached = cache.
|
|
37
|
+
const cached = cache.getJson(key);
|
|
38
38
|
if (cached !== null) {
|
|
39
39
|
res.setHeader("X-Cache", "HIT");
|
|
40
|
-
res.json(cached);
|
|
40
|
+
res.type("application/json").send(cached);
|
|
41
41
|
return;
|
|
42
42
|
}
|
|
43
43
|
res.setHeader("X-Cache", "MISS");
|
|
44
44
|
const originalJson = res.json.bind(res);
|
|
45
45
|
res.json = (body) => {
|
|
46
46
|
if (res.statusCode >= 200 && res.statusCode < 300) {
|
|
47
|
-
|
|
47
|
+
const json = toJson(body);
|
|
48
|
+
if (json !== void 0) {
|
|
49
|
+
cache.setJson(key, json, ttlSeconds ? { ttlSeconds } : void 0);
|
|
50
|
+
}
|
|
48
51
|
}
|
|
49
52
|
return originalJson(body);
|
|
50
53
|
};
|
|
51
54
|
next();
|
|
52
55
|
};
|
|
53
56
|
}
|
|
57
|
+
function toJson(body) {
|
|
58
|
+
try {
|
|
59
|
+
return JSON.stringify(body);
|
|
60
|
+
} catch {
|
|
61
|
+
return void 0;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
54
64
|
// Annotate the CommonJS export names for ESM import in node:
|
|
55
65
|
0 && (module.exports = {
|
|
56
66
|
cacheMiddleware
|
package/dist/express.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../js/express.ts"],"sourcesContent":["/**\n *
|
|
1
|
+
{"version":3,"sources":["../js/express.ts"],"sourcesContent":["/**\n * Express integration: caching of JSON responses.\n *\n * Usage:\n * import { Cache } from \"rust-node-cache\";\n * import { cacheMiddleware } from \"rust-node-cache/express\";\n *\n * const cache = new Cache();\n * app.get(\"/users/:id\", cacheMiddleware({ cache, ttlSeconds: 60 }), handler);\n *\n * The middleware intercepts `res.json`: on a HIT it responds straight from the\n * cache; on a MISS it lets the handler run and stores the response body (2xx only).\n */\n\nimport type { Request, Response, NextFunction } from \"express\";\nimport type { Cache } from \"./index\";\n\nexport interface CacheMiddlewareOptions {\n /** Cache instance to use. */\n cache: Cache;\n /** TTL applied to stored responses (in seconds). */\n ttlSeconds?: number;\n /** HTTP methods eligible for caching. Default: `[\"GET\"]`. */\n methods?: string[];\n /** Builds the cache key from the request. Default: `METHOD:url`. */\n keyGenerator?: (req: Request) => string;\n}\n\n/**\n * Creates an Express middleware that caches JSON responses. Adds an\n * `X-Cache: HIT|MISS` header for observability.\n *\n * What is stored is the JSON the client received (a `Date` in the body is\n * stored as its ISO string, as `res.json` sends it), and a HIT sends that text\n * as is, without parsing or re-serializing it.\n */\nexport function cacheMiddleware(options: CacheMiddlewareOptions) {\n const { cache } = options;\n const ttlSeconds = options.ttlSeconds;\n const methods = options.methods ?? [\"GET\"];\n const keyGenerator =\n options.keyGenerator ?? ((req: Request) => `${req.method}:${req.originalUrl}`);\n\n return function rustNodeCache(\n req: Request,\n res: Response,\n next: NextFunction,\n ): void {\n if (!methods.includes(req.method)) {\n next();\n return;\n }\n\n const key = keyGenerator(req);\n const cached = cache.getJson(key);\n if (cached !== null) {\n res.setHeader(\"X-Cache\", \"HIT\");\n res.type(\"application/json\").send(cached);\n return;\n }\n\n res.setHeader(\"X-Cache\", \"MISS\");\n const originalJson = res.json.bind(res);\n res.json = (body: unknown): Response => {\n // Only successful responses (2xx) are stored.\n if (res.statusCode >= 200 && res.statusCode < 300) {\n const json = toJson(body);\n if (json !== undefined) {\n cache.setJson(key, json, ttlSeconds ? { ttlSeconds } : undefined);\n }\n }\n return originalJson(body);\n };\n\n next();\n };\n}\n\n/** The body as `res.json` would send it, or `undefined` when it cannot be cached. */\nfunction toJson(body: unknown): string | undefined {\n try {\n return JSON.stringify(body);\n } catch {\n return undefined; // circular or BigInt: `res.json` reports it, not the cache.\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAoCO,SAAS,gBAAgB,SAAiC;AAC/D,QAAM,EAAE,MAAM,IAAI;AAClB,QAAM,aAAa,QAAQ;AAC3B,QAAM,UAAU,QAAQ,WAAW,CAAC,KAAK;AACzC,QAAM,eACJ,QAAQ,iBAAiB,CAAC,QAAiB,GAAG,IAAI,MAAM,IAAI,IAAI,WAAW;AAE7E,SAAO,SAAS,cACd,KACA,KACA,MACM;AACN,QAAI,CAAC,QAAQ,SAAS,IAAI,MAAM,GAAG;AACjC,WAAK;AACL;AAAA,IACF;AAEA,UAAM,MAAM,aAAa,GAAG;AAC5B,UAAM,SAAS,MAAM,QAAQ,GAAG;AAChC,QAAI,WAAW,MAAM;AACnB,UAAI,UAAU,WAAW,KAAK;AAC9B,UAAI,KAAK,kBAAkB,EAAE,KAAK,MAAM;AACxC;AAAA,IACF;AAEA,QAAI,UAAU,WAAW,MAAM;AAC/B,UAAM,eAAe,IAAI,KAAK,KAAK,GAAG;AACtC,QAAI,OAAO,CAAC,SAA4B;AAEtC,UAAI,IAAI,cAAc,OAAO,IAAI,aAAa,KAAK;AACjD,cAAM,OAAO,OAAO,IAAI;AACxB,YAAI,SAAS,QAAW;AACtB,gBAAM,QAAQ,KAAK,MAAM,aAAa,EAAE,WAAW,IAAI,MAAS;AAAA,QAClE;AAAA,MACF;AACA,aAAO,aAAa,IAAI;AAAA,IAC1B;AAEA,SAAK;AAAA,EACP;AACF;AAGA,SAAS,OAAO,MAAmC;AACjD,MAAI;AACF,WAAO,KAAK,UAAU,IAAI;AAAA,EAC5B,QAAQ;AACN,WAAO;AAAA,EACT;AACF;","names":[]}
|
package/dist/express.mjs
CHANGED
|
@@ -10,23 +10,33 @@ function cacheMiddleware(options) {
|
|
|
10
10
|
return;
|
|
11
11
|
}
|
|
12
12
|
const key = keyGenerator(req);
|
|
13
|
-
const cached = cache.
|
|
13
|
+
const cached = cache.getJson(key);
|
|
14
14
|
if (cached !== null) {
|
|
15
15
|
res.setHeader("X-Cache", "HIT");
|
|
16
|
-
res.json(cached);
|
|
16
|
+
res.type("application/json").send(cached);
|
|
17
17
|
return;
|
|
18
18
|
}
|
|
19
19
|
res.setHeader("X-Cache", "MISS");
|
|
20
20
|
const originalJson = res.json.bind(res);
|
|
21
21
|
res.json = (body) => {
|
|
22
22
|
if (res.statusCode >= 200 && res.statusCode < 300) {
|
|
23
|
-
|
|
23
|
+
const json = toJson(body);
|
|
24
|
+
if (json !== void 0) {
|
|
25
|
+
cache.setJson(key, json, ttlSeconds ? { ttlSeconds } : void 0);
|
|
26
|
+
}
|
|
24
27
|
}
|
|
25
28
|
return originalJson(body);
|
|
26
29
|
};
|
|
27
30
|
next();
|
|
28
31
|
};
|
|
29
32
|
}
|
|
33
|
+
function toJson(body) {
|
|
34
|
+
try {
|
|
35
|
+
return JSON.stringify(body);
|
|
36
|
+
} catch {
|
|
37
|
+
return void 0;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
30
40
|
export {
|
|
31
41
|
cacheMiddleware
|
|
32
42
|
};
|