@warlock.js/cache 5.28.0 → 5.30.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 (114) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/cjs/index.cjs +91 -47
  3. package/cjs/index.cjs.map +1 -1
  4. package/esm/cache-manager.d.mts +1 -1
  5. package/esm/cache-manager.d.mts.map +1 -1
  6. package/esm/cache-manager.mjs +1 -1
  7. package/esm/cache-manager.mjs.map +1 -1
  8. package/esm/cached/auto-key.d.mts +1 -1
  9. package/esm/cached/auto-key.d.mts.map +1 -1
  10. package/esm/cached/auto-key.mjs +1 -1
  11. package/esm/cached/auto-key.mjs.map +1 -1
  12. package/esm/cached/cached.d.mts +1 -1
  13. package/esm/cached/cached.d.mts.map +1 -1
  14. package/esm/cached/cached.mjs +1 -1
  15. package/esm/cached/cached.mjs.map +1 -1
  16. package/esm/cached/normalize-args.d.mts +1 -1
  17. package/esm/cached/normalize-args.d.mts.map +1 -1
  18. package/esm/cached/normalize-args.mjs +1 -1
  19. package/esm/cached/normalize-args.mjs.map +1 -1
  20. package/esm/drivers/base-cache-driver.d.mts +11 -5
  21. package/esm/drivers/base-cache-driver.d.mts.map +1 -1
  22. package/esm/drivers/base-cache-driver.mjs +29 -13
  23. package/esm/drivers/base-cache-driver.mjs.map +1 -1
  24. package/esm/drivers/file-cache-driver.d.mts +1 -1
  25. package/esm/drivers/file-cache-driver.d.mts.map +1 -1
  26. package/esm/drivers/file-cache-driver.mjs +2 -2
  27. package/esm/drivers/file-cache-driver.mjs.map +1 -1
  28. package/esm/drivers/in-memory-tag-index.d.mts +1 -1
  29. package/esm/drivers/in-memory-tag-index.d.mts.map +1 -1
  30. package/esm/drivers/in-memory-tag-index.mjs +1 -1
  31. package/esm/drivers/in-memory-tag-index.mjs.map +1 -1
  32. package/esm/drivers/index.d.mts +1 -1
  33. package/esm/drivers/index.mjs +1 -1
  34. package/esm/drivers/lru-memory-cache-driver.d.mts +1 -1
  35. package/esm/drivers/lru-memory-cache-driver.d.mts.map +1 -1
  36. package/esm/drivers/lru-memory-cache-driver.mjs +3 -2
  37. package/esm/drivers/lru-memory-cache-driver.mjs.map +1 -1
  38. package/esm/drivers/memory-cache-driver.d.mts +1 -1
  39. package/esm/drivers/memory-cache-driver.d.mts.map +1 -1
  40. package/esm/drivers/memory-cache-driver.mjs +3 -2
  41. package/esm/drivers/memory-cache-driver.mjs.map +1 -1
  42. package/esm/drivers/memory-extended-cache-driver.d.mts +1 -1
  43. package/esm/drivers/memory-extended-cache-driver.d.mts.map +1 -1
  44. package/esm/drivers/memory-extended-cache-driver.mjs +1 -1
  45. package/esm/drivers/memory-extended-cache-driver.mjs.map +1 -1
  46. package/esm/drivers/mock-cache-driver.d.mts +1 -1
  47. package/esm/drivers/mock-cache-driver.d.mts.map +1 -1
  48. package/esm/drivers/mock-cache-driver.mjs +3 -2
  49. package/esm/drivers/mock-cache-driver.mjs.map +1 -1
  50. package/esm/drivers/null-cache-driver.d.mts +5 -1
  51. package/esm/drivers/null-cache-driver.d.mts.map +1 -1
  52. package/esm/drivers/null-cache-driver.mjs +17 -10
  53. package/esm/drivers/null-cache-driver.mjs.map +1 -1
  54. package/esm/drivers/pg-cache-driver.d.mts +1 -1
  55. package/esm/drivers/pg-cache-driver.d.mts.map +1 -1
  56. package/esm/drivers/pg-cache-driver.mjs +3 -2
  57. package/esm/drivers/pg-cache-driver.mjs.map +1 -1
  58. package/esm/drivers/redis-cache-driver.d.mts +14 -2
  59. package/esm/drivers/redis-cache-driver.d.mts.map +1 -1
  60. package/esm/drivers/redis-cache-driver.mjs +24 -4
  61. package/esm/drivers/redis-cache-driver.mjs.map +1 -1
  62. package/esm/index.d.mts +2 -2
  63. package/esm/index.mjs +2 -2
  64. package/esm/list/memory-cache-list.d.mts +1 -1
  65. package/esm/list/memory-cache-list.d.mts.map +1 -1
  66. package/esm/list/memory-cache-list.mjs +1 -1
  67. package/esm/list/memory-cache-list.mjs.map +1 -1
  68. package/esm/metrics.d.mts +1 -1
  69. package/esm/metrics.d.mts.map +1 -1
  70. package/esm/metrics.mjs +1 -1
  71. package/esm/metrics.mjs.map +1 -1
  72. package/esm/scoped-cache.d.mts +1 -1
  73. package/esm/scoped-cache.d.mts.map +1 -1
  74. package/esm/scoped-cache.mjs +1 -1
  75. package/esm/scoped-cache.mjs.map +1 -1
  76. package/esm/tagged-cache.d.mts +1 -1
  77. package/esm/tagged-cache.d.mts.map +1 -1
  78. package/esm/tagged-cache.mjs +1 -1
  79. package/esm/tagged-cache.mjs.map +1 -1
  80. package/esm/tagged-scoped-cache.d.mts +1 -1
  81. package/esm/tagged-scoped-cache.d.mts.map +1 -1
  82. package/esm/tagged-scoped-cache.mjs +1 -1
  83. package/esm/tagged-scoped-cache.mjs.map +1 -1
  84. package/esm/types.d.mts +1 -1
  85. package/esm/types.d.mts.map +1 -1
  86. package/esm/types.mjs +1 -1
  87. package/esm/types.mjs.map +1 -1
  88. package/esm/utils.d.mts +1 -1
  89. package/esm/utils.d.mts.map +1 -1
  90. package/esm/utils.mjs +1 -1
  91. package/esm/utils.mjs.map +1 -1
  92. package/llms-full.txt +107 -103
  93. package/llms.txt +18 -18
  94. package/package.json +5 -4
  95. package/skills/apply-cache-patterns/SKILL.md +10 -10
  96. package/skills/cache-basics/SKILL.md +24 -20
  97. package/skills/configure-pg-cache/SKILL.md +5 -5
  98. package/skills/configure-set-options/SKILL.md +3 -3
  99. package/skills/handle-cache-errors/SKILL.md +3 -3
  100. package/skills/observe-cache/SKILL.md +1 -1
  101. package/skills/overview/SKILL.md +21 -21
  102. package/skills/pick-cache-driver/SKILL.md +4 -4
  103. package/skills/test-cache-code/SKILL.md +2 -2
  104. package/skills/use-cache-atomic/SKILL.md +5 -5
  105. package/skills/use-cache-bulk/SKILL.md +5 -5
  106. package/skills/use-cache-list/SKILL.md +2 -2
  107. package/skills/use-cache-lock/SKILL.md +2 -2
  108. package/skills/use-cache-namespace/SKILL.md +4 -4
  109. package/skills/use-cache-similarity/SKILL.md +2 -2
  110. package/skills/use-cache-tags/SKILL.md +4 -4
  111. package/skills/use-cache-update-merge/SKILL.md +1 -1
  112. package/skills/use-cache-utils/SKILL.md +4 -4
  113. package/skills/use-cached-hof/SKILL.md +2 -2
  114. package/skills/use-swr/SKILL.md +3 -3
package/llms-full.txt CHANGED
@@ -8,12 +8,12 @@
8
8
 
9
9
  ---
10
10
  name: apply-cache-patterns
11
- description: 'Compose cache primitives into real-world patterns — remember() memoization, cross-node stampede protection via a distributed lock (onConflict: ''create''), negative caching, and per-tenant scoping. Triggers: `cache.remember`, `cache.set` with `onConflict: "create"`, `globalPrefix`; "memoize this function", "prevent cache stampede across nodes", "cache not-found results", "per-tenant cache scoping"; typical import `import { cache } from "@warlock.js/cache"`. Skip: counters — `@warlock.js/cache/use-cache-atomic/SKILL.md`; bulk get/set — `@warlock.js/cache/use-cache-bulk/SKILL.md`; TTL constants/utilities — `@warlock.js/cache/use-cache-utils/SKILL.md`; named lock wrapper — `@warlock.js/cache/use-cache-lock/SKILL.md`; SWR — `@warlock.js/cache/use-swr/SKILL.md`; competing libs `lru-cache`, `node-cache`, `keyv`.'
11
+ description: 'Compose cache primitives into real-world patterns — remember() memoization, cross-node stampede protection via a distributed lock (onConflict: ''create''), negative caching, and per-tenant scoping. Triggers: `cache.remember`, `cache.set` with `onConflict: "create"`, `globalPrefix`; "memoize this function", "prevent cache stampede across nodes", "cache not-found results", "per-tenant cache scoping"; typical import `import { cache } from "@warlock.js/cache"`. Skip: counters — the `use-cache-atomic` topic; bulk get/set — the `use-cache-bulk` topic; TTL constants/utilities — the `use-cache-utils` topic; named lock wrapper — the `use-cache-lock` topic; SWR — the `use-swr` topic; competing libs `lru-cache`, `node-cache`, `keyv`.'
12
12
  ---
13
13
 
14
14
  # Real-world caching patterns
15
15
 
16
- Common shapes — the "general patterns" file. Specialized topics have dedicated skills: [`use-cache-tags`](@warlock.js/cache/use-cache-tags/SKILL.md), [`use-cache-namespace`](@warlock.js/cache/use-cache-namespace/SKILL.md), [`use-swr`](@warlock.js/cache/use-swr/SKILL.md), [`use-cache-lock`](@warlock.js/cache/use-cache-lock/SKILL.md), [`use-cache-list`](@warlock.js/cache/use-cache-list/SKILL.md).
16
+ Common shapes — the "general patterns" file. Specialized shapes live in their own topics: `use-cache-tags`, `use-cache-namespace`, `use-swr`, `use-cache-lock`, `use-cache-list`.
17
17
 
18
18
  ## Memoize an expensive function — `remember`
19
19
 
@@ -53,7 +53,7 @@ try {
53
53
  }
54
54
  ```
55
55
 
56
- This requires a driver with atomic `SET NX` — Redis is native, memory/LRU/file emulate (single-process only). For a higher-level wrapper that does the lock-and-release for you, see [`@warlock.js/cache/use-cache-lock/SKILL.md`](@warlock.js/cache/use-cache-lock/SKILL.md).
56
+ This requires a driver with atomic `SET NX` — Redis is native, memory/LRU/file emulate (single-process only). For a higher-level wrapper that does the lock-and-release for you, see the `use-cache-lock` topic.
57
57
 
58
58
  ## Negative caching
59
59
 
@@ -97,13 +97,13 @@ await cache.tags([`tenant-${tenantId}`]).invalidate();
97
97
 
98
98
  ## See also
99
99
 
100
- - [`@warlock.js/cache/use-cache-atomic/SKILL.md`](@warlock.js/cache/use-cache-atomic/SKILL.md) — `increment` / `decrement` counters
101
- - [`@warlock.js/cache/use-cache-bulk/SKILL.md`](@warlock.js/cache/use-cache-bulk/SKILL.md) — `many` / `setMany`
102
- - [`@warlock.js/cache/use-cache-utils/SKILL.md`](@warlock.js/cache/use-cache-utils/SKILL.md) — `CACHE_FOR` constants and TTL/key helpers
103
- - [`@warlock.js/cache/use-cache-tags/SKILL.md`](@warlock.js/cache/use-cache-tags/SKILL.md) — tag-based invalidation
104
- - [`@warlock.js/cache/use-cache-namespace/SKILL.md`](@warlock.js/cache/use-cache-namespace/SKILL.md) — scoped handles and `removeNamespace`
105
- - [`@warlock.js/cache/use-swr/SKILL.md`](@warlock.js/cache/use-swr/SKILL.md) — stale-while-revalidate for slow upstreams
106
- - [`@warlock.js/cache/use-cached-hof/SKILL.md`](@warlock.js/cache/use-cached-hof/SKILL.md) — `cached()` HOF for declarative memoization
100
+ - The `use-cache-atomic` topic — `increment` / `decrement` counters
101
+ - The `use-cache-bulk` topic — `many` / `setMany`
102
+ - The `use-cache-utils` topic — `CACHE_FOR` constants and TTL/key helpers
103
+ - The `use-cache-tags` topic — tag-based invalidation
104
+ - The `use-cache-namespace` topic — scoped handles and `removeNamespace`
105
+ - The `use-swr` topic — stale-while-revalidate for slow upstreams
106
+ - The `use-cached-hof` topic — `cached()` HOF for declarative memoization
107
107
 
108
108
 
109
109
  ## cache-basics `@warlock.js/cache/cache-basics/SKILL.md`
@@ -134,16 +134,18 @@ The 10 things that are true in every cache use:
134
134
  1. **Public API is the `cache` singleton** (`import { cache } from "@warlock.js/cache"`). No `new CacheManager()` for consumers.
135
135
  2. **Every data op runs against the currently selected driver.** Switch via `cache.use("name")` or use a per-call override: `cache.set(k, v, { driver: "redis" })`.
136
136
  3. **Consumers never `await connect()` directly.** `cache.init()` does that once at startup after `cache.setCacheConfigurations(...)`. For drivers needing runtime-built options (e.g. `pg`'s `client: pg.Pool`), skip `init()` and call `cache.use("pg", { client: pool })`.
137
- 4. **TTL accepts three shapes at the call site**: `number` (seconds), `string` (`"1h"`, `"30m"`, `"7d"` — parsed via `ms`), or a full `CacheSetOptions` object. See [`@warlock.js/cache/configure-set-options/SKILL.md`](@warlock.js/cache/configure-set-options/SKILL.md).
138
- 5. **`update` and `merge` throw `CacheUnsupportedError` on the file driver.** Use memory or redis for atomic mutation. See [`@warlock.js/cache/use-cache-update-merge/SKILL.md`](@warlock.js/cache/use-cache-update-merge/SKILL.md).
137
+ 4. **TTL accepts three shapes at the call site**: `number` (seconds), `string` (`"1h"`, `"30m"`, `"7d"` — parsed via `ms`), or a full `CacheSetOptions` object. See the `configure-set-options` topic.
138
+ 5. **`update` and `merge` throw `CacheUnsupportedError` on the file driver.** Use memory or redis for atomic mutation. See the `use-cache-update-merge` topic.
139
139
  6. **The value you read is a deep clone.** `structuredClone` protects the cache from accidental mutation of returned objects.
140
- 7. **`remember()` is stampede-safe within a single process.** Cross-process safety requires `onConflict: "create"` plus TTL (Redis-native). For slow upstreams where slightly-stale data is acceptable, prefer `cache.swr(...)` — see [`@warlock.js/cache/use-swr/SKILL.md`](@warlock.js/cache/use-swr/SKILL.md).
140
+ 7. **`remember()` is stampede-safe within a single process.** Cross-process safety requires `onConflict: "create"` plus TTL (Redis-native). For slow upstreams where slightly-stale data is acceptable, prefer `cache.swr(...)` — see the `use-swr` topic.
141
141
  8. **`cache.metrics()` returns a running snapshot** — counters, hit rate, latency percentiles, per-driver breakdowns. Lazy: collector attaches on first call so apps that never read metrics pay zero cost.
142
- 9. **`cache.namespace(prefix, options?)` returns a scoped handle** — every key auto-prefixed, scope-level `ttl` / `tags` defaults. See [`@warlock.js/cache/use-cache-namespace/SKILL.md`](@warlock.js/cache/use-cache-namespace/SKILL.md).
143
- 10. **Similarity retrieval** lives on the same driver contract — `set(k, v, { vector })` indexes the entry; `cache.similar(vec, ...)` returns nearest hits. See [`@warlock.js/cache/use-cache-similarity/SKILL.md`](@warlock.js/cache/use-cache-similarity/SKILL.md).
142
+ 9. **`cache.namespace(prefix, options?)` returns a scoped handle** — every key auto-prefixed, scope-level `ttl` / `tags` defaults. See the `use-cache-namespace` topic.
143
+ 10. **Similarity retrieval** lives on the same driver contract — `set(k, v, { vector })` indexes the entry; `cache.similar(vec, ...)` returns nearest hits. See the `use-cache-similarity` topic.
144
144
 
145
145
  ## Minimal startup
146
146
 
147
+ > **In a Warlock app** you do not call `cache.setCacheConfigurations(...)` or `cache.init()` yourself. Put the configuration in `src/config/cache.ts`; the framework's cache connector reads it (`config.get("cache")`), calls `cache.setCacheConfigurations(...)`, then `await cache.init()` at boot, and does nothing if no cache config exists. The manual setup below is for standalone use (scripts, tests, non-Warlock services).
148
+
147
149
  ```ts
148
150
  import {
149
151
  cache,
@@ -169,6 +171,8 @@ cache.setCacheConfigurations(config);
169
171
  await cache.init();
170
172
  ```
171
173
 
174
+ `logging` turns the per-operation key log (`<key> fetched`, `<key> not found`, ...) on or off. Email addresses inside logged keys are masked (`user.member@example.com` logs as `u***@example.com`), in operation lines and in error lines (which are written even with logging off); other personal data in a key is logged as is, so keep it out of keys or turn logging off in production.
175
+
172
176
  ## Primary ops
173
177
 
174
178
  ```ts
@@ -204,21 +208,21 @@ const user = await cache.remember("user.1", "1h", async () => db.users.find(1));
204
208
 
205
209
  | If the task is about… | Load |
206
210
  | --- | --- |
207
- | Choosing a driver, configuring it, or understanding what each one does best | [`@warlock.js/cache/pick-cache-driver/SKILL.md`](@warlock.js/cache/pick-cache-driver/SKILL.md) |
208
- | The `set` options object (`ttl`, `expiresAt`, `tags`, `onConflict`, `driver`, `vector`) | [`@warlock.js/cache/configure-set-options/SKILL.md`](@warlock.js/cache/configure-set-options/SKILL.md) |
209
- | Memoization with `remember()`, counters, negative caching, per-tenant scoping, TTL constants | [`@warlock.js/cache/apply-cache-patterns/SKILL.md`](@warlock.js/cache/apply-cache-patterns/SKILL.md) |
210
- | Scoped handles via `cache.namespace(prefix, options?)` | [`@warlock.js/cache/use-cache-namespace/SKILL.md`](@warlock.js/cache/use-cache-namespace/SKILL.md) |
211
- | Tag-based invalidation — `cache.tags([...]).invalidate()` | [`@warlock.js/cache/use-cache-tags/SKILL.md`](@warlock.js/cache/use-cache-tags/SKILL.md) |
212
- | Stale-while-revalidate — `cache.swr(...)` | [`@warlock.js/cache/use-swr/SKILL.md`](@warlock.js/cache/use-swr/SKILL.md) |
213
- | Wrapping a function with `cached()` — HOF memoization with `.invalidate()` | [`@warlock.js/cache/use-cached-hof/SKILL.md`](@warlock.js/cache/use-cached-hof/SKILL.md) |
214
- | Distributed locks — `cache.lock(key, ttl, fn)` with auto-release | [`@warlock.js/cache/use-cache-lock/SKILL.md`](@warlock.js/cache/use-cache-lock/SKILL.md) |
215
- | Queues, recent-N buffers, `push`/`shift`/`trim` — the list sub-API | [`@warlock.js/cache/use-cache-list/SKILL.md`](@warlock.js/cache/use-cache-list/SKILL.md) |
216
- | Atomic read-modify-write via `update()` and `merge()` | [`@warlock.js/cache/use-cache-update-merge/SKILL.md`](@warlock.js/cache/use-cache-update-merge/SKILL.md) |
217
- | Similarity retrieval — `set({ vector })` + `cache.similar(...)` | [`@warlock.js/cache/use-cache-similarity/SKILL.md`](@warlock.js/cache/use-cache-similarity/SKILL.md) |
218
- | Postgres driver setup (KV-only or with pgvector) | [`@warlock.js/cache/configure-pg-cache/SKILL.md`](@warlock.js/cache/configure-pg-cache/SKILL.md) |
219
- | `cache.metrics()` aggregate snapshot + event bus for per-event reactions | [`@warlock.js/cache/observe-cache/SKILL.md`](@warlock.js/cache/observe-cache/SKILL.md) |
220
- | Error classes (`CacheConfigurationError`, `CacheUnsupportedError`, etc.) | [`@warlock.js/cache/handle-cache-errors/SKILL.md`](@warlock.js/cache/handle-cache-errors/SKILL.md) |
221
- | Tests that touch cache code paths — `MockCacheDriver`, `MemoryCacheDriver` | [`@warlock.js/cache/test-cache-code/SKILL.md`](@warlock.js/cache/test-cache-code/SKILL.md) |
211
+ | Choosing a driver, configuring it, or understanding what each one does best | The `pick-cache-driver` topic |
212
+ | The `set` options object (`ttl`, `expiresAt`, `tags`, `onConflict`, `driver`, `vector`) | The `configure-set-options` topic |
213
+ | Memoization with `remember()`, counters, negative caching, per-tenant scoping, TTL constants | The `apply-cache-patterns` topic |
214
+ | Scoped handles via `cache.namespace(prefix, options?)` | The `use-cache-namespace` topic |
215
+ | Tag-based invalidation — `cache.tags([...]).invalidate()` | The `use-cache-tags` topic |
216
+ | Stale-while-revalidate — `cache.swr(...)` | The `use-swr` topic |
217
+ | Wrapping a function with `cached()` — HOF memoization with `.invalidate()` | The `use-cached-hof` topic |
218
+ | Distributed locks — `cache.lock(key, ttl, fn)` with auto-release | The `use-cache-lock` topic |
219
+ | Queues, recent-N buffers, `push`/`shift`/`trim` — the list sub-API | The `use-cache-list` topic |
220
+ | Atomic read-modify-write via `update()` and `merge()` | The `use-cache-update-merge` topic |
221
+ | Similarity retrieval — `set({ vector })` + `cache.similar(...)` | The `use-cache-similarity` topic |
222
+ | Postgres driver setup (KV-only or with pgvector) | The `configure-pg-cache` topic |
223
+ | `cache.metrics()` aggregate snapshot + event bus for per-event reactions | The `observe-cache` topic |
224
+ | Error classes (`CacheConfigurationError`, `CacheUnsupportedError`, etc.) | The `handle-cache-errors` topic |
225
+ | Tests that touch cache code paths — `MockCacheDriver`, `MemoryCacheDriver` | The `test-cache-code` topic |
222
226
 
223
227
  ## Things NOT to do
224
228
 
@@ -237,7 +241,7 @@ const user = await cache.remember("user.1", "1h", async () => db.users.find(1));
237
241
 
238
242
  ---
239
243
  name: configure-pg-cache
240
- description: 'Postgres cache driver setup — KV-only mode (default) or pgvector mode (opt in via options.pg.vector). Caller owns the pg.Pool, driver exposes driver.schema() for one-time DDL. Triggers: `PgCacheDriver`, `driver.schema`, `options.pg.vector`, `pg.Pool`, `hnsw`, `ivfflat`; "use Postgres as cache backend", "set up pgvector semantic cache", "DDL for warlock cache table"; typical import `import { cache, PgCacheDriver } from "@warlock.js/cache"`. Skip: cross-driver similarity API — `@warlock.js/cache/use-cache-similarity/SKILL.md`; driver picker — `@warlock.js/cache/pick-cache-driver/SKILL.md`; competing libs `pg-mem`; raw `pg` / `node-postgres`.'
244
+ description: 'Postgres cache driver setup — KV-only mode (default) or pgvector mode (opt in via options.pg.vector). Caller owns the pg.Pool, driver exposes driver.schema() for one-time DDL. Triggers: `PgCacheDriver`, `driver.schema`, `options.pg.vector`, `pg.Pool`, `hnsw`, `ivfflat`; "use Postgres as cache backend", "set up pgvector semantic cache", "DDL for warlock cache table"; typical import `import { cache, PgCacheDriver } from "@warlock.js/cache"`. Skip: cross-driver similarity API — the `use-cache-similarity` topic; driver picker — the `pick-cache-driver` topic; competing libs `pg-mem`; raw `pg` / `node-postgres`.'
241
245
  ---
242
246
 
243
247
  # `pg` cache driver — Postgres setup
@@ -256,7 +260,7 @@ Persistent cache backed by your existing Postgres pool. Two modes: **KV-only** (
256
260
  - `update` → `UPDATE ... WHERE expires_at IS NULL OR expires_at > now() RETURNING value`.
257
261
  - `upsert` → unconditional `INSERT ... ON CONFLICT DO UPDATE`.
258
262
  7. **Atomic ops are cross-server:** `increment` is one upsert that keeps `expires_at`, `pull` is `DELETE … RETURNING`, `update` is a compare-and-set with bounded retries (callback may run more than once). Tags use a one-statement JSON-array merge.
259
- 8. **`stale_at TIMESTAMPTZ` column** powers [stale-while-revalidate](@warlock.js/cache/use-swr/SKILL.md) — `cache.swr(...)` populates it on writes, plain `set()` leaves it null (always-fresh). Provision via `driver.schema()` like any other column.
263
+ 8. **`stale_at TIMESTAMPTZ` column** powers stale-while-revalidate (see the `use-swr` topic) — `cache.swr(...)` populates it on writes, plain `set()` leaves it null (always-fresh). Provision via `driver.schema()` like any other column.
260
264
  9. **pgvector requires `CREATE EXTENSION vector;` once on the database.** Lazy probe on first vector op throws `CacheConfigurationError` if missing; result is cached.
261
265
  10. **Vectors are passed as text literals** (`'[1,2,3]'::vector`). No binary protocol dependency — works against any pg client.
262
266
 
@@ -337,7 +341,7 @@ Switching strategies requires rebuilding the index.
337
341
  - `CacheConfigurationError: vector dimension mismatch` — input vector length ≠ configured dimensions. Embedder probably changed.
338
342
  - `CacheUnsupportedError: similarity retrieval requires the 'vector' config block` — KV-only mode; add `options.pg.vector`.
339
343
 
340
- See [`@warlock.js/cache/handle-cache-errors/SKILL.md`](@warlock.js/cache/handle-cache-errors/SKILL.md) for the full error class hierarchy.
344
+ See the `handle-cache-errors` topic for the full error class hierarchy.
341
345
 
342
346
  ## Things NOT to do
343
347
 
@@ -349,15 +353,15 @@ See [`@warlock.js/cache/handle-cache-errors/SKILL.md`](@warlock.js/cache/handle-
349
353
 
350
354
  ## Related
351
355
 
352
- - [`@warlock.js/cache/use-cache-similarity/SKILL.md`](@warlock.js/cache/use-cache-similarity/SKILL.md) — the `similar()` API across all drivers
353
- - [`@warlock.js/cache/pick-cache-driver/SKILL.md`](@warlock.js/cache/pick-cache-driver/SKILL.md) — comparing pg with memory / redis / file
356
+ - The `use-cache-similarity` topic — the `similar()` API across all drivers
357
+ - The `pick-cache-driver` topic — comparing pg with memory / redis / file
354
358
 
355
359
 
356
360
  ## configure-set-options `@warlock.js/cache/configure-set-options/SKILL.md`
357
361
 
358
362
  ---
359
363
  name: configure-set-options
360
- description: 'Configure cache.set''s third argument — ttl, expiresAt, tags, onConflict (create / update / upsert), driver, vector. Triggers: `cache.set`, `ttl`, `expiresAt`, `tags`, `onConflict`, `driver`, `vector`, `CacheSetResult`, `wasSet`; "set a key only if missing", "set with absolute deadline", "attach tags inline", "route one cache call to redis"; typical import `import { cache } from "@warlock.js/cache"`. Skip: tag fluent API — `@warlock.js/cache/use-cache-tags/SKILL.md`; vector queries — `@warlock.js/cache/use-cache-similarity/SKILL.md`; competing libs `keyv`, `ioredis`.'
364
+ description: 'Configure cache.set''s third argument — ttl, expiresAt, tags, onConflict (create / update / upsert), driver, vector. Triggers: `cache.set`, `ttl`, `expiresAt`, `tags`, `onConflict`, `driver`, `vector`, `CacheSetResult`, `wasSet`; "set a key only if missing", "set with absolute deadline", "attach tags inline", "route one cache call to redis"; typical import `import { cache } from "@warlock.js/cache"`. Skip: tag fluent API — the `use-cache-tags` topic; vector queries — the `use-cache-similarity` topic; competing libs `keyv`, `ioredis`.'
361
365
  ---
362
366
 
363
367
  # The `set` options object
@@ -389,10 +393,10 @@ await cache.set("user:1", user, {
389
393
  | --- | --- | --- |
390
394
  | `ttl` | `number \| string` | Relative expiry. Mutually exclusive with `expiresAt`. |
391
395
  | `expiresAt` | `number \| Date` | Absolute deadline (epoch ms or Date). Must be in the future. Mutually exclusive with `ttl`. |
392
- | `tags` | `string[]` | Inline equivalent of `cache.tags([...]).set(...)`. See [`@warlock.js/cache/use-cache-tags/SKILL.md`](@warlock.js/cache/use-cache-tags/SKILL.md). |
396
+ | `tags` | `string[]` | Inline equivalent of `cache.tags([...]).set(...)`. See the `use-cache-tags` topic. |
393
397
  | `onConflict` | `"create" \| "update" \| "upsert"` | See below. Default `"upsert"`. |
394
398
  | `driver` | `string` | Per-call driver override by registered name. |
395
- | `vector` | `number[]` | Embedding indexed alongside the entry for [`cache.similar()`](@warlock.js/cache/use-cache-similarity/SKILL.md). Drivers without similarity support throw `CacheUnsupportedError`. |
399
+ | `vector` | `number[]` | Embedding indexed alongside the entry for `cache.similar()` (see the `use-cache-similarity` topic). Drivers without similarity support throw `CacheUnsupportedError`. |
396
400
 
397
401
  ## TTL parsing
398
402
 
@@ -463,7 +467,7 @@ await cache.set("k", v, undefined); // same as no TTL
463
467
 
464
468
  ---
465
469
  name: handle-cache-errors
466
- description: 'Cache error classes — CacheError base, CacheConfigurationError, CacheConnectionError, CacheDriverNotInitializedError, CacheUnsupportedError, CacheConcurrencyError. Triggers: `CacheError`, `CacheConfigurationError`, `CacheConnectionError`, `CacheDriverNotInitializedError`, `CacheUnsupportedError`, `CacheConcurrencyError`; "catch cache errors at the boundary", "degrade when update or merge throws", "what does CacheUnsupportedError mean", "fall back when redis is down"; typical import `import { CacheError, CacheConfigurationError, CacheUnsupportedError } from "@warlock.js/cache"`. Skip: choosing a supported driver — `@warlock.js/cache/pick-cache-driver/SKILL.md`; observing errors via events — `@warlock.js/cache/observe-cache/SKILL.md`; competing libs ignore — generic `Error` patterns.'
470
+ description: 'Cache error classes — CacheError base, CacheConfigurationError, CacheConnectionError, CacheDriverNotInitializedError, CacheUnsupportedError, CacheConcurrencyError. Triggers: `CacheError`, `CacheConfigurationError`, `CacheConnectionError`, `CacheDriverNotInitializedError`, `CacheUnsupportedError`, `CacheConcurrencyError`; "catch cache errors at the boundary", "degrade when update or merge throws", "what does CacheUnsupportedError mean", "fall back when redis is down"; typical import `import { CacheError, CacheConfigurationError, CacheUnsupportedError } from "@warlock.js/cache"`. Skip: choosing a supported driver — the `pick-cache-driver` topic; observing errors via events — the `observe-cache` topic; competing libs ignore — generic `Error` patterns.'
467
471
  ---
468
472
 
469
473
  # Error classes
@@ -489,7 +493,7 @@ import {
489
493
  | `CacheDriverNotInitializedError` | Any data op called before `cache.init()` / `cache.use()` | Call `cache.init()` at app startup. Tests often forget this — add a `beforeEach`. |
490
494
  | `CacheUnsupportedError` | Driver doesn't implement the requested op. Today: `update` / `merge` on the file driver; `set({ vector })` and `similar()` on file / redis / pg-without-`vector`-config. | Switch driver (memory family for dev similarity, `pg` with `vector` config for production), or queue the op. |
491
495
  | `CacheConcurrencyError` | Declared for future optimistic-concurrency exhaustion on Redis `update()` | Not thrown today. Reserved for the v2.1 `WATCH`/`MULTI` implementation. |
492
- | `CacheError` (file driver, path containment) | A key/namespace, after percent-encoding, still resolves outside the cache root — `"Cache key resolves outside the cache directory: ..."`. Last line of defense against path traversal on top of the encoding step; should not happen in normal use. | Treat as a programmer/attacker-input error — don't retry; investigate the key source. See [`@warlock.js/cache/pick-cache-driver/SKILL.md`](@warlock.js/cache/pick-cache-driver/SKILL.md) for the encoding contract. |
496
+ | `CacheError` (file driver, path containment) | A key/namespace, after percent-encoding, still resolves outside the cache root — `"Cache key resolves outside the cache directory: ..."`. Last line of defense against path traversal on top of the encoding step; should not happen in normal use. | Treat as a programmer/attacker-input error — don't retry; investigate the key source. See the `pick-cache-driver` topic for the encoding contract. |
493
497
 
494
498
  ## Special case — `setNX` unsupported
495
499
 
@@ -499,7 +503,7 @@ Calling `cache.setNX(...)` on a driver that doesn't implement it throws a plain
499
503
  // Error: "setNX is not supported by the current cache driver: memory"
500
504
  ```
501
505
 
502
- This is legacy. The v2-preferred way is `cache.set(k, v, { onConflict: "create" })` which works on every driver (Redis native, others emulated). See [`@warlock.js/cache/configure-set-options/SKILL.md`](@warlock.js/cache/configure-set-options/SKILL.md).
506
+ This is legacy. The v2-preferred way is `cache.set(k, v, { onConflict: "create" })` which works on every driver (Redis native, others emulated). See the `configure-set-options` topic.
503
507
 
504
508
  ## Patterns
505
509
 
@@ -559,7 +563,7 @@ try {
559
563
 
560
564
  ---
561
565
  name: observe-cache
562
- description: 'Cache observability — cache.metrics() for aggregate hit rate / latency p50/p95/p99 + event bus (cache.on(''hit'' / ''miss'' / ''set'' / ''removed'' / ''flushed'' / ''expired'' / ''error'', ...)). Triggers: `cache.metrics`, `cache.resetMetrics`, `cache.on`, `hit`, `miss`, `removed`, `flushed`, `error`, `hitRate`, `latencyMs`; "show cache hit rate", "page on cache errors", "is my cache being hit", "export metrics to prometheus"; typical import `import { cache } from "@warlock.js/cache"`. Skip: error classes — `@warlock.js/cache/handle-cache-errors/SKILL.md`; competing libs `prom-client`, `statsd-client`.'
566
+ description: 'Cache observability — cache.metrics() for aggregate hit rate / latency p50/p95/p99 + event bus (cache.on(''hit'' / ''miss'' / ''set'' / ''removed'' / ''flushed'' / ''expired'' / ''error'', ...)). Triggers: `cache.metrics`, `cache.resetMetrics`, `cache.on`, `hit`, `miss`, `removed`, `flushed`, `error`, `hitRate`, `latencyMs`; "show cache hit rate", "page on cache errors", "is my cache being hit", "export metrics to prometheus"; typical import `import { cache } from "@warlock.js/cache"`. Skip: error classes — the `handle-cache-errors` topic; competing libs `prom-client`, `statsd-client`.'
563
567
  ---
564
568
 
565
569
  # Cache observability — `cache.metrics()` and the event bus
@@ -692,37 +696,37 @@ Nineteen task skills. Most apps start with `cache-basics` + `pick-cache-driver`
692
696
 
693
697
  ### Foundations
694
698
 
695
- - [`cache-basics`](@warlock.js/cache/cache-basics/SKILL.md) — the `cache` singleton, primary ops (`set`/`get`/`has`/`pull`/`remove`/`many`/`forever`/`increment`/`decrement`/`remember`), TTL shapes, init flow. **Start here.**
696
- - [`pick-cache-driver`](@warlock.js/cache/pick-cache-driver/SKILL.md) — choose + configure a driver: `null` / `memory` / `memoryExtended` / `lru` / `file` / `redis` / `pg` / `mock`; `globalPrefix` for multi-tenant scoping.
697
- - [`configure-set-options`](@warlock.js/cache/configure-set-options/SKILL.md) — `cache.set`'s third argument: `ttl`, `expiresAt`, `tags`, `onConflict` (create/update/upsert), `driver`, `vector`.
698
- - [`configure-pg-cache`](@warlock.js/cache/configure-pg-cache/SKILL.md) — Postgres driver: KV-only (default) or pgvector mode; caller owns the `pg.Pool`, `driver.schema()` emits the DDL.
699
+ - The `cache-basics` topic — the `cache` singleton, primary ops (`set`/`get`/`has`/`pull`/`remove`/`many`/`forever`/`increment`/`decrement`/`remember`), TTL shapes, init flow. **Start here.**
700
+ - The `pick-cache-driver` topic — choose + configure a driver: `null` / `memory` / `memoryExtended` / `lru` / `file` / `redis` / `pg` / `mock`; `globalPrefix` for multi-tenant scoping.
701
+ - The `configure-set-options` topic — `cache.set`'s third argument: `ttl`, `expiresAt`, `tags`, `onConflict` (create/update/upsert), `driver`, `vector`.
702
+ - The `configure-pg-cache` topic — Postgres driver: KV-only (default) or pgvector mode; caller owns the `pg.Pool`, `driver.schema()` emits the DDL.
699
703
 
700
704
  ### Invalidation + scoping
701
705
 
702
- - [`use-cache-tags`](@warlock.js/cache/use-cache-tags/SKILL.md) — tag on write, `cache.tags([...]).invalidate()` drops every bound key.
703
- - [`use-cache-namespace`](@warlock.js/cache/use-cache-namespace/SKILL.md) — `cache.namespace(prefix, options?)` auto-prefixes keys with scope-level TTL/tag defaults and nested scopes.
706
+ - The `use-cache-tags` topic — tag on write, `cache.tags([...]).invalidate()` drops every bound key.
707
+ - The `use-cache-namespace` topic — `cache.namespace(prefix, options?)` auto-prefixes keys with scope-level TTL/tag defaults and nested scopes.
704
708
 
705
709
  ### Patterns
706
710
 
707
- - [`use-cached-hof`](@warlock.js/cache/use-cached-hof/SKILL.md) — `cached(fn, options)` wraps an async function; one declaration, many call sites, a bound `.invalidate(...args)`.
708
- - [`apply-cache-patterns`](@warlock.js/cache/apply-cache-patterns/SKILL.md) — `remember()` memoization, distributed locks via `onConflict: "create"`, negative caching, counters, per-tenant prefix, `CACHE_FOR.*` TTL constants.
709
- - [`use-cache-lock`](@warlock.js/cache/use-cache-lock/SKILL.md) — `cache.lock(key, ttl, fn)`: acquire → run → auto-release. For cron/imports/migrations and idempotent webhook/payment processing.
710
- - [`use-swr`](@warlock.js/cache/use-swr/SKILL.md) — `cache.swr(key, { freshTtl, staleTtl }, fn)`: instant when fresh, instant + background refresh when stale, blocks only when fully expired.
711
- - [`use-cache-update-merge`](@warlock.js/cache/use-cache-update-merge/SKILL.md) — atomic read-modify-write via `cache.update(key, fn)` / `cache.merge(key, partial)`, serialized per key, TTL-preserving.
712
- - [`use-cache-atomic`](@warlock.js/cache/use-cache-atomic/SKILL.md) — `cache.increment` / `cache.decrement` counters; per-driver atomicity + TTL behavior.
713
- - [`use-cache-bulk`](@warlock.js/cache/use-cache-bulk/SKILL.md) — `cache.many(keys)` / `cache.setMany(record, ttl?)` for batch reads/writes.
714
- - [`use-cache-list`](@warlock.js/cache/use-cache-list/SKILL.md) — `cache.list<T>(key)`: `push`/`unshift`/`pop`/`shift`/`slice`/`trim`/`clear` for queues, recent-N buffers, sliding windows.
715
- - [`use-cache-similarity`](@warlock.js/cache/use-cache-similarity/SKILL.md) — `cache.similar(vector, { topK, threshold?, tags? })` for semantic caches, RAG retrieval, nearest-neighbor lookup.
711
+ - The `use-cached-hof` topic — `cached(fn, options)` wraps an async function; one declaration, many call sites, a bound `.invalidate(...args)`.
712
+ - The `apply-cache-patterns` topic — `remember()` memoization, distributed locks via `onConflict: "create"`, negative caching, counters, per-tenant prefix, `CACHE_FOR.*` TTL constants.
713
+ - The `use-cache-lock` topic — `cache.lock(key, ttl, fn)`: acquire → run → auto-release. For cron/imports/migrations and idempotent webhook/payment processing.
714
+ - The `use-swr` topic — `cache.swr(key, { freshTtl, staleTtl }, fn)`: instant when fresh, instant + background refresh when stale, blocks only when fully expired.
715
+ - The `use-cache-update-merge` topic — atomic read-modify-write via `cache.update(key, fn)` / `cache.merge(key, partial)`, serialized per key, TTL-preserving.
716
+ - The `use-cache-atomic` topic — `cache.increment` / `cache.decrement` counters; per-driver atomicity + TTL behavior.
717
+ - The `use-cache-bulk` topic — `cache.many(keys)` / `cache.setMany(record, ttl?)` for batch reads/writes.
718
+ - The `use-cache-list` topic — `cache.list<T>(key)`: `push`/`unshift`/`pop`/`shift`/`slice`/`trim`/`clear` for queues, recent-N buffers, sliding windows.
719
+ - The `use-cache-similarity` topic — `cache.similar(vector, { topK, threshold?, tags? })` for semantic caches, RAG retrieval, nearest-neighbor lookup.
716
720
 
717
721
  ### Operations
718
722
 
719
- - [`observe-cache`](@warlock.js/cache/observe-cache/SKILL.md) — `cache.metrics()` (hit rate, latency p50/p95/p99) + the event bus (`cache.on("hit" | "miss" | "set" | "removed" | "flushed" | "expired" | "connected" | "disconnected" | "error", …)`).
720
- - [`handle-cache-errors`](@warlock.js/cache/handle-cache-errors/SKILL.md) — the error classes: `CacheError`, `CacheConfigurationError`, `CacheConnectionError`, `CacheDriverNotInitializedError`, `CacheUnsupportedError`, `CacheConcurrencyError`.
721
- - [`test-cache-code`](@warlock.js/cache/test-cache-code/SKILL.md) — `MockCacheDriver` (behavioral assertions), `MemoryCacheDriver` (full-stack), `NullCacheDriver` (graceful degradation).
723
+ - The `observe-cache` topic — `cache.metrics()` (hit rate, latency p50/p95/p99) + the event bus (`cache.on("hit" | "miss" | "set" | "removed" | "flushed" | "expired" | "connected" | "disconnected" | "error", …)`).
724
+ - The `handle-cache-errors` topic — the error classes: `CacheError`, `CacheConfigurationError`, `CacheConnectionError`, `CacheDriverNotInitializedError`, `CacheUnsupportedError`, `CacheConcurrencyError`.
725
+ - The `test-cache-code` topic — `MockCacheDriver` (behavioral assertions), `MemoryCacheDriver` (full-stack), `NullCacheDriver` (graceful degradation).
722
726
 
723
727
  ### Utilities
724
728
 
725
- - [`use-cache-utils`](@warlock.js/cache/use-cache-utils/SKILL.md) — low-level re-exports: `parseTtl`, `parseCacheKey`, `resolveTtl`, `expiresAtToTtl`, `mergeTagSets`, `injectTags`, `cosineSimilarity`, and the `CACHE_FOR` TTL enum.
729
+ - The `use-cache-utils` topic — low-level re-exports: `parseTtl`, `parseCacheKey`, `resolveTtl`, `expiresAtToTtl`, `mergeTagSets`, `injectTags`, `cosineSimilarity`, and the `CACHE_FOR` TTL enum.
726
730
 
727
731
  ## What this package deliberately doesn't do
728
732
 
@@ -732,15 +736,15 @@ Nineteen task skills. Most apps start with `cache-basics` + `pick-cache-driver`
732
736
 
733
737
  ## See also
734
738
 
735
- - [`@warlock.js/core/overview/SKILL.md`](@warlock.js/core/overview/SKILL.md) — wires the cache driver from app config and exposes the singleton.
736
- - `mongez-agent-kit-authoring-skills` (load via agent-kit sync) — how this becomes `.claude/skills/warlock-js-cache-overview/`.
739
+ - The `configure-app` topic of the `warlock-js-core` skill — wires the cache driver from `src/config/cache.ts` and exposes the singleton.
740
+ - `mongez-agent-kit-authoring-skills` (load via agent-kit sync) — how this becomes `.claude/skills/warlock-js-cache/overview.md`.
737
741
 
738
742
 
739
743
  ## pick-cache-driver `@warlock.js/cache/pick-cache-driver/SKILL.md`
740
744
 
741
745
  ---
742
746
  name: pick-cache-driver
743
- 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`.'
747
+ 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 — the `cache-basics` topic; pg setup — the `configure-pg-cache` topic; competing libs `lru-cache`, `node-cache`, `keyv`, `ioredis`; native `Map`.'
744
748
  ---
745
749
 
746
750
  # Cache drivers — pick the right one
@@ -811,7 +815,7 @@ Cache keys can carry untrusted input (e.g. a `cached()` auto-key derived from a
811
815
 
812
816
  - **`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`.
813
817
  - **`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.
814
- - **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).
818
+ - **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 the `handle-cache-errors` topic.
815
819
 
816
820
  ## Registering a custom driver
817
821
 
@@ -865,15 +869,15 @@ The manager loads (and connects) the override driver lazily on first use, then r
865
869
 
866
870
  ## See also
867
871
 
868
- - [`@warlock.js/cache/configure-pg-cache/SKILL.md`](@warlock.js/cache/configure-pg-cache/SKILL.md) — full pg setup (KV-only and pgvector mode)
869
- - [`@warlock.js/cache/test-cache-code/SKILL.md`](@warlock.js/cache/test-cache-code/SKILL.md) — `MockCacheDriver` and `NullCacheDriver` for tests
872
+ - The `configure-pg-cache` topic — full pg setup (KV-only and pgvector mode)
873
+ - The `test-cache-code` topic — `MockCacheDriver` and `NullCacheDriver` for tests
870
874
 
871
875
 
872
876
  ## test-cache-code `@warlock.js/cache/test-cache-code/SKILL.md`
873
877
 
874
878
  ---
875
879
  name: test-cache-code
876
- description: 'Test code that touches cache — MockCacheDriver (behavioral assertions with wasCalled / callLog), MemoryCacheDriver (full-stack), NullCacheDriver (graceful-degradation). Triggers: `MockCacheDriver`, `MemoryCacheDriver`, `NullCacheDriver`, `wasCalled`, `callLog`, `getStored`, `reset`, `cache.on`; "assert cache was invalidated", "test code that uses cache.set", "mock cache in vitest", "test similarity without a real embedder", "stub the pg cache driver"; typical import `import { cache, MockCacheDriver, MemoryCacheDriver } from "@warlock.js/cache"`. Skip: real driver picks — `@warlock.js/cache/pick-cache-driver/SKILL.md`; competing libs `jest-mock`, `sinon`, `redis-mock`; native `vi.fn`.'
880
+ description: 'Test code that touches cache — MockCacheDriver (behavioral assertions with wasCalled / callLog), MemoryCacheDriver (full-stack), NullCacheDriver (graceful-degradation). Triggers: `MockCacheDriver`, `MemoryCacheDriver`, `NullCacheDriver`, `wasCalled`, `callLog`, `getStored`, `reset`, `cache.on`; "assert cache was invalidated", "test code that uses cache.set", "mock cache in vitest", "test similarity without a real embedder", "stub the pg cache driver"; typical import `import { cache, MockCacheDriver, MemoryCacheDriver } from "@warlock.js/cache"`. Skip: real driver picks — the `pick-cache-driver` topic; competing libs `jest-mock`, `sinon`, `redis-mock`; native `vi.fn`.'
877
881
  ---
878
882
 
879
883
  # Testing code that touches cache
@@ -1059,7 +1063,7 @@ await Promise.all(
1059
1063
  await expect(cache.get("counter")).resolves.toBe(10);
1060
1064
  ```
1061
1065
 
1062
- Runs in-process only — for cross-process safety see [`@warlock.js/cache/use-cache-lock/SKILL.md`](@warlock.js/cache/use-cache-lock/SKILL.md).
1066
+ Runs in-process only — for cross-process safety see the `use-cache-lock` topic.
1063
1067
 
1064
1068
  ## Testing similarity code paths
1065
1069
 
@@ -1096,7 +1100,7 @@ The `pg` driver accepts any object satisfying `PgClientLike` (`{ query(text, val
1096
1100
 
1097
1101
  ---
1098
1102
  name: use-cache-atomic
1099
- description: 'Atomic counters via cache.increment(key, by=1) / cache.decrement(key, by=1) — returns the new number, throws on non-numeric values. Triggers: `cache.increment`, `cache.decrement`, "view counter", "page views", "atomic counter", "decrement stock", "rate-limit counter", "INCRBY"; typical import `import { cache } from "@warlock.js/cache"`. Skip: read-modify-write of objects — `@warlock.js/cache/use-cache-update-merge/SKILL.md`; named-lock coordination — `@warlock.js/cache/use-cache-lock/SKILL.md`; competing libs `ioredis` `INCR`, native counters in a `Map`.'
1103
+ description: 'Atomic counters via cache.increment(key, by=1) / cache.decrement(key, by=1) — returns the new number, throws on non-numeric values. Triggers: `cache.increment`, `cache.decrement`, "view counter", "page views", "atomic counter", "decrement stock", "rate-limit counter", "INCRBY"; typical import `import { cache } from "@warlock.js/cache"`. Skip: read-modify-write of objects — the `use-cache-update-merge` topic; named-lock coordination — the `use-cache-lock` topic; competing libs `ioredis` `INCR`, native counters in a `Map`.'
1100
1104
  ---
1101
1105
 
1102
1106
  # Atomic counters — `cache.increment` / `cache.decrement`
@@ -1132,7 +1136,7 @@ Use `redis` or `pg` for anything several servers touch (rate limits, login throt
1132
1136
 
1133
1137
  ## TTL is kept
1134
1138
 
1135
- Every driver keeps the key's **remaining TTL** across `increment`/`decrement`, so a fixed-window counter can't turn permanent. Set the TTL when you create the counter (`cache.set(key, 0, "1m")`, then `increment`). Use [`cache.update`](@warlock.js/cache/use-cache-update-merge/SKILL.md) for objects.
1139
+ Every driver keeps the key's **remaining TTL** across `increment`/`decrement`, so a fixed-window counter can't turn permanent. Set the TTL when you create the counter (`cache.set(key, 0, "1m")`, then `increment`). Use `cache.update` (see the `use-cache-update-merge` topic) for objects.
1136
1140
 
1137
1141
  ## Common shapes
1138
1142
 
@@ -1150,16 +1154,16 @@ if (remaining < 0) {
1150
1154
 
1151
1155
  ## See also
1152
1156
 
1153
- - [`@warlock.js/cache/use-cache-update-merge/SKILL.md`](@warlock.js/cache/use-cache-update-merge/SKILL.md) — atomic read-modify-write for objects, TTL-preserving
1154
- - [`@warlock.js/cache/use-cache-lock/SKILL.md`](@warlock.js/cache/use-cache-lock/SKILL.md) — coordinate multi-step critical sections
1155
- - [`@warlock.js/cache/pick-cache-driver/SKILL.md`](@warlock.js/cache/pick-cache-driver/SKILL.md) — when you need cross-node atomicity
1157
+ - The `use-cache-update-merge` topic — atomic read-modify-write for objects, TTL-preserving
1158
+ - The `use-cache-lock` topic — coordinate multi-step critical sections
1159
+ - The `pick-cache-driver` topic — when you need cross-node atomicity
1156
1160
 
1157
1161
 
1158
1162
  ## use-cache-bulk `@warlock.js/cache/use-cache-bulk/SKILL.md`
1159
1163
 
1160
1164
  ---
1161
1165
  name: use-cache-bulk
1162
- description: 'Bulk reads/writes via cache.many(keys[]) → values[] (nulls for misses, order preserved) and cache.setMany(record, ttl?) → void. Triggers: `cache.many`, `cache.setMany`, "get multiple keys at once", "batch read cache", "warm the cache", "preload many keys", "mget", "mset"; typical import `import { cache } from "@warlock.js/cache"`. Skip: tag-based bulk invalidation — `@warlock.js/cache/use-cache-tags/SKILL.md`; single-key ops — `@warlock.js/cache/cache-basics/SKILL.md`; competing libs `ioredis` `MGET`/`MSET`.'
1166
+ description: 'Bulk reads/writes via cache.many(keys[]) → values[] (nulls for misses, order preserved) and cache.setMany(record, ttl?) → void. Triggers: `cache.many`, `cache.setMany`, "get multiple keys at once", "batch read cache", "warm the cache", "preload many keys", "mget", "mset"; typical import `import { cache } from "@warlock.js/cache"`. Skip: tag-based bulk invalidation — the `use-cache-tags` topic; single-key ops — the `cache-basics` topic; competing libs `ioredis` `MGET`/`MSET`.'
1163
1167
  ---
1164
1168
 
1165
1169
  # Bulk operations — `cache.many` / `cache.setMany`
@@ -1199,7 +1203,7 @@ await cache.setMany({
1199
1203
  - Keys are the object keys; values are the object values.
1200
1204
  - The optional second arg is a single TTL (seconds) applied to **all** entries —
1201
1205
  there's no per-entry TTL or `tags` knob here. When you need tags or mixed TTLs,
1202
- loop with [`cache.set`](@warlock.js/cache/configure-set-options/SKILL.md) and
1206
+ loop with `cache.set` (see the `configure-set-options` topic) and
1203
1207
  the rich options object instead.
1204
1208
 
1205
1209
  ## Performance note
@@ -1211,16 +1215,16 @@ returned promise rejects.
1211
1215
 
1212
1216
  ## See also
1213
1217
 
1214
- - [`@warlock.js/cache/cache-basics/SKILL.md`](@warlock.js/cache/cache-basics/SKILL.md) — single-key `get` / `set` / `remember`
1215
- - [`@warlock.js/cache/configure-set-options/SKILL.md`](@warlock.js/cache/configure-set-options/SKILL.md) — per-entry TTL, tags, conflict policy
1216
- - [`@warlock.js/cache/use-cache-tags/SKILL.md`](@warlock.js/cache/use-cache-tags/SKILL.md) — invalidate a batch by tag
1218
+ - The `cache-basics` topic — single-key `get` / `set` / `remember`
1219
+ - The `configure-set-options` topic — per-entry TTL, tags, conflict policy
1220
+ - The `use-cache-tags` topic — invalidate a batch by tag
1217
1221
 
1218
1222
 
1219
1223
  ## use-cache-list `@warlock.js/cache/use-cache-list/SKILL.md`
1220
1224
 
1221
1225
  ---
1222
1226
  name: use-cache-list
1223
- description: 'Ordered collections via cache.list<T>(key) — push / unshift / pop / shift / slice / all / length / trim / clear. Triggers: `cache.list`, `push`, `unshift`, `pop`, `shift`, `slice`, `trim`, `clear`; "job queue in cache", "keep most recent N events", "audit log buffer", "FIFO queue"; typical import `import { cache } from "@warlock.js/cache"`. Skip: locking around list writes — `@warlock.js/cache/use-cache-lock/SKILL.md`; competing libs `bullmq`, `bee-queue`, `bull`, `ioredis` `LPUSH`; native `Array.push`.'
1227
+ description: 'Ordered collections via cache.list<T>(key) — push / unshift / pop / shift / slice / all / length / trim / clear. Triggers: `cache.list`, `push`, `unshift`, `pop`, `shift`, `slice`, `trim`, `clear`; "job queue in cache", "keep most recent N events", "audit log buffer", "FIFO queue"; typical import `import { cache } from "@warlock.js/cache"`. Skip: locking around list writes — the `use-cache-lock` topic; competing libs `bullmq`, `bee-queue`, `bull`, `ioredis` `LPUSH`; native `Array.push`.'
1224
1228
  ---
1225
1229
 
1226
1230
  # Lists — the `cache.list<T>(key)` sub-API
@@ -1263,7 +1267,7 @@ Redis-native `LPUSH` / `RPUSH` / `LRANGE` / `LTRIM` is planned for v2.1 (see `do
1263
1267
 
1264
1268
  ## Concurrency warning
1265
1269
 
1266
- List writes on memory / file / LRU drivers **race** when two callers push simultaneously — the default read-mutate-write loop has no lock. If you need safe concurrent list writes today, wrap pushes in a distributed-lock pattern (see [`@warlock.js/cache/use-cache-lock/SKILL.md`](@warlock.js/cache/use-cache-lock/SKILL.md)) or use a single writer.
1270
+ List writes on memory / file / LRU drivers **race** when two callers push simultaneously — the default read-mutate-write loop has no lock. If you need safe concurrent list writes today, wrap pushes in a distributed-lock pattern (see the `use-cache-lock` topic) or use a single writer.
1267
1271
 
1268
1272
  Single-process memory with a single writer (typical test / script usage) is fine.
1269
1273
 
@@ -1309,7 +1313,7 @@ These are tracked as candidates for v3.
1309
1313
 
1310
1314
  ---
1311
1315
  name: use-cache-lock
1312
- description: 'Distributed lock via cache.lock(key, ttl, fn) — acquire, run fn, auto-release. Returns {acquired: true, value} or {acquired: false}. Triggers: `cache.lock`, `LockOutcome`, `acquired`, `owner`; "run cron on only one server", "idempotent webhook handler", "dedup payment processing", "lock a task across nodes"; typical import `import { cache } from "@warlock.js/cache"`. Skip: raw `onConflict: "create"` recipe — `@warlock.js/cache/apply-cache-patterns/SKILL.md`; memoization — `@warlock.js/cache/use-cached-hof/SKILL.md`; competing libs `redlock`, `async-mutex`, `proper-lockfile`.'
1316
+ description: 'Distributed lock via cache.lock(key, ttl, fn) — acquire, run fn, auto-release. Returns {acquired: true, value} or {acquired: false}. Triggers: `cache.lock`, `LockOutcome`, `acquired`, `owner`; "run cron on only one server", "idempotent webhook handler", "dedup payment processing", "lock a task across nodes"; typical import `import { cache } from "@warlock.js/cache"`. Skip: raw `onConflict: "create"` recipe — the `apply-cache-patterns` topic; memoization — the `use-cached-hof` topic; competing libs `redlock`, `async-mutex`, `proper-lockfile`.'
1313
1317
  ---
1314
1318
 
1315
1319
  # `cache.lock()` — distributed locks with auto-release
@@ -1322,7 +1326,7 @@ description: 'Distributed lock via cache.lock(key, ttl, fn) — acquire, run fn,
1322
1326
  - Idempotent webhook or payment processing — dedup across retries.
1323
1327
  - Any time you'd otherwise write `try { … } finally { cache.remove(lockKey); }`.
1324
1328
 
1325
- **Not for memoization** — use [`cached()`](@warlock.js/cache/use-cached-hof/SKILL.md) or `cache.remember()`.
1329
+ **Not for memoization** — use `cached()` (see the `use-cached-hof` topic) or `cache.remember()`.
1326
1330
 
1327
1331
  ## Shape
1328
1332
 
@@ -1426,7 +1430,7 @@ await cache.lock(
1426
1430
 
1427
1431
  ---
1428
1432
  name: use-cache-namespace
1429
- description: 'Scope cache keys via cache.namespace(prefix, options?) — every key auto-prefixed, scope-level ttl / tags defaults, nested scopes, .clear() sugar. Triggers: `cache.namespace`, `cache.removeNamespace`, `clear`, `globalPrefix`; "scope cache keys under a prefix", "share TTL across a whole prefix", "drop every key under user.1", "nested cache scopes"; typical import `import { cache } from "@warlock.js/cache"`. Skip: tag-based bulk drop — `@warlock.js/cache/use-cache-tags/SKILL.md`; multi-tenant driver-level prefix — `@warlock.js/cache/pick-cache-driver/SKILL.md`; SWR — `@warlock.js/cache/use-swr/SKILL.md`; competing libs `keyv` namespaces.'
1433
+ description: 'Scope cache keys via cache.namespace(prefix, options?) — every key auto-prefixed, scope-level ttl / tags defaults, nested scopes, .clear() sugar. Triggers: `cache.namespace`, `cache.removeNamespace`, `clear`, `globalPrefix`; "scope cache keys under a prefix", "share TTL across a whole prefix", "drop every key under user.1", "nested cache scopes"; typical import `import { cache } from "@warlock.js/cache"`. Skip: tag-based bulk drop — the `use-cache-tags` topic; multi-tenant driver-level prefix — the `pick-cache-driver` topic; SWR — the `use-swr` topic; competing libs `keyv` namespaces.'
1430
1434
  ---
1431
1435
 
1432
1436
  # Scoped caches — `cache.namespace(prefix, options?)`
@@ -1477,11 +1481,11 @@ await cache.set("user:2:profile", otherProfile);
1477
1481
  await cache.removeNamespace("user.1"); // drops both user:1 entries, keeps user:2
1478
1482
  ```
1479
1483
 
1480
- Cheaper than tags (no reverse index to maintain). Every real driver supports it — memory family and `lru` by prefix-scan, `file` by directory (including dotted sibling keys like `ns.a`), `redis` by non-blocking `SCAN` (not `KEYS`), `pg` by key/`LIKE` prefix; `null` no-ops. Namespace strings are treated as data, not glob/path input — see [`@warlock.js/cache/pick-cache-driver/SKILL.md`](@warlock.js/cache/pick-cache-driver/SKILL.md) for the file-path-containment and redis-glob-escaping details.
1484
+ Cheaper than tags (no reverse index to maintain). Every real driver supports it — memory family and `lru` by prefix-scan, `file` by directory (including dotted sibling keys like `ns.a`), `redis` by non-blocking `SCAN` (not `KEYS`), `pg` by key/`LIKE` prefix; `null` no-ops. Namespace strings are treated as data, not glob/path input — see the `pick-cache-driver` topic for the file-path-containment and redis-glob-escaping details.
1481
1485
 
1482
1486
  ## Multi-tenant scoping at the driver level
1483
1487
 
1484
- Instead of every call passing a tenant prefix, attach `globalPrefix` to the driver config — see [`@warlock.js/cache/pick-cache-driver/SKILL.md`](@warlock.js/cache/pick-cache-driver/SKILL.md). Function form runs per call.
1488
+ Instead of every call passing a tenant prefix, attach `globalPrefix` to the driver config — see the `pick-cache-driver` topic. Function form runs per call.
1485
1489
 
1486
1490
  ```ts
1487
1491
  options: {
@@ -1505,7 +1509,7 @@ await feed.swr(
1505
1509
  // stored at feed.<userId>.home, tagged [user.<userId>, computed]
1506
1510
  ```
1507
1511
 
1508
- Note: scope `ttl` defaults are NOT applied to SWR — `freshTtl` / `staleTtl` always come from the call site. See [`@warlock.js/cache/use-swr/SKILL.md`](@warlock.js/cache/use-swr/SKILL.md).
1512
+ Note: scope `ttl` defaults are NOT applied to SWR — `freshTtl` / `staleTtl` always come from the call site. See the `use-swr` topic.
1509
1513
 
1510
1514
  ## Things NOT to do
1511
1515
 
@@ -1518,7 +1522,7 @@ Note: scope `ttl` defaults are NOT applied to SWR — `freshTtl` / `staleTtl` al
1518
1522
 
1519
1523
  ---
1520
1524
  name: use-cache-similarity
1521
- description: 'Vector retrieval via cache.similar(vector, {topK, threshold?, tags?}) — index with set(k, v, {vector}), query nearest by cosine similarity. Triggers: `cache.similar`, `cache.set` with `vector`, `topK`, `threshold`, `tags`, `cosineSimilarity`; "build a semantic cache for an LLM", "RAG retrieval from cache", "nearest-neighbor over cached entries", "skip the LLM when a similar answer exists"; typical import `import { cache } from "@warlock.js/cache"`. Skip: pgvector setup specifics — `@warlock.js/cache/configure-pg-cache/SKILL.md`; competing libs `pinecone`, `weaviate`, `chromadb`, `lancedb`, `faiss-node`.'
1525
+ description: 'Vector retrieval via cache.similar(vector, {topK, threshold?, tags?}) — index with set(k, v, {vector}), query nearest by cosine similarity. Triggers: `cache.similar`, `cache.set` with `vector`, `topK`, `threshold`, `tags`, `cosineSimilarity`; "build a semantic cache for an LLM", "RAG retrieval from cache", "nearest-neighbor over cached entries", "skip the LLM when a similar answer exists"; typical import `import { cache } from "@warlock.js/cache"`. Skip: pgvector setup specifics — the `configure-pg-cache` topic; competing libs `pinecone`, `weaviate`, `chromadb`, `lancedb`, `faiss-node`.'
1522
1526
  ---
1523
1527
 
1524
1528
  # `cache.similar()` — vector-based retrieval
@@ -1601,7 +1605,7 @@ options: { memory: { ttl: "1h" } }
1601
1605
  options: { pg: { client: pool, vector: { dimensions: 1536 } } }
1602
1606
  ```
1603
1607
 
1604
- See [`@warlock.js/cache/configure-pg-cache/SKILL.md`](@warlock.js/cache/configure-pg-cache/SKILL.md).
1608
+ See the `configure-pg-cache` topic.
1605
1609
 
1606
1610
  ## Things NOT to do
1607
1611
 
@@ -1616,7 +1620,7 @@ See [`@warlock.js/cache/configure-pg-cache/SKILL.md`](@warlock.js/cache/configur
1616
1620
 
1617
1621
  ---
1618
1622
  name: use-cache-tags
1619
- description: 'Tag-based invalidation — attach tags on write, then cache.tags([...]).invalidate() drops every key bound to any of those tags. Triggers: `cache.tags`, `invalidate`, `cache.set` with `tags`; "invalidate every key tagged users", "drop everything for tenant 42", "bulk cache invalidation without knowing keys", "tag a cached value"; typical import `import { cache } from "@warlock.js/cache"`. Skip: prefix-based drop — `@warlock.js/cache/use-cache-namespace/SKILL.md`; HOF memoization with tags — `@warlock.js/cache/use-cached-hof/SKILL.md`; SWR — `@warlock.js/cache/use-swr/SKILL.md`; competing libs `cache-manager` tags, Next.js `revalidateTag`.'
1623
+ description: 'Tag-based invalidation — attach tags on write, then cache.tags([...]).invalidate() drops every key bound to any of those tags. Triggers: `cache.tags`, `invalidate`, `cache.set` with `tags`; "invalidate every key tagged users", "drop everything for tenant 42", "bulk cache invalidation without knowing keys", "tag a cached value"; typical import `import { cache } from "@warlock.js/cache"`. Skip: prefix-based drop — the `use-cache-namespace` topic; HOF memoization with tags — the `use-cached-hof` topic; SWR — the `use-swr` topic; competing libs `cache-manager` tags, Next.js `revalidateTag`.'
1620
1624
  ---
1621
1625
 
1622
1626
  # Tag-based invalidation
@@ -1652,7 +1656,7 @@ Multi-tag is **union** semantics: an entry is invalidated if it carries **at lea
1652
1656
 
1653
1657
  | Use case | Reach for |
1654
1658
  | --- | --- |
1655
- | The keys share a known prefix | `cache.removeNamespace("prefix")` ([`use-cache-namespace`](@warlock.js/cache/use-cache-namespace/SKILL.md)) |
1659
+ | The keys share a known prefix | `cache.removeNamespace("prefix")` (the `use-cache-namespace` topic) |
1656
1660
  | The keys are spread across prefixes, tied by entity | Tags |
1657
1661
  | Both apply | Tags — more flexible; cheap on most drivers |
1658
1662
 
@@ -1684,7 +1688,7 @@ await cache.swr(
1684
1688
  );
1685
1689
  ```
1686
1690
 
1687
- Tags re-apply on every successful refresh — see [`@warlock.js/cache/use-swr/SKILL.md`](@warlock.js/cache/use-swr/SKILL.md).
1691
+ Tags re-apply on every successful refresh — see the `use-swr` topic.
1688
1692
 
1689
1693
  ## `cached()` HOF with tags
1690
1694
 
@@ -1695,7 +1699,7 @@ const getPosts = cached(fn, { key: (u) => `posts.by.${u}`, ttl: "30m", tags: ["u
1695
1699
  await cache.tags(["users"]).invalidate(); // drops both wrappers' caches
1696
1700
  ```
1697
1701
 
1698
- See [`@warlock.js/cache/use-cached-hof/SKILL.md`](@warlock.js/cache/use-cached-hof/SKILL.md).
1702
+ See the `use-cached-hof` topic.
1699
1703
 
1700
1704
  ## Things NOT to do
1701
1705
 
@@ -1708,7 +1712,7 @@ See [`@warlock.js/cache/use-cached-hof/SKILL.md`](@warlock.js/cache/use-cached-h
1708
1712
 
1709
1713
  ---
1710
1714
  name: use-cache-update-merge
1711
- description: 'Atomic read-modify-write via cache.update(key, fn) (callback receives current) or cache.merge(key, partial) (shallow merge). Per-key chain lock serializes concurrent in-process callers. Triggers: `cache.update`, `cache.merge`, `cache.increment`, `cache.pull`; "atomically update a cached counter", "change one field on a cached object", "avoid get-spread-set race", "serialize concurrent cache writers"; typical import `import { cache } from "@warlock.js/cache"`. Skip: cross-process locking — `@warlock.js/cache/use-cache-lock/SKILL.md`; conditional create/update — `@warlock.js/cache/configure-set-options/SKILL.md`; competing libs `lodash.merge`, raw redis `WATCH`/`MULTI`.'
1715
+ description: 'Atomic read-modify-write via cache.update(key, fn) (callback receives current) or cache.merge(key, partial) (shallow merge). Per-key chain lock serializes concurrent in-process callers. Triggers: `cache.update`, `cache.merge`, `cache.increment`, `cache.pull`; "atomically update a cached counter", "change one field on a cached object", "avoid get-spread-set race", "serialize concurrent cache writers"; typical import `import { cache } from "@warlock.js/cache"`. Skip: cross-process locking — the `use-cache-lock` topic; conditional create/update — the `configure-set-options` topic; competing libs `lodash.merge`, raw redis `WATCH`/`MULTI`.'
1712
1716
  ---
1713
1717
 
1714
1718
  # `update` and `merge` — atomic read-modify-write
@@ -1796,7 +1800,7 @@ The per-key lock map lives on the driver instance and is cleared after each chai
1796
1800
 
1797
1801
  ---
1798
1802
  name: use-cache-utils
1799
- description: 'Low-level cache utilities re-exported from @warlock.js/cache — parseTtl, expiresAtToTtl, resolveTtl, normalizeToOptions, normalizeToRememberOptions, parseCacheKey, mergeTagSets, injectTags, cosineSimilarity, and the CACHE_FOR TTL enum. Triggers: `parseTtl`, `parseCacheKey`, `resolveTtl`, `expiresAtToTtl`, `cosineSimilarity`, `mergeTagSets`, `injectTags`, `CACHE_FOR`; "parse a duration to seconds", "build a cache key from an object", "score two vectors", "common TTL constant"; typical import `import { parseTtl, CACHE_FOR } from "@warlock.js/cache"`. Skip: building a whole custom driver — `@warlock.js/cache/pick-cache-driver/SKILL.md`; the high-level set API — `@warlock.js/cache/configure-set-options/SKILL.md`.'
1803
+ description: 'Low-level cache utilities re-exported from @warlock.js/cache — parseTtl, expiresAtToTtl, resolveTtl, normalizeToOptions, normalizeToRememberOptions, parseCacheKey, mergeTagSets, injectTags, cosineSimilarity, and the CACHE_FOR TTL enum. Triggers: `parseTtl`, `parseCacheKey`, `resolveTtl`, `expiresAtToTtl`, `cosineSimilarity`, `mergeTagSets`, `injectTags`, `CACHE_FOR`; "parse a duration to seconds", "build a cache key from an object", "score two vectors", "common TTL constant"; typical import `import { parseTtl, CACHE_FOR } from "@warlock.js/cache"`. Skip: building a whole custom driver — the `pick-cache-driver` topic; the high-level set API — the `configure-set-options` topic.'
1800
1804
  ---
1801
1805
 
1802
1806
  # Cache utilities
@@ -1880,16 +1884,16 @@ For most call sites the duration string (`"1h"`, `"7d"`) reads better.
1880
1884
 
1881
1885
  ## See also
1882
1886
 
1883
- - [`@warlock.js/cache/configure-set-options/SKILL.md`](@warlock.js/cache/configure-set-options/SKILL.md) — the high-level `set` options these normalize
1884
- - [`@warlock.js/cache/use-cache-similarity/SKILL.md`](@warlock.js/cache/use-cache-similarity/SKILL.md) — `cache.similar()`, which uses `cosineSimilarity`
1885
- - [`@warlock.js/cache/pick-cache-driver/SKILL.md`](@warlock.js/cache/pick-cache-driver/SKILL.md) — building a custom driver where these help
1887
+ - The `configure-set-options` topic — the high-level `set` options these normalize
1888
+ - The `use-cache-similarity` topic — `cache.similar()`, which uses `cosineSimilarity`
1889
+ - The `pick-cache-driver` topic — building a custom driver where these help
1886
1890
 
1887
1891
 
1888
1892
  ## use-cached-hof `@warlock.js/cache/use-cached-hof/SKILL.md`
1889
1893
 
1890
1894
  ---
1891
1895
  name: use-cached-hof
1892
- description: 'Wrap an async function with cached(fn, options) — declare the caching strategy once, call from many sites, get a bound .invalidate(...args) helper. Triggers: `cached`, `invalidate`, `key`, `ttl`, `tags`, `driver`; "wrap a DB lookup with caching", "memoize a function and invalidate by args", "one declaration many call sites", "auto-derive cache key from args"; typical import `import { cached } from "@warlock.js/cache"`. Skip: one-shot memoization — `@warlock.js/cache/apply-cache-patterns/SKILL.md` (`cache.remember`); tag bulk drop — `@warlock.js/cache/use-cache-tags/SKILL.md`; competing libs `p-memoize`, `mem`, `lodash.memoize`.'
1896
+ description: 'Wrap an async function with cached(fn, options) — declare the caching strategy once, call from many sites, get a bound .invalidate(...args) helper. Triggers: `cached`, `invalidate`, `key`, `ttl`, `tags`, `driver`; "wrap a DB lookup with caching", "memoize a function and invalidate by args", "one declaration many call sites", "auto-derive cache key from args"; typical import `import { cached } from "@warlock.js/cache"`. Skip: one-shot memoization — the `apply-cache-patterns` topic (`cache.remember`); tag bulk drop — the `use-cache-tags` topic; competing libs `p-memoize`, `mem`, `lodash.memoize`.'
1893
1897
  ---
1894
1898
 
1895
1899
  # `cached()` — function-memoization wrapper
@@ -1968,7 +1972,7 @@ const getPosts = cached(fn, { key: (u) => `posts.by.${u}`, ttl: "30m", tags: ["u
1968
1972
  await cache.tags(["users"]).invalidate(); // drops both wrappers' caches
1969
1973
  ```
1970
1974
 
1971
- See [`@warlock.js/cache/use-cache-tags/SKILL.md`](@warlock.js/cache/use-cache-tags/SKILL.md).
1975
+ See the `use-cache-tags` topic.
1972
1976
 
1973
1977
  ### Project a subset of args into the key
1974
1978
 
@@ -1996,7 +2000,7 @@ const getCategoryMeta = cached(
1996
2000
 
1997
2001
  ---
1998
2002
  name: use-swr
1999
- description: 'Stale-while-revalidate via cache.swr(key, {freshTtl, staleTtl}, fn) — returns cached instantly when fresh, returns cached + background refresh when stale, blocks only when fully expired. Triggers: `cache.swr`, `freshTtl`, `staleTtl`, `tags`, `driver`, `stale_at`; "serve stale while refreshing", "degrade when upstream is down", "never block on cache miss"; typical import `import { cache } from "@warlock.js/cache"`. Skip: block-until-fresh memoization — `@warlock.js/cache/apply-cache-patterns/SKILL.md`; observing refresh failures — `@warlock.js/cache/observe-cache/SKILL.md`; competing libs `swr` (React, client-side).'
2003
+ description: 'Stale-while-revalidate via cache.swr(key, {freshTtl, staleTtl}, fn) — returns cached instantly when fresh, returns cached + background refresh when stale, blocks only when fully expired. Triggers: `cache.swr`, `freshTtl`, `staleTtl`, `tags`, `driver`, `stale_at`; "serve stale while refreshing", "degrade when upstream is down", "never block on cache miss"; typical import `import { cache } from "@warlock.js/cache"`. Skip: block-until-fresh memoization — the `apply-cache-patterns` topic; observing refresh failures — the `observe-cache` topic; competing libs `swr` (React, client-side).'
2000
2004
  ---
2001
2005
 
2002
2006
  # Stale-while-revalidate — `cache.swr(key, options, fn)`
@@ -2007,7 +2011,7 @@ Returns the cached value immediately when it can; refreshes in the background wh
2007
2011
 
2008
2012
  Use `cache.swr()` when **slightly-stale data is acceptable** and **the upstream is slow / occasionally fails**. That's most product-detail pages, dashboards, third-party API responses, expensive aggregations.
2009
2013
 
2010
- Use `cache.remember()` when freshness is non-negotiable — auth, balances, billing, anything where the user must see the latest. Remember blocks every miss; SWR doesn't. See [`@warlock.js/cache/apply-cache-patterns/SKILL.md`](@warlock.js/cache/apply-cache-patterns/SKILL.md).
2014
+ Use `cache.remember()` when freshness is non-negotiable — auth, balances, billing, anything where the user must see the latest. Remember blocks every miss; SWR doesn't. See the `apply-cache-patterns` topic.
2011
2015
 
2012
2016
  ## Three windows
2013
2017
 
@@ -2047,7 +2051,7 @@ await cache.swr(
2047
2051
 
2048
2052
  1. **Concurrent stale-window callers share one refresh.** Per-key dedupe via the driver's existing locks map — no thundering herd on background refresh.
2049
2053
  2. **Failed background refreshes preserve the stale entry.** No retry storm; the next stale-window read tries again. Failures emit `error` events for observability.
2050
- 3. **The caller never sees a refresh failure.** If you returned the stale value, you got your data — failures only show up via `cache.on("error", ...)`. See [`@warlock.js/cache/observe-cache/SKILL.md`](@warlock.js/cache/observe-cache/SKILL.md).
2054
+ 3. **The caller never sees a refresh failure.** If you returned the stale value, you got your data — failures only show up via `cache.on("error", ...)`. See the `observe-cache` topic.
2051
2055
  4. **Tags compose.** Per-call tags + scope tags (when via `cache.namespace().swr(...)`) merge additively.
2052
2056
  5. **Scope `ttl` defaults are NOT applied to SWR.** `freshTtl` / `staleTtl` always come from the call site.
2053
2057