@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.
Files changed (102) hide show
  1. package/CHANGELOG.md +61 -1
  2. package/cjs/index.cjs +1439 -331
  3. package/cjs/index.cjs.map +1 -1
  4. package/esm/cache-manager.d.mts +4 -0
  5. package/esm/cache-manager.d.mts.map +1 -1
  6. package/esm/cache-manager.mjs +23 -5
  7. package/esm/cache-manager.mjs.map +1 -1
  8. package/esm/cached/auto-key.d.mts +14 -14
  9. package/esm/cached/auto-key.d.mts.map +1 -1
  10. package/esm/cached/auto-key.mjs +50 -34
  11. package/esm/cached/auto-key.mjs.map +1 -1
  12. package/esm/cached/cached.mjs +4 -0
  13. package/esm/cached/cached.mjs.map +1 -1
  14. package/esm/drivers/base-cache-driver.d.mts +84 -8
  15. package/esm/drivers/base-cache-driver.d.mts.map +1 -1
  16. package/esm/drivers/base-cache-driver.mjs +152 -42
  17. package/esm/drivers/base-cache-driver.mjs.map +1 -1
  18. package/esm/drivers/file-cache-driver.d.mts +15 -0
  19. package/esm/drivers/file-cache-driver.d.mts.map +1 -1
  20. package/esm/drivers/file-cache-driver.mjs +68 -18
  21. package/esm/drivers/file-cache-driver.mjs.map +1 -1
  22. package/esm/drivers/in-memory-tag-index.d.mts +44 -0
  23. package/esm/drivers/in-memory-tag-index.d.mts.map +1 -0
  24. package/esm/drivers/in-memory-tag-index.mjs +73 -0
  25. package/esm/drivers/in-memory-tag-index.mjs.map +1 -0
  26. package/esm/drivers/index.d.mts +1 -1
  27. package/esm/drivers/index.mjs +1 -1
  28. package/esm/drivers/lru-memory-cache-driver.d.mts +43 -0
  29. package/esm/drivers/lru-memory-cache-driver.d.mts.map +1 -1
  30. package/esm/drivers/lru-memory-cache-driver.mjs +96 -0
  31. package/esm/drivers/lru-memory-cache-driver.mjs.map +1 -1
  32. package/esm/drivers/memory-cache-driver.d.mts +87 -30
  33. package/esm/drivers/memory-cache-driver.d.mts.map +1 -1
  34. package/esm/drivers/memory-cache-driver.mjs +214 -96
  35. package/esm/drivers/memory-cache-driver.mjs.map +1 -1
  36. package/esm/drivers/memory-extended-cache-driver.d.mts +9 -5
  37. package/esm/drivers/memory-extended-cache-driver.d.mts.map +1 -1
  38. package/esm/drivers/memory-extended-cache-driver.mjs +10 -23
  39. package/esm/drivers/memory-extended-cache-driver.mjs.map +1 -1
  40. package/esm/drivers/mock-cache-driver.d.mts +41 -0
  41. package/esm/drivers/mock-cache-driver.d.mts.map +1 -1
  42. package/esm/drivers/mock-cache-driver.mjs +105 -8
  43. package/esm/drivers/mock-cache-driver.mjs.map +1 -1
  44. package/esm/drivers/null-cache-driver.d.mts +8 -2
  45. package/esm/drivers/null-cache-driver.d.mts.map +1 -1
  46. package/esm/drivers/null-cache-driver.mjs +10 -2
  47. package/esm/drivers/null-cache-driver.mjs.map +1 -1
  48. package/esm/drivers/pg-cache-driver.d.mts +77 -3
  49. package/esm/drivers/pg-cache-driver.d.mts.map +1 -1
  50. package/esm/drivers/pg-cache-driver.mjs +229 -7
  51. package/esm/drivers/pg-cache-driver.mjs.map +1 -1
  52. package/esm/drivers/redis-cache-driver.d.mts +86 -1
  53. package/esm/drivers/redis-cache-driver.d.mts.map +1 -1
  54. package/esm/drivers/redis-cache-driver.mjs +252 -16
  55. package/esm/drivers/redis-cache-driver.mjs.map +1 -1
  56. package/esm/index.d.mts +2 -2
  57. package/esm/index.mjs +2 -2
  58. package/esm/list/memory-cache-list.d.mts +11 -8
  59. package/esm/list/memory-cache-list.d.mts.map +1 -1
  60. package/esm/list/memory-cache-list.mjs +39 -37
  61. package/esm/list/memory-cache-list.mjs.map +1 -1
  62. package/esm/scoped-cache.d.mts +9 -3
  63. package/esm/scoped-cache.d.mts.map +1 -1
  64. package/esm/scoped-cache.mjs +19 -7
  65. package/esm/scoped-cache.mjs.map +1 -1
  66. package/esm/tagged-cache.d.mts +20 -0
  67. package/esm/tagged-cache.d.mts.map +1 -1
  68. package/esm/tagged-cache.mjs +62 -24
  69. package/esm/tagged-cache.mjs.map +1 -1
  70. package/esm/tagged-scoped-cache.d.mts +7 -3
  71. package/esm/tagged-scoped-cache.d.mts.map +1 -1
  72. package/esm/tagged-scoped-cache.mjs +19 -7
  73. package/esm/tagged-scoped-cache.mjs.map +1 -1
  74. package/esm/types.d.mts +41 -3
  75. package/esm/types.d.mts.map +1 -1
  76. package/esm/types.mjs.map +1 -1
  77. package/esm/utils.d.mts +9 -3
  78. package/esm/utils.d.mts.map +1 -1
  79. package/esm/utils.mjs +16 -6
  80. package/esm/utils.mjs.map +1 -1
  81. package/llms-full.txt +1685 -1666
  82. package/llms.txt +18 -18
  83. package/package.json +4 -4
  84. package/skills/apply-cache-patterns/SKILL.md +98 -97
  85. package/skills/configure-pg-cache/SKILL.md +6 -5
  86. package/skills/configure-set-options/SKILL.md +102 -96
  87. package/skills/handle-cache-errors/SKILL.md +92 -92
  88. package/skills/index.md +20 -0
  89. package/skills/observe-cache/SKILL.md +103 -103
  90. package/skills/pick-cache-driver/SKILL.md +129 -123
  91. package/skills/test-cache-code/SKILL.md +219 -219
  92. package/skills/use-cache-atomic/SKILL.md +57 -65
  93. package/skills/use-cache-bulk/SKILL.md +57 -57
  94. package/skills/use-cache-list/SKILL.md +85 -85
  95. package/skills/use-cache-lock/SKILL.md +113 -104
  96. package/skills/use-cache-namespace/SKILL.md +88 -88
  97. package/skills/use-cache-similarity/SKILL.md +94 -94
  98. package/skills/use-cache-tags/SKILL.md +7 -4
  99. package/skills/use-cache-update-merge/SKILL.md +84 -84
  100. package/skills/use-cache-utils/SKILL.md +89 -89
  101. package/skills/use-cached-hof/SKILL.md +100 -99
  102. package/skills/use-swr/SKILL.md +104 -104
@@ -1,123 +1,129 @@
1
- ---
2
- name: pick-cache-driver
3
- description: "Cache drivers — pick the right one in @warlock.js/cache; use when you need to choose cache driver."
4
- ---
5
-
6
- # Cache drivers — pick the right one
7
-
8
- Seven production drivers + a mock driver ship in-box. Pick by durability, scope, and workload.
9
-
10
- | Driver | Process scope | Persists on restart | Good for | Avoid when |
11
- | --- | --- | --- | --- | --- |
12
- | `null` | — | — | Disabling cache in tests; feature-flagging off | You actually want caching |
13
- | `memory` | Single process | No | Hot in-process data with default TTL; smallest latency | Multi-process / multi-node |
14
- | `memoryExtended` | Single process | No | Sliding-window TTL (TTL resets on every read) | Any multi-process deploy |
15
- | `lru` | Single process | No | Bounded in-memory caches (capacity-based eviction) | Need cross-process sharing |
16
- | `file` | Single host | Yes | Build artefacts, local dev persistence across restarts | Concurrency (no locks); multi-host |
17
- | `redis` | Shared | Yes (Redis-managed) | Anything shared across processes / nodes | Single-process-only workload — overkill |
18
- | `pg` | Shared | Yes (Postgres-managed) | You already run Postgres; semantic caching / RAG via pgvector | High-throughput hot reads (Redis is faster) |
19
-
20
- ## Capability matrix
21
-
22
- | Capability | null | memory | memoryExt | lru | file | redis | pg |
23
- | --- | :-: | :-: | :-: | :-: | :-: | :-: | :-: |
24
- | `set` / `get` / `remove` / `flush` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
25
- | TTL (number or string) | — | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
26
- | Sliding TTL on read | — | — | ✓ | — | — | — | — |
27
- | `removeNamespace` | noop | ✓ | ✓ | ✓ (prefix-scan) | ✓ | ✓ | ✓ (LIKE prefix) |
28
- | `onConflict: "create"` / `"update"` | noop | emulated | emulated | emulated | emulated | native `NX`/`XX` | native (INSERT ON CONFLICT) |
29
- | Native increment / decrement | — | ✓ | ✓ | ✓ | ✓ | atomic `INCRBY`/`DECRBY` | ✓ |
30
- | `update()` / `merge()` | ✓ | ✓ | ✓ | ✓ | ✗ throws | ✓ (single-process safety only today) | ✓ |
31
- | List sub-API | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ (O(n) JSON blob today; native LPUSH/LRANGE in v2.1) | ✓ |
32
- | Tagged invalidation | noop | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ (native GIN(tags)) |
33
- | `similar()` / `set({ vector })` | returns `[]` / noop | ✓ brute force | ✓ brute force | ✓ brute force | ✗ throws | ✗ throws (Phase 2 backlog) | ✓ (with `vector` config — pgvector) |
34
-
35
- ## Global config TTL — accepts number or string
36
-
37
- ```ts
38
- options: {
39
- redis: { url: "...", ttl: "7d" }, // string OK
40
- memory: { ttl: 3600 }, // number OK
41
- lru: { capacity: 10_000 }, // LRU has no TTL option today
42
- file: { directory: () => "/var/cache/myapp", ttl: "1h" },
43
- pg: { client: pool, ttl: "1h" }, // KV-only
44
- // pg with pgvector:
45
- // pg: { client: pool, vector: { dimensions: 1536, index: "hnsw" } },
46
- }
47
- ```
48
-
49
- ## Global prefix (multi-tenant scoping)
50
-
51
- Every driver accepts `globalPrefix: string | (() => string)`. The function form runs per call — pair it with request-local async context to scope every cached key to the current tenant / user / client automatically:
52
-
53
- ```ts
54
- options: {
55
- redis: {
56
- url: "...",
57
- globalPrefix: () => `tenant-${currentContext.tenantId}`,
58
- },
59
- }
60
- ```
61
-
62
- ## Key & namespace safety (file / redis)
63
-
64
- Cache keys can carry untrusted input (e.g. a `cached()` auto-key derived from a request param), so every built-in driver treats a key as data, never as a path or a query fragment:
65
-
66
- - **`file`** — a key/namespace maps to exactly one on-disk directory component: `%`, `/`, and `\` are percent-encoded before the path is built, so `../../etc` becomes an inert directory name instead of a traversal. The resolved path is also asserted to stay inside the configured cache root (throws `CacheError` otherwise) — a second, encoding-independent check. `removeNamespace` clears both the namespace's own directory and every dotted child (`ns`, `ns.*`), honoring `globalPrefix`.
67
- - **`redis`** — `removeNamespace` escapes glob metacharacters (`*`, `?`, `[`, `\`) in the namespace before building its match pattern, and walks matches with a `SCAN` cursor (`scanIterator`) instead of the blocking `KEYS` command, so clearing a namespace on a large keyspace doesn't stall the event loop for other tenants.
68
- - **Errors that echo a connection string** (Redis `connect()` failures, any driver's failed op) are logged through a `safeErrorInfo()` helper that redacts `scheme://user:pass@` credentials to `scheme://[REDACTED]@` and never prints the raw `Error` object — see [`@warlock.js/cache/handle-cache-errors/SKILL.md`](@warlock.js/cache/handle-cache-errors/SKILL.md).
69
-
70
- ## Registering a custom driver
71
-
72
- ```ts
73
- import { BaseCacheDriver, cache } from "@warlock.js/cache";
74
-
75
- class MemcachedCacheDriver extends BaseCacheDriver<MyClient, MyOptions> {
76
- public name = "memcached";
77
- // … implement set / get / remove / flush / removeNamespace / connect
78
- }
79
-
80
- cache.setCacheConfigurations({
81
- default: "memcached",
82
- drivers: { memcached: MemcachedCacheDriver },
83
- options: { memcached: { host: "localhost" } },
84
- });
85
- ```
86
-
87
- Extending `BaseCacheDriver` gives you free: TTL parsing, key parsing, event emission, stampede-safe `remember`, deep-clone-on-read, default `update` / `merge` / `list` implementations.
88
-
89
- ## Runtime driver options — `cache.use(name, options)`
90
-
91
- Some driver options can only be built at runtime (`pg`'s `client: pg.Pool`, pre-wired clients). Pass them as the second arg to `cache.use` / `cache.load` / `cache.driver` — they merge over `setCacheConfigurations({ options })` per-key, runtime wins.
92
-
93
- ```ts
94
- const pool = new Pool({ connectionString: process.env.DATABASE_URL });
95
-
96
- cache.setCacheConfigurations({
97
- default: "pg",
98
- drivers: { pg: PgCacheDriver },
99
- options: { pg: { table: "cache" } }, // static
100
- });
101
-
102
- await cache.use("pg", { client: pool }); // runtime — skip init() in this case
103
- ```
104
-
105
- Constraints:
106
- - The driver name must be registered in `setCacheConfigurations({ drivers })` — runtime options don't bypass registration.
107
- - Once a driver is loaded, calling `use`/`load`/`driver` again with **non-empty** new options throws `CacheConfigurationError`. Register a second driver name if you need a different config.
108
- - Calling without options (or with `{}`) on an already-loaded driver returns the cached instance silently.
109
-
110
- ## Per-call driver override
111
-
112
- When most writes go to the default driver but one call needs a different one:
113
-
114
- ```ts
115
- await cache.set("audit:event", event, { driver: "redis" });
116
- ```
117
-
118
- The manager loads (and connects) the override driver lazily on first use, then routes that single operation through it without mutating `currentDriver`.
119
-
120
- ## See also
121
-
122
- - [`@warlock.js/cache/configure-pg-cache/SKILL.md`](@warlock.js/cache/configure-pg-cache/SKILL.md) — full pg setup (KV-only and pgvector mode)
123
- - [`@warlock.js/cache/test-cache-code/SKILL.md`](@warlock.js/cache/test-cache-code/SKILL.md) — `MockCacheDriver` and `NullCacheDriver` for tests
1
+ ---
2
+ name: pick-cache-driver
3
+ description: 'Pick a cache driver — null / memory / memoryExtended / lru / file / redis / pg / mock — and configure it. Triggers: `cache.setCacheConfigurations`, `BaseCacheDriver`, `cache.use`, `cache.load`, `cache.driver`, `globalPrefix`; "which cache driver should I use", "configure redis driver", "register custom cache driver", "multi-tenant scoping"; typical import `import { cache, BaseCacheDriver } from "@warlock.js/cache"`. Skip: cache CRUD — `@warlock.js/cache/cache-basics/SKILL.md`; pg setup — `@warlock.js/cache/configure-pg-cache/SKILL.md`; competing libs `lru-cache`, `node-cache`, `keyv`, `ioredis`; native `Map`.'
4
+ ---
5
+
6
+ # Cache drivers — pick the right one
7
+
8
+ Seven production drivers + a mock driver ship in-box. Pick by durability, scope, and workload.
9
+
10
+ | Driver | Process scope | Persists on restart | Good for | Avoid when |
11
+ | --- | --- | --- | --- | --- |
12
+ | `null` | — | — | Disabling cache in tests; feature-flagging off | You actually want caching |
13
+ | `memory` | Single process | No | Hot in-process data with default TTL; smallest latency | Multi-process / multi-node |
14
+ | `memoryExtended` | Single process | No | Sliding-window TTL (TTL resets on every read) | Any multi-process deploy |
15
+ | `lru` | Single process | No | Bounded in-memory caches (capacity-based eviction) | Need cross-process sharing |
16
+ | `file` | Single host | Yes | Build artefacts, local dev persistence across restarts | Concurrency (no locks); multi-host |
17
+ | `redis` | Shared | Yes (Redis-managed) | Anything shared across processes / nodes | Single-process-only workload — overkill |
18
+ | `pg` | Shared | Yes (Postgres-managed) | You already run Postgres; semantic caching / RAG via pgvector | High-throughput hot reads (Redis is faster) |
19
+
20
+ ## Capability matrix
21
+
22
+ | Capability | null | memory | memoryExt | lru | file | redis | pg |
23
+ | --- | :-: | :-: | :-: | :-: | :-: | :-: | :-: |
24
+ | `set` / `get` / `remove` / `flush` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
25
+ | TTL (number or string; numeric strings = seconds) | — | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
26
+ | Sliding TTL on read | — | — | ✓ | — | — | — | — |
27
+ | `removeNamespace` | noop | ✓ | ✓ | ✓ (prefix-scan) | ✓ | ✓ | ✓ (LIKE prefix) |
28
+ | `onConflict: "create"` / `"update"` | noop | emulated | emulated | emulated | emulated | native `NX`/`XX` | native (INSERT ON CONFLICT) |
29
+ | `increment` / `decrement` / `pull` | — | one process | one process | one process | one process | **cross-server** | **cross-server** |
30
+ | `update()` / `merge()` | ✓ | one process | one process | one process | ✗ throws | **cross-server** (compare-and-set) | **cross-server** (compare-and-set) |
31
+ | List sub-API | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ (O(n) JSON blob today; native LPUSH/LRANGE in v2.1) | ✓ |
32
+ | Tagged invalidation | noop | ✓ | ✓ | ✓ | ✓ (serialized index) | ✓ (native SET) | ✓ (JSON-array merge) |
33
+ | `similar()` / `set({ vector })` | returns `[]` / noop | ✓ brute force | ✓ brute force | ✓ brute force | ✗ throws | ✗ throws (Phase 2 backlog) | ✓ (with `vector` config — pgvector) |
34
+
35
+ ## Multi-server: use redis or pg
36
+
37
+ Memory, memoryExtended, lru and file caches are **per-process** (file: per host). With more than one server, use `redis` or `pg` for counters, throttles, one-time tokens (`pull`), shared `update`, `lock()` and tag invalidation; on memory each server counts and invalidates alone. `remember()`/`swr()`/`cached()` stampede protection is per process even on redis. Redis Cluster is unsupported.
38
+
39
+ Driver storage notes: `memory` stores flat keys (`get("users")` after `set("users.1")` is a miss) and `maxSize` counts entries in LRU order; redis `flush()` without a `globalPrefix` runs `FLUSHDB` (whole database), so set a prefix.
40
+
41
+ ## Global config TTL — accepts number or string
42
+
43
+ ```ts
44
+ options: {
45
+ redis: { url: "...", ttl: "7d" }, // string OK
46
+ memory: { ttl: 3600 }, // number OK
47
+ lru: { capacity: 10_000 }, // LRU has no TTL option today
48
+ file: { directory: () => "/var/cache/myapp", ttl: "1h" },
49
+ pg: { client: pool, ttl: "1h" }, // KV-only
50
+ // pg with pgvector:
51
+ // pg: { client: pool, vector: { dimensions: 1536, index: "hnsw" } },
52
+ }
53
+ ```
54
+
55
+ ## Global prefix (multi-tenant scoping)
56
+
57
+ Every driver accepts `globalPrefix: string | (() => string)`. The function form runs per call — pair it with request-local async context to scope every cached key to the current tenant / user / client automatically:
58
+
59
+ ```ts
60
+ options: {
61
+ redis: {
62
+ url: "...",
63
+ globalPrefix: () => `tenant-${currentContext.tenantId}`,
64
+ },
65
+ }
66
+ ```
67
+
68
+ ## Key & namespace safety (file / redis)
69
+
70
+ Cache keys can carry untrusted input (e.g. a `cached()` auto-key derived from a request param), so every built-in driver treats a key as data, never as a path or a query fragment:
71
+
72
+ - **`file`** — a key/namespace maps to exactly one on-disk directory component: `%`, `/`, and `\` are percent-encoded before the path is built, so `../../etc` becomes an inert directory name instead of a traversal. The resolved path is also asserted to stay inside the configured cache root (throws `CacheError` otherwise) — a second, encoding-independent check. `removeNamespace` clears both the namespace's own directory and every dotted child (`ns`, `ns.*`), honoring `globalPrefix`.
73
+ - **`redis`** — `removeNamespace` escapes glob metacharacters (`*`, `?`, `[`, `\`) in the namespace before building its match pattern, and walks matches with a `SCAN` cursor (`scanIterator`) instead of the blocking `KEYS` command, so clearing a namespace on a large keyspace doesn't stall the event loop for other tenants.
74
+ - **Errors that echo a connection string** (Redis `connect()` failures, any driver's failed op) are logged through a `safeErrorInfo()` helper that redacts `scheme://user:pass@` credentials to `scheme://[REDACTED]@` and never prints the raw `Error` object — see [`@warlock.js/cache/handle-cache-errors/SKILL.md`](@warlock.js/cache/handle-cache-errors/SKILL.md).
75
+
76
+ ## Registering a custom driver
77
+
78
+ ```ts
79
+ import { BaseCacheDriver, cache } from "@warlock.js/cache";
80
+
81
+ class MemcachedCacheDriver extends BaseCacheDriver<MyClient, MyOptions> {
82
+ public name = "memcached";
83
+ // … implement set / get / remove / flush / removeNamespace / connect
84
+ }
85
+
86
+ cache.setCacheConfigurations({
87
+ default: "memcached",
88
+ drivers: { memcached: MemcachedCacheDriver },
89
+ options: { memcached: { host: "localhost" } },
90
+ });
91
+ ```
92
+
93
+ Extending `BaseCacheDriver` gives you free: TTL parsing, key parsing, event emission, stampede-safe `remember`, deep-clone-on-read, default `update` / `merge` / `list` implementations.
94
+
95
+ ## Runtime driver options — `cache.use(name, options)`
96
+
97
+ Some driver options can only be built at runtime (`pg`'s `client: pg.Pool`, pre-wired clients). Pass them as the second arg to `cache.use` / `cache.load` / `cache.driver` — they merge over `setCacheConfigurations({ options })` per-key, runtime wins.
98
+
99
+ ```ts
100
+ const pool = new Pool({ connectionString: process.env.DATABASE_URL });
101
+
102
+ cache.setCacheConfigurations({
103
+ default: "pg",
104
+ drivers: { pg: PgCacheDriver },
105
+ options: { pg: { table: "cache" } }, // static
106
+ });
107
+
108
+ await cache.use("pg", { client: pool }); // runtime — skip init() in this case
109
+ ```
110
+
111
+ Constraints:
112
+ - The driver name must be registered in `setCacheConfigurations({ drivers })` — runtime options don't bypass registration.
113
+ - Once a driver is loaded, calling `use`/`load`/`driver` again with **non-empty** new options throws `CacheConfigurationError`. Register a second driver name if you need a different config.
114
+ - Calling without options (or with `{}`) on an already-loaded driver returns the cached instance silently.
115
+
116
+ ## Per-call driver override
117
+
118
+ When most writes go to the default driver but one call needs a different one:
119
+
120
+ ```ts
121
+ await cache.set("audit:event", event, { driver: "redis" });
122
+ ```
123
+
124
+ The manager loads (and connects) the override driver lazily on first use, then routes that single operation through it without mutating `currentDriver`.
125
+
126
+ ## See also
127
+
128
+ - [`@warlock.js/cache/configure-pg-cache/SKILL.md`](@warlock.js/cache/configure-pg-cache/SKILL.md) — full pg setup (KV-only and pgvector mode)
129
+ - [`@warlock.js/cache/test-cache-code/SKILL.md`](@warlock.js/cache/test-cache-code/SKILL.md) — `MockCacheDriver` and `NullCacheDriver` for tests