@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.
- package/CHANGELOG.md +31 -7
- package/README.md +12 -21
- package/cjs/index.cjs +134 -71
- package/cjs/index.cjs.map +1 -1
- package/esm/cache-manager.d.mts +4 -2
- package/esm/cache-manager.d.mts.map +1 -1
- package/esm/cache-manager.mjs +3 -3
- package/esm/cache-manager.mjs.map +1 -1
- package/esm/cached/auto-key.d.mts +1 -1
- package/esm/cached/auto-key.d.mts.map +1 -1
- package/esm/cached/auto-key.mjs +1 -1
- package/esm/cached/auto-key.mjs.map +1 -1
- package/esm/cached/cached.d.mts +1 -1
- package/esm/cached/cached.d.mts.map +1 -1
- package/esm/cached/cached.mjs +1 -1
- package/esm/cached/cached.mjs.map +1 -1
- package/esm/cached/normalize-args.d.mts +1 -1
- package/esm/cached/normalize-args.d.mts.map +1 -1
- package/esm/cached/normalize-args.mjs +1 -1
- package/esm/cached/normalize-args.mjs.map +1 -1
- package/esm/drivers/base-cache-driver.d.mts +14 -6
- package/esm/drivers/base-cache-driver.d.mts.map +1 -1
- package/esm/drivers/base-cache-driver.mjs +32 -15
- package/esm/drivers/base-cache-driver.mjs.map +1 -1
- package/esm/drivers/file-cache-driver.d.mts +1 -1
- package/esm/drivers/file-cache-driver.d.mts.map +1 -1
- package/esm/drivers/file-cache-driver.mjs +2 -2
- package/esm/drivers/file-cache-driver.mjs.map +1 -1
- package/esm/drivers/in-memory-tag-index.d.mts +1 -1
- package/esm/drivers/in-memory-tag-index.d.mts.map +1 -1
- package/esm/drivers/in-memory-tag-index.mjs +1 -1
- package/esm/drivers/in-memory-tag-index.mjs.map +1 -1
- package/esm/drivers/index.d.mts +1 -1
- package/esm/drivers/index.mjs +1 -1
- package/esm/drivers/lru-memory-cache-driver.d.mts +4 -2
- package/esm/drivers/lru-memory-cache-driver.d.mts.map +1 -1
- package/esm/drivers/lru-memory-cache-driver.mjs +9 -6
- package/esm/drivers/lru-memory-cache-driver.mjs.map +1 -1
- package/esm/drivers/memory-cache-driver.d.mts +4 -2
- package/esm/drivers/memory-cache-driver.d.mts.map +1 -1
- package/esm/drivers/memory-cache-driver.mjs +12 -5
- package/esm/drivers/memory-cache-driver.mjs.map +1 -1
- package/esm/drivers/memory-extended-cache-driver.d.mts +1 -1
- package/esm/drivers/memory-extended-cache-driver.d.mts.map +1 -1
- package/esm/drivers/memory-extended-cache-driver.mjs +1 -1
- package/esm/drivers/memory-extended-cache-driver.mjs.map +1 -1
- package/esm/drivers/mock-cache-driver.d.mts +4 -2
- package/esm/drivers/mock-cache-driver.d.mts.map +1 -1
- package/esm/drivers/mock-cache-driver.mjs +14 -6
- package/esm/drivers/mock-cache-driver.mjs.map +1 -1
- package/esm/drivers/null-cache-driver.d.mts +5 -1
- package/esm/drivers/null-cache-driver.d.mts.map +1 -1
- package/esm/drivers/null-cache-driver.mjs +17 -10
- package/esm/drivers/null-cache-driver.mjs.map +1 -1
- package/esm/drivers/pg-cache-driver.d.mts +4 -2
- package/esm/drivers/pg-cache-driver.d.mts.map +1 -1
- package/esm/drivers/pg-cache-driver.mjs +9 -5
- package/esm/drivers/pg-cache-driver.mjs.map +1 -1
- package/esm/drivers/redis-cache-driver.d.mts +17 -3
- package/esm/drivers/redis-cache-driver.d.mts.map +1 -1
- package/esm/drivers/redis-cache-driver.mjs +26 -5
- package/esm/drivers/redis-cache-driver.mjs.map +1 -1
- package/esm/index.d.mts +2 -2
- package/esm/index.mjs +2 -2
- package/esm/list/memory-cache-list.d.mts +1 -1
- package/esm/list/memory-cache-list.d.mts.map +1 -1
- package/esm/list/memory-cache-list.mjs +1 -1
- package/esm/list/memory-cache-list.mjs.map +1 -1
- package/esm/metrics.d.mts +1 -1
- package/esm/metrics.d.mts.map +1 -1
- package/esm/metrics.mjs +1 -1
- package/esm/metrics.mjs.map +1 -1
- package/esm/scoped-cache.d.mts +4 -2
- package/esm/scoped-cache.d.mts.map +1 -1
- package/esm/scoped-cache.mjs +3 -3
- package/esm/scoped-cache.mjs.map +1 -1
- package/esm/tagged-cache.d.mts +4 -2
- package/esm/tagged-cache.d.mts.map +1 -1
- package/esm/tagged-cache.mjs +3 -3
- package/esm/tagged-cache.mjs.map +1 -1
- package/esm/tagged-scoped-cache.d.mts +4 -2
- package/esm/tagged-scoped-cache.d.mts.map +1 -1
- package/esm/tagged-scoped-cache.mjs +3 -3
- package/esm/tagged-scoped-cache.mjs.map +1 -1
- package/esm/types.d.mts +17 -6
- package/esm/types.d.mts.map +1 -1
- package/esm/types.mjs +1 -1
- package/esm/types.mjs.map +1 -1
- package/esm/utils.d.mts +1 -1
- package/esm/utils.d.mts.map +1 -1
- package/esm/utils.mjs +1 -1
- package/esm/utils.mjs.map +1 -1
- package/llms-full.txt +231 -224
- package/llms.txt +1 -1
- package/package.json +5 -4
- package/skills/apply-cache-patterns/SKILL.md +3 -2
- package/skills/cache-basics/SKILL.md +26 -24
- package/skills/configure-pg-cache/SKILL.md +2 -2
- package/skills/configure-set-options/SKILL.md +14 -14
- package/skills/handle-cache-errors/SKILL.md +8 -8
- package/skills/index.md +4 -1
- package/skills/observe-cache/SKILL.md +8 -8
- package/skills/pick-cache-driver/SKILL.md +24 -23
- package/skills/test-cache-code/SKILL.md +15 -10
- package/skills/use-cache-atomic/SKILL.md +17 -11
- package/skills/use-cache-bulk/SKILL.md +8 -5
- package/skills/use-cache-list/SKILL.md +13 -13
- package/skills/use-cache-lock/SKILL.md +11 -17
- package/skills/use-cache-namespace/SKILL.md +11 -13
- package/skills/use-cache-similarity/SKILL.md +9 -9
- package/skills/use-cache-tags/SKILL.md +13 -13
- package/skills/use-cache-update-merge/SKILL.md +10 -10
- package/skills/use-cache-utils/SKILL.md +17 -17
- package/skills/use-cached-hof/SKILL.md +8 -7
- 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
|
|
122
|
-
|
|
|
123
|
-
| `redis`
|
|
124
|
-
| `pg`
|
|
125
|
-
| `memory` / `memoryExtended` / `lru` / `mock` | in-process, synchronous
|
|
126
|
-
| `file`
|
|
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
|
|
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",
|
|
106
|
-
expiresAt: new Date("..."),
|
|
107
|
-
tags: ["users", "active"],
|
|
108
|
-
onConflict: "create",
|
|
109
|
-
driver: "redis",
|
|
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" },
|
|
121
|
-
memory: { ttl: 3600 },
|
|
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;
|
|
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);
|
|
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
|
|