@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/llms-full.txt CHANGED
@@ -8,12 +8,12 @@
8
8
 
9
9
  ---
10
10
  name: apply-cache-patterns
11
- description: 'Compose cache primitives into real-world patterns — remember() memoization, cross-node stampede protection via a distributed lock (onConflict: ''create''), negative caching, and per-tenant scoping. Triggers: `cache.remember`, `cache.set` with `onConflict: "create"`, `globalPrefix`; "memoize this function", "prevent cache stampede across nodes", "cache not-found results", "per-tenant cache scoping"; typical import `import { cache } from "@warlock.js/cache"`. Skip: counters — `@warlock.js/cache/use-cache-atomic/SKILL.md`; bulk get/set — `@warlock.js/cache/use-cache-bulk/SKILL.md`; TTL constants/utilities — `@warlock.js/cache/use-cache-utils/SKILL.md`; named lock wrapper — `@warlock.js/cache/use-cache-lock/SKILL.md`; SWR — `@warlock.js/cache/use-swr/SKILL.md`; competing libs `lru-cache`, `node-cache`, `keyv`.'
11
+ description: 'Compose cache primitives into real-world patterns — remember() memoization, cross-node stampede protection via a distributed lock (onConflict: ''create''), negative caching, and per-tenant scoping. Triggers: `cache.remember`, `cache.set` with `onConflict: "create"`, `globalPrefix`; "memoize this function", "prevent cache stampede across nodes", "cache not-found results", "per-tenant cache scoping"; typical import `import { cache } from "@warlock.js/cache"`. Skip: counters — the `use-cache-atomic` topic; bulk get/set — the `use-cache-bulk` topic; TTL constants/utilities — the `use-cache-utils` topic; named lock wrapper — the `use-cache-lock` topic; SWR — the `use-swr` topic; competing libs `lru-cache`, `node-cache`, `keyv`.'
12
12
  ---
13
13
 
14
14
  # Real-world caching patterns
15
15
 
16
- Common shapes — the "general patterns" file. Specialized topics have dedicated skills: [`use-cache-tags`](@warlock.js/cache/use-cache-tags/SKILL.md), [`use-cache-namespace`](@warlock.js/cache/use-cache-namespace/SKILL.md), [`use-swr`](@warlock.js/cache/use-swr/SKILL.md), [`use-cache-lock`](@warlock.js/cache/use-cache-lock/SKILL.md), [`use-cache-list`](@warlock.js/cache/use-cache-list/SKILL.md).
16
+ Common shapes — the "general patterns" file. Specialized shapes live in their own topics: `use-cache-tags`, `use-cache-namespace`, `use-swr`, `use-cache-lock`, `use-cache-list`.
17
17
 
18
18
  ## Memoize an expensive function — `remember`
19
19
 
@@ -53,7 +53,7 @@ try {
53
53
  }
54
54
  ```
55
55
 
56
- This requires a driver with atomic `SET NX` — Redis is native, memory/LRU/file emulate (single-process only). For a higher-level wrapper that does the lock-and-release for you, see [`@warlock.js/cache/use-cache-lock/SKILL.md`](@warlock.js/cache/use-cache-lock/SKILL.md).
56
+ This requires a driver with atomic `SET NX` — Redis is native, memory/LRU/file emulate (single-process only). For a higher-level wrapper that does the lock-and-release for you, see the `use-cache-lock` topic.
57
57
 
58
58
  ## Negative caching
59
59
 
@@ -97,13 +97,13 @@ await cache.tags([`tenant-${tenantId}`]).invalidate();
97
97
 
98
98
  ## See also
99
99
 
100
- - [`@warlock.js/cache/use-cache-atomic/SKILL.md`](@warlock.js/cache/use-cache-atomic/SKILL.md) — `increment` / `decrement` counters
101
- - [`@warlock.js/cache/use-cache-bulk/SKILL.md`](@warlock.js/cache/use-cache-bulk/SKILL.md) — `many` / `setMany`
102
- - [`@warlock.js/cache/use-cache-utils/SKILL.md`](@warlock.js/cache/use-cache-utils/SKILL.md) — `CACHE_FOR` constants and TTL/key helpers
103
- - [`@warlock.js/cache/use-cache-tags/SKILL.md`](@warlock.js/cache/use-cache-tags/SKILL.md) — tag-based invalidation
104
- - [`@warlock.js/cache/use-cache-namespace/SKILL.md`](@warlock.js/cache/use-cache-namespace/SKILL.md) — scoped handles and `removeNamespace`
105
- - [`@warlock.js/cache/use-swr/SKILL.md`](@warlock.js/cache/use-swr/SKILL.md) — stale-while-revalidate for slow upstreams
106
- - [`@warlock.js/cache/use-cached-hof/SKILL.md`](@warlock.js/cache/use-cached-hof/SKILL.md) — `cached()` HOF for declarative memoization
100
+ - The `use-cache-atomic` topic — `increment` / `decrement` counters
101
+ - The `use-cache-bulk` topic — `many` / `setMany`
102
+ - The `use-cache-utils` topic — `CACHE_FOR` constants and TTL/key helpers
103
+ - The `use-cache-tags` topic — tag-based invalidation
104
+ - The `use-cache-namespace` topic — scoped handles and `removeNamespace`
105
+ - The `use-swr` topic — stale-while-revalidate for slow upstreams
106
+ - The `use-cached-hof` topic — `cached()` HOF for declarative memoization
107
107
 
108
108
 
109
109
  ## cache-basics `@warlock.js/cache/cache-basics/SKILL.md`
@@ -134,16 +134,18 @@ The 10 things that are true in every cache use:
134
134
  1. **Public API is the `cache` singleton** (`import { cache } from "@warlock.js/cache"`). No `new CacheManager()` for consumers.
135
135
  2. **Every data op runs against the currently selected driver.** Switch via `cache.use("name")` or use a per-call override: `cache.set(k, v, { driver: "redis" })`.
136
136
  3. **Consumers never `await connect()` directly.** `cache.init()` does that once at startup after `cache.setCacheConfigurations(...)`. For drivers needing runtime-built options (e.g. `pg`'s `client: pg.Pool`), skip `init()` and call `cache.use("pg", { client: pool })`.
137
- 4. **TTL accepts three shapes at the call site**: `number` (seconds), `string` (`"1h"`, `"30m"`, `"7d"` — parsed via `ms`), or a full `CacheSetOptions` object. See [`@warlock.js/cache/configure-set-options/SKILL.md`](@warlock.js/cache/configure-set-options/SKILL.md).
138
- 5. **`update` and `merge` throw `CacheUnsupportedError` on the file driver.** Use memory or redis for atomic mutation. See [`@warlock.js/cache/use-cache-update-merge/SKILL.md`](@warlock.js/cache/use-cache-update-merge/SKILL.md).
137
+ 4. **TTL accepts three shapes at the call site**: `number` (seconds), `string` (`"1h"`, `"30m"`, `"7d"` — parsed via `ms`), or a full `CacheSetOptions` object. See the `configure-set-options` topic.
138
+ 5. **`update` and `merge` throw `CacheUnsupportedError` on the file driver.** Use memory or redis for atomic mutation. See the `use-cache-update-merge` topic.
139
139
  6. **The value you read is a deep clone.** `structuredClone` protects the cache from accidental mutation of returned objects.
140
- 7. **`remember()` is stampede-safe within a single process.** Cross-process safety requires `onConflict: "create"` plus TTL (Redis-native). For slow upstreams where slightly-stale data is acceptable, prefer `cache.swr(...)` — see [`@warlock.js/cache/use-swr/SKILL.md`](@warlock.js/cache/use-swr/SKILL.md).
140
+ 7. **`remember()` is stampede-safe within a single process.** Cross-process safety requires `onConflict: "create"` plus TTL (Redis-native). For slow upstreams where slightly-stale data is acceptable, prefer `cache.swr(...)` — see the `use-swr` topic.
141
141
  8. **`cache.metrics()` returns a running snapshot** — counters, hit rate, latency percentiles, per-driver breakdowns. Lazy: collector attaches on first call so apps that never read metrics pay zero cost.
142
- 9. **`cache.namespace(prefix, options?)` returns a scoped handle** — every key auto-prefixed, scope-level `ttl` / `tags` defaults. See [`@warlock.js/cache/use-cache-namespace/SKILL.md`](@warlock.js/cache/use-cache-namespace/SKILL.md).
143
- 10. **Similarity retrieval** lives on the same driver contract — `set(k, v, { vector })` indexes the entry; `cache.similar(vec, ...)` returns nearest hits. See [`@warlock.js/cache/use-cache-similarity/SKILL.md`](@warlock.js/cache/use-cache-similarity/SKILL.md).
142
+ 9. **`cache.namespace(prefix, options?)` returns a scoped handle** — every key auto-prefixed, scope-level `ttl` / `tags` defaults. See the `use-cache-namespace` topic.
143
+ 10. **Similarity retrieval** lives on the same driver contract — `set(k, v, { vector })` indexes the entry; `cache.similar(vec, ...)` returns nearest hits. See the `use-cache-similarity` topic.
144
144
 
145
145
  ## Minimal startup
146
146
 
147
+ > **In a Warlock app** you do not call `cache.setCacheConfigurations(...)` or `cache.init()` yourself. Put the configuration in `src/config/cache.ts`; the framework's cache connector reads it (`config.get("cache")`), calls `cache.setCacheConfigurations(...)`, then `await cache.init()` at boot, and does nothing if no cache config exists. The manual setup below is for standalone use (scripts, tests, non-Warlock services).
148
+
147
149
  ```ts
148
150
  import {
149
151
  cache,
@@ -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 | [`@warlock.js/cache/pick-cache-driver/SKILL.md`](@warlock.js/cache/pick-cache-driver/SKILL.md) |
208
- | The `set` options object (`ttl`, `expiresAt`, `tags`, `onConflict`, `driver`, `vector`) | [`@warlock.js/cache/configure-set-options/SKILL.md`](@warlock.js/cache/configure-set-options/SKILL.md) |
209
- | Memoization with `remember()`, counters, negative caching, per-tenant scoping, TTL constants | [`@warlock.js/cache/apply-cache-patterns/SKILL.md`](@warlock.js/cache/apply-cache-patterns/SKILL.md) |
210
- | Scoped handles via `cache.namespace(prefix, options?)` | [`@warlock.js/cache/use-cache-namespace/SKILL.md`](@warlock.js/cache/use-cache-namespace/SKILL.md) |
211
- | Tag-based invalidation — `cache.tags([...]).invalidate()` | [`@warlock.js/cache/use-cache-tags/SKILL.md`](@warlock.js/cache/use-cache-tags/SKILL.md) |
212
- | Stale-while-revalidate — `cache.swr(...)` | [`@warlock.js/cache/use-swr/SKILL.md`](@warlock.js/cache/use-swr/SKILL.md) |
213
- | Wrapping a function with `cached()` — HOF memoization with `.invalidate()` | [`@warlock.js/cache/use-cached-hof/SKILL.md`](@warlock.js/cache/use-cached-hof/SKILL.md) |
214
- | Distributed locks — `cache.lock(key, ttl, fn)` with auto-release | [`@warlock.js/cache/use-cache-lock/SKILL.md`](@warlock.js/cache/use-cache-lock/SKILL.md) |
215
- | Queues, recent-N buffers, `push`/`shift`/`trim` — the list sub-API | [`@warlock.js/cache/use-cache-list/SKILL.md`](@warlock.js/cache/use-cache-list/SKILL.md) |
216
- | Atomic read-modify-write via `update()` and `merge()` | [`@warlock.js/cache/use-cache-update-merge/SKILL.md`](@warlock.js/cache/use-cache-update-merge/SKILL.md) |
217
- | Similarity retrieval — `set({ vector })` + `cache.similar(...)` | [`@warlock.js/cache/use-cache-similarity/SKILL.md`](@warlock.js/cache/use-cache-similarity/SKILL.md) |
218
- | Postgres driver setup (KV-only or with pgvector) | [`@warlock.js/cache/configure-pg-cache/SKILL.md`](@warlock.js/cache/configure-pg-cache/SKILL.md) |
219
- | `cache.metrics()` aggregate snapshot + event bus for per-event reactions | [`@warlock.js/cache/observe-cache/SKILL.md`](@warlock.js/cache/observe-cache/SKILL.md) |
220
- | Error classes (`CacheConfigurationError`, `CacheUnsupportedError`, etc.) | [`@warlock.js/cache/handle-cache-errors/SKILL.md`](@warlock.js/cache/handle-cache-errors/SKILL.md) |
221
- | Tests that touch cache code paths — `MockCacheDriver`, `MemoryCacheDriver` | [`@warlock.js/cache/test-cache-code/SKILL.md`](@warlock.js/cache/test-cache-code/SKILL.md) |
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 — `@warlock.js/cache/use-cache-similarity/SKILL.md`; driver picker — `@warlock.js/cache/pick-cache-driver/SKILL.md`; competing libs `pg-mem`; raw `pg` / `node-postgres`.'
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 [stale-while-revalidate](@warlock.js/cache/use-swr/SKILL.md) — `cache.swr(...)` populates it on writes, plain `set()` leaves it null (always-fresh). Provision via `driver.schema()` like any other column.
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 [`@warlock.js/cache/handle-cache-errors/SKILL.md`](@warlock.js/cache/handle-cache-errors/SKILL.md) for the full error class hierarchy.
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
- - [`@warlock.js/cache/use-cache-similarity/SKILL.md`](@warlock.js/cache/use-cache-similarity/SKILL.md) — the `similar()` API across all drivers
353
- - [`@warlock.js/cache/pick-cache-driver/SKILL.md`](@warlock.js/cache/pick-cache-driver/SKILL.md) — comparing pg with memory / redis / file
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 — `@warlock.js/cache/use-cache-tags/SKILL.md`; vector queries — `@warlock.js/cache/use-cache-similarity/SKILL.md`; competing libs `keyv`, `ioredis`.'
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 [`@warlock.js/cache/use-cache-tags/SKILL.md`](@warlock.js/cache/use-cache-tags/SKILL.md). |
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 [`cache.similar()`](@warlock.js/cache/use-cache-similarity/SKILL.md). Drivers without similarity support throw `CacheUnsupportedError`. |
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 — `@warlock.js/cache/pick-cache-driver/SKILL.md`; observing errors via events — `@warlock.js/cache/observe-cache/SKILL.md`; competing libs ignore — generic `Error` patterns.'
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 [`@warlock.js/cache/pick-cache-driver/SKILL.md`](@warlock.js/cache/pick-cache-driver/SKILL.md) for the encoding contract. |
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 [`@warlock.js/cache/configure-set-options/SKILL.md`](@warlock.js/cache/configure-set-options/SKILL.md).
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 — `@warlock.js/cache/handle-cache-errors/SKILL.md`; competing libs `prom-client`, `statsd-client`.'
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
- - [`cache-basics`](@warlock.js/cache/cache-basics/SKILL.md) — the `cache` singleton, primary ops (`set`/`get`/`has`/`pull`/`remove`/`many`/`forever`/`increment`/`decrement`/`remember`), TTL shapes, init flow. **Start here.**
696
- - [`pick-cache-driver`](@warlock.js/cache/pick-cache-driver/SKILL.md) — choose + configure a driver: `null` / `memory` / `memoryExtended` / `lru` / `file` / `redis` / `pg` / `mock`; `globalPrefix` for multi-tenant scoping.
697
- - [`configure-set-options`](@warlock.js/cache/configure-set-options/SKILL.md) — `cache.set`'s third argument: `ttl`, `expiresAt`, `tags`, `onConflict` (create/update/upsert), `driver`, `vector`.
698
- - [`configure-pg-cache`](@warlock.js/cache/configure-pg-cache/SKILL.md) — Postgres driver: KV-only (default) or pgvector mode; caller owns the `pg.Pool`, `driver.schema()` emits the DDL.
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
- - [`use-cache-tags`](@warlock.js/cache/use-cache-tags/SKILL.md) — tag on write, `cache.tags([...]).invalidate()` drops every bound key.
703
- - [`use-cache-namespace`](@warlock.js/cache/use-cache-namespace/SKILL.md) — `cache.namespace(prefix, options?)` auto-prefixes keys with scope-level TTL/tag defaults and nested scopes.
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
- - [`use-cached-hof`](@warlock.js/cache/use-cached-hof/SKILL.md) — `cached(fn, options)` wraps an async function; one declaration, many call sites, a bound `.invalidate(...args)`.
708
- - [`apply-cache-patterns`](@warlock.js/cache/apply-cache-patterns/SKILL.md) — `remember()` memoization, distributed locks via `onConflict: "create"`, negative caching, counters, per-tenant prefix, `CACHE_FOR.*` TTL constants.
709
- - [`use-cache-lock`](@warlock.js/cache/use-cache-lock/SKILL.md) — `cache.lock(key, ttl, fn)`: acquire → run → auto-release. For cron/imports/migrations and idempotent webhook/payment processing.
710
- - [`use-swr`](@warlock.js/cache/use-swr/SKILL.md) — `cache.swr(key, { freshTtl, staleTtl }, fn)`: instant when fresh, instant + background refresh when stale, blocks only when fully expired.
711
- - [`use-cache-update-merge`](@warlock.js/cache/use-cache-update-merge/SKILL.md) — atomic read-modify-write via `cache.update(key, fn)` / `cache.merge(key, partial)`, serialized per key, TTL-preserving.
712
- - [`use-cache-atomic`](@warlock.js/cache/use-cache-atomic/SKILL.md) — `cache.increment` / `cache.decrement` counters; per-driver atomicity + TTL behavior.
713
- - [`use-cache-bulk`](@warlock.js/cache/use-cache-bulk/SKILL.md) — `cache.many(keys)` / `cache.setMany(record, ttl?)` for batch reads/writes.
714
- - [`use-cache-list`](@warlock.js/cache/use-cache-list/SKILL.md) — `cache.list<T>(key)`: `push`/`unshift`/`pop`/`shift`/`slice`/`trim`/`clear` for queues, recent-N buffers, sliding windows.
715
- - [`use-cache-similarity`](@warlock.js/cache/use-cache-similarity/SKILL.md) — `cache.similar(vector, { topK, threshold?, tags? })` for semantic caches, RAG retrieval, nearest-neighbor lookup.
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
- - [`observe-cache`](@warlock.js/cache/observe-cache/SKILL.md) — `cache.metrics()` (hit rate, latency p50/p95/p99) + the event bus (`cache.on("hit" | "miss" | "set" | "removed" | "flushed" | "expired" | "connected" | "disconnected" | "error", …)`).
720
- - [`handle-cache-errors`](@warlock.js/cache/handle-cache-errors/SKILL.md) — the error classes: `CacheError`, `CacheConfigurationError`, `CacheConnectionError`, `CacheDriverNotInitializedError`, `CacheUnsupportedError`, `CacheConcurrencyError`.
721
- - [`test-cache-code`](@warlock.js/cache/test-cache-code/SKILL.md) — `MockCacheDriver` (behavioral assertions), `MemoryCacheDriver` (full-stack), `NullCacheDriver` (graceful degradation).
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
- - [`use-cache-utils`](@warlock.js/cache/use-cache-utils/SKILL.md) — low-level re-exports: `parseTtl`, `parseCacheKey`, `resolveTtl`, `expiresAtToTtl`, `mergeTagSets`, `injectTags`, `cosineSimilarity`, and the `CACHE_FOR` TTL enum.
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
- - [`@warlock.js/core/overview/SKILL.md`](@warlock.js/core/overview/SKILL.md) — wires the cache driver from app config and exposes the singleton.
736
- - `mongez-agent-kit-authoring-skills` (load via agent-kit sync) — how this becomes `.claude/skills/warlock-js-cache-overview/`.
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 — `@warlock.js/cache/cache-basics/SKILL.md`; pg setup — `@warlock.js/cache/configure-pg-cache/SKILL.md`; competing libs `lru-cache`, `node-cache`, `keyv`, `ioredis`; native `Map`.'
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 [`@warlock.js/cache/handle-cache-errors/SKILL.md`](@warlock.js/cache/handle-cache-errors/SKILL.md).
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
- - [`@warlock.js/cache/configure-pg-cache/SKILL.md`](@warlock.js/cache/configure-pg-cache/SKILL.md) — full pg setup (KV-only and pgvector mode)
869
- - [`@warlock.js/cache/test-cache-code/SKILL.md`](@warlock.js/cache/test-cache-code/SKILL.md) — `MockCacheDriver` and `NullCacheDriver` for tests
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 — `@warlock.js/cache/pick-cache-driver/SKILL.md`; competing libs `jest-mock`, `sinon`, `redis-mock`; native `vi.fn`.'
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 [`@warlock.js/cache/use-cache-lock/SKILL.md`](@warlock.js/cache/use-cache-lock/SKILL.md).
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 — `@warlock.js/cache/use-cache-update-merge/SKILL.md`; named-lock coordination — `@warlock.js/cache/use-cache-lock/SKILL.md`; competing libs `ioredis` `INCR`, native counters in a `Map`.'
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 [`cache.update`](@warlock.js/cache/use-cache-update-merge/SKILL.md) for objects.
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
- - [`@warlock.js/cache/use-cache-update-merge/SKILL.md`](@warlock.js/cache/use-cache-update-merge/SKILL.md) — atomic read-modify-write for objects, TTL-preserving
1154
- - [`@warlock.js/cache/use-cache-lock/SKILL.md`](@warlock.js/cache/use-cache-lock/SKILL.md) — coordinate multi-step critical sections
1155
- - [`@warlock.js/cache/pick-cache-driver/SKILL.md`](@warlock.js/cache/pick-cache-driver/SKILL.md) — when you need cross-node atomicity
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 — `@warlock.js/cache/use-cache-tags/SKILL.md`; single-key ops — `@warlock.js/cache/cache-basics/SKILL.md`; competing libs `ioredis` `MGET`/`MSET`.'
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 [`cache.set`](@warlock.js/cache/configure-set-options/SKILL.md) and
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
- - [`@warlock.js/cache/cache-basics/SKILL.md`](@warlock.js/cache/cache-basics/SKILL.md) — single-key `get` / `set` / `remember`
1215
- - [`@warlock.js/cache/configure-set-options/SKILL.md`](@warlock.js/cache/configure-set-options/SKILL.md) — per-entry TTL, tags, conflict policy
1216
- - [`@warlock.js/cache/use-cache-tags/SKILL.md`](@warlock.js/cache/use-cache-tags/SKILL.md) — invalidate a batch by tag
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 — `@warlock.js/cache/use-cache-lock/SKILL.md`; competing libs `bullmq`, `bee-queue`, `bull`, `ioredis` `LPUSH`; native `Array.push`.'
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 [`@warlock.js/cache/use-cache-lock/SKILL.md`](@warlock.js/cache/use-cache-lock/SKILL.md)) or use a single writer.
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 — `@warlock.js/cache/apply-cache-patterns/SKILL.md`; memoization — `@warlock.js/cache/use-cached-hof/SKILL.md`; competing libs `redlock`, `async-mutex`, `proper-lockfile`.'
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 [`cached()`](@warlock.js/cache/use-cached-hof/SKILL.md) or `cache.remember()`.
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 — `@warlock.js/cache/use-cache-tags/SKILL.md`; multi-tenant driver-level prefix — `@warlock.js/cache/pick-cache-driver/SKILL.md`; SWR — `@warlock.js/cache/use-swr/SKILL.md`; competing libs `keyv` namespaces.'
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 [`@warlock.js/cache/pick-cache-driver/SKILL.md`](@warlock.js/cache/pick-cache-driver/SKILL.md) for the file-path-containment and redis-glob-escaping details.
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 [`@warlock.js/cache/pick-cache-driver/SKILL.md`](@warlock.js/cache/pick-cache-driver/SKILL.md). Function form runs per call.
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 [`@warlock.js/cache/use-swr/SKILL.md`](@warlock.js/cache/use-swr/SKILL.md).
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 — `@warlock.js/cache/configure-pg-cache/SKILL.md`; competing libs `pinecone`, `weaviate`, `chromadb`, `lancedb`, `faiss-node`.'
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 [`@warlock.js/cache/configure-pg-cache/SKILL.md`](@warlock.js/cache/configure-pg-cache/SKILL.md).
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 — `@warlock.js/cache/use-cache-namespace/SKILL.md`; HOF memoization with tags — `@warlock.js/cache/use-cached-hof/SKILL.md`; SWR — `@warlock.js/cache/use-swr/SKILL.md`; competing libs `cache-manager` tags, Next.js `revalidateTag`.'
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")` ([`use-cache-namespace`](@warlock.js/cache/use-cache-namespace/SKILL.md)) |
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 [`@warlock.js/cache/use-swr/SKILL.md`](@warlock.js/cache/use-swr/SKILL.md).
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 [`@warlock.js/cache/use-cached-hof/SKILL.md`](@warlock.js/cache/use-cached-hof/SKILL.md).
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 — `@warlock.js/cache/use-cache-lock/SKILL.md`; conditional create/update — `@warlock.js/cache/configure-set-options/SKILL.md`; competing libs `lodash.merge`, raw redis `WATCH`/`MULTI`.'
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 — `@warlock.js/cache/pick-cache-driver/SKILL.md`; the high-level set API — `@warlock.js/cache/configure-set-options/SKILL.md`.'
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
- - [`@warlock.js/cache/configure-set-options/SKILL.md`](@warlock.js/cache/configure-set-options/SKILL.md) — the high-level `set` options these normalize
1884
- - [`@warlock.js/cache/use-cache-similarity/SKILL.md`](@warlock.js/cache/use-cache-similarity/SKILL.md) — `cache.similar()`, which uses `cosineSimilarity`
1885
- - [`@warlock.js/cache/pick-cache-driver/SKILL.md`](@warlock.js/cache/pick-cache-driver/SKILL.md) — building a custom driver where these help
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 — `@warlock.js/cache/apply-cache-patterns/SKILL.md` (`cache.remember`); tag bulk drop — `@warlock.js/cache/use-cache-tags/SKILL.md`; competing libs `p-memoize`, `mem`, `lodash.memoize`.'
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 [`@warlock.js/cache/use-cache-tags/SKILL.md`](@warlock.js/cache/use-cache-tags/SKILL.md).
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 — `@warlock.js/cache/apply-cache-patterns/SKILL.md`; observing refresh failures — `@warlock.js/cache/observe-cache/SKILL.md`; competing libs `swr` (React, client-side).'
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 [`@warlock.js/cache/apply-cache-patterns/SKILL.md`](@warlock.js/cache/apply-cache-patterns/SKILL.md).
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 [`@warlock.js/cache/observe-cache/SKILL.md`](@warlock.js/cache/observe-cache/SKILL.md).
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