@warlock.js/cache 5.29.0 → 5.31.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 (115) hide show
  1. package/CHANGELOG.md +31 -7
  2. package/README.md +12 -21
  3. package/cjs/index.cjs +134 -71
  4. package/cjs/index.cjs.map +1 -1
  5. package/esm/cache-manager.d.mts +4 -2
  6. package/esm/cache-manager.d.mts.map +1 -1
  7. package/esm/cache-manager.mjs +3 -3
  8. package/esm/cache-manager.mjs.map +1 -1
  9. package/esm/cached/auto-key.d.mts +1 -1
  10. package/esm/cached/auto-key.d.mts.map +1 -1
  11. package/esm/cached/auto-key.mjs +1 -1
  12. package/esm/cached/auto-key.mjs.map +1 -1
  13. package/esm/cached/cached.d.mts +1 -1
  14. package/esm/cached/cached.d.mts.map +1 -1
  15. package/esm/cached/cached.mjs +1 -1
  16. package/esm/cached/cached.mjs.map +1 -1
  17. package/esm/cached/normalize-args.d.mts +1 -1
  18. package/esm/cached/normalize-args.d.mts.map +1 -1
  19. package/esm/cached/normalize-args.mjs +1 -1
  20. package/esm/cached/normalize-args.mjs.map +1 -1
  21. package/esm/drivers/base-cache-driver.d.mts +14 -6
  22. package/esm/drivers/base-cache-driver.d.mts.map +1 -1
  23. package/esm/drivers/base-cache-driver.mjs +32 -15
  24. package/esm/drivers/base-cache-driver.mjs.map +1 -1
  25. package/esm/drivers/file-cache-driver.d.mts +1 -1
  26. package/esm/drivers/file-cache-driver.d.mts.map +1 -1
  27. package/esm/drivers/file-cache-driver.mjs +2 -2
  28. package/esm/drivers/file-cache-driver.mjs.map +1 -1
  29. package/esm/drivers/in-memory-tag-index.d.mts +1 -1
  30. package/esm/drivers/in-memory-tag-index.d.mts.map +1 -1
  31. package/esm/drivers/in-memory-tag-index.mjs +1 -1
  32. package/esm/drivers/in-memory-tag-index.mjs.map +1 -1
  33. package/esm/drivers/index.d.mts +1 -1
  34. package/esm/drivers/index.mjs +1 -1
  35. package/esm/drivers/lru-memory-cache-driver.d.mts +4 -2
  36. package/esm/drivers/lru-memory-cache-driver.d.mts.map +1 -1
  37. package/esm/drivers/lru-memory-cache-driver.mjs +9 -6
  38. package/esm/drivers/lru-memory-cache-driver.mjs.map +1 -1
  39. package/esm/drivers/memory-cache-driver.d.mts +4 -2
  40. package/esm/drivers/memory-cache-driver.d.mts.map +1 -1
  41. package/esm/drivers/memory-cache-driver.mjs +12 -5
  42. package/esm/drivers/memory-cache-driver.mjs.map +1 -1
  43. package/esm/drivers/memory-extended-cache-driver.d.mts +1 -1
  44. package/esm/drivers/memory-extended-cache-driver.d.mts.map +1 -1
  45. package/esm/drivers/memory-extended-cache-driver.mjs +1 -1
  46. package/esm/drivers/memory-extended-cache-driver.mjs.map +1 -1
  47. package/esm/drivers/mock-cache-driver.d.mts +4 -2
  48. package/esm/drivers/mock-cache-driver.d.mts.map +1 -1
  49. package/esm/drivers/mock-cache-driver.mjs +14 -6
  50. package/esm/drivers/mock-cache-driver.mjs.map +1 -1
  51. package/esm/drivers/null-cache-driver.d.mts +5 -1
  52. package/esm/drivers/null-cache-driver.d.mts.map +1 -1
  53. package/esm/drivers/null-cache-driver.mjs +17 -10
  54. package/esm/drivers/null-cache-driver.mjs.map +1 -1
  55. package/esm/drivers/pg-cache-driver.d.mts +4 -2
  56. package/esm/drivers/pg-cache-driver.d.mts.map +1 -1
  57. package/esm/drivers/pg-cache-driver.mjs +9 -5
  58. package/esm/drivers/pg-cache-driver.mjs.map +1 -1
  59. package/esm/drivers/redis-cache-driver.d.mts +17 -3
  60. package/esm/drivers/redis-cache-driver.d.mts.map +1 -1
  61. package/esm/drivers/redis-cache-driver.mjs +26 -5
  62. package/esm/drivers/redis-cache-driver.mjs.map +1 -1
  63. package/esm/index.d.mts +2 -2
  64. package/esm/index.mjs +2 -2
  65. package/esm/list/memory-cache-list.d.mts +1 -1
  66. package/esm/list/memory-cache-list.d.mts.map +1 -1
  67. package/esm/list/memory-cache-list.mjs +1 -1
  68. package/esm/list/memory-cache-list.mjs.map +1 -1
  69. package/esm/metrics.d.mts +1 -1
  70. package/esm/metrics.d.mts.map +1 -1
  71. package/esm/metrics.mjs +1 -1
  72. package/esm/metrics.mjs.map +1 -1
  73. package/esm/scoped-cache.d.mts +4 -2
  74. package/esm/scoped-cache.d.mts.map +1 -1
  75. package/esm/scoped-cache.mjs +3 -3
  76. package/esm/scoped-cache.mjs.map +1 -1
  77. package/esm/tagged-cache.d.mts +4 -2
  78. package/esm/tagged-cache.d.mts.map +1 -1
  79. package/esm/tagged-cache.mjs +3 -3
  80. package/esm/tagged-cache.mjs.map +1 -1
  81. package/esm/tagged-scoped-cache.d.mts +4 -2
  82. package/esm/tagged-scoped-cache.d.mts.map +1 -1
  83. package/esm/tagged-scoped-cache.mjs +3 -3
  84. package/esm/tagged-scoped-cache.mjs.map +1 -1
  85. package/esm/types.d.mts +17 -6
  86. package/esm/types.d.mts.map +1 -1
  87. package/esm/types.mjs +1 -1
  88. package/esm/types.mjs.map +1 -1
  89. package/esm/utils.d.mts +1 -1
  90. package/esm/utils.d.mts.map +1 -1
  91. package/esm/utils.mjs +1 -1
  92. package/esm/utils.mjs.map +1 -1
  93. package/llms-full.txt +231 -224
  94. package/llms.txt +1 -1
  95. package/package.json +5 -4
  96. package/skills/apply-cache-patterns/SKILL.md +3 -2
  97. package/skills/cache-basics/SKILL.md +26 -24
  98. package/skills/configure-pg-cache/SKILL.md +2 -2
  99. package/skills/configure-set-options/SKILL.md +14 -14
  100. package/skills/handle-cache-errors/SKILL.md +8 -8
  101. package/skills/index.md +4 -1
  102. package/skills/observe-cache/SKILL.md +8 -8
  103. package/skills/pick-cache-driver/SKILL.md +24 -23
  104. package/skills/test-cache-code/SKILL.md +15 -10
  105. package/skills/use-cache-atomic/SKILL.md +17 -11
  106. package/skills/use-cache-bulk/SKILL.md +8 -5
  107. package/skills/use-cache-list/SKILL.md +13 -13
  108. package/skills/use-cache-lock/SKILL.md +11 -17
  109. package/skills/use-cache-namespace/SKILL.md +11 -13
  110. package/skills/use-cache-similarity/SKILL.md +9 -9
  111. package/skills/use-cache-tags/SKILL.md +13 -13
  112. package/skills/use-cache-update-merge/SKILL.md +10 -10
  113. package/skills/use-cache-utils/SKILL.md +17 -17
  114. package/skills/use-cached-hof/SKILL.md +8 -7
  115. package/skills/use-swr/SKILL.md +14 -18
package/CHANGELOG.md CHANGED
@@ -4,6 +4,28 @@ All notable changes to `@warlock.js/cache` are documented in this file.
4
4
 
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). `@warlock.js/*` packages are released in lockstep — every package shares the same version number, so a version below may list only the changes that affected this package.
6
6
 
7
+ ## 5.31.0 - 2026-10-18
8
+
9
+ ### Added
10
+
11
+ - Real Redis integration coverage for atomic counters and their expiry behavior, including concurrent requests, preserved deadlines and counters recreated after a seeded key expires. Local runs opt in with `WARLOCK_REDIS_INTEGRATION=1`; CI requires a live Redis service. Tests use UUID prefixes and clean only their own keys.
12
+
13
+ ### Fixed
14
+
15
+ - `cache.increment(key, amount, { ttl })` supplies a TTL when an atomic increment creates or recreates a counter, through direct, tagged and scoped calls. Existing expiry deadlines are preserved; an explicit TTL also repairs a live permanent counter across drivers. Redis applies the count and TTL together, so an expired seed cannot leave a permanent counter even when the driver has no default TTL.
16
+
17
+ ## 5.30.0 - 2026-10-16
18
+
19
+ ### Changed
20
+
21
+ - Email addresses inside logged cache keys are masked (`user.member@example.com` logs as `u***@example.com`), in the base driver's operation log, in error lines (written even with logging off: SWR refresh failures, lock release, clone failures), in the "Cannot increment non-numeric value" error, and in the null driver.
22
+
23
+ ### Fixed
24
+
25
+ - **`redis` driver `increment()` works on Redis 6.** With a default `ttl` configured it ran `EXPIRE … NX`, which only Redis 7+ accepts: on Redis 6 the call threw after the count had already gone up, which silently disabled login throttles and the cache-backed rate limiter. The increment and the default TTL are now applied together by one Lua script, and a TTL the counter already has is never shortened.
26
+ - The default `increment()` (file and null drivers) reads a counter's value and deadline in one read. A counter that expired between two separate reads used to be rewritten from its stale value with no expiry at all, and every increment rounded the remaining TTL up, pushing the deadline up to a second later each time.
27
+ - `file` driver: an entry stored without a TTL (its `expiresAt` is written to JSON as `null`) was read back by the internal entry reader as already expired, so `swr()` freshness checks and TTL-preserving writes treated it as missing.
28
+
7
29
  ## 5.29.0 - 2026-10-01
8
30
 
9
31
  ### Changed
@@ -75,6 +97,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
75
97
  ### Changed
76
98
 
77
99
  - Lockstep release maintenance and dependency refresh.
100
+
78
101
  ## 5.20.0 - 2026-09-24
79
102
 
80
103
  ### Breaking
@@ -118,14 +141,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
118
141
 
119
142
  - **Atomic `increment`/`decrement`, `pull` and `update`**, with a per-driver guarantee:
120
143
 
121
- | Driver | `increment` / `decrement` | `pull` | `update` |
122
- | --- | --- | --- | --- |
123
- | `redis` | cross-server (`INCRBY`, keeps TTL) | cross-server (`GETDEL`, Lua fallback) | cross-server (Lua compare-and-set, bounded retries, `KEEPTTL`) |
124
- | `pg` | cross-server (one upsert, keeps `expires_at`) | cross-server (`DELETE … RETURNING`) | cross-server (compare-and-set, bounded retries) |
125
- | `memory` / `memoryExtended` / `lru` / `mock` | in-process, synchronous | in-process, synchronous | serialized per key, in-process only |
126
- | `file` | serialized per key, in-process only | serialized per key, in-process only | not supported (throws) |
144
+ | Driver | `increment` / `decrement` | `pull` | `update` |
145
+ | -------------------------------------------- | --------------------------------------------- | ------------------------------------- | -------------------------------------------------------------- |
146
+ | `redis` | cross-server (`INCRBY`, keeps TTL) | cross-server (`GETDEL`, Lua fallback) | cross-server (Lua compare-and-set, bounded retries, `KEEPTTL`) |
147
+ | `pg` | cross-server (one upsert, keeps `expires_at`) | cross-server (`DELETE … RETURNING`) | cross-server (compare-and-set, bounded retries) |
148
+ | `memory` / `memoryExtended` / `lru` / `mock` | in-process, synchronous | in-process, synchronous | serialized per key, in-process only |
149
+ | `file` | serialized per key, in-process only | serialized per key, in-process only | not supported (throws) |
127
150
 
128
151
  `update()` callbacks on Redis and Postgres may run more than once under contention, so keep them side-effect free. To coordinate more than one server, use `redis` or `pg`; `memory` is per-process.
152
+
129
153
  - **Tag index primitives** on the driver contract: `tagAdd`, `tagMembers`, `tagRemove`. Redis uses a native SET inside the prefix (a legacy JSON index is upgraded on first touch), Postgres a one-statement JSON-array merge on the index row, and the in-memory drivers a separate store that eviction and expiry never touch. `file` and `null` keep the serialized fallback. `invalidate()` deletes exactly the members it read, writes prune up to 20 dead members, `remove()`/`pull()` detach the key, and tagged `increment`/`pull` use the atomic ops.
130
154
  - **`PgCacheDriver.prune(limit = 1000)`** deletes expired rows in batches and returns the count. `set()` also runs it in the background on about 1 in 200 writes, so unique-key workloads no longer grow the table forever.
131
155
  - **`lock()` successor-safe release** (see Fixed). There is no renewal, so the TTL must exceed the worst-case duration of the locked work.
@@ -230,7 +254,7 @@ changelogs.
230
254
  - Removed the raw `console.log(value)` dump of the full cached payload when `structuredClone` fails in `parseCachedData` — cached values (potentially PII/tokens) no longer leak to stdout; the structured error log with the value's type is kept.
231
255
  - **Credential leak via error logging (Medium):** `logError()` and the Redis driver's `connect()` failure path printed the raw `Error` object straight to stdout (`console.log`) or to `log.fatal`, which could include the connection URL — and password — that some Redis/Node client errors echo back in `error.message`/`cause` on connection failure. Both call sites now go through a new `safeErrorInfo()` helper that logs only a redacted `{ message, code }` shape (never the raw error object), with any `scheme://user:pass@` credentials in the message masked to `scheme://[REDACTED]@`. The bare `console.log(error)`/`console.log("Err", error)` calls are gone entirely.
232
256
  - **Redis `removeNamespace` blocking `KEYS` scan (Medium):** replaced the blocking `KEYS` command with a non-blocking `SCAN` cursor loop (`client.scanIterator`), so clearing a namespace on a large keyspace no longer stalls the single-threaded Redis event loop for every other tenant/consumer. The existing glob-escaping fix (above) is unchanged.
233
- - **File driver `removeNamespace` dotted-key gap (Medium):** dotted keys (`ns.a`) are stored as *sibling* directories under the cache root (see the path-traversal fix above), so removing namespace `ns` — which only ever deleted a directory literally named `ns` — silently left every `ns.*` key on disk. `removeNamespace` now lists the cache root's immediate children, decodes each back to its logical key, and removes every directory whose logical key equals the namespace or starts with `<namespace>.`, matching the boundary semantics the `pg` driver already used for the same contract. Honors `globalPrefix` (previously ignored, so a global flush could wipe the whole cache root instead of scoping to the tenant) and preserves the existing path-containment guard.
257
+ - **File driver `removeNamespace` dotted-key gap (Medium):** dotted keys (`ns.a`) are stored as _sibling_ directories under the cache root (see the path-traversal fix above), so removing namespace `ns` — which only ever deleted a directory literally named `ns` — silently left every `ns.*` key on disk. `removeNamespace` now lists the cache root's immediate children, decodes each back to its logical key, and removes every directory whose logical key equals the namespace or starts with `<namespace>.`, matching the boundary semantics the `pg` driver already used for the same contract. Honors `globalPrefix` (previously ignored, so a global flush could wipe the whole cache root instead of scoping to the tenant) and preserves the existing path-containment guard.
234
258
 
235
259
  ### Dependencies
236
260
 
package/README.md CHANGED
@@ -33,11 +33,7 @@ Or
33
33
  First step, we need to define the cache configurations, in your configuration directory create a file to define the cache configurations.
34
34
 
35
35
  ```ts
36
- import {
37
- cache,
38
- type CacheConfigurations,
39
- MemoryCacheDriver,
40
- } from "@mongez/warlock.cache";
36
+ import { cache, type CacheConfigurations, MemoryCacheDriver } from "@mongez/warlock.cache";
41
37
 
42
38
  const configurations: CacheConfigurations = {
43
39
  drivers: {
@@ -102,11 +98,11 @@ await cache.set("report", report, "7d");
102
98
 
103
99
  // 3. Rich options object
104
100
  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
101
+ ttl: "1h", // or number of seconds — mutually exclusive with expiresAt
102
+ expiresAt: new Date("..."), // absolute deadline (Date or epoch ms)
103
+ tags: ["users", "active"], // inline tags
104
+ onConflict: "create", // "create" | "update" | "upsert" (default)
105
+ driver: "redis", // route this single call to a non-default driver
110
106
  });
111
107
  ```
112
108
 
@@ -117,8 +113,8 @@ cache.setCacheConfigurations({
117
113
  default: "redis",
118
114
  drivers: { redis: RedisCacheDriver, memory: MemoryCacheDriver },
119
115
  options: {
120
- redis: { url: "...", ttl: "7d" }, // string OK
121
- memory: { ttl: 3600 }, // number OK
116
+ redis: { url: "...", ttl: "7d" }, // string OK
117
+ memory: { ttl: 3600 }, // number OK
122
118
  },
123
119
  });
124
120
  ```
@@ -142,7 +138,7 @@ Redis users who know the shorthand: `"create"` maps to `NX`, `"update"` maps to
142
138
  ```ts
143
139
  // Read → transform → write under a per-key in-process lock.
144
140
  await cache.update<User>("user:1", (current) => {
145
- if (!current) return null; // returning null removes the entry
141
+ if (!current) return null; // returning null removes the entry
146
142
  return { ...current, lastSeen: Date.now() };
147
143
  });
148
144
 
@@ -166,7 +162,7 @@ const head = await recent.shift();
166
162
  const firstTen = await recent.slice(0, 10);
167
163
  const count = await recent.length();
168
164
 
169
- await recent.trim(0, 99); // keep only indices 0–99
165
+ await recent.trim(0, 99); // keep only indices 0–99
170
166
  await recent.clear();
171
167
  ```
172
168
 
@@ -231,11 +227,7 @@ It could be useful when working with multi tenant applications, we can use the t
231
227
  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
228
 
233
229
  ```ts
234
- import {
235
- cache,
236
- type CacheConfigurations,
237
- MemoryCacheDriver,
238
- } from "@mongez/warlock.cache";
230
+ import { cache, type CacheConfigurations, MemoryCacheDriver } from "@mongez/warlock.cache";
239
231
 
240
232
  const configurations: CacheConfigurations = {
241
233
  drivers: {
@@ -416,10 +408,9 @@ async function main() {
416
408
  }
417
409
  ```
418
410
 
419
-
420
411
  ### LRU Cache Driver
421
412
 
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.
413
+ [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
414
 
424
415
  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
416