@warlock.js/cache 5.28.0 → 5.29.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 +6 -0
- package/llms-full.txt +105 -103
- package/llms.txt +18 -18
- package/package.json +3 -3
- package/skills/apply-cache-patterns/SKILL.md +10 -10
- package/skills/cache-basics/SKILL.md +22 -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,
|
|
@@ -204,21 +206,21 @@ const user = await cache.remember("user.1", "1h", async () => db.users.find(1));
|
|
|
204
206
|
|
|
205
207
|
| If the task is about… | Load |
|
|
206
208
|
| --- | --- |
|
|
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` |
|
|
209
|
+
| Choosing a driver, configuring it, or understanding what each one does best | The `pick-cache-driver` topic |
|
|
210
|
+
| The `set` options object (`ttl`, `expiresAt`, `tags`, `onConflict`, `driver`, `vector`) | The `configure-set-options` topic |
|
|
211
|
+
| Memoization with `remember()`, counters, negative caching, per-tenant scoping, TTL constants | The `apply-cache-patterns` topic |
|
|
212
|
+
| Scoped handles via `cache.namespace(prefix, options?)` | The `use-cache-namespace` topic |
|
|
213
|
+
| Tag-based invalidation — `cache.tags([...]).invalidate()` | The `use-cache-tags` topic |
|
|
214
|
+
| Stale-while-revalidate — `cache.swr(...)` | The `use-swr` topic |
|
|
215
|
+
| Wrapping a function with `cached()` — HOF memoization with `.invalidate()` | The `use-cached-hof` topic |
|
|
216
|
+
| Distributed locks — `cache.lock(key, ttl, fn)` with auto-release | The `use-cache-lock` topic |
|
|
217
|
+
| Queues, recent-N buffers, `push`/`shift`/`trim` — the list sub-API | The `use-cache-list` topic |
|
|
218
|
+
| Atomic read-modify-write via `update()` and `merge()` | The `use-cache-update-merge` topic |
|
|
219
|
+
| Similarity retrieval — `set({ vector })` + `cache.similar(...)` | The `use-cache-similarity` topic |
|
|
220
|
+
| Postgres driver setup (KV-only or with pgvector) | The `configure-pg-cache` topic |
|
|
221
|
+
| `cache.metrics()` aggregate snapshot + event bus for per-event reactions | The `observe-cache` topic |
|
|
222
|
+
| Error classes (`CacheConfigurationError`, `CacheUnsupportedError`, etc.) | The `handle-cache-errors` topic |
|
|
223
|
+
| Tests that touch cache code paths — `MockCacheDriver`, `MemoryCacheDriver` | The `test-cache-code` topic |
|
|
222
224
|
|
|
223
225
|
## Things NOT to do
|
|
224
226
|
|
|
@@ -237,7 +239,7 @@ const user = await cache.remember("user.1", "1h", async () => db.users.find(1));
|
|
|
237
239
|
|
|
238
240
|
---
|
|
239
241
|
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 —
|
|
242
|
+
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
243
|
---
|
|
242
244
|
|
|
243
245
|
# `pg` cache driver — Postgres setup
|
|
@@ -256,7 +258,7 @@ Persistent cache backed by your existing Postgres pool. Two modes: **KV-only** (
|
|
|
256
258
|
- `update` → `UPDATE ... WHERE expires_at IS NULL OR expires_at > now() RETURNING value`.
|
|
257
259
|
- `upsert` → unconditional `INSERT ... ON CONFLICT DO UPDATE`.
|
|
258
260
|
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
|
|
261
|
+
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
262
|
9. **pgvector requires `CREATE EXTENSION vector;` once on the database.** Lazy probe on first vector op throws `CacheConfigurationError` if missing; result is cached.
|
|
261
263
|
10. **Vectors are passed as text literals** (`'[1,2,3]'::vector`). No binary protocol dependency — works against any pg client.
|
|
262
264
|
|
|
@@ -337,7 +339,7 @@ Switching strategies requires rebuilding the index.
|
|
|
337
339
|
- `CacheConfigurationError: vector dimension mismatch` — input vector length ≠ configured dimensions. Embedder probably changed.
|
|
338
340
|
- `CacheUnsupportedError: similarity retrieval requires the 'vector' config block` — KV-only mode; add `options.pg.vector`.
|
|
339
341
|
|
|
340
|
-
See
|
|
342
|
+
See the `handle-cache-errors` topic for the full error class hierarchy.
|
|
341
343
|
|
|
342
344
|
## Things NOT to do
|
|
343
345
|
|
|
@@ -349,15 +351,15 @@ See [`@warlock.js/cache/handle-cache-errors/SKILL.md`](@warlock.js/cache/handle-
|
|
|
349
351
|
|
|
350
352
|
## Related
|
|
351
353
|
|
|
352
|
-
-
|
|
353
|
-
-
|
|
354
|
+
- The `use-cache-similarity` topic — the `similar()` API across all drivers
|
|
355
|
+
- The `pick-cache-driver` topic — comparing pg with memory / redis / file
|
|
354
356
|
|
|
355
357
|
|
|
356
358
|
## configure-set-options `@warlock.js/cache/configure-set-options/SKILL.md`
|
|
357
359
|
|
|
358
360
|
---
|
|
359
361
|
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 —
|
|
362
|
+
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
363
|
---
|
|
362
364
|
|
|
363
365
|
# The `set` options object
|
|
@@ -389,10 +391,10 @@ await cache.set("user:1", user, {
|
|
|
389
391
|
| --- | --- | --- |
|
|
390
392
|
| `ttl` | `number \| string` | Relative expiry. Mutually exclusive with `expiresAt`. |
|
|
391
393
|
| `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
|
|
394
|
+
| `tags` | `string[]` | Inline equivalent of `cache.tags([...]).set(...)`. See the `use-cache-tags` topic. |
|
|
393
395
|
| `onConflict` | `"create" \| "update" \| "upsert"` | See below. Default `"upsert"`. |
|
|
394
396
|
| `driver` | `string` | Per-call driver override by registered name. |
|
|
395
|
-
| `vector` | `number[]` | Embedding indexed alongside the entry for
|
|
397
|
+
| `vector` | `number[]` | Embedding indexed alongside the entry for `cache.similar()` (see the `use-cache-similarity` topic). Drivers without similarity support throw `CacheUnsupportedError`. |
|
|
396
398
|
|
|
397
399
|
## TTL parsing
|
|
398
400
|
|
|
@@ -463,7 +465,7 @@ await cache.set("k", v, undefined); // same as no TTL
|
|
|
463
465
|
|
|
464
466
|
---
|
|
465
467
|
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 —
|
|
468
|
+
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
469
|
---
|
|
468
470
|
|
|
469
471
|
# Error classes
|
|
@@ -489,7 +491,7 @@ import {
|
|
|
489
491
|
| `CacheDriverNotInitializedError` | Any data op called before `cache.init()` / `cache.use()` | Call `cache.init()` at app startup. Tests often forget this — add a `beforeEach`. |
|
|
490
492
|
| `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
493
|
| `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
|
|
494
|
+
| `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
495
|
|
|
494
496
|
## Special case — `setNX` unsupported
|
|
495
497
|
|
|
@@ -499,7 +501,7 @@ Calling `cache.setNX(...)` on a driver that doesn't implement it throws a plain
|
|
|
499
501
|
// Error: "setNX is not supported by the current cache driver: memory"
|
|
500
502
|
```
|
|
501
503
|
|
|
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
|
|
504
|
+
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
505
|
|
|
504
506
|
## Patterns
|
|
505
507
|
|
|
@@ -559,7 +561,7 @@ try {
|
|
|
559
561
|
|
|
560
562
|
---
|
|
561
563
|
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 —
|
|
564
|
+
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
565
|
---
|
|
564
566
|
|
|
565
567
|
# Cache observability — `cache.metrics()` and the event bus
|
|
@@ -692,37 +694,37 @@ Nineteen task skills. Most apps start with `cache-basics` + `pick-cache-driver`
|
|
|
692
694
|
|
|
693
695
|
### Foundations
|
|
694
696
|
|
|
695
|
-
-
|
|
696
|
-
-
|
|
697
|
-
-
|
|
698
|
-
-
|
|
697
|
+
- 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.**
|
|
698
|
+
- The `pick-cache-driver` topic — choose + configure a driver: `null` / `memory` / `memoryExtended` / `lru` / `file` / `redis` / `pg` / `mock`; `globalPrefix` for multi-tenant scoping.
|
|
699
|
+
- The `configure-set-options` topic — `cache.set`'s third argument: `ttl`, `expiresAt`, `tags`, `onConflict` (create/update/upsert), `driver`, `vector`.
|
|
700
|
+
- The `configure-pg-cache` topic — Postgres driver: KV-only (default) or pgvector mode; caller owns the `pg.Pool`, `driver.schema()` emits the DDL.
|
|
699
701
|
|
|
700
702
|
### Invalidation + scoping
|
|
701
703
|
|
|
702
|
-
-
|
|
703
|
-
-
|
|
704
|
+
- The `use-cache-tags` topic — tag on write, `cache.tags([...]).invalidate()` drops every bound key.
|
|
705
|
+
- The `use-cache-namespace` topic — `cache.namespace(prefix, options?)` auto-prefixes keys with scope-level TTL/tag defaults and nested scopes.
|
|
704
706
|
|
|
705
707
|
### Patterns
|
|
706
708
|
|
|
707
|
-
-
|
|
708
|
-
-
|
|
709
|
-
-
|
|
710
|
-
-
|
|
711
|
-
-
|
|
712
|
-
-
|
|
713
|
-
-
|
|
714
|
-
-
|
|
715
|
-
-
|
|
709
|
+
- The `use-cached-hof` topic — `cached(fn, options)` wraps an async function; one declaration, many call sites, a bound `.invalidate(...args)`.
|
|
710
|
+
- The `apply-cache-patterns` topic — `remember()` memoization, distributed locks via `onConflict: "create"`, negative caching, counters, per-tenant prefix, `CACHE_FOR.*` TTL constants.
|
|
711
|
+
- The `use-cache-lock` topic — `cache.lock(key, ttl, fn)`: acquire → run → auto-release. For cron/imports/migrations and idempotent webhook/payment processing.
|
|
712
|
+
- The `use-swr` topic — `cache.swr(key, { freshTtl, staleTtl }, fn)`: instant when fresh, instant + background refresh when stale, blocks only when fully expired.
|
|
713
|
+
- The `use-cache-update-merge` topic — atomic read-modify-write via `cache.update(key, fn)` / `cache.merge(key, partial)`, serialized per key, TTL-preserving.
|
|
714
|
+
- The `use-cache-atomic` topic — `cache.increment` / `cache.decrement` counters; per-driver atomicity + TTL behavior.
|
|
715
|
+
- The `use-cache-bulk` topic — `cache.many(keys)` / `cache.setMany(record, ttl?)` for batch reads/writes.
|
|
716
|
+
- The `use-cache-list` topic — `cache.list<T>(key)`: `push`/`unshift`/`pop`/`shift`/`slice`/`trim`/`clear` for queues, recent-N buffers, sliding windows.
|
|
717
|
+
- The `use-cache-similarity` topic — `cache.similar(vector, { topK, threshold?, tags? })` for semantic caches, RAG retrieval, nearest-neighbor lookup.
|
|
716
718
|
|
|
717
719
|
### Operations
|
|
718
720
|
|
|
719
|
-
-
|
|
720
|
-
-
|
|
721
|
-
-
|
|
721
|
+
- 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", …)`).
|
|
722
|
+
- The `handle-cache-errors` topic — the error classes: `CacheError`, `CacheConfigurationError`, `CacheConnectionError`, `CacheDriverNotInitializedError`, `CacheUnsupportedError`, `CacheConcurrencyError`.
|
|
723
|
+
- The `test-cache-code` topic — `MockCacheDriver` (behavioral assertions), `MemoryCacheDriver` (full-stack), `NullCacheDriver` (graceful degradation).
|
|
722
724
|
|
|
723
725
|
### Utilities
|
|
724
726
|
|
|
725
|
-
-
|
|
727
|
+
- The `use-cache-utils` topic — low-level re-exports: `parseTtl`, `parseCacheKey`, `resolveTtl`, `expiresAtToTtl`, `mergeTagSets`, `injectTags`, `cosineSimilarity`, and the `CACHE_FOR` TTL enum.
|
|
726
728
|
|
|
727
729
|
## What this package deliberately doesn't do
|
|
728
730
|
|
|
@@ -732,15 +734,15 @@ Nineteen task skills. Most apps start with `cache-basics` + `pick-cache-driver`
|
|
|
732
734
|
|
|
733
735
|
## See also
|
|
734
736
|
|
|
735
|
-
-
|
|
736
|
-
- `mongez-agent-kit-authoring-skills` (load via agent-kit sync) — how this becomes `.claude/skills/warlock-js-cache
|
|
737
|
+
- The `configure-app` topic of the `warlock-js-core` skill — wires the cache driver from `src/config/cache.ts` and exposes the singleton.
|
|
738
|
+
- `mongez-agent-kit-authoring-skills` (load via agent-kit sync) — how this becomes `.claude/skills/warlock-js-cache/overview.md`.
|
|
737
739
|
|
|
738
740
|
|
|
739
741
|
## pick-cache-driver `@warlock.js/cache/pick-cache-driver/SKILL.md`
|
|
740
742
|
|
|
741
743
|
---
|
|
742
744
|
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 —
|
|
745
|
+
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
746
|
---
|
|
745
747
|
|
|
746
748
|
# Cache drivers — pick the right one
|
|
@@ -811,7 +813,7 @@ Cache keys can carry untrusted input (e.g. a `cached()` auto-key derived from a
|
|
|
811
813
|
|
|
812
814
|
- **`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
815
|
- **`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
|
|
816
|
+
- **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
817
|
|
|
816
818
|
## Registering a custom driver
|
|
817
819
|
|
|
@@ -865,15 +867,15 @@ The manager loads (and connects) the override driver lazily on first use, then r
|
|
|
865
867
|
|
|
866
868
|
## See also
|
|
867
869
|
|
|
868
|
-
-
|
|
869
|
-
-
|
|
870
|
+
- The `configure-pg-cache` topic — full pg setup (KV-only and pgvector mode)
|
|
871
|
+
- The `test-cache-code` topic — `MockCacheDriver` and `NullCacheDriver` for tests
|
|
870
872
|
|
|
871
873
|
|
|
872
874
|
## test-cache-code `@warlock.js/cache/test-cache-code/SKILL.md`
|
|
873
875
|
|
|
874
876
|
---
|
|
875
877
|
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 —
|
|
878
|
+
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
879
|
---
|
|
878
880
|
|
|
879
881
|
# Testing code that touches cache
|
|
@@ -1059,7 +1061,7 @@ await Promise.all(
|
|
|
1059
1061
|
await expect(cache.get("counter")).resolves.toBe(10);
|
|
1060
1062
|
```
|
|
1061
1063
|
|
|
1062
|
-
Runs in-process only — for cross-process safety see
|
|
1064
|
+
Runs in-process only — for cross-process safety see the `use-cache-lock` topic.
|
|
1063
1065
|
|
|
1064
1066
|
## Testing similarity code paths
|
|
1065
1067
|
|
|
@@ -1096,7 +1098,7 @@ The `pg` driver accepts any object satisfying `PgClientLike` (`{ query(text, val
|
|
|
1096
1098
|
|
|
1097
1099
|
---
|
|
1098
1100
|
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 —
|
|
1101
|
+
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
1102
|
---
|
|
1101
1103
|
|
|
1102
1104
|
# Atomic counters — `cache.increment` / `cache.decrement`
|
|
@@ -1132,7 +1134,7 @@ Use `redis` or `pg` for anything several servers touch (rate limits, login throt
|
|
|
1132
1134
|
|
|
1133
1135
|
## TTL is kept
|
|
1134
1136
|
|
|
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
|
|
1137
|
+
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
1138
|
|
|
1137
1139
|
## Common shapes
|
|
1138
1140
|
|
|
@@ -1150,16 +1152,16 @@ if (remaining < 0) {
|
|
|
1150
1152
|
|
|
1151
1153
|
## See also
|
|
1152
1154
|
|
|
1153
|
-
-
|
|
1154
|
-
-
|
|
1155
|
-
-
|
|
1155
|
+
- The `use-cache-update-merge` topic — atomic read-modify-write for objects, TTL-preserving
|
|
1156
|
+
- The `use-cache-lock` topic — coordinate multi-step critical sections
|
|
1157
|
+
- The `pick-cache-driver` topic — when you need cross-node atomicity
|
|
1156
1158
|
|
|
1157
1159
|
|
|
1158
1160
|
## use-cache-bulk `@warlock.js/cache/use-cache-bulk/SKILL.md`
|
|
1159
1161
|
|
|
1160
1162
|
---
|
|
1161
1163
|
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 —
|
|
1164
|
+
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
1165
|
---
|
|
1164
1166
|
|
|
1165
1167
|
# Bulk operations — `cache.many` / `cache.setMany`
|
|
@@ -1199,7 +1201,7 @@ await cache.setMany({
|
|
|
1199
1201
|
- Keys are the object keys; values are the object values.
|
|
1200
1202
|
- The optional second arg is a single TTL (seconds) applied to **all** entries —
|
|
1201
1203
|
there's no per-entry TTL or `tags` knob here. When you need tags or mixed TTLs,
|
|
1202
|
-
loop with
|
|
1204
|
+
loop with `cache.set` (see the `configure-set-options` topic) and
|
|
1203
1205
|
the rich options object instead.
|
|
1204
1206
|
|
|
1205
1207
|
## Performance note
|
|
@@ -1211,16 +1213,16 @@ returned promise rejects.
|
|
|
1211
1213
|
|
|
1212
1214
|
## See also
|
|
1213
1215
|
|
|
1214
|
-
-
|
|
1215
|
-
-
|
|
1216
|
-
-
|
|
1216
|
+
- The `cache-basics` topic — single-key `get` / `set` / `remember`
|
|
1217
|
+
- The `configure-set-options` topic — per-entry TTL, tags, conflict policy
|
|
1218
|
+
- The `use-cache-tags` topic — invalidate a batch by tag
|
|
1217
1219
|
|
|
1218
1220
|
|
|
1219
1221
|
## use-cache-list `@warlock.js/cache/use-cache-list/SKILL.md`
|
|
1220
1222
|
|
|
1221
1223
|
---
|
|
1222
1224
|
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 —
|
|
1225
|
+
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
1226
|
---
|
|
1225
1227
|
|
|
1226
1228
|
# Lists — the `cache.list<T>(key)` sub-API
|
|
@@ -1263,7 +1265,7 @@ Redis-native `LPUSH` / `RPUSH` / `LRANGE` / `LTRIM` is planned for v2.1 (see `do
|
|
|
1263
1265
|
|
|
1264
1266
|
## Concurrency warning
|
|
1265
1267
|
|
|
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
|
|
1268
|
+
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
1269
|
|
|
1268
1270
|
Single-process memory with a single writer (typical test / script usage) is fine.
|
|
1269
1271
|
|
|
@@ -1309,7 +1311,7 @@ These are tracked as candidates for v3.
|
|
|
1309
1311
|
|
|
1310
1312
|
---
|
|
1311
1313
|
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 —
|
|
1314
|
+
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
1315
|
---
|
|
1314
1316
|
|
|
1315
1317
|
# `cache.lock()` — distributed locks with auto-release
|
|
@@ -1322,7 +1324,7 @@ description: 'Distributed lock via cache.lock(key, ttl, fn) — acquire, run fn,
|
|
|
1322
1324
|
- Idempotent webhook or payment processing — dedup across retries.
|
|
1323
1325
|
- Any time you'd otherwise write `try { … } finally { cache.remove(lockKey); }`.
|
|
1324
1326
|
|
|
1325
|
-
**Not for memoization** — use
|
|
1327
|
+
**Not for memoization** — use `cached()` (see the `use-cached-hof` topic) or `cache.remember()`.
|
|
1326
1328
|
|
|
1327
1329
|
## Shape
|
|
1328
1330
|
|
|
@@ -1426,7 +1428,7 @@ await cache.lock(
|
|
|
1426
1428
|
|
|
1427
1429
|
---
|
|
1428
1430
|
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 —
|
|
1431
|
+
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
1432
|
---
|
|
1431
1433
|
|
|
1432
1434
|
# Scoped caches — `cache.namespace(prefix, options?)`
|
|
@@ -1477,11 +1479,11 @@ await cache.set("user:2:profile", otherProfile);
|
|
|
1477
1479
|
await cache.removeNamespace("user.1"); // drops both user:1 entries, keeps user:2
|
|
1478
1480
|
```
|
|
1479
1481
|
|
|
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
|
|
1482
|
+
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
1483
|
|
|
1482
1484
|
## Multi-tenant scoping at the driver level
|
|
1483
1485
|
|
|
1484
|
-
Instead of every call passing a tenant prefix, attach `globalPrefix` to the driver config — see
|
|
1486
|
+
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
1487
|
|
|
1486
1488
|
```ts
|
|
1487
1489
|
options: {
|
|
@@ -1505,7 +1507,7 @@ await feed.swr(
|
|
|
1505
1507
|
// stored at feed.<userId>.home, tagged [user.<userId>, computed]
|
|
1506
1508
|
```
|
|
1507
1509
|
|
|
1508
|
-
Note: scope `ttl` defaults are NOT applied to SWR — `freshTtl` / `staleTtl` always come from the call site. See
|
|
1510
|
+
Note: scope `ttl` defaults are NOT applied to SWR — `freshTtl` / `staleTtl` always come from the call site. See the `use-swr` topic.
|
|
1509
1511
|
|
|
1510
1512
|
## Things NOT to do
|
|
1511
1513
|
|
|
@@ -1518,7 +1520,7 @@ Note: scope `ttl` defaults are NOT applied to SWR — `freshTtl` / `staleTtl` al
|
|
|
1518
1520
|
|
|
1519
1521
|
---
|
|
1520
1522
|
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 —
|
|
1523
|
+
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
1524
|
---
|
|
1523
1525
|
|
|
1524
1526
|
# `cache.similar()` — vector-based retrieval
|
|
@@ -1601,7 +1603,7 @@ options: { memory: { ttl: "1h" } }
|
|
|
1601
1603
|
options: { pg: { client: pool, vector: { dimensions: 1536 } } }
|
|
1602
1604
|
```
|
|
1603
1605
|
|
|
1604
|
-
See
|
|
1606
|
+
See the `configure-pg-cache` topic.
|
|
1605
1607
|
|
|
1606
1608
|
## Things NOT to do
|
|
1607
1609
|
|
|
@@ -1616,7 +1618,7 @@ See [`@warlock.js/cache/configure-pg-cache/SKILL.md`](@warlock.js/cache/configur
|
|
|
1616
1618
|
|
|
1617
1619
|
---
|
|
1618
1620
|
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 —
|
|
1621
|
+
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
1622
|
---
|
|
1621
1623
|
|
|
1622
1624
|
# Tag-based invalidation
|
|
@@ -1652,7 +1654,7 @@ Multi-tag is **union** semantics: an entry is invalidated if it carries **at lea
|
|
|
1652
1654
|
|
|
1653
1655
|
| Use case | Reach for |
|
|
1654
1656
|
| --- | --- |
|
|
1655
|
-
| The keys share a known prefix | `cache.removeNamespace("prefix")` (
|
|
1657
|
+
| The keys share a known prefix | `cache.removeNamespace("prefix")` (the `use-cache-namespace` topic) |
|
|
1656
1658
|
| The keys are spread across prefixes, tied by entity | Tags |
|
|
1657
1659
|
| Both apply | Tags — more flexible; cheap on most drivers |
|
|
1658
1660
|
|
|
@@ -1684,7 +1686,7 @@ await cache.swr(
|
|
|
1684
1686
|
);
|
|
1685
1687
|
```
|
|
1686
1688
|
|
|
1687
|
-
Tags re-apply on every successful refresh — see
|
|
1689
|
+
Tags re-apply on every successful refresh — see the `use-swr` topic.
|
|
1688
1690
|
|
|
1689
1691
|
## `cached()` HOF with tags
|
|
1690
1692
|
|
|
@@ -1695,7 +1697,7 @@ const getPosts = cached(fn, { key: (u) => `posts.by.${u}`, ttl: "30m", tags: ["u
|
|
|
1695
1697
|
await cache.tags(["users"]).invalidate(); // drops both wrappers' caches
|
|
1696
1698
|
```
|
|
1697
1699
|
|
|
1698
|
-
See
|
|
1700
|
+
See the `use-cached-hof` topic.
|
|
1699
1701
|
|
|
1700
1702
|
## Things NOT to do
|
|
1701
1703
|
|
|
@@ -1708,7 +1710,7 @@ See [`@warlock.js/cache/use-cached-hof/SKILL.md`](@warlock.js/cache/use-cached-h
|
|
|
1708
1710
|
|
|
1709
1711
|
---
|
|
1710
1712
|
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 —
|
|
1713
|
+
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
1714
|
---
|
|
1713
1715
|
|
|
1714
1716
|
# `update` and `merge` — atomic read-modify-write
|
|
@@ -1796,7 +1798,7 @@ The per-key lock map lives on the driver instance and is cleared after each chai
|
|
|
1796
1798
|
|
|
1797
1799
|
---
|
|
1798
1800
|
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 —
|
|
1801
|
+
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
1802
|
---
|
|
1801
1803
|
|
|
1802
1804
|
# Cache utilities
|
|
@@ -1880,16 +1882,16 @@ For most call sites the duration string (`"1h"`, `"7d"`) reads better.
|
|
|
1880
1882
|
|
|
1881
1883
|
## See also
|
|
1882
1884
|
|
|
1883
|
-
-
|
|
1884
|
-
-
|
|
1885
|
-
-
|
|
1885
|
+
- The `configure-set-options` topic — the high-level `set` options these normalize
|
|
1886
|
+
- The `use-cache-similarity` topic — `cache.similar()`, which uses `cosineSimilarity`
|
|
1887
|
+
- The `pick-cache-driver` topic — building a custom driver where these help
|
|
1886
1888
|
|
|
1887
1889
|
|
|
1888
1890
|
## use-cached-hof `@warlock.js/cache/use-cached-hof/SKILL.md`
|
|
1889
1891
|
|
|
1890
1892
|
---
|
|
1891
1893
|
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 —
|
|
1894
|
+
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
1895
|
---
|
|
1894
1896
|
|
|
1895
1897
|
# `cached()` — function-memoization wrapper
|
|
@@ -1968,7 +1970,7 @@ const getPosts = cached(fn, { key: (u) => `posts.by.${u}`, ttl: "30m", tags: ["u
|
|
|
1968
1970
|
await cache.tags(["users"]).invalidate(); // drops both wrappers' caches
|
|
1969
1971
|
```
|
|
1970
1972
|
|
|
1971
|
-
See
|
|
1973
|
+
See the `use-cache-tags` topic.
|
|
1972
1974
|
|
|
1973
1975
|
### Project a subset of args into the key
|
|
1974
1976
|
|
|
@@ -1996,7 +1998,7 @@ const getCategoryMeta = cached(
|
|
|
1996
1998
|
|
|
1997
1999
|
---
|
|
1998
2000
|
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 —
|
|
2001
|
+
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
2002
|
---
|
|
2001
2003
|
|
|
2002
2004
|
# Stale-while-revalidate — `cache.swr(key, options, fn)`
|
|
@@ -2007,7 +2009,7 @@ Returns the cached value immediately when it can; refreshes in the background wh
|
|
|
2007
2009
|
|
|
2008
2010
|
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
2011
|
|
|
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
|
|
2012
|
+
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
2013
|
|
|
2012
2014
|
## Three windows
|
|
2013
2015
|
|
|
@@ -2047,7 +2049,7 @@ await cache.swr(
|
|
|
2047
2049
|
|
|
2048
2050
|
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
2051
|
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
|
|
2052
|
+
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
2053
|
4. **Tags compose.** Per-call tags + scope tags (when via `cache.namespace().swr(...)`) merge additively.
|
|
2052
2054
|
5. **Scope `ttl` defaults are NOT applied to SWR.** `freshTtl` / `staleTtl` always come from the call site.
|
|
2053
2055
|
|