@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.
- package/CHANGELOG.md +18 -0
- package/cjs/index.cjs +91 -47
- package/cjs/index.cjs.map +1 -1
- package/esm/cache-manager.d.mts +1 -1
- package/esm/cache-manager.d.mts.map +1 -1
- package/esm/cache-manager.mjs +1 -1
- package/esm/cache-manager.mjs.map +1 -1
- package/esm/cached/auto-key.d.mts +1 -1
- package/esm/cached/auto-key.d.mts.map +1 -1
- package/esm/cached/auto-key.mjs +1 -1
- package/esm/cached/auto-key.mjs.map +1 -1
- package/esm/cached/cached.d.mts +1 -1
- package/esm/cached/cached.d.mts.map +1 -1
- package/esm/cached/cached.mjs +1 -1
- package/esm/cached/cached.mjs.map +1 -1
- package/esm/cached/normalize-args.d.mts +1 -1
- package/esm/cached/normalize-args.d.mts.map +1 -1
- package/esm/cached/normalize-args.mjs +1 -1
- package/esm/cached/normalize-args.mjs.map +1 -1
- package/esm/drivers/base-cache-driver.d.mts +11 -5
- package/esm/drivers/base-cache-driver.d.mts.map +1 -1
- package/esm/drivers/base-cache-driver.mjs +29 -13
- package/esm/drivers/base-cache-driver.mjs.map +1 -1
- package/esm/drivers/file-cache-driver.d.mts +1 -1
- package/esm/drivers/file-cache-driver.d.mts.map +1 -1
- package/esm/drivers/file-cache-driver.mjs +2 -2
- package/esm/drivers/file-cache-driver.mjs.map +1 -1
- package/esm/drivers/in-memory-tag-index.d.mts +1 -1
- package/esm/drivers/in-memory-tag-index.d.mts.map +1 -1
- package/esm/drivers/in-memory-tag-index.mjs +1 -1
- package/esm/drivers/in-memory-tag-index.mjs.map +1 -1
- package/esm/drivers/index.d.mts +1 -1
- package/esm/drivers/index.mjs +1 -1
- package/esm/drivers/lru-memory-cache-driver.d.mts +1 -1
- package/esm/drivers/lru-memory-cache-driver.d.mts.map +1 -1
- package/esm/drivers/lru-memory-cache-driver.mjs +3 -2
- package/esm/drivers/lru-memory-cache-driver.mjs.map +1 -1
- package/esm/drivers/memory-cache-driver.d.mts +1 -1
- package/esm/drivers/memory-cache-driver.d.mts.map +1 -1
- package/esm/drivers/memory-cache-driver.mjs +3 -2
- package/esm/drivers/memory-cache-driver.mjs.map +1 -1
- package/esm/drivers/memory-extended-cache-driver.d.mts +1 -1
- package/esm/drivers/memory-extended-cache-driver.d.mts.map +1 -1
- package/esm/drivers/memory-extended-cache-driver.mjs +1 -1
- package/esm/drivers/memory-extended-cache-driver.mjs.map +1 -1
- package/esm/drivers/mock-cache-driver.d.mts +1 -1
- package/esm/drivers/mock-cache-driver.d.mts.map +1 -1
- package/esm/drivers/mock-cache-driver.mjs +3 -2
- package/esm/drivers/mock-cache-driver.mjs.map +1 -1
- package/esm/drivers/null-cache-driver.d.mts +5 -1
- package/esm/drivers/null-cache-driver.d.mts.map +1 -1
- package/esm/drivers/null-cache-driver.mjs +17 -10
- package/esm/drivers/null-cache-driver.mjs.map +1 -1
- package/esm/drivers/pg-cache-driver.d.mts +1 -1
- package/esm/drivers/pg-cache-driver.d.mts.map +1 -1
- package/esm/drivers/pg-cache-driver.mjs +3 -2
- package/esm/drivers/pg-cache-driver.mjs.map +1 -1
- package/esm/drivers/redis-cache-driver.d.mts +14 -2
- package/esm/drivers/redis-cache-driver.d.mts.map +1 -1
- package/esm/drivers/redis-cache-driver.mjs +24 -4
- package/esm/drivers/redis-cache-driver.mjs.map +1 -1
- package/esm/index.d.mts +2 -2
- package/esm/index.mjs +2 -2
- package/esm/list/memory-cache-list.d.mts +1 -1
- package/esm/list/memory-cache-list.d.mts.map +1 -1
- package/esm/list/memory-cache-list.mjs +1 -1
- package/esm/list/memory-cache-list.mjs.map +1 -1
- package/esm/metrics.d.mts +1 -1
- package/esm/metrics.d.mts.map +1 -1
- package/esm/metrics.mjs +1 -1
- package/esm/metrics.mjs.map +1 -1
- package/esm/scoped-cache.d.mts +1 -1
- package/esm/scoped-cache.d.mts.map +1 -1
- package/esm/scoped-cache.mjs +1 -1
- package/esm/scoped-cache.mjs.map +1 -1
- package/esm/tagged-cache.d.mts +1 -1
- package/esm/tagged-cache.d.mts.map +1 -1
- package/esm/tagged-cache.mjs +1 -1
- package/esm/tagged-cache.mjs.map +1 -1
- package/esm/tagged-scoped-cache.d.mts +1 -1
- package/esm/tagged-scoped-cache.d.mts.map +1 -1
- package/esm/tagged-scoped-cache.mjs +1 -1
- package/esm/tagged-scoped-cache.mjs.map +1 -1
- package/esm/types.d.mts +1 -1
- package/esm/types.d.mts.map +1 -1
- package/esm/types.mjs +1 -1
- package/esm/types.mjs.map +1 -1
- package/esm/utils.d.mts +1 -1
- package/esm/utils.d.mts.map +1 -1
- package/esm/utils.mjs +1 -1
- package/esm/utils.mjs.map +1 -1
- package/llms-full.txt +107 -103
- package/llms.txt +18 -18
- package/package.json +5 -4
- package/skills/apply-cache-patterns/SKILL.md +10 -10
- package/skills/cache-basics/SKILL.md +24 -20
- package/skills/configure-pg-cache/SKILL.md +5 -5
- package/skills/configure-set-options/SKILL.md +3 -3
- package/skills/handle-cache-errors/SKILL.md +3 -3
- package/skills/observe-cache/SKILL.md +1 -1
- package/skills/overview/SKILL.md +21 -21
- package/skills/pick-cache-driver/SKILL.md +4 -4
- package/skills/test-cache-code/SKILL.md +2 -2
- package/skills/use-cache-atomic/SKILL.md +5 -5
- package/skills/use-cache-bulk/SKILL.md +5 -5
- package/skills/use-cache-list/SKILL.md +2 -2
- package/skills/use-cache-lock/SKILL.md +2 -2
- package/skills/use-cache-namespace/SKILL.md +4 -4
- package/skills/use-cache-similarity/SKILL.md +2 -2
- package/skills/use-cache-tags/SKILL.md +4 -4
- package/skills/use-cache-update-merge/SKILL.md +1 -1
- package/skills/use-cache-utils/SKILL.md +4 -4
- package/skills/use-cached-hof/SKILL.md +2 -2
- 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 —
|
|
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
|
|
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
|
|
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
|
-
-
|
|
101
|
-
-
|
|
102
|
-
-
|
|
103
|
-
-
|
|
104
|
-
-
|
|
105
|
-
-
|
|
106
|
-
-
|
|
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
|
|
138
|
-
5. **`update` and `merge` throw `CacheUnsupportedError` on the file driver.** Use memory or redis for atomic mutation. See
|
|
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
|
|
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
|
|
143
|
-
10. **Similarity retrieval** lives on the same driver contract — `set(k, v, { vector })` indexes the entry; `cache.similar(vec, ...)` returns nearest hits. See
|
|
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 |
|
|
208
|
-
| The `set` options object (`ttl`, `expiresAt`, `tags`, `onConflict`, `driver`, `vector`) |
|
|
209
|
-
| Memoization with `remember()`, counters, negative caching, per-tenant scoping, TTL constants |
|
|
210
|
-
| Scoped handles via `cache.namespace(prefix, options?)` |
|
|
211
|
-
| Tag-based invalidation — `cache.tags([...]).invalidate()` |
|
|
212
|
-
| Stale-while-revalidate — `cache.swr(...)` |
|
|
213
|
-
| Wrapping a function with `cached()` — HOF memoization with `.invalidate()` |
|
|
214
|
-
| Distributed locks — `cache.lock(key, ttl, fn)` with auto-release |
|
|
215
|
-
| Queues, recent-N buffers, `push`/`shift`/`trim` — the list sub-API |
|
|
216
|
-
| Atomic read-modify-write via `update()` and `merge()` |
|
|
217
|
-
| Similarity retrieval — `set({ vector })` + `cache.similar(...)` |
|
|
218
|
-
| Postgres driver setup (KV-only or with pgvector) |
|
|
219
|
-
| `cache.metrics()` aggregate snapshot + event bus for per-event reactions |
|
|
220
|
-
| Error classes (`CacheConfigurationError`, `CacheUnsupportedError`, etc.) |
|
|
221
|
-
| Tests that touch cache code paths — `MockCacheDriver`, `MemoryCacheDriver` |
|
|
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 —
|
|
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
|
|
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
|
|
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
|
-
-
|
|
353
|
-
-
|
|
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 —
|
|
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
|
|
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
|
|
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 —
|
|
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
|
|
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
|
|
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 —
|
|
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
|
-
-
|
|
696
|
-
-
|
|
697
|
-
-
|
|
698
|
-
-
|
|
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
|
-
-
|
|
703
|
-
-
|
|
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
|
-
-
|
|
708
|
-
-
|
|
709
|
-
-
|
|
710
|
-
-
|
|
711
|
-
-
|
|
712
|
-
-
|
|
713
|
-
-
|
|
714
|
-
-
|
|
715
|
-
-
|
|
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
|
-
-
|
|
720
|
-
-
|
|
721
|
-
-
|
|
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
|
-
-
|
|
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
|
-
-
|
|
736
|
-
- `mongez-agent-kit-authoring-skills` (load via agent-kit sync) — how this becomes `.claude/skills/warlock-js-cache
|
|
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 —
|
|
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
|
|
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
|
-
-
|
|
869
|
-
-
|
|
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 —
|
|
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
|
|
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 —
|
|
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
|
|
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
|
-
-
|
|
1154
|
-
-
|
|
1155
|
-
-
|
|
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 —
|
|
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
|
|
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
|
-
-
|
|
1215
|
-
-
|
|
1216
|
-
-
|
|
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 —
|
|
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
|
|
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 —
|
|
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
|
|
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 —
|
|
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
|
|
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
|
|
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
|
|
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 —
|
|
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
|
|
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 —
|
|
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")` (
|
|
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
|
|
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
|
|
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 —
|
|
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 —
|
|
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
|
-
-
|
|
1884
|
-
-
|
|
1885
|
-
-
|
|
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 —
|
|
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
|
|
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 —
|
|
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
|
|
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
|
|
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
|
|