opticore-cache 1.0.0 → 1.0.2
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 +446 -155
- package/dist/index.cjs +214 -59
- package/dist/index.d.cts +54 -14
- package/dist/index.d.ts +54 -14
- package/dist/index.js +205 -60
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,39 +1,34 @@
|
|
|
1
1
|
# opticore-cache
|
|
2
2
|
|
|
3
|
-
### Enterprise‑grade Universal HTTP Cache for OptiCoreJs and
|
|
3
|
+
### Enterprise‑grade Universal HTTP Cache for OptiCoreJs and Node.js
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/opticore-cache)
|
|
7
|
+
[](#license)
|
|
8
8
|
[](https://www.typescriptlang.org/)
|
|
9
9
|
[](https://nodejs.org/)
|
|
10
10
|
|
|
11
|
-
High‑performance, flexible and production‑ready HTTP caching layer
|
|
12
|
-
compatible with
|
|
13
|
-
**Express · Fetch · Axios · Native Node HTTP · cURL**
|
|
14
|
-
:::
|
|
11
|
+
High‑performance, flexible and production‑ready HTTP caching layer\
|
|
12
|
+
compatible with **Express · Fetch · Axios · Native Node HTTP · cURL**
|
|
15
13
|
|
|
16
14
|
|
|
17
|
-
|
|
18
|
-
# 📑 Table of Contents
|
|
15
|
+
# Table of Contents
|
|
19
16
|
|
|
20
17
|
- [Why opticore-cache ?](#why-opticore-cache)
|
|
21
18
|
- [Key Features](#key-features)
|
|
22
19
|
- [Installation](#installation)
|
|
23
20
|
- [Quick Start](#quick-start)
|
|
24
|
-
- [
|
|
25
|
-
- [
|
|
21
|
+
- [HttpCacheFactory Usage](#HttpCacheFactory-Usage)
|
|
22
|
+
- [Configuration Options](#Configuration-Options)
|
|
23
|
+
- [Advanced Usage](#Advanced-Usage)
|
|
26
24
|
- [API Reference](#api-reference)
|
|
27
|
-
- [Cache Strategy & Architecture](#cache-strategy--architecture)
|
|
28
|
-
- [Performance Considerations](#performance-considerations)
|
|
29
25
|
- [Testing](#testing)
|
|
30
26
|
- [Security](#security)
|
|
31
27
|
- [License](#license)
|
|
32
28
|
- [Contributing](#contributing)
|
|
33
29
|
|
|
34
30
|
|
|
35
|
-
|
|
36
|
-
# Why opticore-cache?
|
|
31
|
+
# Why opticore-cache?
|
|
37
32
|
|
|
38
33
|
Modern applications require:
|
|
39
34
|
|
|
@@ -43,23 +38,21 @@ Modern applications require:
|
|
|
43
38
|
- Observability & metrics
|
|
44
39
|
- Flexible storage strategies
|
|
45
40
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
engine.
|
|
49
|
-
|
|
41
|
+
`opticore-cache` provides a unified abstraction layer over multiple HTTP
|
|
42
|
+
clients with an extensible and configurable caching engine.
|
|
50
43
|
|
|
51
44
|
|
|
52
45
|
# Key Features
|
|
53
46
|
|
|
54
|
-
-
|
|
55
|
-
-
|
|
56
|
-
-
|
|
57
|
-
-
|
|
58
|
-
-
|
|
59
|
-
-
|
|
60
|
-
-
|
|
61
|
-
-
|
|
62
|
-
-
|
|
47
|
+
- Automatic HTTP response caching
|
|
48
|
+
- Multi-client support (Fetch, Axios, Node HTTP, cURL)
|
|
49
|
+
- Memory, Disk or Hybrid storage
|
|
50
|
+
- Per-request TTL override
|
|
51
|
+
- Pattern-based invalidation (wildcards supported)
|
|
52
|
+
- Built-in statistics & hit-rate tracking
|
|
53
|
+
- Namespaces for multi-tenant systems
|
|
54
|
+
- TypeScript-first design
|
|
55
|
+
- Instance lifecycle management (create / destroy)
|
|
63
56
|
|
|
64
57
|
|
|
65
58
|
|
|
@@ -69,7 +62,7 @@ engine.
|
|
|
69
62
|
npm install opticore-cache
|
|
70
63
|
```
|
|
71
64
|
|
|
72
|
-
|
|
65
|
+
Optional dependencies:
|
|
73
66
|
|
|
74
67
|
``` bash
|
|
75
68
|
npm install axios express
|
|
@@ -78,212 +71,515 @@ npm install axios express
|
|
|
78
71
|
|
|
79
72
|
# Quick Start
|
|
80
73
|
|
|
81
|
-
## Express Middleware (Zero‑Config)
|
|
82
|
-
|
|
83
74
|
``` ts
|
|
84
|
-
import
|
|
85
|
-
import { createExpressMiddleware } from "opticore-cache";
|
|
75
|
+
import { HttpCacheFactory } from "opticore-cache";
|
|
86
76
|
|
|
87
|
-
const
|
|
77
|
+
const cache = HttpCacheFactory.create("my-app");
|
|
88
78
|
|
|
89
|
-
|
|
90
|
-
cache: { ttl: 60000 }
|
|
91
|
-
}));
|
|
79
|
+
const data = await cache.getWithCache("https://api.example.com/users");
|
|
92
80
|
|
|
93
|
-
|
|
94
|
-
res.json({ id: req.params.id, name: "John Doe" });
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
app.listen(3000);
|
|
81
|
+
console.log(data);
|
|
98
82
|
```
|
|
99
83
|
|
|
100
84
|
|
|
85
|
+
# [HttpCacheFactory Usage
|
|
101
86
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
## Default Configuration
|
|
105
|
-
|
|
106
|
-
``` ts
|
|
107
|
-
const defaultConfig = {
|
|
108
|
-
cache: {
|
|
109
|
-
enabled: true,
|
|
110
|
-
ttl: 300000,
|
|
111
|
-
maxSize: 10000,
|
|
112
|
-
storage: "disk", // memory | disk | hybrid
|
|
113
|
-
storageOptions: {
|
|
114
|
-
diskDir: "./storage/cache/http"
|
|
115
|
-
}
|
|
116
|
-
},
|
|
117
|
-
http: {
|
|
118
|
-
defaultClient: "fetch",
|
|
119
|
-
timeout: 30000,
|
|
120
|
-
retries: 3,
|
|
121
|
-
retryDelay: 1000
|
|
122
|
-
},
|
|
123
|
-
clients: {
|
|
124
|
-
fetch: { credentials: "omit" },
|
|
125
|
-
axios: { baseURL: "" },
|
|
126
|
-
nodeHttp: { keepAlive: true },
|
|
127
|
-
curl: { binaryPath: "/usr/bin/curl" }
|
|
128
|
-
}
|
|
129
|
-
};
|
|
130
|
-
```
|
|
131
|
-
|
|
87
|
+
The `HttpCacheFactory` is responsible for:
|
|
132
88
|
|
|
89
|
+
- Creating configured cache instances
|
|
90
|
+
- Managing instance reuse
|
|
91
|
+
- Supporting multiple HTTP clients
|
|
92
|
+
- Destroying instances when needed
|
|
93
|
+
- Adapting existing cache services
|
|
133
94
|
|
|
134
|
-
# Usage Examples
|
|
135
95
|
|
|
136
|
-
## Fetch
|
|
96
|
+
## Create with Fetch (default)
|
|
137
97
|
|
|
138
98
|
``` ts
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
const cache = HttpCacheFactory.create("my-app", {
|
|
99
|
+
const cache = HttpCacheFactory.create("app-fetch", {
|
|
142
100
|
clientType: "fetch",
|
|
143
101
|
storageType: "disk",
|
|
144
|
-
diskDir: "./storage/cache
|
|
102
|
+
diskDir: "./storage/cache",
|
|
145
103
|
defaultTTL: 60000
|
|
146
104
|
});
|
|
147
|
-
|
|
148
|
-
const user = await cache.getWithCache(
|
|
149
|
-
"https://jsonplaceholder.typicode.com/users/1",
|
|
150
|
-
{},
|
|
151
|
-
{ timeToLive: 30000 }
|
|
152
|
-
);
|
|
153
|
-
|
|
154
|
-
console.log(user._metadata.cached ? "From cache" : "From API");
|
|
155
105
|
```
|
|
156
106
|
|
|
157
107
|
|
|
158
|
-
|
|
159
|
-
## Axios Client
|
|
108
|
+
## Create with Axios
|
|
160
109
|
|
|
161
110
|
``` ts
|
|
162
|
-
const
|
|
111
|
+
const axiosCache = HttpCacheFactory.create("app-axios", {
|
|
163
112
|
clientType: "axios",
|
|
164
113
|
clientOptions: {
|
|
165
114
|
baseURL: "https://jsonplaceholder.typicode.com",
|
|
166
115
|
timeout: 5000
|
|
167
116
|
}
|
|
168
117
|
});
|
|
169
|
-
|
|
170
|
-
const data = await cache.getWithCache("/users/1");
|
|
171
118
|
```
|
|
172
119
|
|
|
173
120
|
|
|
121
|
+
## Create with Node HTTP
|
|
174
122
|
|
|
175
|
-
|
|
123
|
+
``` ts
|
|
124
|
+
const nodeCache = HttpCacheFactory.create("app-node", {
|
|
125
|
+
clientType: "node-http",
|
|
126
|
+
clientOptions: {
|
|
127
|
+
timeout: 8000
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
```
|
|
176
131
|
|
|
177
|
-
|
|
132
|
+
|
|
133
|
+
## Create with cURL
|
|
178
134
|
|
|
179
135
|
``` ts
|
|
180
|
-
HttpCacheFactory.create(
|
|
136
|
+
const curlCache = HttpCacheFactory.create("app-curl", {
|
|
137
|
+
clientType: "curl",
|
|
138
|
+
clientOptions: {
|
|
139
|
+
timeout: 10000
|
|
140
|
+
}
|
|
141
|
+
});
|
|
181
142
|
```
|
|
182
143
|
|
|
183
|
-
Returns: `IHttpCacheService`
|
|
184
144
|
|
|
145
|
+
## Destroy an Instance
|
|
185
146
|
|
|
147
|
+
``` ts
|
|
148
|
+
HttpCacheFactory.destroy("app-fetch", "fetch");
|
|
149
|
+
```
|
|
186
150
|
|
|
187
|
-
## Core Methods
|
|
188
151
|
|
|
189
|
-
|
|
152
|
+
## Adapt Existing Cache
|
|
190
153
|
|
|
191
154
|
``` ts
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
timeToLive?: number;
|
|
197
|
-
customKey?: string;
|
|
198
|
-
bypassCache?: boolean;
|
|
199
|
-
namespace?: string;
|
|
200
|
-
}
|
|
201
|
-
): Promise<T>
|
|
155
|
+
const adapted = HttpCacheFactory.createFromExistingCache(
|
|
156
|
+
customCacheService,
|
|
157
|
+
"custom-namespace"
|
|
158
|
+
);
|
|
202
159
|
```
|
|
203
160
|
|
|
204
161
|
|
|
162
|
+
## Configuration Options
|
|
205
163
|
|
|
206
|
-
|
|
164
|
+
When creating an instance of the HTTP cache service using `HttpCacheFactory.create()`, you can pass an options object to customize its behavior. Below are the available options:
|
|
207
165
|
|
|
208
|
-
|
|
166
|
+
| Option | Type | Description | Exemple |
|
|
167
|
+
|-------------|--------------------|-------------------------------------------------------------------------------------------------------------|----------------------------------------------------------|
|
|
168
|
+
| storageType | 'memory' ou 'disk' | Defines the storage type: in-memory (faster, volatile) or on disk (persistent). | 'disk' |
|
|
169
|
+
| diskDir | string | Required if `storageType: 'disk'`. Path to the directory where cache files will be stored. | 'src/core/cache/app' |
|
|
170
|
+
| defaultTTL | number | Default time-to-live for cache entries (in milliseconds). Used if no `timeToLive` is provided in a request. | 60000 (1 minute) |
|
|
171
|
+
| maxSize | number | Maximum number of entries in the cache. When exceeded, the oldest entries are removed (LRU). | 100 |
|
|
172
|
+
| namespace | string | (Optional) Namespace used to isolate cache keys. Useful when using multiple instances. | 'external-api' |
|
|
173
|
+
| serializer | object | (Optional) Overrides the default serialization/deserialization functions (JSON by default). | {{ serialize: JSON.stringify, deserialize: JSON.parse }} |
|
|
209
174
|
|
|
210
175
|
|
|
176
|
+
### Example initialization with all options:
|
|
211
177
|
|
|
212
|
-
|
|
178
|
+
```typescript
|
|
179
|
+
const httpCache = HttpCacheFactory.create('external-api', {
|
|
180
|
+
storageType: 'disk',
|
|
181
|
+
diskDir: 'src/core/cache/demo',
|
|
182
|
+
defaultTTL: 10000, // 10 secondes
|
|
183
|
+
maxSize: 100,
|
|
184
|
+
namespace: 'api-v1'
|
|
185
|
+
});
|
|
186
|
+
```
|
|
213
187
|
|
|
214
|
-
Wildcard invalidation:
|
|
215
188
|
|
|
216
|
-
|
|
217
|
-
|
|
189
|
+
# Advanced Usage
|
|
190
|
+
|
|
191
|
+
The `IHttpCacheService` service offers several advanced features for fine-grained HTTP cache control.
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
## 1. Per-Request Cache Bypass
|
|
196
|
+
|
|
197
|
+
You can bypass the cache for a specific request using the `bypassCache` option. This forces an API call and updates the cache with the new response.
|
|
198
|
+
|
|
199
|
+
```typescript
|
|
200
|
+
const cacheOptions = {
|
|
201
|
+
bypassCache: req.query.bypass === 'true', // e.g. ?bypass=true
|
|
202
|
+
timeToLive: 60000
|
|
203
|
+
};
|
|
204
|
+
const data = await httpCache.getWithCache(url, {}, cacheOptions);
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
## 2. Custom Time-To-Live
|
|
210
|
+
|
|
211
|
+
The `timeToLive` option lets you define a custom expiration duration for a specific request, overriding the default value.
|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
## 3. Caching POST Requests
|
|
216
|
+
|
|
217
|
+
The `postWithCache` method allows you to cache the response of a POST request. This can be useful when the API always returns the same response for identical input data.
|
|
218
|
+
|
|
219
|
+
```typescript
|
|
220
|
+
const response = await httpCache.postWithCache(
|
|
221
|
+
url,
|
|
222
|
+
{ title, body, userId }, // request body
|
|
223
|
+
{}, // additional headers
|
|
224
|
+
true, // cache the response
|
|
225
|
+
{ timeToLive: 30000 } // cache options
|
|
226
|
+
);
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
## 4. Pattern-Based Invalidation
|
|
232
|
+
|
|
233
|
+
You can invalidate all cache entries whose key matches a given pattern. This is useful for refreshing a set of related resources.
|
|
234
|
+
|
|
235
|
+
```typescript
|
|
236
|
+
const countExact = await httpCache.invalidateCache('/posts/42'); // Invalidates an exact key
|
|
237
|
+
const countPattern = await httpCache.invalidateCache('/posts/*'); // Invalidates all keys starting with /posts/
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
---
|
|
241
|
+
|
|
242
|
+
## 5. Cache Statistics
|
|
243
|
+
|
|
244
|
+
Retrieve information about the current state of the cache (number of entries, size, etc.).
|
|
245
|
+
|
|
246
|
+
```typescript
|
|
247
|
+
const stats = await httpCache.getStats();
|
|
248
|
+
console.log(stats);
|
|
249
|
+
// Example output:
|
|
250
|
+
// { size: 45, maxSize: 100, hits: 120, misses: 30, ... }
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
---
|
|
254
|
+
|
|
255
|
+
## 6. Full Cache Flush
|
|
256
|
+
|
|
257
|
+
To completely clear the cache (all entries), use:
|
|
258
|
+
|
|
259
|
+
```typescript
|
|
260
|
+
await httpCache.clearHttpCache();
|
|
218
261
|
```
|
|
219
262
|
|
|
263
|
+
---
|
|
264
|
+
|
|
265
|
+
## 7. Response Metadata
|
|
266
|
+
|
|
267
|
+
Every object returned by `getWithCache` or `postWithCache` includes a `_metadata` property containing information about the origin of the data.
|
|
268
|
+
|
|
269
|
+
```typescript
|
|
270
|
+
const data = await httpCache.getWithCache(url);
|
|
271
|
+
console.log(data._metadata);
|
|
272
|
+
// {
|
|
273
|
+
// source: 'cache' | 'api',
|
|
274
|
+
// cached: boolean,
|
|
275
|
+
// createdAt: timestamp,
|
|
276
|
+
// ttl: number
|
|
277
|
+
// }
|
|
278
|
+
```
|
|
220
279
|
|
|
280
|
+
---
|
|
221
281
|
|
|
222
|
-
|
|
282
|
+
## 8. Error Handling
|
|
223
283
|
|
|
224
|
-
|
|
284
|
+
The service encapsulates both network errors and cache errors. You can intercept them as shown in the controller example.
|
|
225
285
|
|
|
286
|
+
---
|
|
226
287
|
|
|
288
|
+
## 9. Usage with Dynamic Parameters
|
|
227
289
|
|
|
228
|
-
|
|
290
|
+
Build the URL with variable parameters (such as `postId`) to create unique cache keys per resource.
|
|
291
|
+
|
|
292
|
+
```typescript
|
|
293
|
+
const url = `https://api.example.com/posts/${postId}`;
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
## Complete example
|
|
297
|
+
```typescript
|
|
298
|
+
export class UserController {
|
|
299
|
+
private httpCache: IHttpCacheService;
|
|
300
|
+
|
|
301
|
+
constructor() {
|
|
302
|
+
this.httpCache = HttpCacheFactory.create('external-api', {
|
|
303
|
+
storageType: 'disk',
|
|
304
|
+
diskDir: 'src/core/cache/demo',
|
|
305
|
+
defaultTTL: 10000,
|
|
306
|
+
maxSize: 100
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
public async getPostById(req: Request, res: Response) {
|
|
311
|
+
const postId = req.params.id;
|
|
312
|
+
const bypassCache = req.query.bypass === 'true'; // Allows bypassing the cache with ?bypass=true
|
|
313
|
+
|
|
314
|
+
try {
|
|
315
|
+
const url = `https://jsonplaceholder.typicode.com/posts/${postId}`;
|
|
316
|
+
|
|
317
|
+
const cacheOptions = {
|
|
318
|
+
bypassCache: bypassCache,
|
|
319
|
+
timeToLive: 60000 // 1 minute (or use the default value)
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
console.log(`Requête pour le post ${postId}, bypass: ${bypassCache}`);
|
|
323
|
+
|
|
324
|
+
const post = await this.httpCache.getWithCache(url, {}, cacheOptions);
|
|
325
|
+
|
|
326
|
+
// Add information about the source (cache or API)
|
|
327
|
+
const source = post?._metadata?.source || 'unknown';
|
|
328
|
+
const cached = post?._metadata?.cached || false;
|
|
329
|
+
|
|
330
|
+
res.json({
|
|
331
|
+
success: true,
|
|
332
|
+
data: post,
|
|
333
|
+
source: source,
|
|
334
|
+
cached: cached,
|
|
335
|
+
timestamp: new Date().toISOString()
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
} catch (error: any) {
|
|
339
|
+
console.error(`Erreur lors de la récupération du post ${postId}:`, error);
|
|
340
|
+
res.status(500).json({
|
|
341
|
+
success: false,
|
|
342
|
+
error: error.message
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Method to retrieve comments for a post, using cache
|
|
348
|
+
public async getCommentsByPostId(req: Request, res: Response) {
|
|
349
|
+
const postId = req.params.id;
|
|
350
|
+
const bypassCache = req.query.bypass === 'true';
|
|
351
|
+
|
|
352
|
+
try {
|
|
353
|
+
const url = `https://jsonplaceholder.typicode.com/posts/${postId}/comments`;
|
|
354
|
+
|
|
355
|
+
const cacheOptions = {
|
|
356
|
+
bypassCache: bypassCache,
|
|
357
|
+
timeToLive: 300000 // 5 minutes
|
|
358
|
+
};
|
|
359
|
+
|
|
360
|
+
const comments: unknown = await this.httpCache.getWithCache(url, {}, cacheOptions);
|
|
361
|
+
|
|
362
|
+
const source: any = comments?._metadata ? comments?._metadata.source : 'unknown';
|
|
363
|
+
const cached: any = comments?._metadata ? comments?._metadata?.cached : false;
|
|
364
|
+
|
|
365
|
+
res.json({
|
|
366
|
+
success: true,
|
|
367
|
+
data: comments,
|
|
368
|
+
source: source,
|
|
369
|
+
cached: cached,
|
|
370
|
+
count: Array.isArray(comments) ? comments.length : 1,
|
|
371
|
+
timestamp: new Date().toISOString()
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
} catch (error: any) {
|
|
375
|
+
console.error(`Erreur pour les commentaires du post ${postId}:`, error);
|
|
376
|
+
res.status(500).json({
|
|
377
|
+
success: false,
|
|
378
|
+
error: error.message
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// Method for making a POST request with optional caching
|
|
384
|
+
public async createPost(req: Request, res: Response) {
|
|
385
|
+
const { title, body, userId, cacheResponse } = req.body;
|
|
386
|
+
try {
|
|
387
|
+
const url = 'https://jsonplaceholder.typicode.com/posts';
|
|
388
|
+
const response = await this.httpCache.postWithCache(
|
|
389
|
+
url,
|
|
390
|
+
{ title, body, userId },
|
|
391
|
+
{},
|
|
392
|
+
cacheResponse || true,
|
|
393
|
+
{ timeToLive: 30000 }
|
|
394
|
+
);
|
|
395
|
+
res.json({
|
|
396
|
+
success: true,
|
|
397
|
+
data: response,
|
|
398
|
+
cached: cacheResponse || false,
|
|
399
|
+
timestamp: new Date().toISOString()
|
|
400
|
+
});
|
|
401
|
+
} catch (error: any) {
|
|
402
|
+
console.error('Erreur création de post:', error);
|
|
403
|
+
res.status(500).json({
|
|
404
|
+
success: false,
|
|
405
|
+
error: error.message
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// Method for obtaining HTTP cache statistics
|
|
411
|
+
public async getCacheStats(req: Request, res: Response) {
|
|
412
|
+
try {
|
|
413
|
+
const stats = await this.httpCache.getStats();
|
|
414
|
+
res.json(stats);
|
|
415
|
+
} catch (error: any) {
|
|
416
|
+
res.status(500).json({
|
|
417
|
+
success: false,
|
|
418
|
+
error: error.message
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// Method to clear the HTTP cache
|
|
424
|
+
public async clearHttpCache(req: Request, res: Response) {
|
|
425
|
+
try {
|
|
426
|
+
await this.httpCache.clearHttpCache();
|
|
427
|
+
res.json({
|
|
428
|
+
success: true,
|
|
429
|
+
message: 'Cache HTTP vidé avec succès',
|
|
430
|
+
timestamp: new Date().toISOString()
|
|
431
|
+
});
|
|
432
|
+
} catch (error: any) {
|
|
433
|
+
res.status(500).json({
|
|
434
|
+
success: false,
|
|
435
|
+
error: error.message
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// Method to invalidate a pattern in the HTTP cache
|
|
441
|
+
public async invalidateCachePattern(req: Request, res: Response) {
|
|
442
|
+
const pattern = req.query.pattern as string;
|
|
443
|
+
if (!pattern) {
|
|
444
|
+
return res.status(400).json({
|
|
445
|
+
success: false,
|
|
446
|
+
error: 'Le paramètre "pattern" est requis'
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
try {
|
|
451
|
+
const count = await this.httpCache.invalidateCache(pattern);
|
|
452
|
+
res.json({
|
|
453
|
+
success: true,
|
|
454
|
+
message: `Cache invalidé pour le pattern: ${pattern}`,
|
|
455
|
+
invalidatedCount: count,
|
|
456
|
+
timestamp: new Date().toISOString()
|
|
457
|
+
});
|
|
458
|
+
} catch (error: any) {
|
|
459
|
+
res.status(500).json({
|
|
460
|
+
success: false,
|
|
461
|
+
error: error.message
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
|
|
470
|
+
# API Reference
|
|
471
|
+
|
|
472
|
+
## Factory
|
|
229
473
|
|
|
230
474
|
``` ts
|
|
231
|
-
|
|
232
|
-
totalRequests: number;
|
|
233
|
-
cacheHits: number;
|
|
234
|
-
cacheMisses: number;
|
|
235
|
-
cacheSize: number;
|
|
236
|
-
hitRate: number;
|
|
237
|
-
cachedUrls: string[];
|
|
238
|
-
timestamp: string;
|
|
239
|
-
enabled: boolean;
|
|
240
|
-
}
|
|
475
|
+
HttpCacheFactory.create(appName?: string, config?)
|
|
241
476
|
```
|
|
242
477
|
|
|
478
|
+
Returns: `IHttpCacheService`
|
|
243
479
|
|
|
244
480
|
|
|
245
|
-
# Cache Strategy & Architecture
|
|
246
481
|
|
|
247
|
-
|
|
482
|
+
## Core Methods
|
|
248
483
|
|
|
249
|
-
|
|
250
|
-
---------- ----------------------------------
|
|
251
|
-
memory High-speed, short-lived cache
|
|
252
|
-
disk Persistent cache across restarts
|
|
253
|
-
hybrid Memory-first with disk fallback
|
|
484
|
+
### getWithCache
|
|
254
485
|
|
|
255
|
-
|
|
486
|
+
``` ts
|
|
487
|
+
await cache.getWithCache("https://api.example.com/data");
|
|
488
|
+
```
|
|
256
489
|
|
|
257
|
-
|
|
258
|
-
- Disk fallback
|
|
259
|
-
- Background refresh supported
|
|
490
|
+
### postWithCache
|
|
260
491
|
|
|
492
|
+
``` ts
|
|
493
|
+
await cache.postWithCache("https://api.example.com/data", payload, {}, true);
|
|
494
|
+
```
|
|
261
495
|
|
|
496
|
+
### invalidateCache
|
|
262
497
|
|
|
263
|
-
|
|
498
|
+
``` ts
|
|
499
|
+
await cache.invalidateCache("users:*");
|
|
500
|
+
```
|
|
264
501
|
|
|
265
|
-
|
|
266
|
-
- Improves P95 & P99 latency
|
|
267
|
-
- Reduces infrastructure cost
|
|
268
|
-
- Prevents API rate-limit exhaustion
|
|
502
|
+
### clearHttpCache
|
|
269
503
|
|
|
270
|
-
|
|
504
|
+
``` ts
|
|
505
|
+
await cache.clearHttpCache();
|
|
506
|
+
```
|
|
271
507
|
|
|
272
|
-
|
|
273
|
-
- SaaS platforms
|
|
274
|
-
- Multi-tenant APIs
|
|
275
|
-
- Serverless environments
|
|
508
|
+
### getStats
|
|
276
509
|
|
|
510
|
+
``` ts
|
|
511
|
+
const stats = await cache.getStats();
|
|
512
|
+
console.log(stats.hitRate);
|
|
513
|
+
```
|
|
277
514
|
|
|
278
515
|
|
|
279
516
|
# Testing
|
|
280
517
|
|
|
281
518
|
``` bash
|
|
282
|
-
|
|
519
|
+
Run the example project by this command :
|
|
520
|
+
tsx example/src/bootstrap.ts
|
|
521
|
+
|
|
522
|
+
then run :
|
|
523
|
+
|
|
524
|
+
Method POST :
|
|
525
|
+
curl -X POST "http://localhost:4200/api/users/posts" \
|
|
526
|
+
-H "Content-Type: application/json" \
|
|
527
|
+
-d '{
|
|
528
|
+
"url": "https://jsonplaceholder.typicode.com/posts",
|
|
529
|
+
"data": {
|
|
530
|
+
"title": "test",
|
|
531
|
+
"body": "content",
|
|
532
|
+
"userId": 2
|
|
533
|
+
},
|
|
534
|
+
"cacheResponse": true
|
|
535
|
+
}'
|
|
536
|
+
|
|
537
|
+
|
|
538
|
+
Response :
|
|
539
|
+
{
|
|
540
|
+
"success":true,
|
|
541
|
+
"data": {
|
|
542
|
+
"id": 101,
|
|
543
|
+
"_metadata": {
|
|
544
|
+
"cached": true,
|
|
545
|
+
"timestamp": "2026-02-20T10:56:25.444Z",
|
|
546
|
+
"url": "https://jsonplaceholder.typicode.com/posts",
|
|
547
|
+
"method": "POST",
|
|
548
|
+
"responseTime":433
|
|
549
|
+
}
|
|
550
|
+
},
|
|
551
|
+
"cached": true,
|
|
552
|
+
"timestamp": "2026-02-20T10:56:25.444Z"
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
|
|
556
|
+
Method GET
|
|
557
|
+
curl -X GET "http://localhost:4200/api/users/posts/1"
|
|
558
|
+
|
|
559
|
+
Response :
|
|
560
|
+
{
|
|
561
|
+
"success": true,
|
|
562
|
+
"data": {
|
|
563
|
+
"userId": 1,
|
|
564
|
+
"id": 1,
|
|
565
|
+
"title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
|
|
566
|
+
"body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto",
|
|
567
|
+
"_metadata": {
|
|
568
|
+
"cached": false,
|
|
569
|
+
"timestamp": "2026-02-20T14:20:38.751Z",
|
|
570
|
+
"url": "https://jsonplaceholder.typicode.com/posts/1",
|
|
571
|
+
"method": "GET",
|
|
572
|
+
"responseTime": 103,
|
|
573
|
+
"cacheHit": false
|
|
574
|
+
}
|
|
575
|
+
},
|
|
576
|
+
"source": "unknown",
|
|
577
|
+
"cached": false,
|
|
578
|
+
"timestamp": "2026-02-20T14:20:38.751Z"
|
|
579
|
+
}
|
|
283
580
|
```
|
|
284
581
|
|
|
285
582
|
|
|
286
|
-
|
|
287
583
|
# Security
|
|
288
584
|
|
|
289
585
|
- No automatic caching of sensitive headers
|
|
@@ -292,13 +588,11 @@ npm test
|
|
|
292
588
|
- Safe retry mechanism
|
|
293
589
|
|
|
294
590
|
|
|
295
|
-
|
|
296
591
|
# License
|
|
297
592
|
|
|
298
593
|
MIT
|
|
299
594
|
|
|
300
595
|
|
|
301
|
-
|
|
302
596
|
# Contributing
|
|
303
597
|
|
|
304
598
|
Contributions are welcome.
|
|
@@ -308,7 +602,4 @@ Contributions are welcome.
|
|
|
308
602
|
3. Submit a Pull Request
|
|
309
603
|
|
|
310
604
|
|
|
311
|
-
|
|
312
|
-
:::
|
|
313
|
-
Built for scalable OptiCoreJs and any Node.js applications
|
|
314
|
-
:::
|
|
605
|
+
Built for scalable OptiCoreJs and Node.js applications.
|