@warlock.js/cache 5.24.0 → 5.26.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 (35) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +616 -616
  3. package/cjs/index.cjs +21 -1
  4. package/cjs/index.cjs.map +1 -1
  5. package/esm/drivers/base-cache-driver.d.mts +12 -0
  6. package/esm/drivers/base-cache-driver.d.mts.map +1 -1
  7. package/esm/drivers/base-cache-driver.mjs +10 -0
  8. package/esm/drivers/base-cache-driver.mjs.map +1 -1
  9. package/esm/drivers/null-cache-driver.mjs.map +1 -1
  10. package/esm/tagged-cache.d.mts.map +1 -1
  11. package/esm/tagged-cache.mjs +11 -1
  12. package/esm/tagged-cache.mjs.map +1 -1
  13. package/esm/types.d.mts +5 -2
  14. package/esm/types.d.mts.map +1 -1
  15. package/esm/types.mjs.map +1 -1
  16. package/esm/utils.mjs.map +1 -1
  17. package/llms-full.txt +1632 -1631
  18. package/llms.txt +1 -1
  19. package/package.json +3 -3
  20. package/skills/apply-cache-patterns/SKILL.md +95 -95
  21. package/skills/configure-set-options/SKILL.md +96 -96
  22. package/skills/handle-cache-errors/SKILL.md +92 -92
  23. package/skills/observe-cache/SKILL.md +104 -103
  24. package/skills/pick-cache-driver/SKILL.md +121 -121
  25. package/skills/test-cache-code/SKILL.md +219 -219
  26. package/skills/use-cache-atomic/SKILL.md +42 -42
  27. package/skills/use-cache-bulk/SKILL.md +57 -57
  28. package/skills/use-cache-list/SKILL.md +85 -85
  29. package/skills/use-cache-lock/SKILL.md +101 -101
  30. package/skills/use-cache-namespace/SKILL.md +88 -88
  31. package/skills/use-cache-similarity/SKILL.md +94 -94
  32. package/skills/use-cache-update-merge/SKILL.md +84 -84
  33. package/skills/use-cache-utils/SKILL.md +89 -89
  34. package/skills/use-cached-hof/SKILL.md +93 -93
  35. package/skills/use-swr/SKILL.md +104 -104
package/README.md CHANGED
@@ -1,616 +1,616 @@
1
- # Warlock Cache
2
-
3
- Cache manager for nodejs applications.
4
-
5
- ## Features
6
-
7
- - **Simple**: The cache manager is simple to use, you can cache any data you want with just a few lines of code.
8
- - **Multiple Drivers**: The cache manager supports `+6` drivers, you can use the memory driver, redis driver, or create your own custom driver.
9
- - **Custom Drivers**: You can create your own custom driver, the cache manager is designed to be extensible.
10
- - **Unified Configurations**: The cache manager allows you to define the cache configurations in one place, and use them across the application.
11
- - **Multiple Drivers Usage**: the default cache driver is used by the cache manager, however, you can use any other driver for certain tasks.
12
- - **Global Prefix**: Every driver has a global prefix function, this function is called each time an operation is performed, and it returns a string, this string is used as a prefix for the key.
13
- - **Namespace**: The cache manager allows you to clear the cache for a specific namespace, this is useful when working with multi-tenant applications.
14
-
15
- ## Server-only
16
-
17
- `@warlock.js/cache`'s entire runtime surface is server-only — its `package.json` declares `"warlock": { "environment": "server" }`. `@warlock.js/web`'s build treats this as a boundary: app client code cannot value-import this package (type-only imports are still allowed), while server loaders/controllers/modules can import it freely.
18
-
19
- ## Installation
20
-
21
- `yarn create @mongez/warlock.cache`
22
-
23
- Or
24
-
25
- `npx create @mongez/warlock.cache`
26
-
27
- Or
28
-
29
- `pnpm create @mongez/warlock.cache`
30
-
31
- ## Configurations
32
-
33
- First step, we need to define the cache configurations, in your configuration directory create a file to define the cache configurations.
34
-
35
- ```ts
36
- import {
37
- cache,
38
- type CacheConfigurations,
39
- MemoryCacheDriver,
40
- } from "@mongez/warlock.cache";
41
-
42
- const configurations: CacheConfigurations = {
43
- drivers: {
44
- memory: MemoryCacheDriver,
45
- },
46
- default: "memory",
47
- memory: {
48
- // time of expiration in seconds
49
- ttl: 60 * 60 * 24 * 7, // 7 days
50
- },
51
- };
52
-
53
- cache.setCacheConfigurations(configurations);
54
- ```
55
-
56
- This will allow using memory cache driver, which will cache the data in the memory, and it will be removed when the application is restarted.
57
-
58
- ## Usage
59
-
60
- It is that just simple, you can use the cache manager to cache any data you want.
61
-
62
- ```ts
63
- import { cache } from "@mongez/warlock.cache";
64
-
65
- async function main() {
66
- // get the name from the cache
67
- const name = await cache.get("name");
68
-
69
- // if the name is not cached, we will get null, we can set a default value
70
- const name = await cache.get("name", "default name");
71
- }
72
- ```
73
-
74
- ## Setting Values
75
-
76
- To set a value, we can use the `set` method.
77
-
78
- ```ts
79
- import { cache } from "@mongez/warlock.cache";
80
-
81
- async function main() {
82
- // set the name in the cache
83
- await cache.set("name", "John Doe");
84
-
85
- // set the name in the cache for 10 minutes
86
- await cache.set("name", "John Doe", 60 * 10);
87
- }
88
- ```
89
-
90
- ### TTL — three accepted shapes (v2+)
91
-
92
- The 3rd argument of `set` accepts three shapes:
93
-
94
- ```ts
95
- // 1. Number of seconds (back-compat)
96
- await cache.set("name", "John Doe", 600);
97
-
98
- // 2. Human-readable duration string (parsed via the `ms` package)
99
- await cache.set("name", "John Doe", "10m");
100
- await cache.set("session", token, "1h");
101
- await cache.set("report", report, "7d");
102
-
103
- // 3. Rich options object
104
- await cache.set("user:1", user, {
105
- ttl: "1h", // or number of seconds — mutually exclusive with expiresAt
106
- expiresAt: new Date("..."), // absolute deadline (Date or epoch ms)
107
- tags: ["users", "active"], // inline tags
108
- onConflict: "create", // "create" | "update" | "upsert" (default)
109
- driver: "redis", // route this single call to a non-default driver
110
- });
111
- ```
112
-
113
- Global driver config accepts shapes 1 and 2:
114
-
115
- ```ts
116
- cache.setCacheConfigurations({
117
- default: "redis",
118
- drivers: { redis: RedisCacheDriver, memory: MemoryCacheDriver },
119
- options: {
120
- redis: { url: "...", ttl: "7d" }, // string OK
121
- memory: { ttl: 3600 }, // number OK
122
- },
123
- });
124
- ```
125
-
126
- ### Conditional writes (`onConflict`)
127
-
128
- ```ts
129
- // Create only if missing — Redis-native SET … NX; emulated on other drivers.
130
- const result = await cache.set("session:abc", session, { onConflict: "create", ttl: "1h" });
131
- // { wasSet: true, existing: null } when new
132
- // { wasSet: false, existing: <prior value> } when key already existed
133
-
134
- // Update only if present — Redis SET … XX; emulated elsewhere.
135
- await cache.set("user:1", patch, { onConflict: "update" });
136
- ```
137
-
138
- Redis users who know the shorthand: `"create"` maps to `NX`, `"update"` maps to `XX`.
139
-
140
- ### Atomic update and merge
141
-
142
- ```ts
143
- // Read → transform → write under a per-key in-process lock.
144
- await cache.update<User>("user:1", (current) => {
145
- if (!current) return null; // returning null removes the entry
146
- return { ...current, lastSeen: Date.now() };
147
- });
148
-
149
- // Shallow-merge sugar.
150
- await cache.merge<User>("user:1", { name: "Jane" });
151
- ```
152
-
153
- `update` and `merge` throw `CacheUnsupportedError` on the file driver until a file-lock primitive lands.
154
-
155
- ### List sub-API
156
-
157
- For queues, recent-N buffers, and ordered collections:
158
-
159
- ```ts
160
- const recent = cache.list<Event>("recent-events");
161
-
162
- await recent.push(event);
163
- await recent.unshift(priorityEvent);
164
- const tail = await recent.pop();
165
- const head = await recent.shift();
166
- const firstTen = await recent.slice(0, 10);
167
- const count = await recent.length();
168
-
169
- await recent.trim(0, 99); // keep only indices 0–99
170
- await recent.clear();
171
- ```
172
-
173
- Default implementation stores the list as a single cache entry and reads/writes the whole array per op. A Redis-native override using `LPUSH` / `LRANGE` / `LTRIM` is planned for v2.1.
174
-
175
- ### Storing objects and arrays
176
-
177
- By default, the cache manager out of the box handles primitive values along with objects and arrays, they are serialized into string if the driver allows only primitive values then deserialized when getting the value, if the driver allows setting objects and arrays, then they will be stored as is like in the memory driver.
178
-
179
- ## Removing Values
180
-
181
- To remove a value from the cache, we can use the `remove` method.
182
-
183
- ```ts
184
- import { cache } from "@mongez/warlock.cache";
185
-
186
- async function main() {
187
- // remove the name from the cache
188
- await cache.remove("name");
189
- }
190
- ```
191
-
192
- ## Clearing the Cache
193
-
194
- To clear the cache, we can use the `flush` method.
195
-
196
- ```ts
197
- import { cache } from "@mongez/warlock.cache";
198
-
199
- async function main() {
200
- // clear the cache
201
- await cache.flush();
202
- }
203
- ```
204
-
205
- ## Clearing By Namespace
206
-
207
- In some situations, we need to clear the cache for a specific namespace, for example, we have a cache for the user profile, and we need to clear it when the user updates his profile, we can use the `removeByNamespace` method.
208
-
209
- ```ts
210
- import { cache } from "@mongez/warlock.cache";
211
-
212
- async function main() {
213
- // set user profile
214
- await cache.set("user.profile", { name: "John Doe" });
215
-
216
- // set user totals
217
-
218
- await cache.set("user.totals", { posts: 10, comments: 20 });
219
-
220
- // clear the cache
221
- await cache.removeByNamespace("user");
222
- }
223
- ```
224
-
225
- This will remove both `user.profile` and `user.totals` from the cache.
226
-
227
- It could be useful when working with multi tenant applications, we can use the tenant ID as a namespace, and when we need to clear the cache for a specific tenant, we can use the `removeByNamespace` method.
228
-
229
- ## Global Prefix
230
-
231
- Every driver's options have by default `globalPrefix` async function, this function is called each time an operation is performed, and it returns a string, this string is used as a prefix for the key, let's add it to our memory driver.
232
-
233
- ```ts
234
- import {
235
- cache,
236
- type CacheConfigurations,
237
- MemoryCacheDriver,
238
- } from "@mongez/warlock.cache";
239
-
240
- const configurations: CacheConfigurations = {
241
- drivers: {
242
- memory: MemoryCacheDriver,
243
- },
244
- default: "memory",
245
- memory: {
246
- // time of expiration in seconds
247
- ttl: 60 * 60 * 24 * 7, // 7 days
248
- globalPrefix: async () => {
249
- // get the current client ID
250
- const clientId = await getCurrentClientId();
251
-
252
- // return the prefix
253
- return `client-${clientId}`;
254
- },
255
- },
256
- };
257
-
258
- cache.setCacheConfigurations(configurations);
259
- ```
260
-
261
- ```ts
262
- import { cache } from "@mongez/warlock.cache";
263
-
264
- async function main() {
265
- // set the name in the cache
266
- await cache.set("name", "John Doe"); // will be stored as client-1:name
267
- }
268
- ```
269
-
270
- ## Available Drivers
271
-
272
- The cache manager is shipped with 4 drivers:
273
-
274
- - [Null Cache Driver](#null-cache-driver): This is the default used driver, it doesn't cache anything, it is useful for mocking the cache implementation.
275
- - [Memory Cache Driver](#memory-cache-driver): This driver caches the data in the memory, it means that the data will be removed when the application is restarted.
276
- - [Memory Extended Cache Driver](#memory-extended-cache-driver): Memory Cache Driver with extended **TTL**.
277
- - [LRU Cache Driver](#lru-cache-driver): This driver caches the data in the memory with LRU algorithm.
278
- - [Redis Cache Driver](#redis-cache-driver): This driver caches the data in the redis server.
279
- - [File Cache Driver](#file-cache-driver): This driver caches the data in the file system.
280
-
281
- ### Redis Cache Driver
282
-
283
- [Redis](https://redis.io/) is a popular cache driver, it is fast and reliable, to use it, we just need to define the driver and its configurations in the settings, the package is installed by default, so we don't need to install it.
284
-
285
- ```ts
286
- import {
287
- cache,
288
- type CacheConfigurations,
289
- MemoryCacheDriver,
290
- RedisCacheDriver,
291
- } from "@mongez/warlock.cache";
292
-
293
- const configurations: CacheConfigurations = {
294
- drivers: {
295
- memory: MemoryCacheDriver,
296
- redis: RedisCacheDriver,
297
- },
298
- default: "redis",
299
- options: {
300
- redis: {
301
- // time of expiration in seconds
302
- ttl: 60 * 60 * 24 * 7, // 7 days
303
- // the redis connection options
304
- host: "127.0.0.1",
305
- port: 6379,
306
- },
307
- },
308
- };
309
-
310
- cache.setCacheConfigurations(configurations);
311
- ```
312
-
313
- You can also pass `url` option directly if you're using a remote redis server.
314
-
315
- ```ts
316
- const configurations: CacheConfigurations = {
317
- drivers: {
318
- memory: MemoryCacheDriver,
319
- redis: RedisCacheDriver,
320
- },
321
- default: "redis",
322
- options: {
323
- redis: {
324
- // time of expiration in seconds
325
- ttl: 60 * 60 * 24 * 7, // 7 days
326
- // the redis connection options
327
- url: "redis://host:port",
328
- },
329
- },
330
- };
331
-
332
- cache.setCacheConfigurations(configurations);
333
- ```
334
-
335
- Any other options that are not defined in the driver options will be passed to the redis client, so you can pass any other options that are supported by the redis client.
336
-
337
- ```ts
338
- import { cache, RedisCacheDriver } from "@mongez/warlock.cache";
339
-
340
- cache.setCacheConfigurations({
341
- drivers: {
342
- redis: RedisCacheDriver,
343
- },
344
- default: "redis",
345
- options: {
346
- redis: {
347
- // time of expiration in seconds
348
- ttl: 60 * 60 * 24 * 7, // 7 days
349
- // the redis connection options
350
- ...redisConnectionOptions,
351
- },
352
- },
353
- });
354
- ```
355
-
356
- > Redis is a dependency to this package so you don't need to install it manually.
357
-
358
- ### Memory Cache Driver
359
-
360
- This driver caches the data in the memory, it means that the data will be removed when the application is restarted.
361
-
362
- ```ts
363
- import { cache, MemoryCacheDriver } from "@mongez/warlock.cache";
364
-
365
- cache.setCacheConfigurations({
366
- drivers: {
367
- memory: MemoryCacheDriver,
368
- },
369
- default: "memory",
370
- });
371
- ```
372
-
373
- ### Memory Extended Cache Driver
374
-
375
- Works exactly like the Memory Cache Driver, the main difference is that whenever the cache is hit, the TTL is extended by the same amount of time.
376
-
377
- For example, if a cache value is set to expire in 10 minutes, and it is hit after 5 minutes, the TTL will be reset again to be 10 minutes.
378
-
379
- ```ts
380
- import { cache, MemoryExtendedCacheDriver } from "@mongez/warlock.cache";
381
-
382
- cache.setCacheConfigurations({
383
- drivers: {
384
- memoryExtended: MemoryExtendedCacheDriver,
385
- },
386
- default: "memoryExtended",
387
- });
388
- ```
389
-
390
- You can also define it as a replacement to `MemoryCacheDriver` as it already extends it:
391
-
392
- ```ts
393
- import { cache, MemoryExtendedCacheDriver } from "@mongez/warlock.cache";
394
-
395
- cache.setCacheConfigurations({
396
- drivers: {
397
- memory: MemoryExtendedCacheDriver,
398
- },
399
- default: "memory",
400
- });
401
- ```
402
-
403
- Example of usage:
404
-
405
- ```ts
406
- import { cache } from "@mongez/warlock.cache";
407
-
408
- async function main() {
409
- // set the name in the cache
410
- await cache.set("name", "John Doe", 60 * 10); // will be stored for 10 minutes
411
-
412
- // later after 3 minutes...
413
-
414
- // get the name from the cache
415
- const name = await cache.get("name"); // will be stored for 10 minutes from now
416
- }
417
- ```
418
-
419
-
420
- ### LRU Cache Driver
421
-
422
- [LRU Cache](https://en.wikipedia.org/wiki/Cache_replacement_policies#Least_recently_used_(LRU)) is a cache driver that caches the data in the memory with LRU algorithm.
423
-
424
- So how this works? The LRU cache driver will store the data in the memory, and when the memory is full, it will remove the least recently used item.
425
-
426
- ```ts
427
- import { cache, LRUCacheDriver } from "@mongez/warlock.cache";
428
-
429
- cache.setCacheConfigurations({
430
- drivers: {
431
- lru: LRUCacheDriver,
432
- },
433
- default: "lru",
434
- options: {
435
- lru: {
436
- // the maximum number of items to be stored in the cache, default to 1000
437
- max: 1000,
438
- },
439
- },
440
- });
441
- ```
442
-
443
- > At the time being, there is no TTL for this driver
444
-
445
- ### File Cache Driver
446
-
447
- This driver caches the data in the file system.
448
-
449
- ```ts
450
- import { cache, FileCacheDriver } from "@mongez/warlock.cache";
451
-
452
- cache.setCacheConfigurations({
453
- drivers: {
454
- file: FileCacheDriver,
455
- },
456
- default: "file",
457
- options: {
458
- file: {
459
- // time of expiration in seconds
460
- ttl: 60 * 60 * 24 * 7, // 7 days
461
- // the directory to store the cache files
462
- directory: "path/to/cache/directory",
463
- },
464
- },
465
- });
466
- ```
467
-
468
- ### Null Cache Driver
469
-
470
- This driver doesn't cache anything, it is useful for mocking the cache implementation.
471
-
472
- ```ts
473
- import { cache, NullCacheDriver } from "@mongez/warlock.cache";
474
-
475
- cache.setCacheConfigurations({
476
- drivers: {
477
- null: NullCacheDriver,
478
- },
479
- default: "null",
480
- });
481
- ```
482
-
483
- ## Custom Drivers
484
-
485
- To create a custom driver, we need to create a class that implements the `CacheDriver` interface
486
-
487
- ```ts
488
- export interface CacheDriver<ClientType, Options> {
489
- /**
490
- * The cache driver options
491
- */
492
- options: Options;
493
- /**
494
- * Cache driver name
495
- */
496
- name: string;
497
- /**
498
- * Remove all cached items by namespace
499
- */
500
- removeNamespace(namespace: string): Promise<any>;
501
- /**
502
- * Set the cache driver options
503
- */
504
- setOptions(options: Options): any;
505
- /**
506
- * Parse the key to be used in the cache
507
- */
508
- parseKey(key: string | GenericObject): Promise<string>;
509
- /**
510
- * Set a value in the cache
511
- *
512
- * @param key The cache key, could be an object or string
513
- * @param value The value to be stored in the cache
514
- * @param ttl The time to live in seconds
515
- */
516
- set(key: string | GenericObject, value: any, ttl?: number): Promise<any>;
517
- /**
518
- * Get a value from the cache
519
- */
520
- get(key: string | GenericObject): Promise<any | null>;
521
- /**
522
- * Remove a value from the cache
523
- */
524
- remove(key: string | GenericObject): Promise<void>;
525
- /**
526
- * Flush the entire cache
527
- */
528
- flush(): Promise<void>;
529
- /**
530
- * Connect to the cache driver
531
- */
532
- connect(): Promise<any>;
533
- /**
534
- * The cache client
535
- */
536
- client?: ClientType;
537
- }
538
- ```
539
-
540
- ## Base Cache Driver
541
-
542
- This is an abstract class that implements the `CacheDriver` interface, it handles multiple things for you, like **logging**, **parsing TTL** and **parsing key** so we can easily create a new driver by extending this class.
543
-
544
- ```ts
545
- import { BaseCacheDriver, type CacheDriver } from "@mongez/warlock.cache";
546
-
547
- export class MyCacheDriver
548
- extends BaseCacheDriver<>
549
- implements CacheDriver<any, any>
550
- {
551
- /**
552
- * The cache driver name
553
- */
554
- public name = "database";
555
-
556
- /**
557
- * Database connection
558
- */
559
- public connection: any;
560
-
561
- /**
562
- * Connect to the cache driver
563
- */
564
- public async connect() {
565
- // connect to database
566
- // options are handled by the base class
567
- this.connection = await connectToDatabase(this.options);
568
- }
569
-
570
- /**
571
- * Set a value in the cache
572
- *
573
- * @param key The cache key, could be an object or string
574
- * @param value The value to be stored in the cache
575
- * @param ttl The time to live in seconds
576
- */
577
- public async set(key: string | GenericObject, value: any, ttl?: number) {
578
- // set the value in the database
579
- }
580
-
581
- /**
582
- * Get a value from the database
583
- */
584
- public async get(key: string | GenericObject) {
585
- // get the value from the database
586
- }
587
-
588
- /**
589
- * Remove a value from the database
590
- */
591
- public async remove(key: string | GenericObject) {
592
- // remove the value from the database
593
- }
594
-
595
- /**
596
- * Flush the entire cache
597
- */
598
- public async flush() {
599
- // flush the database
600
- }
601
- }
602
- ```
603
-
604
- ## Using certain drivers
605
-
606
- The `cache` manager will always use the default cache you set in the cache options, however, you could use any another driver for certain tasks by calling `use` method, this method receives the driver name and return the driver instance, if the driver is not loaded yet it will be loaded (and connected) before returning the driver instance.
607
-
608
- ```ts
609
- import { cache } from "@mongez/warlock.cache";
610
-
611
- async function main() {
612
- // set the name in the cache
613
- const redis = cache.use("redis");
614
- const memory = cache.use("memory");
615
- }
616
- ```
1
+ # Warlock Cache
2
+
3
+ Cache manager for nodejs applications.
4
+
5
+ ## Features
6
+
7
+ - **Simple**: The cache manager is simple to use, you can cache any data you want with just a few lines of code.
8
+ - **Multiple Drivers**: The cache manager supports `+6` drivers, you can use the memory driver, redis driver, or create your own custom driver.
9
+ - **Custom Drivers**: You can create your own custom driver, the cache manager is designed to be extensible.
10
+ - **Unified Configurations**: The cache manager allows you to define the cache configurations in one place, and use them across the application.
11
+ - **Multiple Drivers Usage**: the default cache driver is used by the cache manager, however, you can use any other driver for certain tasks.
12
+ - **Global Prefix**: Every driver has a global prefix function, this function is called each time an operation is performed, and it returns a string, this string is used as a prefix for the key.
13
+ - **Namespace**: The cache manager allows you to clear the cache for a specific namespace, this is useful when working with multi-tenant applications.
14
+
15
+ ## Server-only
16
+
17
+ `@warlock.js/cache`'s entire runtime surface is server-only — its `package.json` declares `"warlock": { "environment": "server" }`. `@warlock.js/web`'s build treats this as a boundary: app client code cannot value-import this package (type-only imports are still allowed), while server loaders/controllers/modules can import it freely.
18
+
19
+ ## Installation
20
+
21
+ `yarn create @mongez/warlock.cache`
22
+
23
+ Or
24
+
25
+ `npx create @mongez/warlock.cache`
26
+
27
+ Or
28
+
29
+ `pnpm create @mongez/warlock.cache`
30
+
31
+ ## Configurations
32
+
33
+ First step, we need to define the cache configurations, in your configuration directory create a file to define the cache configurations.
34
+
35
+ ```ts
36
+ import {
37
+ cache,
38
+ type CacheConfigurations,
39
+ MemoryCacheDriver,
40
+ } from "@mongez/warlock.cache";
41
+
42
+ const configurations: CacheConfigurations = {
43
+ drivers: {
44
+ memory: MemoryCacheDriver,
45
+ },
46
+ default: "memory",
47
+ memory: {
48
+ // time of expiration in seconds
49
+ ttl: 60 * 60 * 24 * 7, // 7 days
50
+ },
51
+ };
52
+
53
+ cache.setCacheConfigurations(configurations);
54
+ ```
55
+
56
+ This will allow using memory cache driver, which will cache the data in the memory, and it will be removed when the application is restarted.
57
+
58
+ ## Usage
59
+
60
+ It is that just simple, you can use the cache manager to cache any data you want.
61
+
62
+ ```ts
63
+ import { cache } from "@mongez/warlock.cache";
64
+
65
+ async function main() {
66
+ // get the name from the cache
67
+ const name = await cache.get("name");
68
+
69
+ // if the name is not cached, we will get null, we can set a default value
70
+ const name = await cache.get("name", "default name");
71
+ }
72
+ ```
73
+
74
+ ## Setting Values
75
+
76
+ To set a value, we can use the `set` method.
77
+
78
+ ```ts
79
+ import { cache } from "@mongez/warlock.cache";
80
+
81
+ async function main() {
82
+ // set the name in the cache
83
+ await cache.set("name", "John Doe");
84
+
85
+ // set the name in the cache for 10 minutes
86
+ await cache.set("name", "John Doe", 60 * 10);
87
+ }
88
+ ```
89
+
90
+ ### TTL — three accepted shapes (v2+)
91
+
92
+ The 3rd argument of `set` accepts three shapes:
93
+
94
+ ```ts
95
+ // 1. Number of seconds (back-compat)
96
+ await cache.set("name", "John Doe", 600);
97
+
98
+ // 2. Human-readable duration string (parsed via the `ms` package)
99
+ await cache.set("name", "John Doe", "10m");
100
+ await cache.set("session", token, "1h");
101
+ await cache.set("report", report, "7d");
102
+
103
+ // 3. Rich options object
104
+ await cache.set("user:1", user, {
105
+ ttl: "1h", // or number of seconds — mutually exclusive with expiresAt
106
+ expiresAt: new Date("..."), // absolute deadline (Date or epoch ms)
107
+ tags: ["users", "active"], // inline tags
108
+ onConflict: "create", // "create" | "update" | "upsert" (default)
109
+ driver: "redis", // route this single call to a non-default driver
110
+ });
111
+ ```
112
+
113
+ Global driver config accepts shapes 1 and 2:
114
+
115
+ ```ts
116
+ cache.setCacheConfigurations({
117
+ default: "redis",
118
+ drivers: { redis: RedisCacheDriver, memory: MemoryCacheDriver },
119
+ options: {
120
+ redis: { url: "...", ttl: "7d" }, // string OK
121
+ memory: { ttl: 3600 }, // number OK
122
+ },
123
+ });
124
+ ```
125
+
126
+ ### Conditional writes (`onConflict`)
127
+
128
+ ```ts
129
+ // Create only if missing — Redis-native SET … NX; emulated on other drivers.
130
+ const result = await cache.set("session:abc", session, { onConflict: "create", ttl: "1h" });
131
+ // { wasSet: true, existing: null } when new
132
+ // { wasSet: false, existing: <prior value> } when key already existed
133
+
134
+ // Update only if present — Redis SET … XX; emulated elsewhere.
135
+ await cache.set("user:1", patch, { onConflict: "update" });
136
+ ```
137
+
138
+ Redis users who know the shorthand: `"create"` maps to `NX`, `"update"` maps to `XX`.
139
+
140
+ ### Atomic update and merge
141
+
142
+ ```ts
143
+ // Read → transform → write under a per-key in-process lock.
144
+ await cache.update<User>("user:1", (current) => {
145
+ if (!current) return null; // returning null removes the entry
146
+ return { ...current, lastSeen: Date.now() };
147
+ });
148
+
149
+ // Shallow-merge sugar.
150
+ await cache.merge<User>("user:1", { name: "Jane" });
151
+ ```
152
+
153
+ `update` and `merge` throw `CacheUnsupportedError` on the file driver until a file-lock primitive lands.
154
+
155
+ ### List sub-API
156
+
157
+ For queues, recent-N buffers, and ordered collections:
158
+
159
+ ```ts
160
+ const recent = cache.list<Event>("recent-events");
161
+
162
+ await recent.push(event);
163
+ await recent.unshift(priorityEvent);
164
+ const tail = await recent.pop();
165
+ const head = await recent.shift();
166
+ const firstTen = await recent.slice(0, 10);
167
+ const count = await recent.length();
168
+
169
+ await recent.trim(0, 99); // keep only indices 0–99
170
+ await recent.clear();
171
+ ```
172
+
173
+ Default implementation stores the list as a single cache entry and reads/writes the whole array per op. A Redis-native override using `LPUSH` / `LRANGE` / `LTRIM` is planned for v2.1.
174
+
175
+ ### Storing objects and arrays
176
+
177
+ By default, the cache manager out of the box handles primitive values along with objects and arrays, they are serialized into string if the driver allows only primitive values then deserialized when getting the value, if the driver allows setting objects and arrays, then they will be stored as is like in the memory driver.
178
+
179
+ ## Removing Values
180
+
181
+ To remove a value from the cache, we can use the `remove` method.
182
+
183
+ ```ts
184
+ import { cache } from "@mongez/warlock.cache";
185
+
186
+ async function main() {
187
+ // remove the name from the cache
188
+ await cache.remove("name");
189
+ }
190
+ ```
191
+
192
+ ## Clearing the Cache
193
+
194
+ To clear the cache, we can use the `flush` method.
195
+
196
+ ```ts
197
+ import { cache } from "@mongez/warlock.cache";
198
+
199
+ async function main() {
200
+ // clear the cache
201
+ await cache.flush();
202
+ }
203
+ ```
204
+
205
+ ## Clearing By Namespace
206
+
207
+ In some situations, we need to clear the cache for a specific namespace, for example, we have a cache for the user profile, and we need to clear it when the user updates his profile, we can use the `removeByNamespace` method.
208
+
209
+ ```ts
210
+ import { cache } from "@mongez/warlock.cache";
211
+
212
+ async function main() {
213
+ // set user profile
214
+ await cache.set("user.profile", { name: "John Doe" });
215
+
216
+ // set user totals
217
+
218
+ await cache.set("user.totals", { posts: 10, comments: 20 });
219
+
220
+ // clear the cache
221
+ await cache.removeByNamespace("user");
222
+ }
223
+ ```
224
+
225
+ This will remove both `user.profile` and `user.totals` from the cache.
226
+
227
+ It could be useful when working with multi tenant applications, we can use the tenant ID as a namespace, and when we need to clear the cache for a specific tenant, we can use the `removeByNamespace` method.
228
+
229
+ ## Global Prefix
230
+
231
+ Every driver's options have by default `globalPrefix` async function, this function is called each time an operation is performed, and it returns a string, this string is used as a prefix for the key, let's add it to our memory driver.
232
+
233
+ ```ts
234
+ import {
235
+ cache,
236
+ type CacheConfigurations,
237
+ MemoryCacheDriver,
238
+ } from "@mongez/warlock.cache";
239
+
240
+ const configurations: CacheConfigurations = {
241
+ drivers: {
242
+ memory: MemoryCacheDriver,
243
+ },
244
+ default: "memory",
245
+ memory: {
246
+ // time of expiration in seconds
247
+ ttl: 60 * 60 * 24 * 7, // 7 days
248
+ globalPrefix: async () => {
249
+ // get the current client ID
250
+ const clientId = await getCurrentClientId();
251
+
252
+ // return the prefix
253
+ return `client-${clientId}`;
254
+ },
255
+ },
256
+ };
257
+
258
+ cache.setCacheConfigurations(configurations);
259
+ ```
260
+
261
+ ```ts
262
+ import { cache } from "@mongez/warlock.cache";
263
+
264
+ async function main() {
265
+ // set the name in the cache
266
+ await cache.set("name", "John Doe"); // will be stored as client-1:name
267
+ }
268
+ ```
269
+
270
+ ## Available Drivers
271
+
272
+ The cache manager is shipped with 4 drivers:
273
+
274
+ - [Null Cache Driver](#null-cache-driver): This is the default used driver, it doesn't cache anything, it is useful for mocking the cache implementation.
275
+ - [Memory Cache Driver](#memory-cache-driver): This driver caches the data in the memory, it means that the data will be removed when the application is restarted.
276
+ - [Memory Extended Cache Driver](#memory-extended-cache-driver): Memory Cache Driver with extended **TTL**.
277
+ - [LRU Cache Driver](#lru-cache-driver): This driver caches the data in the memory with LRU algorithm.
278
+ - [Redis Cache Driver](#redis-cache-driver): This driver caches the data in the redis server.
279
+ - [File Cache Driver](#file-cache-driver): This driver caches the data in the file system.
280
+
281
+ ### Redis Cache Driver
282
+
283
+ [Redis](https://redis.io/) is a popular cache driver, it is fast and reliable, to use it, we just need to define the driver and its configurations in the settings, the package is installed by default, so we don't need to install it.
284
+
285
+ ```ts
286
+ import {
287
+ cache,
288
+ type CacheConfigurations,
289
+ MemoryCacheDriver,
290
+ RedisCacheDriver,
291
+ } from "@mongez/warlock.cache";
292
+
293
+ const configurations: CacheConfigurations = {
294
+ drivers: {
295
+ memory: MemoryCacheDriver,
296
+ redis: RedisCacheDriver,
297
+ },
298
+ default: "redis",
299
+ options: {
300
+ redis: {
301
+ // time of expiration in seconds
302
+ ttl: 60 * 60 * 24 * 7, // 7 days
303
+ // the redis connection options
304
+ host: "127.0.0.1",
305
+ port: 6379,
306
+ },
307
+ },
308
+ };
309
+
310
+ cache.setCacheConfigurations(configurations);
311
+ ```
312
+
313
+ You can also pass `url` option directly if you're using a remote redis server.
314
+
315
+ ```ts
316
+ const configurations: CacheConfigurations = {
317
+ drivers: {
318
+ memory: MemoryCacheDriver,
319
+ redis: RedisCacheDriver,
320
+ },
321
+ default: "redis",
322
+ options: {
323
+ redis: {
324
+ // time of expiration in seconds
325
+ ttl: 60 * 60 * 24 * 7, // 7 days
326
+ // the redis connection options
327
+ url: "redis://host:port",
328
+ },
329
+ },
330
+ };
331
+
332
+ cache.setCacheConfigurations(configurations);
333
+ ```
334
+
335
+ Any other options that are not defined in the driver options will be passed to the redis client, so you can pass any other options that are supported by the redis client.
336
+
337
+ ```ts
338
+ import { cache, RedisCacheDriver } from "@mongez/warlock.cache";
339
+
340
+ cache.setCacheConfigurations({
341
+ drivers: {
342
+ redis: RedisCacheDriver,
343
+ },
344
+ default: "redis",
345
+ options: {
346
+ redis: {
347
+ // time of expiration in seconds
348
+ ttl: 60 * 60 * 24 * 7, // 7 days
349
+ // the redis connection options
350
+ ...redisConnectionOptions,
351
+ },
352
+ },
353
+ });
354
+ ```
355
+
356
+ > Redis is a dependency to this package so you don't need to install it manually.
357
+
358
+ ### Memory Cache Driver
359
+
360
+ This driver caches the data in the memory, it means that the data will be removed when the application is restarted.
361
+
362
+ ```ts
363
+ import { cache, MemoryCacheDriver } from "@mongez/warlock.cache";
364
+
365
+ cache.setCacheConfigurations({
366
+ drivers: {
367
+ memory: MemoryCacheDriver,
368
+ },
369
+ default: "memory",
370
+ });
371
+ ```
372
+
373
+ ### Memory Extended Cache Driver
374
+
375
+ Works exactly like the Memory Cache Driver, the main difference is that whenever the cache is hit, the TTL is extended by the same amount of time.
376
+
377
+ For example, if a cache value is set to expire in 10 minutes, and it is hit after 5 minutes, the TTL will be reset again to be 10 minutes.
378
+
379
+ ```ts
380
+ import { cache, MemoryExtendedCacheDriver } from "@mongez/warlock.cache";
381
+
382
+ cache.setCacheConfigurations({
383
+ drivers: {
384
+ memoryExtended: MemoryExtendedCacheDriver,
385
+ },
386
+ default: "memoryExtended",
387
+ });
388
+ ```
389
+
390
+ You can also define it as a replacement to `MemoryCacheDriver` as it already extends it:
391
+
392
+ ```ts
393
+ import { cache, MemoryExtendedCacheDriver } from "@mongez/warlock.cache";
394
+
395
+ cache.setCacheConfigurations({
396
+ drivers: {
397
+ memory: MemoryExtendedCacheDriver,
398
+ },
399
+ default: "memory",
400
+ });
401
+ ```
402
+
403
+ Example of usage:
404
+
405
+ ```ts
406
+ import { cache } from "@mongez/warlock.cache";
407
+
408
+ async function main() {
409
+ // set the name in the cache
410
+ await cache.set("name", "John Doe", 60 * 10); // will be stored for 10 minutes
411
+
412
+ // later after 3 minutes...
413
+
414
+ // get the name from the cache
415
+ const name = await cache.get("name"); // will be stored for 10 minutes from now
416
+ }
417
+ ```
418
+
419
+
420
+ ### LRU Cache Driver
421
+
422
+ [LRU Cache](https://en.wikipedia.org/wiki/Cache_replacement_policies#Least_recently_used_(LRU)) is a cache driver that caches the data in the memory with LRU algorithm.
423
+
424
+ So how this works? The LRU cache driver will store the data in the memory, and when the memory is full, it will remove the least recently used item.
425
+
426
+ ```ts
427
+ import { cache, LRUCacheDriver } from "@mongez/warlock.cache";
428
+
429
+ cache.setCacheConfigurations({
430
+ drivers: {
431
+ lru: LRUCacheDriver,
432
+ },
433
+ default: "lru",
434
+ options: {
435
+ lru: {
436
+ // the maximum number of items to be stored in the cache, default to 1000
437
+ max: 1000,
438
+ },
439
+ },
440
+ });
441
+ ```
442
+
443
+ > At the time being, there is no TTL for this driver
444
+
445
+ ### File Cache Driver
446
+
447
+ This driver caches the data in the file system.
448
+
449
+ ```ts
450
+ import { cache, FileCacheDriver } from "@mongez/warlock.cache";
451
+
452
+ cache.setCacheConfigurations({
453
+ drivers: {
454
+ file: FileCacheDriver,
455
+ },
456
+ default: "file",
457
+ options: {
458
+ file: {
459
+ // time of expiration in seconds
460
+ ttl: 60 * 60 * 24 * 7, // 7 days
461
+ // the directory to store the cache files
462
+ directory: "path/to/cache/directory",
463
+ },
464
+ },
465
+ });
466
+ ```
467
+
468
+ ### Null Cache Driver
469
+
470
+ This driver doesn't cache anything, it is useful for mocking the cache implementation.
471
+
472
+ ```ts
473
+ import { cache, NullCacheDriver } from "@mongez/warlock.cache";
474
+
475
+ cache.setCacheConfigurations({
476
+ drivers: {
477
+ null: NullCacheDriver,
478
+ },
479
+ default: "null",
480
+ });
481
+ ```
482
+
483
+ ## Custom Drivers
484
+
485
+ To create a custom driver, we need to create a class that implements the `CacheDriver` interface
486
+
487
+ ```ts
488
+ export interface CacheDriver<ClientType, Options> {
489
+ /**
490
+ * The cache driver options
491
+ */
492
+ options: Options;
493
+ /**
494
+ * Cache driver name
495
+ */
496
+ name: string;
497
+ /**
498
+ * Remove all cached items by namespace
499
+ */
500
+ removeNamespace(namespace: string): Promise<any>;
501
+ /**
502
+ * Set the cache driver options
503
+ */
504
+ setOptions(options: Options): any;
505
+ /**
506
+ * Parse the key to be used in the cache
507
+ */
508
+ parseKey(key: string | GenericObject): Promise<string>;
509
+ /**
510
+ * Set a value in the cache
511
+ *
512
+ * @param key The cache key, could be an object or string
513
+ * @param value The value to be stored in the cache
514
+ * @param ttl The time to live in seconds
515
+ */
516
+ set(key: string | GenericObject, value: any, ttl?: number): Promise<any>;
517
+ /**
518
+ * Get a value from the cache
519
+ */
520
+ get(key: string | GenericObject): Promise<any | null>;
521
+ /**
522
+ * Remove a value from the cache
523
+ */
524
+ remove(key: string | GenericObject): Promise<void>;
525
+ /**
526
+ * Flush the entire cache
527
+ */
528
+ flush(): Promise<void>;
529
+ /**
530
+ * Connect to the cache driver
531
+ */
532
+ connect(): Promise<any>;
533
+ /**
534
+ * The cache client
535
+ */
536
+ client?: ClientType;
537
+ }
538
+ ```
539
+
540
+ ## Base Cache Driver
541
+
542
+ This is an abstract class that implements the `CacheDriver` interface, it handles multiple things for you, like **logging**, **parsing TTL** and **parsing key** so we can easily create a new driver by extending this class.
543
+
544
+ ```ts
545
+ import { BaseCacheDriver, type CacheDriver } from "@mongez/warlock.cache";
546
+
547
+ export class MyCacheDriver
548
+ extends BaseCacheDriver<>
549
+ implements CacheDriver<any, any>
550
+ {
551
+ /**
552
+ * The cache driver name
553
+ */
554
+ public name = "database";
555
+
556
+ /**
557
+ * Database connection
558
+ */
559
+ public connection: any;
560
+
561
+ /**
562
+ * Connect to the cache driver
563
+ */
564
+ public async connect() {
565
+ // connect to database
566
+ // options are handled by the base class
567
+ this.connection = await connectToDatabase(this.options);
568
+ }
569
+
570
+ /**
571
+ * Set a value in the cache
572
+ *
573
+ * @param key The cache key, could be an object or string
574
+ * @param value The value to be stored in the cache
575
+ * @param ttl The time to live in seconds
576
+ */
577
+ public async set(key: string | GenericObject, value: any, ttl?: number) {
578
+ // set the value in the database
579
+ }
580
+
581
+ /**
582
+ * Get a value from the database
583
+ */
584
+ public async get(key: string | GenericObject) {
585
+ // get the value from the database
586
+ }
587
+
588
+ /**
589
+ * Remove a value from the database
590
+ */
591
+ public async remove(key: string | GenericObject) {
592
+ // remove the value from the database
593
+ }
594
+
595
+ /**
596
+ * Flush the entire cache
597
+ */
598
+ public async flush() {
599
+ // flush the database
600
+ }
601
+ }
602
+ ```
603
+
604
+ ## Using certain drivers
605
+
606
+ The `cache` manager will always use the default cache you set in the cache options, however, you could use any another driver for certain tasks by calling `use` method, this method receives the driver name and return the driver instance, if the driver is not loaded yet it will be loaded (and connected) before returning the driver instance.
607
+
608
+ ```ts
609
+ import { cache } from "@mongez/warlock.cache";
610
+
611
+ async function main() {
612
+ // set the name in the cache
613
+ const redis = cache.use("redis");
614
+ const memory = cache.use("memory");
615
+ }
616
+ ```