rust-node-cache 0.1.1 → 0.2.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 CHANGED
@@ -24,8 +24,10 @@ is **no compiler required at install time** on supported platforms.
24
24
  - 🦀 **Rust-powered** core for predictable, low-overhead performance
25
25
  - 🔒 **Thread-safe** via `DashMap` (lock-sharded concurrent map)
26
26
  - ⏱️ **TTL support** per entry (lazy expiration + active cleanup)
27
+ - 🧹 **Background expiration** — optional sweeper thread (`cleanupIntervalSeconds`)
28
+ - 🧠 **LRU eviction** — bound the cache by `maxSize` with `evictionPolicy: "lru"`
27
29
  - ⚡ **Fast reads** and **fast writes**
28
- - 📊 Built-in **statistics** (hits, misses, sets, deletes, expired, size)
30
+ - 📊 Built-in **statistics** (hits, misses, sets, deletes, expired, evicted, size)
29
31
  - 🧩 **TypeScript** support with generics out of the box
30
32
  - 🌐 **Framework agnostic** — works with Express, Fastify, NestJS, Hono,
31
33
  Next.js, or plain Node.js
@@ -42,6 +44,25 @@ Works with both **ESM** and **CommonJS**, and requires **Node.js 18+**.
42
44
 
43
45
  ---
44
46
 
47
+ ## Supported platforms
48
+
49
+ Prebuilt binaries are published for:
50
+
51
+ | OS | Architecture | Triple |
52
+ | ------- | --------------------- | ---------------------------- |
53
+ | macOS | arm64 | `aarch64-apple-darwin` |
54
+ | macOS | x64 | `x86_64-apple-darwin` |
55
+ | Linux | x64 (glibc) | `x86_64-unknown-linux-gnu` |
56
+ | Linux | arm64 (glibc) | `aarch64-unknown-linux-gnu` |
57
+ | Linux | x64 (musl, Alpine) | `x86_64-unknown-linux-musl` |
58
+ | Linux | arm64 (musl, Alpine) | `aarch64-unknown-linux-musl` |
59
+ | Windows | x64 | `x86_64-pc-windows-msvc` |
60
+
61
+ Every release is installed from the packed tarball and exercised on each of these platforms
62
+ in CI before it is published.
63
+
64
+ ---
65
+
45
66
  ## Quick Start
46
67
 
47
68
  ```ts
@@ -142,6 +163,35 @@ an active sweep:
142
163
  const removed = cache.cleanupExpired(); // number of entries removed
143
164
  ```
144
165
 
166
+ Or let the cache sweep for you in the background — set `cleanupIntervalSeconds`
167
+ and a thread reclaims expired entries on its own (no manual `cleanupExpired()`):
168
+
169
+ ```ts
170
+ const cache = new Cache({ cleanupIntervalSeconds: 30 });
171
+ ```
172
+
173
+ The thread is tied to the cache's lifetime: it stops automatically when the cache
174
+ is garbage-collected.
175
+
176
+ ---
177
+
178
+ ## Eviction (maxSize)
179
+
180
+ Cap the number of keys with `maxSize`. When the cache is full and a **new** key
181
+ is written, `evictionPolicy` decides what happens:
182
+
183
+ ```ts
184
+ // Default: reject the write (set returns false), keep existing entries.
185
+ const a = new Cache({ maxSize: 1000 }); // evictionPolicy: "reject"
186
+
187
+ // LRU: evict the least-recently-used entry to make room, then insert.
188
+ const b = new Cache({ maxSize: 1000, evictionPolicy: "lru" });
189
+ b.evictionPolicy; // "lru"
190
+ ```
191
+
192
+ LRU recency is tracked per entry (updated on every `get` hit) and the evicted
193
+ count is surfaced in `stats().evicted`. Overwriting an existing key never evicts.
194
+
145
195
  ---
146
196
 
147
197
  ## Statistics
@@ -159,6 +209,7 @@ Returns:
159
209
  sets: 800,
160
210
  deletes: 20,
161
211
  expired: 15,
212
+ evicted: 40,
162
213
  size: 780
163
214
  }
164
215
  ```
@@ -208,8 +259,9 @@ Bincode) later without touching the cache logic.
208
259
 
209
260
  | Method | Returns | Description |
210
261
  | ------------------------------ | -------------- | ----------------------------------------------------------------- |
211
- | `new Cache(options?)` | `Cache` | Create a cache. `options.maxSize` caps the number of keys. |
262
+ | `new Cache(options?)` | `Cache` | Create a cache (`maxSize`, `evictionPolicy`, `cleanupIntervalSeconds`). |
212
263
  | `set(key, value, options?)` | `boolean` | Store/overwrite a value. `options.ttlSeconds` sets expiration. |
264
+ | `evictionPolicy` (getter) | `string` | The active policy: `"reject"` or `"lru"`. |
213
265
  | `get<T>(key)` | `T \| null` | Read a value (lazy-expires stale entries). |
214
266
  | `delete(key)` | `boolean` | Remove a key. `true` if it existed. |
215
267
  | `exists(key)` | `boolean` | Whether the key exists and is valid. |
@@ -223,6 +275,8 @@ Bincode) later without touching the cache logic.
223
275
  ```ts
224
276
  interface CacheOptions {
225
277
  maxSize?: number;
278
+ evictionPolicy?: "reject" | "lru"; // default "reject"
279
+ cleanupIntervalSeconds?: number; // enables the background sweeper
226
280
  }
227
281
 
228
282
  interface SetOptions {
@@ -235,6 +289,7 @@ interface CacheStats {
235
289
  sets: number;
236
290
  deletes: number;
237
291
  expired: number;
292
+ evicted: number;
238
293
  size: number;
239
294
  }
240
295
  ```
@@ -323,15 +378,14 @@ Coming soon.
323
378
 
324
379
  ## Roadmap
325
380
 
326
- | Version | Feature |
327
- | ------- | ------------------------ |
328
- | v0.1 | Basic cache |
329
- | v0.2 | TTL cleanup thread |
330
- | v0.3 | LRU cache |
331
- | v0.4 | LFU cache |
332
- | v0.5 | Redis synchronization |
333
- | v0.6 | Prometheus metrics |
334
- | v0.7 | ImmutableLog integration |
381
+ | Version | Feature | Status |
382
+ | ------- | ------------------------ | ------ |
383
+ | v0.1 | Basic cache | ✅ |
384
+ | v0.2 | TTL cleanup thread + LRU eviction | ✅ |
385
+ | v0.3 | LFU cache | 🔜 |
386
+ | v0.4 | Redis synchronization | 🔜 |
387
+ | v0.5 | Prometheus metrics | 🔜 |
388
+ | v0.6 | ImmutableLog integration | 🔜 |
335
389
 
336
390
  ### Future: ImmutableLog Integration
337
391
 
package/binding.d.ts CHANGED
@@ -3,52 +3,71 @@
3
3
 
4
4
  /* auto-generated by NAPI-RS */
5
5
 
6
- /** Opções do construtor: `new Cache({ maxSize })`. */
6
+ /**
7
+ * Constructor options:
8
+ * `new Cache({ maxSize, evictionPolicy, cleanupIntervalSeconds })`.
9
+ */
7
10
  export interface CacheOptions {
8
- /** Limite opcional de chaves. Ao atingir, `set` de chave nova retorna `false`. */
11
+ /**
12
+ * Optional limit on the number of keys. What happens when it is reached
13
+ * depends on `evictionPolicy`.
14
+ */
9
15
  maxSize?: number
16
+ /**
17
+ * `"reject"` (default) — `set` of a new key returns `false` when full;
18
+ * `"lru"` — removes the least recently used entry and goes on.
19
+ */
20
+ evictionPolicy?: string
21
+ /**
22
+ * When set (> 0), starts a thread that sweeps expired entries every N
23
+ * seconds (background expiration, on top of the lazy one).
24
+ */
25
+ cleanupIntervalSeconds?: number
10
26
  }
11
- /** Opções por escrita: `cache.set(key, value, { ttlSeconds })`. */
27
+ /** Per-write options: `cache.set(key, value, { ttlSeconds })`. */
12
28
  export interface SetOptions {
13
- /** Tempo de vida em segundos. Ausente => a entrada não expira por tempo. */
29
+ /** Time to live in seconds. Absent => the entry does not expire by time. */
14
30
  ttlSeconds?: number
15
31
  }
16
- /** Objeto devolvido por `cache.stats()`. Campos `i64` viram `number` no JS. */
32
+ /** Object returned by `cache.stats()`. `i64` fields become `number` in JS. */
17
33
  export interface CacheStatsObject {
18
34
  hits: number
19
35
  misses: number
20
36
  sets: number
21
37
  deletes: number
22
38
  expired: number
39
+ evicted: number
23
40
  size: number
24
41
  }
25
42
  /**
26
- * Cache em memória exposto ao Node como a classe `Cache`.
43
+ * In-memory cache exposed to Node as the `Cache` class.
27
44
  *
28
- * Internamente delega tudo para o `RustCache` (que carrega o `DashMap` e os
29
- * contadores atômicos). Todos os métodos recebem `&self`: como o estado é
30
- * concorrente por dentro, várias chamadas podem rodar em paralelo.
45
+ * It delegates everything to `RustCache` (which holds the `DashMap` and the
46
+ * atomic counters). Every method takes `&self`: the state is concurrent
47
+ * inside, so it is safe to use from several threads (such as the sweeper).
31
48
  */
32
49
  export declare class Cache {
33
- /** `new Cache()` ou `new Cache({ maxSize })`. */
50
+ /** `new Cache()` or `new Cache({ maxSize, evictionPolicy, cleanupIntervalSeconds })`. */
34
51
  constructor(options?: CacheOptions | undefined | null)
52
+ /** The active eviction policy: `"reject"` or `"lru"`. */
53
+ get evictionPolicy(): string
35
54
  /**
36
- * Insere/atualiza uma chave. Retorna `true` em sucesso, `false` se o cache
37
- * estiver cheio (`maxSize`) e a chave for nova.
55
+ * Inserts or updates a key. Returns `true` on success, `false` if the cache
56
+ * is full (`maxSize`) and the key is new.
38
57
  */
39
58
  set(key: string, value: any, options?: SetOptions | undefined | null): boolean
40
- /** Lê uma chave. Retorna o valor ou `null` se ausente/expirada. */
59
+ /** Reads a key. Returns the value, or `null` if missing or expired. */
41
60
  get(key: string): any | null
42
- /** Remove uma chave. Retorna `true` se existia, `false` caso contrário. */
61
+ /** Removes a key. Returns `true` if it existed, `false` otherwise. */
43
62
  delete(key: string): boolean
44
- /** Indica se a chave existe e está válida (não expirada). */
63
+ /** Whether the key exists and is still valid (not expired). */
45
64
  exists(key: string): boolean
46
- /** Esvazia todas as entradas do cache. */
65
+ /** Removes every entry from the cache. */
47
66
  clear(): void
48
- /** Quantidade de chaves armazenadas no momento. */
67
+ /** Number of keys currently stored. */
49
68
  size(): number
50
- /** Varre e remove todas as entradas expiradas. Retorna quantas removeu. */
69
+ /** Sweeps and removes every expired entry. Returns how many it removed. */
51
70
  cleanupExpired(): number
52
- /** Estatísticas acumuladas + tamanho atual. */
71
+ /** Accumulated statistics + the current size. */
53
72
  stats(): CacheStatsObject
54
73
  }
@@ -2,32 +2,32 @@ import { Request, Response, NextFunction } from 'express';
2
2
  import { Cache } from './index.mjs';
3
3
 
4
4
  /**
5
- * Integração com Express: cache de respostas JSON.
5
+ * Express integration: caching of JSON responses.
6
6
  *
7
- * Uso:
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
- * O middleware intercepta `res.json`: num HIT responde direto do cache; num
15
- * MISS deixa o handler rodar e guarda o corpo da resposta (apenas em 2xx).
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
- /** Instância de cache a usar. */
19
+ /** Cache instance to use. */
20
20
  cache: Cache;
21
- /** TTL aplicado às respostas guardadas (em segundos). */
21
+ /** TTL applied to stored responses (in seconds). */
22
22
  ttlSeconds?: number;
23
- /** Métodos HTTP elegíveis para cache. Padrão: `["GET"]`. */
23
+ /** HTTP methods eligible for caching. Default: `["GET"]`. */
24
24
  methods?: string[];
25
- /** Gera a chave de cache a partir da requisição. Padrão: `METHOD:url`. */
25
+ /** Builds the cache key from the request. Default: `METHOD:url`. */
26
26
  keyGenerator?: (req: Request) => string;
27
27
  }
28
28
  /**
29
- * Cria um middleware Express que cacheia respostas JSON. Adiciona o cabeçalho
30
- * `X-Cache: HIT|MISS` para facilitar a observabilidade.
29
+ * Creates an Express middleware that caches JSON responses. Adds an
30
+ * `X-Cache: HIT|MISS` header for observability.
31
31
  */
32
32
  declare function cacheMiddleware(options: CacheMiddlewareOptions): (req: Request, res: Response, next: NextFunction) => void;
33
33
 
package/dist/express.d.ts CHANGED
@@ -2,32 +2,32 @@ import { Request, Response, NextFunction } from 'express';
2
2
  import { Cache } from './index.js';
3
3
 
4
4
  /**
5
- * Integração com Express: cache de respostas JSON.
5
+ * Express integration: caching of JSON responses.
6
6
  *
7
- * Uso:
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
- * O middleware intercepta `res.json`: num HIT responde direto do cache; num
15
- * MISS deixa o handler rodar e guarda o corpo da resposta (apenas em 2xx).
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
- /** Instância de cache a usar. */
19
+ /** Cache instance to use. */
20
20
  cache: Cache;
21
- /** TTL aplicado às respostas guardadas (em segundos). */
21
+ /** TTL applied to stored responses (in seconds). */
22
22
  ttlSeconds?: number;
23
- /** Métodos HTTP elegíveis para cache. Padrão: `["GET"]`. */
23
+ /** HTTP methods eligible for caching. Default: `["GET"]`. */
24
24
  methods?: string[];
25
- /** Gera a chave de cache a partir da requisição. Padrão: `METHOD:url`. */
25
+ /** Builds the cache key from the request. Default: `METHOD:url`. */
26
26
  keyGenerator?: (req: Request) => string;
27
27
  }
28
28
  /**
29
- * Cria um middleware Express que cacheia respostas JSON. Adiciona o cabeçalho
30
- * `X-Cache: HIT|MISS` para facilitar a observabilidade.
29
+ * Creates an Express middleware that caches JSON responses. Adds an
30
+ * `X-Cache: HIT|MISS` header for observability.
31
31
  */
32
32
  declare function cacheMiddleware(options: CacheMiddlewareOptions): (req: Request, res: Response, next: NextFunction) => void;
33
33
 
@@ -1 +1 @@
1
- {"version":3,"sources":["../js/express.ts"],"sourcesContent":["/**\n * Integração com Express: cache de respostas JSON.\n *\n * Uso:\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 * O middleware intercepta `res.json`: num HIT responde direto do cache; num\n * MISS deixa o handler rodar e guarda o corpo da resposta (apenas em 2xx).\n */\n\nimport type { Request, Response, NextFunction } from \"express\";\nimport type { Cache } from \"./index\";\n\nexport interface CacheMiddlewareOptions {\n /** Instância de cache a usar. */\n cache: Cache;\n /** TTL aplicado às respostas guardadas (em segundos). */\n ttlSeconds?: number;\n /** Métodos HTTP elegíveis para cache. Padrão: `[\"GET\"]`. */\n methods?: string[];\n /** Gera a chave de cache a partir da requisição. Padrão: `METHOD:url`. */\n keyGenerator?: (req: Request) => string;\n}\n\n/**\n * Cria um middleware Express que cacheia respostas JSON. Adiciona o cabeçalho\n * `X-Cache: HIT|MISS` para facilitar a observabilidade.\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.get(key);\n if (cached !== null) {\n res.setHeader(\"X-Cache\", \"HIT\");\n res.json(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 // Só guardamos respostas bem-sucedidas (2xx).\n if (res.statusCode >= 200 && res.statusCode < 300) {\n cache.set(key, body, ttlSeconds ? { ttlSeconds } : undefined);\n }\n return originalJson(body);\n };\n\n next();\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAgCO,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,IAAI,GAAG;AAC5B,QAAI,WAAW,MAAM;AACnB,UAAI,UAAU,WAAW,KAAK;AAC9B,UAAI,KAAK,MAAM;AACf;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,IAAI,KAAK,MAAM,aAAa,EAAE,WAAW,IAAI,MAAS;AAAA,MAC9D;AACA,aAAO,aAAa,IAAI;AAAA,IAC1B;AAEA,SAAK;AAAA,EACP;AACF;","names":[]}
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 */\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.get(key);\n if (cached !== null) {\n res.setHeader(\"X-Cache\", \"HIT\");\n res.json(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 cache.set(key, body, ttlSeconds ? { ttlSeconds } : undefined);\n }\n return originalJson(body);\n };\n\n next();\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAgCO,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,IAAI,GAAG;AAC5B,QAAI,WAAW,MAAM;AACnB,UAAI,UAAU,WAAW,KAAK;AAC9B,UAAI,KAAK,MAAM;AACf;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,IAAI,KAAK,MAAM,aAAa,EAAE,WAAW,IAAI,MAAS;AAAA,MAC9D;AACA,aAAO,aAAa,IAAI;AAAA,IAC1B;AAEA,SAAK;AAAA,EACP;AACF;","names":[]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../js/express.ts"],"sourcesContent":["/**\n * Integração com Express: cache de respostas JSON.\n *\n * Uso:\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 * O middleware intercepta `res.json`: num HIT responde direto do cache; num\n * MISS deixa o handler rodar e guarda o corpo da resposta (apenas em 2xx).\n */\n\nimport type { Request, Response, NextFunction } from \"express\";\nimport type { Cache } from \"./index\";\n\nexport interface CacheMiddlewareOptions {\n /** Instância de cache a usar. */\n cache: Cache;\n /** TTL aplicado às respostas guardadas (em segundos). */\n ttlSeconds?: number;\n /** Métodos HTTP elegíveis para cache. Padrão: `[\"GET\"]`. */\n methods?: string[];\n /** Gera a chave de cache a partir da requisição. Padrão: `METHOD:url`. */\n keyGenerator?: (req: Request) => string;\n}\n\n/**\n * Cria um middleware Express que cacheia respostas JSON. Adiciona o cabeçalho\n * `X-Cache: HIT|MISS` para facilitar a observabilidade.\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.get(key);\n if (cached !== null) {\n res.setHeader(\"X-Cache\", \"HIT\");\n res.json(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 // Só guardamos respostas bem-sucedidas (2xx).\n if (res.statusCode >= 200 && res.statusCode < 300) {\n cache.set(key, body, ttlSeconds ? { ttlSeconds } : undefined);\n }\n return originalJson(body);\n };\n\n next();\n };\n}\n"],"mappings":";AAgCO,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,IAAI,GAAG;AAC5B,QAAI,WAAW,MAAM;AACnB,UAAI,UAAU,WAAW,KAAK;AAC9B,UAAI,KAAK,MAAM;AACf;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,IAAI,KAAK,MAAM,aAAa,EAAE,WAAW,IAAI,MAAS;AAAA,MAC9D;AACA,aAAO,aAAa,IAAI;AAAA,IAC1B;AAEA,SAAK;AAAA,EACP;AACF;","names":[]}
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 */\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.get(key);\n if (cached !== null) {\n res.setHeader(\"X-Cache\", \"HIT\");\n res.json(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 cache.set(key, body, ttlSeconds ? { ttlSeconds } : undefined);\n }\n return originalJson(body);\n };\n\n next();\n };\n}\n"],"mappings":";AAgCO,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,IAAI,GAAG;AAC5B,QAAI,WAAW,MAAM;AACnB,UAAI,UAAU,WAAW,KAAK;AAC9B,UAAI,KAAK,MAAM;AACf;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,IAAI,KAAK,MAAM,aAAa,EAAE,WAAW,IAAI,MAAS;AAAA,MAC9D;AACA,aAAO,aAAa,IAAI;AAAA,IAC1B;AAEA,SAAK;AAAA,EACP;AACF;","names":[]}
@@ -2,31 +2,31 @@ import { FastifyRequest, FastifyInstance } from 'fastify';
2
2
  import { Cache } from './index.mjs';
3
3
 
4
4
  /**
5
- * Integração com Fastify: cache de respostas JSON via plugin.
5
+ * Fastify integration: caching of JSON responses through a plugin.
6
6
  *
7
- * Uso:
7
+ * Usage:
8
8
  * import { Cache } from "rust-node-cache";
9
9
  * import { cachePlugin } from "rust-node-cache/fastify";
10
10
  *
11
11
  * const cache = new Cache();
12
12
  * fastify.register(cachePlugin, { cache, ttlSeconds: 60 });
13
13
  *
14
- * Fluxo: o hook `onRequest` checa o cache e, num HIT, responde imediatamente
15
- * (curto-circuito). Num MISS, o hook `onSend` guarda o payload serializado.
14
+ * Flow: the `onRequest` hook checks the cache and, on a HIT, responds right away
15
+ * (short-circuit). On a MISS, the `onSend` hook stores the serialized payload.
16
16
  */
17
17
 
18
18
  interface CachePluginOptions {
19
- /** Instância de cache a usar. */
19
+ /** Cache instance to use. */
20
20
  cache: Cache;
21
- /** TTL aplicado às respostas guardadas (em segundos). */
21
+ /** TTL applied to stored responses (in seconds). */
22
22
  ttlSeconds?: number;
23
- /** Métodos HTTP elegíveis para cache. Padrão: `["GET"]`. */
23
+ /** HTTP methods eligible for caching. Default: `["GET"]`. */
24
24
  methods?: string[];
25
- /** Gera a chave de cache a partir da requisição. Padrão: `METHOD:url`. */
25
+ /** Builds the cache key from the request. Default: `METHOD:url`. */
26
26
  keyGenerator?: (req: FastifyRequest) => string;
27
27
  }
28
28
  /**
29
- * Plugin Fastify que cacheia respostas JSON. Use com `fastify.register`.
29
+ * Fastify plugin that caches JSON responses. Use it with `fastify.register`.
30
30
  */
31
31
  declare function cachePlugin(fastify: FastifyInstance, options: CachePluginOptions): Promise<void>;
32
32
 
package/dist/fastify.d.ts CHANGED
@@ -2,31 +2,31 @@ import { FastifyRequest, FastifyInstance } from 'fastify';
2
2
  import { Cache } from './index.js';
3
3
 
4
4
  /**
5
- * Integração com Fastify: cache de respostas JSON via plugin.
5
+ * Fastify integration: caching of JSON responses through a plugin.
6
6
  *
7
- * Uso:
7
+ * Usage:
8
8
  * import { Cache } from "rust-node-cache";
9
9
  * import { cachePlugin } from "rust-node-cache/fastify";
10
10
  *
11
11
  * const cache = new Cache();
12
12
  * fastify.register(cachePlugin, { cache, ttlSeconds: 60 });
13
13
  *
14
- * Fluxo: o hook `onRequest` checa o cache e, num HIT, responde imediatamente
15
- * (curto-circuito). Num MISS, o hook `onSend` guarda o payload serializado.
14
+ * Flow: the `onRequest` hook checks the cache and, on a HIT, responds right away
15
+ * (short-circuit). On a MISS, the `onSend` hook stores the serialized payload.
16
16
  */
17
17
 
18
18
  interface CachePluginOptions {
19
- /** Instância de cache a usar. */
19
+ /** Cache instance to use. */
20
20
  cache: Cache;
21
- /** TTL aplicado às respostas guardadas (em segundos). */
21
+ /** TTL applied to stored responses (in seconds). */
22
22
  ttlSeconds?: number;
23
- /** Métodos HTTP elegíveis para cache. Padrão: `["GET"]`. */
23
+ /** HTTP methods eligible for caching. Default: `["GET"]`. */
24
24
  methods?: string[];
25
- /** Gera a chave de cache a partir da requisição. Padrão: `METHOD:url`. */
25
+ /** Builds the cache key from the request. Default: `METHOD:url`. */
26
26
  keyGenerator?: (req: FastifyRequest) => string;
27
27
  }
28
28
  /**
29
- * Plugin Fastify que cacheia respostas JSON. Use com `fastify.register`.
29
+ * Fastify plugin that caches JSON responses. Use it with `fastify.register`.
30
30
  */
31
31
  declare function cachePlugin(fastify: FastifyInstance, options: CachePluginOptions): Promise<void>;
32
32
 
@@ -1 +1 @@
1
- {"version":3,"sources":["../js/fastify.ts"],"sourcesContent":["/**\n * Integração com Fastify: cache de respostas JSON via plugin.\n *\n * Uso:\n * import { Cache } from \"rust-node-cache\";\n * import { cachePlugin } from \"rust-node-cache/fastify\";\n *\n * const cache = new Cache();\n * fastify.register(cachePlugin, { cache, ttlSeconds: 60 });\n *\n * Fluxo: o hook `onRequest` checa o cache e, num HIT, responde imediatamente\n * (curto-circuito). Num MISS, o hook `onSend` guarda o payload serializado.\n */\n\nimport type {\n FastifyInstance,\n FastifyReply,\n FastifyRequest,\n} from \"fastify\";\nimport type { Cache } from \"./index\";\n\nexport interface CachePluginOptions {\n /** Instância de cache a usar. */\n cache: Cache;\n /** TTL aplicado às respostas guardadas (em segundos). */\n ttlSeconds?: number;\n /** Métodos HTTP elegíveis para cache. Padrão: `[\"GET\"]`. */\n methods?: string[];\n /** Gera a chave de cache a partir da requisição. Padrão: `METHOD:url`. */\n keyGenerator?: (req: FastifyRequest) => string;\n}\n\n// Guardamos a chave calculada na própria request entre os hooks.\nconst CACHE_KEY = Symbol(\"rustNodeCacheKey\");\n\n/**\n * Plugin Fastify que cacheia respostas JSON. Use com `fastify.register`.\n */\nexport async function cachePlugin(\n fastify: FastifyInstance,\n options: CachePluginOptions,\n): Promise<void> {\n const { cache } = options;\n const ttlSeconds = options.ttlSeconds;\n const methods = options.methods ?? [\"GET\"];\n const keyGenerator =\n options.keyGenerator ?? ((req: FastifyRequest) => `${req.method}:${req.url}`);\n\n fastify.addHook(\n \"onRequest\",\n (request: FastifyRequest, reply: FastifyReply, done: () => void) => {\n if (!methods.includes(request.method)) {\n done();\n return;\n }\n\n const key = keyGenerator(request);\n const cached = cache.get(key);\n if (cached !== null) {\n reply.header(\"X-Cache\", \"HIT\");\n reply.send(cached);\n return; // não chama done(): a resposta já foi enviada.\n }\n\n reply.header(\"X-Cache\", \"MISS\");\n (request as unknown as Record<symbol, string>)[CACHE_KEY] = key;\n done();\n },\n );\n\n fastify.addHook(\n \"onSend\",\n (\n request: FastifyRequest,\n reply: FastifyReply,\n payload: unknown,\n done: (err: Error | null, value?: unknown) => void,\n ) => {\n const key = (request as unknown as Record<symbol, string | undefined>)[\n CACHE_KEY\n ];\n if (\n key &&\n typeof payload === \"string\" &&\n reply.statusCode >= 200 &&\n reply.statusCode < 300\n ) {\n try {\n cache.set(\n key,\n JSON.parse(payload),\n ttlSeconds ? { ttlSeconds } : undefined,\n );\n } catch {\n // payload não-JSON: simplesmente não cacheia.\n }\n }\n done(null, payload);\n },\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAiCA,IAAM,YAAY,uBAAO,kBAAkB;AAK3C,eAAsB,YACpB,SACA,SACe;AACf,QAAM,EAAE,MAAM,IAAI;AAClB,QAAM,aAAa,QAAQ;AAC3B,QAAM,UAAU,QAAQ,WAAW,CAAC,KAAK;AACzC,QAAM,eACJ,QAAQ,iBAAiB,CAAC,QAAwB,GAAG,IAAI,MAAM,IAAI,IAAI,GAAG;AAE5E,UAAQ;AAAA,IACN;AAAA,IACA,CAAC,SAAyB,OAAqB,SAAqB;AAClE,UAAI,CAAC,QAAQ,SAAS,QAAQ,MAAM,GAAG;AACrC,aAAK;AACL;AAAA,MACF;AAEA,YAAM,MAAM,aAAa,OAAO;AAChC,YAAM,SAAS,MAAM,IAAI,GAAG;AAC5B,UAAI,WAAW,MAAM;AACnB,cAAM,OAAO,WAAW,KAAK;AAC7B,cAAM,KAAK,MAAM;AACjB;AAAA,MACF;AAEA,YAAM,OAAO,WAAW,MAAM;AAC9B,MAAC,QAA8C,SAAS,IAAI;AAC5D,WAAK;AAAA,IACP;AAAA,EACF;AAEA,UAAQ;AAAA,IACN;AAAA,IACA,CACE,SACA,OACA,SACA,SACG;AACH,YAAM,MAAO,QACX,SACF;AACA,UACE,OACA,OAAO,YAAY,YACnB,MAAM,cAAc,OACpB,MAAM,aAAa,KACnB;AACA,YAAI;AACF,gBAAM;AAAA,YACJ;AAAA,YACA,KAAK,MAAM,OAAO;AAAA,YAClB,aAAa,EAAE,WAAW,IAAI;AAAA,UAChC;AAAA,QACF,QAAQ;AAAA,QAER;AAAA,MACF;AACA,WAAK,MAAM,OAAO;AAAA,IACpB;AAAA,EACF;AACF;","names":[]}
1
+ {"version":3,"sources":["../js/fastify.ts"],"sourcesContent":["/**\n * Fastify integration: caching of JSON responses through a plugin.\n *\n * Usage:\n * import { Cache } from \"rust-node-cache\";\n * import { cachePlugin } from \"rust-node-cache/fastify\";\n *\n * const cache = new Cache();\n * fastify.register(cachePlugin, { cache, ttlSeconds: 60 });\n *\n * Flow: the `onRequest` hook checks the cache and, on a HIT, responds right away\n * (short-circuit). On a MISS, the `onSend` hook stores the serialized payload.\n */\n\nimport type {\n FastifyInstance,\n FastifyReply,\n FastifyRequest,\n} from \"fastify\";\nimport type { Cache } from \"./index\";\n\nexport interface CachePluginOptions {\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: FastifyRequest) => string;\n}\n\n// The computed key is kept on the request itself between hooks.\nconst CACHE_KEY = Symbol(\"rustNodeCacheKey\");\n\n/**\n * Fastify plugin that caches JSON responses. Use it with `fastify.register`.\n */\nexport async function cachePlugin(\n fastify: FastifyInstance,\n options: CachePluginOptions,\n): Promise<void> {\n const { cache } = options;\n const ttlSeconds = options.ttlSeconds;\n const methods = options.methods ?? [\"GET\"];\n const keyGenerator =\n options.keyGenerator ?? ((req: FastifyRequest) => `${req.method}:${req.url}`);\n\n fastify.addHook(\n \"onRequest\",\n (request: FastifyRequest, reply: FastifyReply, done: () => void) => {\n if (!methods.includes(request.method)) {\n done();\n return;\n }\n\n const key = keyGenerator(request);\n const cached = cache.get(key);\n if (cached !== null) {\n reply.header(\"X-Cache\", \"HIT\");\n reply.send(cached);\n return; // done() is not called: the response was already sent.\n }\n\n reply.header(\"X-Cache\", \"MISS\");\n (request as unknown as Record<symbol, string>)[CACHE_KEY] = key;\n done();\n },\n );\n\n fastify.addHook(\n \"onSend\",\n (\n request: FastifyRequest,\n reply: FastifyReply,\n payload: unknown,\n done: (err: Error | null, value?: unknown) => void,\n ) => {\n const key = (request as unknown as Record<symbol, string | undefined>)[\n CACHE_KEY\n ];\n if (\n key &&\n typeof payload === \"string\" &&\n reply.statusCode >= 200 &&\n reply.statusCode < 300\n ) {\n try {\n cache.set(\n key,\n JSON.parse(payload),\n ttlSeconds ? { ttlSeconds } : undefined,\n );\n } catch {\n // non-JSON payload: simply not cached.\n }\n }\n done(null, payload);\n },\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAiCA,IAAM,YAAY,uBAAO,kBAAkB;AAK3C,eAAsB,YACpB,SACA,SACe;AACf,QAAM,EAAE,MAAM,IAAI;AAClB,QAAM,aAAa,QAAQ;AAC3B,QAAM,UAAU,QAAQ,WAAW,CAAC,KAAK;AACzC,QAAM,eACJ,QAAQ,iBAAiB,CAAC,QAAwB,GAAG,IAAI,MAAM,IAAI,IAAI,GAAG;AAE5E,UAAQ;AAAA,IACN;AAAA,IACA,CAAC,SAAyB,OAAqB,SAAqB;AAClE,UAAI,CAAC,QAAQ,SAAS,QAAQ,MAAM,GAAG;AACrC,aAAK;AACL;AAAA,MACF;AAEA,YAAM,MAAM,aAAa,OAAO;AAChC,YAAM,SAAS,MAAM,IAAI,GAAG;AAC5B,UAAI,WAAW,MAAM;AACnB,cAAM,OAAO,WAAW,KAAK;AAC7B,cAAM,KAAK,MAAM;AACjB;AAAA,MACF;AAEA,YAAM,OAAO,WAAW,MAAM;AAC9B,MAAC,QAA8C,SAAS,IAAI;AAC5D,WAAK;AAAA,IACP;AAAA,EACF;AAEA,UAAQ;AAAA,IACN;AAAA,IACA,CACE,SACA,OACA,SACA,SACG;AACH,YAAM,MAAO,QACX,SACF;AACA,UACE,OACA,OAAO,YAAY,YACnB,MAAM,cAAc,OACpB,MAAM,aAAa,KACnB;AACA,YAAI;AACF,gBAAM;AAAA,YACJ;AAAA,YACA,KAAK,MAAM,OAAO;AAAA,YAClB,aAAa,EAAE,WAAW,IAAI;AAAA,UAChC;AAAA,QACF,QAAQ;AAAA,QAER;AAAA,MACF;AACA,WAAK,MAAM,OAAO;AAAA,IACpB;AAAA,EACF;AACF;","names":[]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../js/fastify.ts"],"sourcesContent":["/**\n * Integração com Fastify: cache de respostas JSON via plugin.\n *\n * Uso:\n * import { Cache } from \"rust-node-cache\";\n * import { cachePlugin } from \"rust-node-cache/fastify\";\n *\n * const cache = new Cache();\n * fastify.register(cachePlugin, { cache, ttlSeconds: 60 });\n *\n * Fluxo: o hook `onRequest` checa o cache e, num HIT, responde imediatamente\n * (curto-circuito). Num MISS, o hook `onSend` guarda o payload serializado.\n */\n\nimport type {\n FastifyInstance,\n FastifyReply,\n FastifyRequest,\n} from \"fastify\";\nimport type { Cache } from \"./index\";\n\nexport interface CachePluginOptions {\n /** Instância de cache a usar. */\n cache: Cache;\n /** TTL aplicado às respostas guardadas (em segundos). */\n ttlSeconds?: number;\n /** Métodos HTTP elegíveis para cache. Padrão: `[\"GET\"]`. */\n methods?: string[];\n /** Gera a chave de cache a partir da requisição. Padrão: `METHOD:url`. */\n keyGenerator?: (req: FastifyRequest) => string;\n}\n\n// Guardamos a chave calculada na própria request entre os hooks.\nconst CACHE_KEY = Symbol(\"rustNodeCacheKey\");\n\n/**\n * Plugin Fastify que cacheia respostas JSON. Use com `fastify.register`.\n */\nexport async function cachePlugin(\n fastify: FastifyInstance,\n options: CachePluginOptions,\n): Promise<void> {\n const { cache } = options;\n const ttlSeconds = options.ttlSeconds;\n const methods = options.methods ?? [\"GET\"];\n const keyGenerator =\n options.keyGenerator ?? ((req: FastifyRequest) => `${req.method}:${req.url}`);\n\n fastify.addHook(\n \"onRequest\",\n (request: FastifyRequest, reply: FastifyReply, done: () => void) => {\n if (!methods.includes(request.method)) {\n done();\n return;\n }\n\n const key = keyGenerator(request);\n const cached = cache.get(key);\n if (cached !== null) {\n reply.header(\"X-Cache\", \"HIT\");\n reply.send(cached);\n return; // não chama done(): a resposta já foi enviada.\n }\n\n reply.header(\"X-Cache\", \"MISS\");\n (request as unknown as Record<symbol, string>)[CACHE_KEY] = key;\n done();\n },\n );\n\n fastify.addHook(\n \"onSend\",\n (\n request: FastifyRequest,\n reply: FastifyReply,\n payload: unknown,\n done: (err: Error | null, value?: unknown) => void,\n ) => {\n const key = (request as unknown as Record<symbol, string | undefined>)[\n CACHE_KEY\n ];\n if (\n key &&\n typeof payload === \"string\" &&\n reply.statusCode >= 200 &&\n reply.statusCode < 300\n ) {\n try {\n cache.set(\n key,\n JSON.parse(payload),\n ttlSeconds ? { ttlSeconds } : undefined,\n );\n } catch {\n // payload não-JSON: simplesmente não cacheia.\n }\n }\n done(null, payload);\n },\n );\n}\n"],"mappings":";AAiCA,IAAM,YAAY,uBAAO,kBAAkB;AAK3C,eAAsB,YACpB,SACA,SACe;AACf,QAAM,EAAE,MAAM,IAAI;AAClB,QAAM,aAAa,QAAQ;AAC3B,QAAM,UAAU,QAAQ,WAAW,CAAC,KAAK;AACzC,QAAM,eACJ,QAAQ,iBAAiB,CAAC,QAAwB,GAAG,IAAI,MAAM,IAAI,IAAI,GAAG;AAE5E,UAAQ;AAAA,IACN;AAAA,IACA,CAAC,SAAyB,OAAqB,SAAqB;AAClE,UAAI,CAAC,QAAQ,SAAS,QAAQ,MAAM,GAAG;AACrC,aAAK;AACL;AAAA,MACF;AAEA,YAAM,MAAM,aAAa,OAAO;AAChC,YAAM,SAAS,MAAM,IAAI,GAAG;AAC5B,UAAI,WAAW,MAAM;AACnB,cAAM,OAAO,WAAW,KAAK;AAC7B,cAAM,KAAK,MAAM;AACjB;AAAA,MACF;AAEA,YAAM,OAAO,WAAW,MAAM;AAC9B,MAAC,QAA8C,SAAS,IAAI;AAC5D,WAAK;AAAA,IACP;AAAA,EACF;AAEA,UAAQ;AAAA,IACN;AAAA,IACA,CACE,SACA,OACA,SACA,SACG;AACH,YAAM,MAAO,QACX,SACF;AACA,UACE,OACA,OAAO,YAAY,YACnB,MAAM,cAAc,OACpB,MAAM,aAAa,KACnB;AACA,YAAI;AACF,gBAAM;AAAA,YACJ;AAAA,YACA,KAAK,MAAM,OAAO;AAAA,YAClB,aAAa,EAAE,WAAW,IAAI;AAAA,UAChC;AAAA,QACF,QAAQ;AAAA,QAER;AAAA,MACF;AACA,WAAK,MAAM,OAAO;AAAA,IACpB;AAAA,EACF;AACF;","names":[]}
1
+ {"version":3,"sources":["../js/fastify.ts"],"sourcesContent":["/**\n * Fastify integration: caching of JSON responses through a plugin.\n *\n * Usage:\n * import { Cache } from \"rust-node-cache\";\n * import { cachePlugin } from \"rust-node-cache/fastify\";\n *\n * const cache = new Cache();\n * fastify.register(cachePlugin, { cache, ttlSeconds: 60 });\n *\n * Flow: the `onRequest` hook checks the cache and, on a HIT, responds right away\n * (short-circuit). On a MISS, the `onSend` hook stores the serialized payload.\n */\n\nimport type {\n FastifyInstance,\n FastifyReply,\n FastifyRequest,\n} from \"fastify\";\nimport type { Cache } from \"./index\";\n\nexport interface CachePluginOptions {\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: FastifyRequest) => string;\n}\n\n// The computed key is kept on the request itself between hooks.\nconst CACHE_KEY = Symbol(\"rustNodeCacheKey\");\n\n/**\n * Fastify plugin that caches JSON responses. Use it with `fastify.register`.\n */\nexport async function cachePlugin(\n fastify: FastifyInstance,\n options: CachePluginOptions,\n): Promise<void> {\n const { cache } = options;\n const ttlSeconds = options.ttlSeconds;\n const methods = options.methods ?? [\"GET\"];\n const keyGenerator =\n options.keyGenerator ?? ((req: FastifyRequest) => `${req.method}:${req.url}`);\n\n fastify.addHook(\n \"onRequest\",\n (request: FastifyRequest, reply: FastifyReply, done: () => void) => {\n if (!methods.includes(request.method)) {\n done();\n return;\n }\n\n const key = keyGenerator(request);\n const cached = cache.get(key);\n if (cached !== null) {\n reply.header(\"X-Cache\", \"HIT\");\n reply.send(cached);\n return; // done() is not called: the response was already sent.\n }\n\n reply.header(\"X-Cache\", \"MISS\");\n (request as unknown as Record<symbol, string>)[CACHE_KEY] = key;\n done();\n },\n );\n\n fastify.addHook(\n \"onSend\",\n (\n request: FastifyRequest,\n reply: FastifyReply,\n payload: unknown,\n done: (err: Error | null, value?: unknown) => void,\n ) => {\n const key = (request as unknown as Record<symbol, string | undefined>)[\n CACHE_KEY\n ];\n if (\n key &&\n typeof payload === \"string\" &&\n reply.statusCode >= 200 &&\n reply.statusCode < 300\n ) {\n try {\n cache.set(\n key,\n JSON.parse(payload),\n ttlSeconds ? { ttlSeconds } : undefined,\n );\n } catch {\n // non-JSON payload: simply not cached.\n }\n }\n done(null, payload);\n },\n );\n}\n"],"mappings":";AAiCA,IAAM,YAAY,uBAAO,kBAAkB;AAK3C,eAAsB,YACpB,SACA,SACe;AACf,QAAM,EAAE,MAAM,IAAI;AAClB,QAAM,aAAa,QAAQ;AAC3B,QAAM,UAAU,QAAQ,WAAW,CAAC,KAAK;AACzC,QAAM,eACJ,QAAQ,iBAAiB,CAAC,QAAwB,GAAG,IAAI,MAAM,IAAI,IAAI,GAAG;AAE5E,UAAQ;AAAA,IACN;AAAA,IACA,CAAC,SAAyB,OAAqB,SAAqB;AAClE,UAAI,CAAC,QAAQ,SAAS,QAAQ,MAAM,GAAG;AACrC,aAAK;AACL;AAAA,MACF;AAEA,YAAM,MAAM,aAAa,OAAO;AAChC,YAAM,SAAS,MAAM,IAAI,GAAG;AAC5B,UAAI,WAAW,MAAM;AACnB,cAAM,OAAO,WAAW,KAAK;AAC7B,cAAM,KAAK,MAAM;AACjB;AAAA,MACF;AAEA,YAAM,OAAO,WAAW,MAAM;AAC9B,MAAC,QAA8C,SAAS,IAAI;AAC5D,WAAK;AAAA,IACP;AAAA,EACF;AAEA,UAAQ;AAAA,IACN;AAAA,IACA,CACE,SACA,OACA,SACA,SACG;AACH,YAAM,MAAO,QACX,SACF;AACA,UACE,OACA,OAAO,YAAY,YACnB,MAAM,cAAc,OACpB,MAAM,aAAa,KACnB;AACA,YAAI;AACF,gBAAM;AAAA,YACJ;AAAA,YACA,KAAK,MAAM,OAAO;AAAA,YAClB,aAAa,EAAE,WAAW,IAAI;AAAA,UAChC;AAAA,QACF,QAAQ;AAAA,QAER;AAAA,MACF;AACA,WAAK,MAAM,OAAO;AAAA,IACpB;AAAA,EACF;AACF;","names":[]}