express-memorize 1.4.0 → 1.5.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 (57) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +205 -82
  3. package/dist/MemorizeStore.d.ts +31 -8
  4. package/dist/MemorizeStore.d.ts.map +1 -1
  5. package/dist/MemorizeStore.js +98 -14
  6. package/dist/MemorizeStore.js.map +1 -1
  7. package/dist/adapters/express.d.ts +25 -0
  8. package/dist/adapters/express.d.ts.map +1 -0
  9. package/dist/adapters/express.js +62 -0
  10. package/dist/adapters/express.js.map +1 -0
  11. package/dist/adapters/fetch.d.ts +45 -0
  12. package/dist/adapters/fetch.d.ts.map +1 -0
  13. package/dist/adapters/fetch.js +72 -0
  14. package/dist/adapters/fetch.js.map +1 -0
  15. package/dist/adapters/hono.d.ts +34 -0
  16. package/dist/adapters/hono.d.ts.map +1 -0
  17. package/dist/adapters/hono.js +63 -0
  18. package/dist/adapters/hono.js.map +1 -0
  19. package/dist/adapters/nestjs.d.ts +84 -0
  20. package/dist/adapters/nestjs.d.ts.map +1 -0
  21. package/dist/adapters/nestjs.js +194 -0
  22. package/dist/adapters/nestjs.js.map +1 -0
  23. package/dist/domain/CacheEntry.d.ts +3 -1
  24. package/dist/domain/CacheEntry.d.ts.map +1 -1
  25. package/dist/domain/CacheInfo.d.ts +1 -1
  26. package/dist/domain/CacheInfo.d.ts.map +1 -1
  27. package/dist/domain/Memorize.d.ts +99 -4
  28. package/dist/domain/Memorize.d.ts.map +1 -1
  29. package/dist/domain/MemorizeCallOptions.d.ts +2 -1
  30. package/dist/domain/MemorizeCallOptions.d.ts.map +1 -1
  31. package/dist/domain/MemorizeEvent.d.ts +2 -1
  32. package/dist/domain/MemorizeEvent.d.ts.map +1 -1
  33. package/dist/domain/MemorizeEventType.d.ts +3 -1
  34. package/dist/domain/MemorizeEventType.d.ts.map +1 -1
  35. package/dist/domain/MemorizeEventType.js +2 -0
  36. package/dist/domain/MemorizeEventType.js.map +1 -1
  37. package/dist/domain/MemorizeEvictEvent.d.ts +18 -0
  38. package/dist/domain/MemorizeEvictEvent.d.ts.map +1 -0
  39. package/dist/domain/MemorizeEvictEvent.js +3 -0
  40. package/dist/domain/MemorizeEvictEvent.js.map +1 -0
  41. package/dist/domain/MemorizeOptions.d.ts +14 -1
  42. package/dist/domain/MemorizeOptions.d.ts.map +1 -1
  43. package/dist/domain/MemorizeSetEvent.d.ts +3 -1
  44. package/dist/domain/MemorizeSetEvent.d.ts.map +1 -1
  45. package/dist/domain/MemorizeStats.d.ts +12 -0
  46. package/dist/domain/MemorizeStats.d.ts.map +1 -0
  47. package/dist/domain/MemorizeStats.js +3 -0
  48. package/dist/domain/MemorizeStats.js.map +1 -0
  49. package/dist/domain/index.d.ts +2 -0
  50. package/dist/domain/index.d.ts.map +1 -1
  51. package/dist/index.d.ts +1 -1
  52. package/dist/index.d.ts.map +1 -1
  53. package/dist/memorize.d.ts +19 -16
  54. package/dist/memorize.d.ts.map +1 -1
  55. package/dist/memorize.js +51 -50
  56. package/dist/memorize.js.map +1 -1
  57. package/package.json +50 -12
package/CHANGELOG.md CHANGED
@@ -1,3 +1,16 @@
1
+ # [1.5.0](https://github.com/ElJijuna/express-memorize/compare/v1.4.0...v1.5.0) (2026-05-11)
2
+
3
+
4
+ ### Features
5
+
6
+ * add framework-agnostic cache API and Express adapter architecture ([78b605d](https://github.com/ElJijuna/express-memorize/commit/78b605d1dc33a361f31339c661f5a527bda47a02)), closes [#6](https://github.com/ElJijuna/express-memorize/issues/6)
7
+ * add generic Fetch API cache adapter ([b36bf48](https://github.com/ElJijuna/express-memorize/commit/b36bf489c5a92cef05a2551df37b3d014d3588db)), closes [#12](https://github.com/ElJijuna/express-memorize/issues/12)
8
+ * add Hono adapter for edge/serverless runtimes ([eb2f967](https://github.com/ElJijuna/express-memorize/commit/eb2f9675cb8f668bad76a88aabb19b622d83fac3)), closes [#13](https://github.com/ElJijuna/express-memorize/issues/13)
9
+ * add Infinity TTL support and finite default TTL ([e353785](https://github.com/ElJijuna/express-memorize/commit/e353785966e3ae425610cba200df6f89691beaa0))
10
+ * add maxEntries limit, LRU eviction, and cache size metrics ([14e4c5a](https://github.com/ElJijuna/express-memorize/commit/14e4c5a670d9bd2dcb253130b89f994988dfe466)), closes [#14](https://github.com/ElJijuna/express-memorize/issues/14)
11
+ * configure package exports for tree-shaking and adapter subpaths ([14d4dcc](https://github.com/ElJijuna/express-memorize/commit/14d4dccd145d02e3966a8f977f0cf719c7123e81)), closes [#10](https://github.com/ElJijuna/express-memorize/issues/10)
12
+ * **nestjs:** add cache interceptor adapter (closes [#7](https://github.com/ElJijuna/express-memorize/issues/7)) ([f1b9707](https://github.com/ElJijuna/express-memorize/commit/f1b970792123062dc32d464d2ccb1ccea7a3d7a1))
13
+
1
14
  # Changelog
2
15
 
3
16
  All notable changes to this project will be documented in this file.
package/README.md CHANGED
@@ -9,7 +9,7 @@
9
9
  </p>
10
10
 
11
11
  <p align="center">
12
- In-memory cache middleware for <strong>Express.js</strong>.<br/>
12
+ In-memory HTTP cache for <strong>Express, NestJS, Hono, Fetch API</strong>, and more.<br/>
13
13
  Caches <code>GET</code> responses with optional TTL — zero dependencies, fully typed.
14
14
  </p>
15
15
 
@@ -18,13 +18,16 @@
18
18
  ## Features
19
19
 
20
20
  - Caches `GET` responses automatically when status code is `2xx`
21
- - Per-route TTL override
22
- - Event hooks: `set`, `delete`, `expire`
23
- - Cache inspection and invalidation API
21
+ - Works with **Express**, **NestJS**, **Hono**, **Fetch API / serverless**, and direct service-level usage
22
+ - Per-route TTL override and `noCache` bypass
23
+ - **`maxEntries` cap with LRU eviction** to bound memory usage
24
+ - **Size metrics**: `size()`, `byteSize()`, `getStats()`
25
+ - **Service-level cache**: `remember()`, `set()`, `getValue()`
26
+ - Event hooks: `set`, `delete`, `expire`, `evict`
27
+ - Cache inspection and invalidation API (`get`, `getAll`, `delete`, `deleteMatching`, `clear`)
24
28
  - Hit counter per cache entry
25
29
  - `X-Cache: HIT | MISS | BYPASS` response header
26
- - Zero runtime dependencies
27
- - Full TypeScript support
30
+ - Zero runtime dependencies, fully typed
28
31
 
29
32
  ## Installation
30
33
 
@@ -32,14 +35,23 @@
32
35
  npm install express-memorize
33
36
  ```
34
37
 
38
+ Adapters for non-Express runtimes are optional — install only what you need:
39
+
40
+ ```bash
41
+ npm install hono # only if using the Hono adapter
42
+ npm install @nestjs/common @nestjs/core rxjs # only if using the NestJS adapter
43
+ ```
44
+
35
45
  ## Quick Start
36
46
 
47
+ ### Express
48
+
37
49
  ```typescript
38
50
  import express from 'express';
39
51
  import { memorize } from 'express-memorize';
40
52
 
41
53
  const app = express();
42
- const cache = memorize({ ttl: 30_000 }); // 30 seconds global TTL
54
+ const cache = memorize({ ttl: 30_000 });
43
55
 
44
56
  app.get('/users', cache(), async (req, res) => {
45
57
  const users = await db.getUsers();
@@ -49,32 +61,92 @@ app.get('/users', cache(), async (req, res) => {
49
61
  app.listen(3000);
50
62
  ```
51
63
 
52
- The first request computes the response normally. Every subsequent `GET /users` is served from memory until the TTL expires.
64
+ ### Hono
53
65
 
54
- ## Usage
66
+ ```typescript
67
+ import { Hono } from 'hono';
68
+ import { memorize } from 'express-memorize';
69
+ import { createHonoMiddleware } from 'express-memorize/hono';
55
70
 
56
- ### Global middleware
71
+ const app = new Hono();
72
+ const cache = memorize({ ttl: 30_000 });
57
73
 
58
- Apply the cache to the entire application with `app.use()`. Every `GET` route is cached automatically — non-`GET` requests are bypassed without any extra configuration.
74
+ app.get('/users', createHonoMiddleware(cache), async (c) => {
75
+ return c.json(await usersService.findAll());
76
+ });
77
+ ```
78
+
79
+ ### NestJS
80
+
81
+ ```typescript
82
+ import { Module } from '@nestjs/common';
83
+ import { APP_INTERCEPTOR } from '@nestjs/core';
84
+ import {
85
+ MemorizeCacheKey,
86
+ MemorizeInterceptor,
87
+ MemorizeModule,
88
+ MemorizeTtl,
89
+ } from 'express-memorize/nestjs';
90
+
91
+ @Module({
92
+ imports: [MemorizeModule.forRoot({ ttl: 30_000 })],
93
+ providers: [
94
+ {
95
+ provide: APP_INTERCEPTOR,
96
+ useExisting: MemorizeInterceptor,
97
+ },
98
+ ],
99
+ })
100
+ export class AppModule {}
101
+
102
+ export class UsersController {
103
+ @MemorizeCacheKey('users:list')
104
+ @MemorizeTtl(10_000)
105
+ findAll() {
106
+ return usersService.findAll();
107
+ }
108
+ }
109
+ ```
110
+
111
+ ### Fetch API / Serverless
112
+
113
+ ```typescript
114
+ import { memorize } from 'express-memorize';
115
+ import { cacheFetchHandler } from 'express-memorize/fetch';
116
+
117
+ const cache = memorize({ ttl: 30_000 });
118
+
119
+ export default cacheFetchHandler(cache, async (request) => {
120
+ const users = await usersService.findAll();
121
+ return Response.json(users);
122
+ });
123
+ ```
124
+
125
+ ### Service-level caching
126
+
127
+ Cache arbitrary values directly — no HTTP layer required.
59
128
 
60
129
  ```typescript
61
130
  const cache = memorize({ ttl: 60_000 });
62
131
 
63
- app.use(cache()); // applies to all GET routes
132
+ // Compute-and-cache pattern
133
+ const users = await cache.remember('users:list', () => usersService.findAll());
64
134
 
65
- app.get('/users', (req, res) => { res.json({ data: users }) });
66
- app.get('/products', (req, res) => { res.json({ data: products }) });
67
- // POST, PUT, PATCH, DELETE routes are unaffected
135
+ // Explicit set/get
136
+ cache.set('config', appConfig);
137
+ const config = cache.getValue<AppConfig>('config');
68
138
  ```
69
139
 
70
- ### Per-route cache
140
+ ---
141
+
142
+ ## Usage
143
+
144
+ ### Global middleware (Express)
71
145
 
72
146
  ```typescript
73
147
  const cache = memorize({ ttl: 60_000 });
74
148
 
75
- app.get('/products', cache(), (req, res) => {
76
- res.json({ data: products });
77
- });
149
+ app.use(cache()); // applies to all GET routes
78
150
  ```
79
151
 
80
152
  ### Per-route TTL override
@@ -82,40 +154,70 @@ app.get('/products', cache(), (req, res) => {
82
154
  ```typescript
83
155
  const cache = memorize({ ttl: 60_000 }); // global: 60s
84
156
 
85
- app.get('/users', cache(), handler); // 60s
86
- app.get('/products', cache({ ttl: 10_000 }), handler); // override: 10s
87
- app.get('/config', cache({ ttl: 0 }), handler); // no expiry
157
+ app.get('/users', cache(), handler); // 60s
158
+ app.get('/products', cache({ ttl: 10_000 }), handler); // 10s
159
+ app.get('/config', cache({ ttl: Infinity }), handler); // no expiry
88
160
  ```
89
161
 
90
- ### Cache invalidation
162
+ ### noCache bypass
91
163
 
92
164
  ```typescript
93
- const cache = memorize({ ttl: 30_000 });
165
+ app.get('/live-feed', cache({ noCache: true }), handler);
166
+ // Sets X-Cache: BYPASS, never reads or writes the cache
167
+ ```
94
168
 
95
- app.get('/users', cache(), (req, res) => {
96
- res.json({ data: users });
97
- });
169
+ ### NestJS decorators
170
+
171
+ Use `MemorizeInterceptor` on a controller or globally, then configure caching at the controller or method level.
172
+
173
+ ```typescript
174
+ import { Controller, Get, UseInterceptors } from '@nestjs/common';
175
+ import {
176
+ MemorizeCacheKey,
177
+ MemorizeInterceptor,
178
+ MemorizeNoCache,
179
+ MemorizeTtl,
180
+ } from 'express-memorize/nestjs';
181
+
182
+ @Controller('users')
183
+ @UseInterceptors(MemorizeInterceptor)
184
+ @MemorizeTtl(30_000)
185
+ export class UsersController {
186
+ @Get()
187
+ @MemorizeCacheKey('users:list')
188
+ findAll() {
189
+ return usersService.findAll();
190
+ }
191
+
192
+ @Get('live')
193
+ @MemorizeNoCache()
194
+ live() {
195
+ return usersService.live();
196
+ }
197
+ }
198
+ ```
98
199
 
200
+ For global usage, import `MemorizeModule.forRoot()` and register `APP_INTERCEPTOR` with `useExisting: MemorizeInterceptor` so the interceptor receives the module's shared cache instance.
201
+
202
+ ### Cache invalidation
203
+
204
+ ```typescript
99
205
  app.post('/users', (req, res) => {
100
206
  users.push(req.body);
101
- cache.delete('/users'); // invalidate after mutation
102
- res.status(201).json({ data: req.body });
207
+ cache.delete('/users');
208
+ res.status(201).json(req.body);
103
209
  });
104
210
  ```
105
211
 
106
212
  ### Pattern-based invalidation
107
213
 
108
- Use `cache.deleteMatching(pattern)` to remove all cache entries whose keys match a glob pattern. This is useful when you don't know the exact key — for example, when a URL may have different query strings.
214
+ Use `cache.deleteMatching(pattern)` to remove entries by glob pattern.
109
215
 
110
216
  ```typescript
111
- // Cached keys: /api/users/abc123, /api/users/abc123?lang=es, /api/users/abc123?page=1
112
217
  app.put('/users/:id', (req, res) => {
113
218
  users.update(req.params.id, req.body);
114
-
115
- // Remove all cached variants of this user, regardless of query params
116
219
  const deleted = cache.deleteMatching(`**/users/${req.params.id}*`);
117
- console.log(`${deleted} cache entries removed`);
118
-
220
+ console.log(`${deleted} entries removed`);
119
221
  res.json({ ok: true });
120
222
  });
121
223
  ```
@@ -124,35 +226,33 @@ app.put('/users/:id', (req, res) => {
124
226
 
125
227
  | Pattern | Behaviour |
126
228
  |---------|-----------|
127
- | `*` | Matches any sequence of characters **within** a single path segment (does not cross `/`) |
128
- | `**` | Matches any sequence of characters **across** path segments (crosses `/`) |
229
+ | `*` | Matches any sequence within a single path segment (does not cross `/`) |
230
+ | `**` | Matches any sequence across path segments (crosses `/`) |
129
231
  | `?` | Matches any single character except `/` |
130
232
 
131
- `deleteMatching` returns the number of entries removed and emits a `delete` event for each one.
233
+ ### Bounding memory with `maxEntries`
132
234
 
133
- ### Event hooks
235
+ Prevent unbounded growth by setting a maximum number of entries. When the limit is reached, the **least-recently-used (LRU)** entry is evicted before the new one is stored.
134
236
 
135
237
  ```typescript
136
- const cache = memorize({ ttl: 30_000 });
238
+ const cache = memorize({ ttl: 30_000, maxEntries: 1_000 });
239
+ ```
137
240
 
138
- cache.on('set', (e) => {
139
- console.log(`[cache] stored ${e.key} — expires in ${e.expiresAt ? e.expiresAt - Date.now() : '∞'}ms`);
140
- });
241
+ ### Size metrics
141
242
 
142
- cache.on('delete', (e) => {
143
- console.log(`[cache] deleted ${e.key}`);
144
- });
145
-
146
- cache.on('expire', (e) => {
147
- console.log(`[cache] expired ${e.key}`);
148
- });
243
+ ```typescript
244
+ cache.size(); // number of active entries
245
+ cache.byteSize(); // approximate total body size in bytes
246
+ cache.getStats(); // { entries, maxEntries, byteSize }
149
247
  ```
150
248
 
249
+ > `byteSize()` is an estimate based on UTF-8 encoding for strings and `byteLength` for buffers. It may not reflect actual VM memory usage.
250
+
151
251
  ### Inspect the cache
152
252
 
153
253
  ```typescript
154
- cache.get('/users'); // CacheInfo | null — single entry
155
- cache.getAll(); // Record<string, CacheInfo> — all active entries
254
+ cache.get('/users'); // CacheInfo | null
255
+ cache.getAll(); // Record<string, CacheInfo>
156
256
  ```
157
257
 
158
258
  `CacheInfo` shape:
@@ -164,31 +264,28 @@ cache.getAll(); // Record<string, CacheInfo> — all active entries
164
264
  statusCode: number;
165
265
  contentType: string;
166
266
  expiresAt: number | null;
167
- remainingTtl: number | null; // ms until expiry, null if no TTL
168
- hits: number; // total times this key was requested
267
+ remainingTtl: number | null; // ms until expiry, null when ttl is Infinity
268
+ hits: number; // times this key was served from cache
269
+ size: number; // approximate body size in bytes
169
270
  }
170
271
  ```
171
272
 
172
- `hits` starts at `1` on the initial cache miss (when the entry is stored) and increments by `1` on every subsequent cache hit. It resets to `1` if the entry is evicted and re-cached.
273
+ `hits` starts at `1` on the initial cache miss and increments on every hit. It resets to `1` if the entry is evicted and re-cached.
173
274
 
174
- ```typescript
175
- // Example: monitoring hot keys
176
- const entries = cache.getAll();
177
- for (const [key, info] of Object.entries(entries)) {
178
- console.log(`${key} → ${info.hits} hits`);
179
- }
180
- // /users → 42 hits
181
- // /products → 7 hits
182
- ```
183
-
184
- ### Clear the cache
275
+ ### Event hooks
185
276
 
186
277
  ```typescript
187
- cache.delete('/users'); // remove one entry
188
- cache.deleteMatching('**/users/*'); // remove all /users/* entries
189
- cache.clear(); // remove all entries
278
+ import { MemorizeEventType } from 'express-memorize';
279
+
280
+ cache.on(MemorizeEventType.Set, (e) => console.log('stored', e.key));
281
+ cache.on(MemorizeEventType.Delete, (e) => console.log('deleted', e.key));
282
+ cache.on(MemorizeEventType.Expire, (e) => console.log('expired', e.key));
283
+ cache.on(MemorizeEventType.Evict, (e) => console.log('evicted', e.key)); // maxEntries LRU
284
+ cache.on(MemorizeEventType.Empty, () => console.log('cache is empty'));
190
285
  ```
191
286
 
287
+ ---
288
+
192
289
  ## API Reference
193
290
 
194
291
  ### `memorize(options?)`
@@ -197,33 +294,58 @@ Creates a cache instance. Returns a `Memorize` object.
197
294
 
198
295
  | Option | Type | Default | Description |
199
296
  |--------|------|---------|-------------|
200
- | `ttl` | `number` | `undefined` | Time-to-live in milliseconds. Omit for no expiry. |
297
+ | `ttl` | `number` | `60_000` | Time-to-live in milliseconds. Pass `Infinity` for no expiry. |
298
+ | `maxEntries` | `number` | `undefined` | Maximum number of entries. LRU eviction when reached. |
201
299
 
202
- ### `cache(options?)`
300
+ ### `cache(options?)` / `cache.express(options?)`
203
301
 
204
- Returns an Express `RequestHandler` middleware. Can override the global TTL.
302
+ Returns an Express `RequestHandler`. `cache()` is a backwards-compatible alias for `cache.express()`.
205
303
 
206
304
  | Option | Type | Default | Description |
207
305
  |--------|------|---------|-------------|
208
- | `ttl` | `number` | global `ttl` | TTL override for this specific route. |
306
+ | `ttl` | `number` | global `ttl` | TTL override for this route. Pass `Infinity` for no expiry. |
307
+ | `noCache` | `boolean` | `false` | Skip cache entirely. Sets `X-Cache: BYPASS`. |
308
+
309
+ ### Service-level cache methods
310
+
311
+ | Method | Signature | Description |
312
+ |--------|-----------|-------------|
313
+ | `remember` | `(key, factory, ttl?) => Promise<T>` | Return cached value or call factory and cache the result. |
314
+ | `set` | `(key, value, ttl?) => void` | Store an arbitrary value. |
315
+ | `getValue` | `(key) => T \| undefined` | Retrieve a value stored via `set` or `remember`. |
209
316
 
210
317
  ### Cache management
211
318
 
212
319
  | Method | Signature | Description |
213
320
  |--------|-----------|-------------|
214
- | `get` | `(key: string) => CacheInfo \| null` | Returns info for a cached key. |
215
- | `getAll` | `() => Record<string, CacheInfo>` | Returns all active cache entries. |
216
- | `delete` | `(key: string) => boolean` | Removes a single entry. Returns `false` if not found. |
217
- | `deleteMatching` | `(pattern: string) => number` | Removes all entries matching a glob pattern. Returns the count removed. |
321
+ | `get` | `(key) => CacheInfo \| null` | Returns info for a cached key. |
322
+ | `getAll` | `() => Record<string, CacheInfo>` | Returns all active entries. |
323
+ | `delete` | `(key) => boolean` | Removes a single entry. |
324
+ | `deleteMatching` | `(pattern) => number` | Removes entries matching a glob pattern. |
218
325
  | `clear` | `() => void` | Removes all entries. |
326
+ | `size` | `() => number` | Number of active entries. |
327
+ | `byteSize` | `() => number` | Approximate total body size in bytes. |
328
+ | `getStats` | `() => MemorizeStats` | Aggregate stats: `{ entries, maxEntries, byteSize }`. |
329
+
330
+ ### Adapters
331
+
332
+ | Import path | Export | Framework |
333
+ |-------------|--------|-----------|
334
+ | `express-memorize` | `memorize` | Core factory |
335
+ | `express-memorize/express` | `createExpressAdapter(cache, options?)` | Express |
336
+ | `express-memorize/nestjs` | `MemorizeModule`, `MemorizeInterceptor`, decorators | NestJS |
337
+ | `express-memorize/hono` | `createHonoMiddleware(cache, options?)` | Hono |
338
+ | `express-memorize/fetch` | `cacheFetchHandler(cache, handler, options?)` | Fetch API / Serverless |
219
339
 
220
340
  ### Events
221
341
 
222
342
  | Event | Payload | When |
223
343
  |-------|---------|------|
224
- | `set` | `{ type, key, body, statusCode, contentType, expiresAt }` | A response is stored |
225
- | `delete` | `{ type, key }` | `cache.delete()`, `cache.deleteMatching()`, or `cache.clear()` is called |
344
+ | `set` | `{ type, key, body, statusCode, contentType, expiresAt, size }` | A response is stored |
345
+ | `delete` | `{ type, key }` | Manual removal via `delete`, `deleteMatching`, or `clear` |
226
346
  | `expire` | `{ type, key }` | TTL timer fires or lazy expiry is detected |
347
+ | `evict` | `{ type, key }` | LRU eviction due to `maxEntries` limit |
348
+ | `empty` | `{ type }` | Last entry removed, cache is now empty |
227
349
 
228
350
  ## Response Headers
229
351
 
@@ -231,14 +353,15 @@ Returns an Express `RequestHandler` middleware. Can override the global TTL.
231
353
  |--------|-------|-------------|
232
354
  | `X-Cache` | `HIT` | Response served from cache |
233
355
  | `X-Cache` | `MISS` | Response computed and stored |
234
- | `X-Cache` | `BYPASS` | Cache skipped — `noCache: true` was set for this route |
356
+ | `X-Cache` | `BYPASS` | Cache skipped — `noCache: true` |
235
357
 
236
358
  ## Behavior
237
359
 
238
- - Only `GET` requests are cached. All other methods bypass the middleware entirely.
360
+ - Only `GET` requests are cached. All other methods bypass the cache entirely.
239
361
  - Only responses with a `2xx` status code are stored.
240
- - Each call to `cache()` returns an independent middleware handler, but all handlers created from the same `memorize()` instance **share the same store**.
362
+ - All middleware and adapter instances created from the same `memorize()` call **share the same store**.
241
363
  - Two separate `memorize()` calls produce **independent stores**.
364
+ - Byte size is an approximation — strings use UTF-8 encoding, objects use `JSON.stringify` length.
242
365
 
243
366
  ## License
244
367
 
@@ -1,23 +1,28 @@
1
1
  import { CacheEntry } from './domain/CacheEntry';
2
2
  import { CacheInfo } from './domain/CacheInfo';
3
+ import { MemorizeStats } from './domain/MemorizeStats';
3
4
  import { MemorizeEventType } from './domain/MemorizeEventType';
4
5
  import { MemorizeEvent } from './domain/MemorizeEvent';
5
6
  import { MemorizeSetEvent } from './domain/MemorizeSetEvent';
6
7
  import { MemorizeDeleteEvent } from './domain/MemorizeDeleteEvent';
7
8
  import { MemorizeExpireEvent } from './domain/MemorizeExpireEvent';
8
9
  import { MemorizeEmptyEvent } from './domain/MemorizeEmptyEvent';
9
- export type { CacheEntry, CacheInfo, MemorizeEvent, MemorizeSetEvent, MemorizeDeleteEvent, MemorizeExpireEvent, MemorizeEmptyEvent, };
10
+ import { MemorizeEvictEvent } from './domain/MemorizeEvictEvent';
11
+ export type { CacheEntry, CacheInfo, MemorizeStats, MemorizeEvent, MemorizeSetEvent, MemorizeDeleteEvent, MemorizeExpireEvent, MemorizeEmptyEvent, MemorizeEvictEvent, };
10
12
  export { MemorizeEventType };
11
13
  /**
12
- * Low-level in-memory key-value store with optional TTL and event emission.
14
+ * Low-level in-memory key-value store with optional TTL, LRU eviction, and event emission.
13
15
  *
14
16
  * You do not usually interact with this class directly — use the {@link memorize} factory
15
17
  * instead, which wraps this store in an Express middleware.
16
18
  */
17
19
  export declare class MemorizeStore {
20
+ private readonly _maxEntries?;
18
21
  private _store;
19
22
  private _timers;
23
+ private _totalByteSize;
20
24
  private _listeners;
25
+ constructor(_maxEntries?: number | undefined);
21
26
  /**
22
27
  * Registers an event listener.
23
28
  *
@@ -26,25 +31,28 @@ export declare class MemorizeStore {
26
31
  *
27
32
  * @example
28
33
  * ```ts
29
- * store.on(MemorizeEventType.Set, (e) => console.log('cached', e.key));
30
- * store.on(MemorizeEventType.Expire, (e) => console.log('expired', e.key));
34
+ * store.on(MemorizeEventType.Set, (e) => console.log('cached', e.key));
35
+ * store.on(MemorizeEventType.Evict, (e) => console.log('evicted', e.key));
31
36
  * ```
32
37
  */
33
38
  on(event: MemorizeEventType.Set, handler: (e: MemorizeSetEvent) => void): void;
34
39
  on(event: MemorizeEventType.Delete, handler: (e: MemorizeDeleteEvent) => void): void;
35
40
  on(event: MemorizeEventType.Expire, handler: (e: MemorizeExpireEvent) => void): void;
36
41
  on(event: MemorizeEventType.Empty, handler: (e: MemorizeEmptyEvent) => void): void;
42
+ on(event: MemorizeEventType.Evict, handler: (e: MemorizeEvictEvent) => void): void;
37
43
  /**
38
44
  * Stores an entry in the cache.
39
45
  *
40
46
  * If an entry already exists for the given key its TTL timer is reset and the
41
- * value is overwritten. Emits a {@link MemorizeEventType.Set} event.
47
+ * value is overwritten. If `maxEntries` is configured and the store is full,
48
+ * the least-recently-used entry is evicted first. Emits a {@link MemorizeEventType.Set} event.
42
49
  *
43
50
  * @param key - The cache key (typically `req.originalUrl`).
44
51
  * @param entry - The response data to store.
45
- * @param ttl - Time-to-live in milliseconds. Omit or pass `null` for no expiry.
52
+ * @param ttl - Time-to-live in milliseconds. Omit or pass `null` to use the default TTL.
53
+ * Pass `Infinity` for no expiry.
46
54
  */
47
- set(key: string, entry: Omit<CacheEntry, 'expiresAt' | 'hits'>, ttl?: number | null): void;
55
+ set(key: string, entry: Omit<CacheEntry, 'expiresAt' | 'hits' | 'size'>, ttl?: number | null): void;
48
56
  /**
49
57
  * Returns the formatted {@link CacheInfo} for the given key, or `null` if the
50
58
  * key does not exist or its TTL has elapsed.
@@ -82,15 +90,30 @@ export declare class MemorizeStore {
82
90
  * for each entry.
83
91
  */
84
92
  clear(): void;
93
+ /**
94
+ * Returns the number of active cache entries.
95
+ */
96
+ size(): number;
97
+ /**
98
+ * Returns the approximate total byte size of all cached bodies.
99
+ *
100
+ * The value is an estimate and may not reflect actual memory usage.
101
+ */
102
+ byteSize(): number;
103
+ /**
104
+ * Returns aggregate cache statistics.
105
+ */
106
+ getStats(): MemorizeStats;
85
107
  /**
86
108
  * Returns the raw {@link CacheEntry} for the given key without formatting metadata,
87
109
  * or `null` if the entry is missing or expired. Used internally by the middleware
88
- * to serve cached responses.
110
+ * to serve cached responses. Updates LRU order and increments the hit counter.
89
111
  *
90
112
  * @param key - The cache key to look up.
91
113
  * @internal
92
114
  */
93
115
  getRaw(key: string): CacheEntry | null;
116
+ private _evictLRU;
94
117
  private _evict;
95
118
  private _emit;
96
119
  private _format;
@@ -1 +1 @@
1
- {"version":3,"file":"MemorizeStore.d.ts","sourceRoot":"","sources":["../src/MemorizeStore.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC/C,OAAO,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAC/D,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAC7D,OAAO,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AACnE,OAAO,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AACnE,OAAO,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AAEjE,YAAY,EACV,UAAU,EACV,SAAS,EACT,aAAa,EACb,gBAAgB,EAChB,mBAAmB,EACnB,mBAAmB,EACnB,kBAAkB,GACnB,CAAC;AACF,OAAO,EAAE,iBAAiB,EAAE,CAAC;AAS7B;;;;;GAKG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,MAAM,CAAiC;IAC/C,OAAO,CAAC,OAAO,CAAoD;IACnE,OAAO,CAAC,UAAU,CAKhB;IAEF;;;;;;;;;;;OAWG;IACH,EAAE,CAAC,KAAK,EAAE,iBAAiB,CAAC,GAAG,EAAK,OAAO,EAAE,CAAC,CAAC,EAAE,gBAAgB,KAAK,IAAI,GAAG,IAAI;IACjF,EAAE,CAAC,KAAK,EAAE,iBAAiB,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC,EAAE,mBAAmB,KAAK,IAAI,GAAG,IAAI;IACpF,EAAE,CAAC,KAAK,EAAE,iBAAiB,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC,EAAE,mBAAmB,KAAK,IAAI,GAAG,IAAI;IACpF,EAAE,CAAC,KAAK,EAAE,iBAAiB,CAAC,KAAK,EAAG,OAAO,EAAE,CAAC,CAAC,EAAE,kBAAkB,KAAK,IAAI,GAAG,IAAI;IAMnF;;;;;;;;;OASG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC,UAAU,EAAE,WAAW,GAAG,MAAM,CAAC,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAsB1F;;;;;OAKG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI;IAYlC;;;OAGG;IACH,MAAM,IAAI,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC;IAcnC;;;;;OAKG;IACH,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAM5B;;;;;;;;;;;OAWG;IACH,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;IAYvC;;;OAGG;IACH,KAAK,IAAI,IAAI;IAMb;;;;;;;OAOG;IACH,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI;IAatC,OAAO,CAAC,MAAM;IAYd,OAAO,CAAC,KAAK;IAMb,OAAO,CAAC,OAAO;CAWhB"}
1
+ {"version":3,"file":"MemorizeStore.d.ts","sourceRoot":"","sources":["../src/MemorizeStore.ts"],"names":[],"mappings":"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"}