@warlock.js/cache 5.19.1 → 5.21.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 +61 -1
- package/cjs/index.cjs +1439 -331
- package/cjs/index.cjs.map +1 -1
- package/esm/cache-manager.d.mts +4 -0
- package/esm/cache-manager.d.mts.map +1 -1
- package/esm/cache-manager.mjs +23 -5
- package/esm/cache-manager.mjs.map +1 -1
- package/esm/cached/auto-key.d.mts +14 -14
- package/esm/cached/auto-key.d.mts.map +1 -1
- package/esm/cached/auto-key.mjs +50 -34
- package/esm/cached/auto-key.mjs.map +1 -1
- package/esm/cached/cached.mjs +4 -0
- package/esm/cached/cached.mjs.map +1 -1
- package/esm/drivers/base-cache-driver.d.mts +84 -8
- package/esm/drivers/base-cache-driver.d.mts.map +1 -1
- package/esm/drivers/base-cache-driver.mjs +152 -42
- package/esm/drivers/base-cache-driver.mjs.map +1 -1
- package/esm/drivers/file-cache-driver.d.mts +15 -0
- package/esm/drivers/file-cache-driver.d.mts.map +1 -1
- package/esm/drivers/file-cache-driver.mjs +68 -18
- package/esm/drivers/file-cache-driver.mjs.map +1 -1
- package/esm/drivers/in-memory-tag-index.d.mts +44 -0
- package/esm/drivers/in-memory-tag-index.d.mts.map +1 -0
- package/esm/drivers/in-memory-tag-index.mjs +73 -0
- package/esm/drivers/in-memory-tag-index.mjs.map +1 -0
- package/esm/drivers/index.d.mts +1 -1
- package/esm/drivers/index.mjs +1 -1
- package/esm/drivers/lru-memory-cache-driver.d.mts +43 -0
- package/esm/drivers/lru-memory-cache-driver.d.mts.map +1 -1
- package/esm/drivers/lru-memory-cache-driver.mjs +96 -0
- package/esm/drivers/lru-memory-cache-driver.mjs.map +1 -1
- package/esm/drivers/memory-cache-driver.d.mts +87 -30
- package/esm/drivers/memory-cache-driver.d.mts.map +1 -1
- package/esm/drivers/memory-cache-driver.mjs +214 -96
- package/esm/drivers/memory-cache-driver.mjs.map +1 -1
- package/esm/drivers/memory-extended-cache-driver.d.mts +9 -5
- package/esm/drivers/memory-extended-cache-driver.d.mts.map +1 -1
- package/esm/drivers/memory-extended-cache-driver.mjs +10 -23
- package/esm/drivers/memory-extended-cache-driver.mjs.map +1 -1
- package/esm/drivers/mock-cache-driver.d.mts +41 -0
- package/esm/drivers/mock-cache-driver.d.mts.map +1 -1
- package/esm/drivers/mock-cache-driver.mjs +105 -8
- package/esm/drivers/mock-cache-driver.mjs.map +1 -1
- package/esm/drivers/null-cache-driver.d.mts +8 -2
- package/esm/drivers/null-cache-driver.d.mts.map +1 -1
- package/esm/drivers/null-cache-driver.mjs +10 -2
- package/esm/drivers/null-cache-driver.mjs.map +1 -1
- package/esm/drivers/pg-cache-driver.d.mts +77 -3
- package/esm/drivers/pg-cache-driver.d.mts.map +1 -1
- package/esm/drivers/pg-cache-driver.mjs +229 -7
- package/esm/drivers/pg-cache-driver.mjs.map +1 -1
- package/esm/drivers/redis-cache-driver.d.mts +86 -1
- package/esm/drivers/redis-cache-driver.d.mts.map +1 -1
- package/esm/drivers/redis-cache-driver.mjs +252 -16
- 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 +11 -8
- package/esm/list/memory-cache-list.d.mts.map +1 -1
- package/esm/list/memory-cache-list.mjs +39 -37
- package/esm/list/memory-cache-list.mjs.map +1 -1
- package/esm/scoped-cache.d.mts +9 -3
- package/esm/scoped-cache.d.mts.map +1 -1
- package/esm/scoped-cache.mjs +19 -7
- package/esm/scoped-cache.mjs.map +1 -1
- package/esm/tagged-cache.d.mts +20 -0
- package/esm/tagged-cache.d.mts.map +1 -1
- package/esm/tagged-cache.mjs +62 -24
- package/esm/tagged-cache.mjs.map +1 -1
- package/esm/tagged-scoped-cache.d.mts +7 -3
- package/esm/tagged-scoped-cache.d.mts.map +1 -1
- package/esm/tagged-scoped-cache.mjs +19 -7
- package/esm/tagged-scoped-cache.mjs.map +1 -1
- package/esm/types.d.mts +41 -3
- package/esm/types.d.mts.map +1 -1
- package/esm/types.mjs.map +1 -1
- package/esm/utils.d.mts +9 -3
- package/esm/utils.d.mts.map +1 -1
- package/esm/utils.mjs +16 -6
- package/esm/utils.mjs.map +1 -1
- package/llms-full.txt +1685 -1666
- package/llms.txt +18 -18
- package/package.json +4 -4
- package/skills/apply-cache-patterns/SKILL.md +98 -97
- package/skills/configure-pg-cache/SKILL.md +6 -5
- package/skills/configure-set-options/SKILL.md +102 -96
- package/skills/handle-cache-errors/SKILL.md +92 -92
- package/skills/index.md +20 -0
- package/skills/observe-cache/SKILL.md +103 -103
- package/skills/pick-cache-driver/SKILL.md +129 -123
- package/skills/test-cache-code/SKILL.md +219 -219
- package/skills/use-cache-atomic/SKILL.md +57 -65
- package/skills/use-cache-bulk/SKILL.md +57 -57
- package/skills/use-cache-list/SKILL.md +85 -85
- package/skills/use-cache-lock/SKILL.md +113 -104
- package/skills/use-cache-namespace/SKILL.md +88 -88
- package/skills/use-cache-similarity/SKILL.md +94 -94
- package/skills/use-cache-tags/SKILL.md +7 -4
- package/skills/use-cache-update-merge/SKILL.md +84 -84
- package/skills/use-cache-utils/SKILL.md +89 -89
- package/skills/use-cached-hof/SKILL.md +100 -99
- package/skills/use-swr/SKILL.md +104 -104
package/CHANGELOG.md
CHANGED
|
@@ -1,9 +1,69 @@
|
|
|
1
|
-
# Changelog — @warlock.js/cache
|
|
1
|
+
# Changelog — @warlock.js/cache
|
|
2
2
|
|
|
3
3
|
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.21.0 - 2026-09-25
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- Lockstep release maintenance and dependency refresh.
|
|
12
|
+
## 5.20.0 - 2026-09-24
|
|
13
|
+
|
|
14
|
+
### Breaking
|
|
15
|
+
|
|
16
|
+
- **`cached()` auto-key format changed.** The shorthand form now derives `<prefix>.<16 hex chars of sha1 of a stable JSON encoding of the args>` instead of joining args with dots. Entries written by earlier versions are never read again: every `cached()` call misses once after the upgrade and repopulates. Old entries expire by TTL, or `flush()` clears them. `Map`, `Set`, functions, symbols and class instances now throw `CacheConfigurationError` instead of silently encoding as `{}`; use the options form with a `key` function.
|
|
17
|
+
- **The memory driver stores flat keys.** `get("users")` after `set("users.1", …)` is now a miss; before, it returned the whole `users` subtree. `set("users", …)` no longer replaces the `users.*` entries. `removeNamespace("users")` still clears `users` and `users.*`.
|
|
18
|
+
- **`lock()` refuses a TTL that never expires.** `0`, `Infinity`, negative, `NaN` or unparseable TTLs throw `CacheConfigurationError` before anything is written. A lock with no expiry stayed held forever, on every server, once its holder crashed.
|
|
19
|
+
- **Redis `flush()` without a `globalPrefix` runs `FLUSHDB`** (it ran `FLUSHALL`), so it clears only the connected database, not every database on the server.
|
|
20
|
+
- **The stored lock value is now `<owner>#<token>`** (a per-acquisition random token is appended to the owner, which still defaults to `pid.<pid>`). Code that reads a lock key directly and compares it to the bare owner string must match the `<owner>#` prefix instead.
|
|
21
|
+
- **Numeric TTL strings are seconds, and sub-second TTLs round up to 1s.** `"3600"` (for example from an env var) is 3600 seconds; it was parsed as 3600 ms, which is 3 s. `"500ms"` and `0.5` are 1 second; they used to floor to `0`, which means "never expires". Fractions round up to whole seconds. `NaN` and negative numbers throw `CacheConfigurationError`.
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
|
|
25
|
+
- `lock()` no longer deletes a successor's lock after its own TTL expired. Release is an ownership-checked compare-and-delete (atomic on the memory, Redis and Postgres drivers), so a slow holder can't free a lock that another caller has since acquired.
|
|
26
|
+
- A failing lock release (for example a Redis blip) no longer replaces `fn`'s result or error. It is logged, so callers no longer retry a job that had already succeeded.
|
|
27
|
+
- Two concurrent `lock()` calls in one process could both acquire the lock on the memory, mock and file drivers. `onConflict: "create"` is now a synchronous check-and-insert on memory and an exclusive create on file.
|
|
28
|
+
- Redis `flush()` without a prefix no longer wipes every database on the server (queues, sessions, other apps).
|
|
29
|
+
- Redis `removeNamespace("users")` also deleted `users2.*` and `usersettings.*`, and flushing tenant `app` also deleted `app2.*` and `apple.*`. It now matches `ns` and `ns.*` only, in batched `UNLINK` calls rather than one giant `DEL`.
|
|
30
|
+
- Redis 5 `scanIterator()` batches keys per cursor response. `removeNamespace()` now flattens those batches before deleting them, while retaining compatibility with older clients that yield one key at a time.
|
|
31
|
+
- Redis SWR metadata now lives inside the namespace, so `flush()` clears it, and a plain `set` clears it, so a stale `staleAt` no longer poisons later reads.
|
|
32
|
+
- A failed Redis `connect()` is rethrown and can be retried; it used to be logged, swallowed and never retried.
|
|
33
|
+
- `increment()`/`decrement()` dropped the key's TTL on non-Redis drivers, so rate-limit counters could become permanent and block an IP forever. The remaining TTL is now kept.
|
|
34
|
+
- `pull()` could hand a one-time token to two concurrent requests, and `update()` lost increments across servers (the login throttle and AI budget counters were looser by N×). Both are atomic where the driver allows; see Added.
|
|
35
|
+
- `remember()` recomputed on every call for `0`, `false` and `""`. Only `null` is a miss now.
|
|
36
|
+
- `remember`, `swr` and `update` shared one in-flight map: `remember()` could return an SWR refresh's `undefined`, and a `remember()` could break an `update()` chain. Each now has its own map. `swr()` also single-flights a cold miss.
|
|
37
|
+
- With the `null` driver, `remember("user.1")` and `remember("user.2")` ran concurrently and returned user 1's data for user 2. The null driver keeps real keys now.
|
|
38
|
+
- `cached().invalidate()` ignored `config.driver` and left stale data on the custom driver. Auto-keys no longer fold punctuation, so `profile("1.private", "")` and `profile("1", "private")`, or `{ q: "x" }` and `("q", "x")`, no longer share an entry (request-controlled args could reach another caller's cache entry).
|
|
39
|
+
- Memory `maxSize` counted top-level namespaces, so dotted keys cascaded evictions that wiped the whole cache, and with a `globalPrefix` it never evicted at all. It counts entries now, in LRU order.
|
|
40
|
+
- The memory sweep scanned every key every second, and a stale timer record could delete a newer permanent value. Only finite-TTL entries are swept.
|
|
41
|
+
- The memory, LRU and mock drivers stored objects by reference, so a caller mutating a value corrupted the cache. Values are cloned on write.
|
|
42
|
+
- `memoryExtended` slid `expiresAt` before checking expiry, so expired entries came back and failed `lock()` attempts extended the holder's lock forever. Expiry is checked first.
|
|
43
|
+
- File driver: writes are atomic (temp file + rename), and a corrupt or half-written file is a miss instead of deleting the key directory (a reader used to delete the entry being written). An empty key is rejected; it used to resolve to the cache root, so `remove()` wiped the whole cache directory.
|
|
44
|
+
- `cache.list().trim(start, -1)` emptied the list, so `trim(-50, -1)` ("keep the last 50") deleted everything. `trim` is LTRIM-inclusive now, and list operations keep the key's TTL.
|
|
45
|
+
- `ScopedCache.update()`/`merge()` reset a session's remaining TTL on every call. They keep it.
|
|
46
|
+
- Concurrent first use of a driver created duplicate clients and leaked sockets. The manager runs a single in-flight load per driver, and `disconnect()` closes every loaded driver, not only the current one (shutdown used to hang or leak).
|
|
47
|
+
- Postgres set `expires_at` from the app clock but compared against the DB `now()`, so clock skew shifted TTLs and locks could be born expired. Expiry is computed on the DB clock.
|
|
48
|
+
- Lazy expiry deleted `prefix.prefix.key` when a `globalPrefix` was set, so expired entries lingered. It re-parsed the key and didn't await the delete.
|
|
49
|
+
- Concurrent tagged writes dropped keys from the tag index, so `tags().invalidate()` left stale entries, and the index grew forever. The index is now a set primitive that is never lost and is pruned, and LRU capacity or memory `maxSize` can no longer evict it.
|
|
50
|
+
|
|
51
|
+
### Added
|
|
52
|
+
|
|
53
|
+
- **Atomic `increment`/`decrement`, `pull` and `update`**, with a per-driver guarantee:
|
|
54
|
+
|
|
55
|
+
| Driver | `increment` / `decrement` | `pull` | `update` |
|
|
56
|
+
| --- | --- | --- | --- |
|
|
57
|
+
| `redis` | cross-server (`INCRBY`, keeps TTL) | cross-server (`GETDEL`, Lua fallback) | cross-server (Lua compare-and-set, bounded retries, `KEEPTTL`) |
|
|
58
|
+
| `pg` | cross-server (one upsert, keeps `expires_at`) | cross-server (`DELETE … RETURNING`) | cross-server (compare-and-set, bounded retries) |
|
|
59
|
+
| `memory` / `memoryExtended` / `lru` / `mock` | in-process, synchronous | in-process, synchronous | serialized per key, in-process only |
|
|
60
|
+
| `file` | serialized per key, in-process only | serialized per key, in-process only | not supported (throws) |
|
|
61
|
+
|
|
62
|
+
`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.
|
|
63
|
+
- **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.
|
|
64
|
+
- **`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.
|
|
65
|
+
- **`lock()` successor-safe release** (see Fixed). There is no renewal, so the TTL must exceed the worst-case duration of the locked work.
|
|
66
|
+
|
|
7
67
|
## 5.19.0 - 2026-09-23
|
|
8
68
|
|
|
9
69
|
### Changed
|